You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用ScreenManager引入菜单后Kivy乒乓游戏碰撞偏移问题求助

ScreenManager引入菜单后Kivy乒乓游戏碰撞偏移问题求助

我完全理解你现在的困扰——刚给Kivy乒乓游戏加上菜单和ScreenManager,结果好好的游戏突然碰撞偏移、图形错位,调位置又把整个游戏搞崩,作为新手踩这种坑真的很挫败😅。让我帮你分析下问题,再给你一个能直接跑通的解决方案。

问题根源

你遇到的核心问题是坐标系不匹配:

  • 原来的PongGame是直接作为根控件,默认会占满整个窗口,内部元素的位置、碰撞都是基于全屏坐标系计算的。
  • 加入ScreenManager后,你把PongGame作为子控件加到了PongScreen里,但Kivy的Widget默认尺寸是(100,100),size_hint是(None, None),所以PongGame实际只占了屏幕左下角的一小块区域,而游戏里的元素(球拍、球)还是基于这个100x100的小容器来定位,触摸事件的坐标却是整个屏幕的,这就导致了碰撞偏移、元素位置错乱的问题。

解决方案

我们只需要让PongGame的尺寸和Screen完全绑定,让游戏内部的坐标系和屏幕坐标系对齐,就能解决所有问题。具体改两处:

1. 修改kv文件,让PongGame自适应全屏

在pong.kv的<PongGame>规则开头添加size_hint: 1, 1,让它自动占满父容器(PongScreen)的全部空间:

#:kivy 2.3.1
<PongBall>:
    canvas:
        Ellipse:
            pos: self.pos
            size: 50, 50

<PongPaddle>:
    canvas:
        Rectangle:
            pos: self.pos
            size: 25, 200

<PongGame>:
    size_hint: 1, 1  # 新增:让游戏占满整个Screen
    ball: pong_ball
    player1: player_left
    player2: player_right
    # 以下内容保持不变
    canvas:
        Rectangle:
            pos: self.center_x - 5, 0
            size: 10, self.height
    Label:
        font_size: 70
        center_x: root.width / 4
        top: root.top - 50
        text: str(root.player1.score)
    Label:
        font_size: 70
        center_x: root.width * 3 / 4
        top: root.top - 50
        text: str(root.player2.score)
    PongBall:
        id: pong_ball
        center: self.parent.center
    PongPaddle:
        id: player_left
        x: self.parent.x
        center_y: self.parent.center_y
    PongPaddle:
        id: player_right
        x: self.parent.width - self.width
        center_y: self.parent.center_y

<PongMenu>:
    start_btn: button_start
    BoxLayout:
        orientation: 'vertical'
        Button:
            id: button_start
            text: "[b]Start Game[/b]"
            markup: True
            width: 200
            height: 200
            on_press: root.manager.current = "game"

2. 优化PongScreen的游戏初始化逻辑(可选但更稳健)

在main.py的PongScreen类的on_enter方法中,绑定PongGame的尺寸到Screen的尺寸,这样窗口缩放时游戏也能自适应:

from kivy.app import App
from kivy.properties import NumericProperty, ReferenceListProperty, ObjectProperty
from kivy.uix.screenmanager import Screen, ScreenManager, NoTransition
from kivy.uix.widget import Widget
from kivy.vector import Vector
from kivy.clock import Clock

class PongPaddle(Widget):
    score = NumericProperty(0)
    def bounce_ball(self, ball):
        if self.collide_widget(ball):
            vx, vy = ball.velocity
            offset = (ball.center_y - self.center_y) / (self.height / 2)
            bounced = Vector(-1 * vx, vy)
            vel = bounced * 1.1
            ball.velocity = vel.x, vel.y + offset

class PongBall(Widget):
    velocity_x = NumericProperty(0)
    velocity_y = NumericProperty(0)
    velocity = ReferenceListProperty(velocity_x, velocity_y)
    def move(self):
        self.pos = Vector(*self.velocity) + self.pos

class PongGame(Widget):
    ball = ObjectProperty(None)
    player1 = ObjectProperty(None)
    player2 = ObjectProperty(None)
    def serve_ball(self, vel=(4, 0)):
        self.ball.center = self.center
        self.ball.velocity = vel
    def update(self, dt):
        self.ball.move()
        self.player1.bounce_ball(self.ball)
        self.player2.bounce_ball(self.ball)
        if (self.ball.y < 0) or (self.ball.top > self.height):
            self.ball.velocity_y *= -1
        if self.ball.x < self.x:
            self.player2.score += 1
            self.serve_ball(vel=(4,0))
        if self.ball.x > self.width:
            self.player1.score += 1
            self.serve_ball(vel=(-4,0))
    def on_touch_move(self, touch):
        if touch.x < self.width / 3:
            self.player1.center_y = touch.y
        if touch.x > self.width - self.width / 3:
            self.player2.center_y = touch.y

class PongScreen(Screen):
    def on_enter(self, *args):
        game = PongGame()
        self.add_widget(game)
        # 新增:绑定游戏尺寸到Screen尺寸,窗口缩放时自动适配
        game.bind(size=self.setter('size'))
        game.serve_ball()
        Clock.schedule_interval(game.update, 1 / 60)
        return game

class PongMenu(Screen):
    pass

class PongApp(App):
    def build(self):
        sm = ScreenManager(transition=NoTransition())
        sm.add_widget(PongMenu(name="menu"))
        sm.add_widget(PongScreen(name="game"))
        return sm

if __name__ == "__main__":
    PongApp().run()

为什么这样改能解决问题?

  1. size_hint:1,1:让PongGame自动填满父容器(PongScreen)的全部空间,游戏内部的width、height就是屏幕的实际宽高,元素的位置计算(比如球拍的x坐标、分数标签的位置)就和屏幕完全对齐了。
  2. 尺寸绑定:game.bind(size=self.setter('size'))确保当窗口缩放时,PongGame的尺寸会跟着Screen一起变化,避免窗口调整后游戏又错位。
  3. 原来的碰撞和触摸逻辑都是基于PongGame内部的坐标系,现在这个坐标系和屏幕坐标系完全一致,所以碰撞检测不会偏移,触摸控制的左右半区判断也能正常工作。

新手友好提示

  • Kivy的Widget默认尺寸是(100,100),size_hint是(None,None),如果要让控件自适应父容器,记得设置size_hint:1,1。
  • ScreenManager的每个Screen都是独立的布局容器,所有子控件的位置、尺寸都要基于这个容器来设置,不能直接用全局窗口的尺寸。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 08:23:07