Reputation: 450
I'm new to Kivy, I was doing pong tutorial and everything was going great, I adapted it to 4 players, each paddle has its own color and if the ball hits a paddle it gets the color of it, this way if the ball gets off the screen the last-hitter gets a score point.
I'm running in Ubuntu 14,04, with a virtualenv.
For easier download and checking, all code can be found here: Pong_kivy
What's the problem?
After a game is over once it restarts the ball seems like it's increasing speed everytime, until it crashes, which is not caused by velocity vector, I checked it, hence I think that I'm handling wrong the Clock.Schedule function. I can't seem to find whats wrong with so any help is appreciated.
pong.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
from kivy.graphics import *
from kivy.uix.button import Button, Label
from random import randint
class PongPaddle(Widget):
score = NumericProperty(0)
orientation = ObjectProperty([0, 0])
color = ObjectProperty([0,0,0,1])
def bounce_ball(self, ball):
if self.collide_widget(ball):
vx, vy = ball.velocity
ball.color = self.color
ball.last_hit = int(self.custom_id)
if self.orientation[0] == 25:
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
else:
offset = (ball.center_x - self.center_x) / (self.width / 2)
bounced = Vector(vx, -1 * vy)
vel = bounced * 1.1
ball.velocity = vel.x + offset, vel.y
class PongBall(Widget):
velocity_x = NumericProperty(0)
velocity_y = NumericProperty(0)
last_hit = 0
color = ObjectProperty([0,0,0,1])
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)
player3 = ObjectProperty(None)
player4 = ObjectProperty(None)
l = Label()
btn1 = Button(text='Restart')
win_game = 1
def start(self):
Clock.schedule_interval(self.update, 1.0 / 60.0)
def stop(self):
# Stop updating
Clock.unschedule(self.update)
def init(self):
## Setup players
self.player1.orientation = [25, 200]
self.player1.color = [1,0,0,1]
self.player1.score = 0
# Player 2
self.player2.orientation = [25, 200]
self.player2.color = [0,1,0,1]
self.player2.score = 0
# Player 3
self.player3.orientation = [200, 25]
self.player3.color = [0,0,1,1]
self.player3.score = 0
# Player 4
self.player4.orientation = [200, 25]
self.player4.color = [1,1,0,1]
self.player4.score = 0
def serve_ball(self):
# Ball velocity - Add 2 to avoid 0
vel = (randint(-8,6)+2, randint(-8,6)+2)
# Setup ball
self.ball.center = self.center
self.ball.velocity = vel
self.ball.last_hit = 0
self.ball.color = [1,1,1,1]
def update(self, dt):
self.ball.move()
#Bounce out the of paddles - Why do all check? Only can bounce on one any given time
self.player1.bounce_ball(self.ball)
self.player2.bounce_ball(self.ball)
self.player3.bounce_ball(self.ball)
self.player4.bounce_ball(self.ball)
#bounce ball off bottom or top - This is for 2 players game
# if (self.ball.y < self.y) or (self.ball.top > self.top):
# self.ball.velocity_y *= -1
# Went of any side? - Last hitter gets a goal
if self.ball.x < self.x or self.ball.x > self.width or self.ball.y < self.y or self.ball.y > self.height:
if self.ball.last_hit == 1:
self.player1.score += 1
elif self.ball.last_hit == 2:
self.player2.score += 1
elif self.ball.last_hit == 3:
self.player3.score += 1
elif self.ball.last_hit == 4:
self.player4.score += 1
self.serve_ball()
if self.player1.score >= self.win_game:
self.player_win(1)
elif self.player2.score >= self.win_game:
self.player_win(2)
elif self.player3.score >= self.win_game:
self.player_win(3)
elif self.player4.score >= self.win_game:
self.player_win(4)
def player_win(self, player_int):
# Remove Ball and players
self.clear_widgets()
# Configure Label and Btn
self.l.text ='Player ' + str(player_int) + ' Wins!!'
self.l.top = self.top-50
self.l.font_size = 50
self.l.center_x = self.width/2
self.btn1.bind(on_press=self.restart)
self.btn1.center_x = self.width/2
self.btn1.top = self.top/2
self.add_widget(self.l)
self.add_widget(self.btn1)
self.stop()
def on_touch_move(self, touch):
if touch.x < self.width / 3 and touch.y > self.height / 6 \
and touch.y < 5 * self.height/6:
self.player1.center_y = touch.y
if touch.x > self.width - self.width / 3 and touch.y > self.height / 6 \
and touch.y < 5 * self.height / 6:
self.player2.center_y = touch.y
if touch.y < self.height / 3 and touch.x > self.width / 6 \
and touch.x < 5 * self.width / 6:
self.player4.center_x = touch.x
if touch.y > 2* self.height / 3 and touch.x > self.width / 6 \
and touch.x < 5 * self.width / 6:
self.player3.center_x = touch.x
# Method update layout
def update_rect(instance, value):
instance.rect.pos = instance.pos
instance.rect.size = instance.size
def restart(self, instance):
# Remove btn and labels
self.clear_widgets()
# Add what I want
self.add_widget(self.ball)
self.add_widget(self.player1)
self.add_widget(self.player2)
self.add_widget(self.player3)
self.add_widget(self.player4)
self.init()
self.serve_ball()
self.start()
class PongApp(App):
def build(self):
game = PongGame()
game.init()
game.serve_ball()
game.start()
return game
if __name__ == '__main__':
PongApp().run()
pong.kv file
#:kivy 1.0.9
<PongBall>:
size: 50, 50
canvas:
Color:
rgba: self.color[0], self.color[1], self.color[2], self.color[3]
Ellipse:
pos: self.pos
size: self.size
<PongPaddle>:
size: root.orientation[0], root.orientation[1]
canvas:
Color:
rgba: self.color[0], self.color[1], self.color[2], self.color[3]
Rectangle:
pos:self.pos
size:self.size
<PongGame>:
ball: pong_ball
player1: player_left
player2: player_right
player3: player_top
player4: player_bottom
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: 'black.jpg'
Label:
font_size: 15
center_x: root.width / 8
top: self.height*0.95
text: "P1: " + str(root.player1.score)
Label:
font_size: 15
center_x: root.width / 8
top: self.height*0.80
text: "P2: " + str(root.player2.score)
Label:
font_size: 15
center_x: root.width / 5
top: self.height*0.95
text: "P3: " + str(root.player3.score)
Label:
font_size: 15
center_x: root.width / 5
top: self.height*0.80
text: "P4: " + str(root.player4.score)
PongBall:
id: pong_ball
center: self.parent.center
PongPaddle:
id: player_left
custom_id: "1"
x: root.x
center_y: root.center_y
PongPaddle:
id: player_right
custom_id: "2"
x: root.width-self.width
center_y: root.center_y
PongPaddle:
id: player_top
custom_id: "3"
y: root.height - self.height
center_x: root.center_x
PongPaddle:
id: player_bottom
custom_id: "4"
y: root.y
center_x: root.center_x
Upvotes: 3
Views: 14832
Reputation: 33
Refer to section 9.2.3 Trigger Events in Kivy Documentation
Sometimes you may want to schedule a function to be called only once for the next frame, preventing duplicate calls. You might be tempted to achieve that like so:
EXAMPLE CODE:
//First, schedule once.
event = Clock.schedule_once(my_callback, 0)
//Then, in another place you will have to unschedule first to avoid duplicate call. Then you can schedule again.
Clock.unschedule(event)
event = Clock.schedule_once(my_callback, 0)
This way of programming a trigger is expensive, since you’ll always call unschedule, even if the event has already completed. In addition, a new event is created every time. Use a trigger instead:
trigger = Clock.create_trigger(my_callback)
//later
trigger()
Each time you call trigger(), it will schedule a single call of your callback. If it was already scheduled, it will not be rescheduled.
Upvotes: 1
Reputation: 456
The problem doesn't seem to be the Clock. The restart
method is being called multiple times and it appears to be because you're binding the on_press
call every time you call init()
If you move that line to an __init__
function then bind is called only once, the button only calls restart once for every press and the error doesn't occur:
def __init__(self, **kwargs):
super(PongGame, self).__init__(**kwargs)
self.btn1.bind(on_press=self.restart)
Upvotes: 1
Reputation: 450
I found out what was wrong, by changing:
def start(self):
Clock.schedule_interval(self.update, 1.0 / 60.0)
To:
def start(self):
Clock.unschedule(self.update)
Clock.schedule_interval(self.update, 1.0 / 60.0)
I don't understand why this change is needed but it seems I'm creating events in differents threads maybe someone could explain it better.
Upvotes: 0