Luis Enrique
Luis Enrique

Reputation: 450

Kivy - Trying to Understand Clock.schedule_interval

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

Answers (3)

Nerdcore
Nerdcore

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

tiktok
tiktok

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

Luis Enrique
Luis Enrique

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

Related Questions