I am new to python and kivy, and was following the Kivy tutorial on creating the PongApp line by line when I noticed that after the ball collides with a paddle 25 times, it will not register the 26th collision and thus the player's score will increase.
I assume this issue is related to the velocity moving past a certain speed along the x axis where the ball's position will never interact with the paddle's x position. However what's confusing me is when changing the speed increase per collision from 1.1 to 1.2, the maximum number of collisions before score increases varies from 11 to 18 times.
My question is, what is actually the root cause of this issue, and how can I go about ensuring the ball will always collide with the paddle regardless of the velocity?
main.py:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import (NumericProperty, ReferenceListProperty, ObjectProperty)
from kivy.vector import Vector
from kivy.clock import Clock
from random import randint
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 * 3
ball.velocity = vel.x, vel.y + offset
class PongBall(Widget):
# velocity of the ball on x and y axis
velocity_x = NumericProperty(0)
velocity_y = NumericProperty(0)
# referencelist property so we can use ball.velocity as
# a shorthand, just like e.g. w.pos for w.x and w.y
velocity = ReferenceListProperty(velocity_x, velocity_y)
# ``move`` function will move the ball one step. This
# will be called in equal intervals to animate the ball
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()
#bounce off paddles
self.player1.bounce_ball(self.ball)
self.player2.bounce_ball(self.ball)
# bounce off top and bottom
if (self.ball.y < 0) or (self.ball.top > self.height):
self.ball.velocity_y *= -1
# bounce off left and right to score point
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 PongApp(App):
def build(self):
game = PongGame()
game.serve_ball()
Clock.schedule_interval(game.update, 1.0/60.0)
return game
if __name__ == '__main__':
PongApp().run()
pong.kv:
#:kivy 2.0.0
<PongBall>:
size: 50, 50
canvas:
Ellipse:
pos: self.pos
size: self.size
<PongPaddle>:
size: 25, 200
canvas:
Rectangle:
pos: self.pos
size: self.size
<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: root.x
center_y: root.center_y
PongPaddle:
id: player_right
x: root.width - self.width
center_y: root.center_y
This is a fundamental problem with simple collision algorithms: collision is only checked once each frame after moving the objects, and if they move through each other during that frame the algorithm has no way to notice. The issue is well known, for instance versions of this problem are one reason it's often possible to contrive to clip through walls in computer games (although in general games nowadays do use more complex algorithms, and are tricked in more complex ways).
In a general sense the way to solve this is to use a better collision detection algorithm. For instance, for two spheres you can work out if they ever will have intersected during that frame by working out the point of closest approach of their centre points during the linear movement step of the frame, and comparing that distance to their radii to see if they would have collided.
Doing generic collisions efficiently is in general a hard problem, with various trade-offs you can make depending on what you want to optimise for (e.g. if you want to handle approximate collisions of many objects efficiently you have different problems to if you want to handle precisely a small number of objects with complex shapes). Kivy's example uses a simple algorithm to avoid making the example complex, not because it's a good solution. If you really care about doing collisions efficiently I'd recommend using a physics library to handle it, there are various libraries with python interfaces.