pythonkivy

Collision Changed in Kivy Due to Using ScreenManager


I am attempting to use Kivy to modify the example Pong game to have a main menu. It took me ages, but I finally got the menu system to work and start the game object itself in a new screen. However, this has broken the graphics, and I cannot for the life of me figure out how to restore them. The collision between the ball and paddles still works, but it is badly offset. I understand that this has something to do with the fact that the ScreenManager uses a relative layout, where the default widget I was using before introducing screens was absolute. However, this page describes the exact problem I'm having, where the paddles and ball are offset from their intended center, but the proposed solution of setting pos: 0,0 or just removing pos altogether doesn't help, it just puts everything in the bottom left corner of the screen, and now nothing moves.

I've tried many solutions involving size and position hinting, but in the end, they all break my game completely. The code provided below is my initial starting code, which is still the most functional of everything I've tried.

Please keep in mind that I'm relatively new to Python and still experimenting with Kivy.

Edit: I nearly forgot to include a gif of the offset collision. GIF to incorrect play

main.py

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)
        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()

pong.kv

#:kivy 2.3.1

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

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

<PongGame>:
    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"

Solution

  • I was wondering why right paddle doesn't touch screen border.
    I checked what size has PongBall and PongPaddle
    and for PongBall I get 100,100 but you draw Elipse with size 50, 50.

    If I set sizes for objects like this

    <PongBall>:
        size: 50, 50
        # ... rest ...
    
    <PongPaddle>:
        size: 25, 200
        # ... rest ...
    

    then collision looks correctly and right pad is in correct place (at least on Linux)

    enter image description here


    If I resize window then collision still look correctly and objects have the same sizes but distances are bigger - so I'm not sure if this is expected effect.