I have a simple code in kivy where the ball bounces off the paddle and what i would like to achieve is make ball grounded after the bouncing of the ball ends. So i would like to simulate gravity. Problem is that collide_widget does not detect collision properly. I expect collide_widget will detect collision when ball.y == paddle.top but it detect collision when ball.y < paddle.top and it is always different depending on starting position of ball.
gravity.py
import kivy
kivy.require('1.11.1')
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty
from kivy.vector import Vector
from kivy.clock import Clock
DELTA_TIME = 1.0 / 60.0
class Ball(Widget):
velocity_x = NumericProperty(0)
velocity_y = NumericProperty(0)
velocity = ReferenceListProperty(velocity_x, velocity_y)
def move(self, dt):
self.pos = Vector(*self.velocity) *dt + self.pos
class Paddle(Widget):
velocity_x = NumericProperty(0)
velocity_y = NumericProperty(0)
velocity = ReferenceListProperty(velocity_x, velocity_y)
def move(self, dt):
self.pos = Vector(*self.velocity) *dt + self.pos
class GravityBall(Ball):
def update(self, dt, idle):
if idle == 0:
self.velocity_y -= 20
self.move(dt)
else:
self.velocity_y = 0
self.move(dt)
class GravityGame(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.idle = 0
self.ball = GravityBall()
self.ball.center = (200,300)
self.add_widget(self.ball)
self.paddle = Paddle()
self.paddle.center = (200,100)
self.add_widget(self.paddle)
Clock.schedule_interval(self.update, DELTA_TIME)
def update(self, dt):
self.ball.update(dt, self.idle)
self.paddle.move(dt)
if self.ball.collide_widget(self.paddle) and self.ball.y+5 >= self.paddle.top:
self.ball.velocity_y *= -1
self.idle = 0
elif self.ball.collide_widget(self.paddle) and self.ball.y+5 < self.paddle.top:
self.idle = 1
class GravityApp(App):
def build(self):
return GravityGame()
if __name__ == '__main__':
GravityApp().run()
gravity.kv
#:kivy 1.11.1
<GravityBall>:
size: 30, 30
canvas:
Ellipse:
pos: self.pos
size: self.size
<Paddle>:
size: 100, 30
canvas:
Color:
rgb: 1, 0, 1
Rectangle:
pos: self.pos
size: self.size
The collide_point()
method is not likely to capture the exact moment when your ball touches the paddle, so you must an approximation. Here is a modified version of your GravityGame
class that does so:
class GravityGame(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.idle = 0
self.ball = GravityBall()
self.ball.center = (200, 300)
self.add_widget(self.ball)
self.paddle = Paddle()
self.paddle.center = (200, 100)
self.add_widget(self.paddle)
self.sched = Clock.schedule_interval(self.update, DELTA_TIME) # save a reference to this event
def update(self, dt):
self.ball.update(dt, self.idle)
self.paddle.move(dt)
# capture approximate collision event
if self.ball.collide_widget(self.paddle):
self.ball.y = self.paddle.top # adjust ball position to top of paddle
self.ball.velocity_y *= -0.75 # collision is not perfectly elastic, so use a value between 0 and -1
# check if ball velocity is nearly stopped (does not move a single pixel in an interval)
if self.ball.velocity_y * DELTA_TIME < 1.0:
self.idle = 1
self.sched.cancel() # stop updating
else:
self.idle = 0
Note a reference to the scheduled update event is saved, so that the updates can be cancelled when the ball stops. The reversing of the velocity on a collision should emulate a non-elastic collision, so I have changed *= -1
to *= -0.75
. Finally, if the ball velocity is low enough that the ball does not move in a time interval, then consider the ball to have stopped.