0

I'm making a game with kivy for android that has the player controlled by touching on certain areas on the screen. I create invisible buttons (char_controls) that when tapped down, the character moves. The moment the finger is released, I would like the character to stop.

I've bound a function to each button that calls Clock.schedule_interval on a move_up function in the character class (only working with the up button right now). When the button is released, it calls another function that should unschedule the original function (with Clock.unschedule). However, it doesn't do this, and the character keeps on moving.

Am I misusing kivy's bind() function when I use it to bind the button's on_press and on_release behaviours to functions defined in another class? I've noticed that I get an AttributeError: 'Button' object has no attribute 'move_up' when I use self to reference move_up — I must instead refer to the move_up function as character.move_up even when I am referencing it in the character class. If the issue doesn't have to do with the bind() function, how can I make the program unschedule the move_up function?

Below is my code:

from kivy.uix.widget import Widget
from kivy.graphics import Canvas, Rectangle, Color
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.clock import Clock
from kivy.properties import *
from kivy.core.window import Window
from main import *
from render import Layer


class char_controls(FloatLayout):
    '''controls where the character moves. There are 4 regions where the player can tap:
       the top third to go up, the bottom third to go down, the center left to go left
       and the center right to go right. They are buttons.'''

    def __init__(self, **kwargs):
        super(char_controls, self).__init__(**kwargs)
        self.opacity = 0
        self.size = (Window.width, Window.height)

        anchor_bc = AnchorLayout(anchor_x = 'center', anchor_y = 'bottom')
        down_btn = Button(text='', size_hint = (1, 0.3333))
        down_btn.bind(on_press=character.move_down, on_release=character.stop)
        down_btn.bind(on_press=Layer.move_down, on_release=Layer.stop)
        anchor_bc.add_widget(down_btn)
        self.add_widget(anchor_bc)

        anchor_cl = AnchorLayout(anchor_x = 'left', anchor_y = 'center')
        left_btn = Button(text='', size_hint = (0.5, 0.3333))
        left_btn.bind(on_press=character.move_left, on_release=character.stop)
        left_btn.bind(on_press=Layer.move_left, on_release=Layer.stop)
        anchor_cl.add_widget(left_btn)
        self.add_widget(anchor_cl)

        anchor_cr = AnchorLayout(anchor_x = 'right', anchor_y = 'center')
        right_btn = Button(text='', size_hint = (0.5, 0.3333))
        right_btn.bind(on_press=character.move_right, on_release=character.stop)
        right_btn.bind(on_press=Layer.move_right, on_release=Layer.stop)
        anchor_cr.add_widget(right_btn)
        self.add_widget(anchor_cr)

        #button of interest
        anchor_tc = AnchorLayout(anchor_x = 'center', anchor_y = 'top')
        up_btn = Button(text='', size_hint = (1, 0.3333))
        up_btn.bind(on_press=character.schedule_up, on_release=character.stop)
        up_btn.bind(on_press=Layer.move_up, on_release=Layer.stop)
        anchor_tc.add_widget(up_btn)
        self.add_widget(anchor_tc)



class character(Widget):
    '''The character class.'''
    x_pos = 0
    y_pos = 0
    pos = (x_pos, y_pos)

    def __init__(self, **kwargs):
        super(character, self).__init__(**kwargs)
        with self.canvas:
            Color(1., 0, 0)
            character.sprite = Rectangle(pos=self.pos, size=(32, 32))


    #is there a cleaner way to call the movement functions than this? (Eg lambda)
    def schedule_up(self):
        Clock.schedule_interval(character.move_up, 1/30.)

    def move_up(self):
        character.y_pos += 1
        character.pos = (character.x_pos, character.y_pos)
        character.sprite.pos = character.pos
        print('run')

    def move_down(self):
        print('down')

    def move_right(self):
        print('right')

    def move_left(self):
        print('left')

    def stop(self):
        Clock.unschedule(character.move_up) #this is not actually unscheduling the move_up function.
        print('stop') #prints, so the function is working

Thanks in advance!

Bam8000
  • 15
  • 2
  • 7

1 Answers1

0

Watch the console:

from kivy.base import runTouchApp
from kivy.uix.boxlayout import BoxLayout
class Test(BoxLayout):
    def __init__(self, **kw):
        super(Test,self).__init__(**kw)
        from kivy.clock import Clock
        func1=Test.foo
        print func1
        Clock.schedule_interval(func1,1)
        func2=Test.foo
        print func2
        print func1 is func2 # <--------------------Here(False) ^^
        Clock.unschedule(func2)
    def foo(self):
        print 'bar'
runTouchApp(Test())

You are using two different things character.<function> which is scheduled inside the class(seems weird to me if you have self accessible) and you unschedule the self.<function> another function that you only call the same way.

The first is unbound method Test.foo, the second one is bound to a class another unbound that is not the same function, therefore you are unscheduling incorrect function i.e. the one that haven't been scheduled. Either use the same wording everywhere, or use it properly.

Also what you want is class communication, which is explained in some questions under this tag e.g. here - using main App class to handle connecting.

I'm not really sure if you are misusing bind() because I do these thing inside kv, but you should be able to use different keybords at once i.e. bind(on_press=...,on_release=...)

Community
  • 1
  • 1
Peter Badida
  • 11,310
  • 10
  • 44
  • 90
  • Oh dear... I made a typo in my question. In the line clock.unschedule(character.move_up) I accidentally typed clock.unschedule(self.move_up)—obviously, that would be a crucial mistake in the program. So the question I'm asking is why isn't the clock unscheduling the function even when it is referenced as character.move_up? I also don't have access to self for some odd reason... – Bam8000 May 14 '16 at 14:10
  • @Bam8000 nothing really changed, you still unschedule different funtion, watch the example again, I edited it ^^ – Peter Badida May 14 '16 at 14:14
  • Thanks for pointing out the problem! I see the issue now. But why aren't they the same function? I'm referencing them the same way. Should I be taking a different approach? – Bam8000 May 14 '16 at 14:24
  • @Bam8000 edit `foo()` to `return self` and you'll see. With each call you'll create another object - another class, therefore you won't have the same functions. – Peter Badida May 14 '16 at 14:36
  • So how do I make sure that they all reference the same object? – Bam8000 May 14 '16 at 22:10
  • Use `self` when it's possible, let there be only one object of your class(if you don't use more) and that's it. For example if your class is a child in kivy widget, that's already one object. If you call class again e.g. Myclass(), another (and different) object is created - you need to watch more carefully for these mistakes. Also, you can assign function to some global-like variable e.g. to App class., or some class that's above the other classes you will be calling Clock from. – Peter Badida May 14 '16 at 22:23
  • 1
    Okay—I finally figured out the problem. I transferred all of the functions are referenced by the buttons (`schedule_up` and `stop`) to the `char_controls` class (to use `self` instead of `character`) and then I also made the `move_up` function a static method so that it isn't duplicated when it is referenced by by `clock.schedule` and `clock.unschedule`. Thanks so much for your help!! – Bam8000 May 14 '16 at 23:29