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.
Related
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.
I'm trying to make my bird image move up and then automatically come down. I was watching a youtube video on this here's the link https://www.youtube.com/watch?v=2dn_ohAqkus&ab_channel=ErikSandberg
his seems to be working but not mines. Around Minute 14 he starts coding about what I'm talking about. One thing that does work for me is when I click on the screen the bird changes. Here's my code
class GameScreen(Screen):
pass
class Bird(Image):
velocity = NumericProperty(0)
def on_touch_down(self, touch):
self.source = "icons/bird2.png"
self.velocity = 150
super().on_touch_down(touch)
def on_touch_up(self, touch):
self.source = "icons/bird1.png"
super().on_touch_up(touch)
def move_bird(self, time_passed):
bird = self.root.ids.bird
bird.y = bird.y + bird.velocity * time_passed
bird.velocity = bird.velocity - self.GRAVITY * time_passed
Clock.schedule_interval(self.move_bird, 1/60)
my kivy code note this in inside a FloatLayout
Bird:
source: "icons/bird1.png"
size_hint: None, None
size: 475, 475
pos_hint: { "center_x": .5, "center_y": .4}
id: bird
You will not be able to change the bird position when you have set pos_hint (as you did in the kivy code). The pos_hint will take priority over pos. Try replacing the pos_hint with some initial pos in the kivy code.
If your run it once the ball is about to stop it starts skipping up and down a little bit and disappears through the floor.
So i was following the kivy tutorial for the pong game and half way through i thought why not make the ball have gravity...i kind of figured that out though I don't know wether it's good code or not but that's not my problem. I already compared it to some other code basically doing the same and didn't find any differences. Could somebody tell me what i have done wrong? (Sorry i had to paste all my code in here but i don't know where the problem is...)
import kivy
kivy.require("1.10.1")
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 PongBall(Widget):
velocity_x = NumericProperty(0)
velocity_y = NumericProperty(0)
velocity = ReferenceListProperty(velocity_x, velocity_y)
def update_speed(self):
self.velocity[1] = self.velocity[1] - 15/60
self.pos = Vector(*self.velocity) + self.pos
class PongGame(Widget):
ball = ObjectProperty(None)
def serve_ball(self):
self.ball.center = self.center
self.ball.velocity = Vector(0, 0)
def update(self, dt):
self.ball.update_speed()
if (self.ball.y < 0) or (self.ball.top > self.height):
self.ball.velocity_y *= -1
if (self.ball.x < 0) or (self.ball.right > self.width):
self.serve_ball()
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()
This is my kv file:
#:kivy 1.10.1
<PongBall>:
size: 50, 50
canvas:
Ellipse:
pos: self.pos
size: self.size
<PongGame>:
ball: pong_ball
canvas:
Rectangle:
pos: self.center_x - 5, 0
size: 10, self.height
Label:
font_size: 70
center_x: root.width / 4
top: root.height * 9 / 10
text: "0"
Label:
font_size: 70
center_x: root.width * 3 / 4
top: root.height * 9 / 10
text: str(pong_ball.velocity[1])
PongBall:
id: pong_ball
center: self.parent.center
I want the ball to slow down until it lays on the ground and doesn't move.
this is just a common problem with floats ...
just in your update speed function do something like
if velocity <= 1e-6: # some really small number
velocity = 0.0
its also probably a good idea to fix your y position in your move function
if (self.ball.y < 0) or (self.ball.top > self.height):
self.ball.y = 0.1
self.ball.velocity_y *= -1
I'm trying to make a basic visual exercise in Python and Kivy, but i'm stuck in this part of the code, how to make the ball start on the bottom of the screen?
Here's the .py file:
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
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)
def serve_ball(self, vel=(0, 4)):
self.ball.center = self.center
self.ball.velocity = vel
def update(self, dt):
self.ball.move()
# bounce ball off bottom or top or left or right
if (self.ball.y < self.y) or (self.ball.top > self.top):
self.ball.velocity_y = -2
self.ball.velocity_x = -4
# bounce ball off
if(self.ball.x < 0):
self.ball.velocity_x = 4
self.ball.velocity_y = 0
if(self.ball.x + self.ball.width > self.right):
self.ball.velocity_x *= -1
self.ball.velocity_y = 4
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()
.kv file:
<PongBall>:
size: 50, 50
canvas:
Ellipse:
pos: self.pos
size: self.size
<PongGame>:
ball: pong_ball
PongBall:
id: pong_ball
center: self.parent.center
This code make the widget starts on center of the screen, i'm trying to make start on bottom, without success
Thanks for any tips!
The problem in your case is that the PongBall's initial position is the center of the PongGame:
<PongGame>:
ball: pong_ball
PongBall:
id: pong_ball
center: self.parent.center # <---
What you must do is that when you create the initial position is established. Another error is that you are launching the game before the window is displayed, instead of launching it in build() you must do it in on_start():
*.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
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)
def serve_ball(self, vel=(0, 4)):
self.ball.center = self.initial_pos
self.ball.velocity = vel
def update(self, dt):
self.ball.move()
# bounce ball off bottom or top or left or right
if self.ball.y < self.y or self.ball.top > self.top:
self.ball.velocity_y = -2
self.ball.velocity_x = -4
# bounce ball off
if self.ball.x < 0:
self.ball.velocity_x = 4
self.ball.velocity_y = 0
if self.ball.x + self.ball.width > self.right:
self.ball.velocity_x *= -1
self.ball.velocity_y = 4
class PongApp(App):
def build(self):
game = PongGame()
return game
def on_start(self):
self.root.serve_ball()
Clock.schedule_interval(self.root.update, 1.0 / 60.0)
if __name__ == '__main__':
PongApp().run()
*.kv
<PongBall>:
size: 50, 50
canvas:
Ellipse:
pos: self.pos
size: self.size
<PongGame>:
ball: pong_ball
initial_pos: [self.center_x , pong_ball.height/2]
PongBall:
id: pong_ball
In build() an incorrect size of the widgets is obtained since these are updated instants before being launched, it is convenient to use on_start() to obtain the size on which the initial position depends.
I'm trying to make an kivy app with starting menu, but I can't display my Pong Game on the second screen. How should I refer to the game to make it visible? I tried and searched but can't find anything. I'm 100% sure that PongGame work corectly, I just can't display it. It would be great if someone could show me how to do it corectly.
Main.py:
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen, WipeTransition
from kivy.properties import ObjectProperty
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty,\
ObjectProperty
from kivy.vector import Vector
from kivy.clock import Clock
from kivy.uix.popup import Popup
from kivy.uix.label import Label
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()
#bounce of paddles
self.player1.bounce_ball(self.ball)
self.player2.bounce_ball(self.ball)
#bounce ball off bottom or top
if (self.ball.y < self.y) or (self.ball.top > self.top):
self.ball.velocity_y *= -1
#went of to a side 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))
if self.player1.score == 10:
popup = Popup(title='Test popup', content=Label(text='Hello world'), auto_dismiss=False)
return popup
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 ScreenThree(Screen):
pass
class ScreenTwo(Screen):
pass
class ScreenOne(Screen):
pass
class Manager(ScreenManager):
screen_one = ObjectProperty(None)
screen_two = ObjectProperty(None)
screen_three = ObjectProperty(None)
class ScreensApp(App):
def build(self):
m = Manager(transition=WipeTransition())
return m
if __name__ == '__main__':
ScreensApp().run()
screen.kv:
<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
<ScreenOne>:
Button:
text: "Screen 1 >> Screen 2"
on_press: root.manager.current = 'screen2'
<ScreenTwo>:
def build(self):
game = PongGame()
game.serve_ball()
Clock.schedule_interval(game.update, 1.0 / 60.0)
return game
<ScreenThree>:
Button:
text: "Screen 3 >> Screen 1"
on_press: root.manager.current = 'screen1'
<Manager>:
id: screen_manager
screen_one: screen_one
screen_two: screen_two
screen_three: screen_three
ScreenOne:
id: screen_one
name: 'screen1'
manager: screen_manager
ScreenTwo:
id: screen_two
name: 'screen2'
manager: screen_manager
ScreenThree:
id: screen_three
name: 'screen3'
manager: screen_manager
Well! there were a lot of errors in your program and I had to make ton's of improvement. (I understand it as you are beginner)
First of all, please read complete kivy language documentation, as I can clearly see that you directly started with coding without grasping the basics.
You may make couple of good games but in the long run you will face such problems which can't be solved without clear concepts.
And unfortunately you won't be able to discover the true power of kivy. :)
You might also wanna do revision of your python concepts.
Some improvements aren't worth mentioning but were important, you will get an idea when you read the code.
Improvement 1:
An application can be built if you return a widget on build(), or if you set self.root.(But you cannot make the application again n again)
as you did here:
<ScreenTwo>:
def build(self):
game = PongGame()
game.serve_ball()
Clock.schedule_interval(game.update, 1.0 / 60.0)
return game
Improvement 2:
As you click on button play ping pong which is of screen game. your game starts with serve of ball.
on_release: root.current = 'game';game.serve_ball()
(for knowledge)
If you still get black screen you might want to check the name of kivy file, for that you could either go to kivy documentation or this link
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 __init__(self, *args, **kwargs):
super(PongGame, self).__init__(*args, **kwargs)
Clock.schedule_interval(self.update, 1.0 / 60.0)
def serve_ball(self, vel=(4, 0)):
self.ball.center = self.center
self.ball.velocity = vel
def update(self, dt):
self.ball.move()
#bounce of paddles
self.player1.bounce_ball(self.ball)
self.player2.bounce_ball(self.ball)
#bounce ball off bottom or top
if (self.ball.y < self.y) or (self.ball.top > self.top):
self.ball.velocity_y *= -1
#went of to a side 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 Manager(ScreenManager):
pass
class ScreensApp(App):
def build(self):
self.load_kv('t6.kv')
return Manager(transition=WipeTransition())
if __name__ == '__main__':
ScreensApp().run()
Here is the kv file.
<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
<Manager>:
id: screen_manager
Screen:
name: 'home'
Button:
text: 'Play Ping Pong'
halign: 'center'
valign: 'middle'
font_size: 100
text_size: self.size
on_release: root.current = 'game';game.serve_ball()
Screen:
name: 'game'
PongGame:
id: game