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"
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)
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.