Search code examples
pythonandroidkivymovekivy-language

how to have fluid image movement? | Kivy


I have a code with an image that I can move with my keyboard. At first I had a very jerky movement and so I added a kivy clock. Now when you press a key to move the image the image moves 30 times per second and then stops, which allows for a rather fluid movement.

But when I keep the key, which allows to move the image, pressed the image no longer moves.

I also tried to have a smooth movement with the speed of kivy ( as in the pong game tutorial https://kivy.org/doc/stable/tutorials/pong.html) but it does not work

How to fix this problem please? ( or how to have a smooth image movement)

I hope my question is clear :),

Thank you in advance for your help

Here is my code:

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.core.window import Window
from kivy.clock import Clock
from kivy.properties import (
    NumericProperty, ReferenceListProperty, ObjectProperty
)
from kivy.vector import Vector

class character(Widget):
    pass


class MoveableImage(Image):

    def __init__(self, **kwargs):
        super(MoveableImage, self).__init__(**kwargs)
        self._keyboard = Window.request_keyboard(None, self)
        if not self._keyboard:
            return
        self._keyboard.bind(on_key_down=self.on_keyboard_down)

    def on_keyboard_down(self, keyboard, keycode, text, modifiers):
        if keycode[1] == 'right':
            Clock.schedule_interval(self.droite, 1.0 / 30.0)
            Clock.schedule_once(self.stop_droite , 0.1)
        elif keycode[1] == 'left':
            Clock.schedule_interval(self.gauche, 1.0 / 30.0)
            Clock.schedule_once(self.stop_gauche , 0.1)
        elif keycode[1] == 'up':
            Clock.schedule_interval(self.up, 1.0 / 30.0)
            Clock.schedule_once(self.stop_up, 0.1)
            Clock.schedule_once(self.down1, 0.2)
        else:
            return False
        return True
    
    def saut(self, keyboard):
        self.y -= 70

    def droite(self, keyboard):
        self.x += 12
        
    def stop_droite(self, dt):
        Clock.unschedule(self.droite)
        
    def gauche(self, keyboard):
        self.x -= 12
        
    def stop_gauche(self, dt):
        Clock.unschedule(self.gauche)
        
    def up(self, keyboard):
        self.y += 50
        
    def stop_up(self, dt):
        Clock.unschedule(self.up)
    
    def down1(self, keyboard):
        Clock.schedule_interval(self.down2, 1.0 / 30.0)
        Clock.schedule_once(self.stop_down, 0.1)
    
    def down2(self, keyboard):
        self.y -= 50
        
    def stop_down(self, dt):
        Clock.unschedule(self.down2)
        
            
class gameApp(App):
    def build(self):
        wimg = MoveableImage(source='tools/theming/defaulttheme/slider_cursor.png')
        m = character()
        m.add_widget(wimg)
        return m


if __name__ == '__main__':
    gameApp().run()

Solution

  • You are scheduling the repeating functions for movement and at the same time you schedule a function that unschedules the repeating fuction after 0.1 s, which will behave somewhat weird.

    If you look at the pong tutorial more closely, the code they use is Clock.schedule_interval(game.update, 1.0/60.0) and that's it basically.

    So for your needs, you would call Clock.schedule_interval(self.update, 1.0/30.0) in your __init__. From that point on, the self.update would be called 30 times per second. The self.update would look something like this:

    def update(self):
        if self.rightPressed:
            self.x += 12
        if self.leftPressed:
            self.x -= 12
        if self.upPressed:
            self.y += 50
    

    We should make sure to unschedule the self.update at some point. A destructor is convenient for this:

    def __del__(self):
        self.unschedule(self.update)
    

    And finally utilizing self._keyboard.bind(on_key_down=self.on_keyboard_down, on_key_up=self.on_keyboard_up):

    def on_keyboard_down(self, keyboard, keycode, text, modifiers):
        if keycode[1] == 'right':
            self.rightPressed = True
        elif keycode[1] == 'left':
            self.leftPressed = True
        elif keycode[1] == 'up':
            self.upPressed = True
        else:
            return False
        return True
    
    def on_keyboard_up(self, keyboard, keycode, text, modifiers):
        if keycode[1] == 'right':
            self.rightPressed = False
        elif keycode[1] == 'left':
            self.leftPressed = False
        elif keycode[1] == 'up':
            self.upPressed = False
        else:
            return False
        return True
    

    So your code might look like this:

    from kivy.app import App
    from kivy.uix.widget import Widget
    from kivy.uix.image import Image
    from kivy.core.window import Window
    from kivy.clock import Clock
    from kivy.properties import (
        NumericProperty, ReferenceListProperty, ObjectProperty
    )
    from kivy.vector import Vector
    
    class character(Widget):
        pass
    
    
    class MoveableImage(Image):
    
        def __init__(self, **kwargs):
            super(MoveableImage, self).__init__(**kwargs)
            self._keyboard = Window.request_keyboard(None, self)
            if not self._keyboard:
                return
            self._keyboard.bind(on_key_down=self.on_keyboard_down, on_key_up=self.on_keyboard_up)
            Clock.schedule_interval(self.update, 1.0/30.0)
    
        def __del__(self):
            Clock.unschedule(self.update);
    
        def on_keyboard_down(self, keyboard, keycode, text, modifiers):
            if keycode[1] == 'right':
                self.rightPressed = True
            elif keycode[1] == 'left':
                self.leftPressed = True
            elif keycode[1] == 'up':
                self.upPressed = True
            else:
                return False
            return True
    
        def on_keyboard_up(self, keyboard, keycode, text, modifiers):
            if keycode[1] == 'right':
                self.rightPressed = False
            elif keycode[1] == 'left':
                self.leftPressed = False
            elif keycode[1] == 'up':
                self.upPressed = False
            else:
                return False
            return True
    
    
        def update(self):
            if self.rightPressed:
                self.x += 12
            if self.leftPressed:
                self.x -= 12
            if self.upPressed:
                self.y += 50   
                
    class gameApp(App):
        def build(self):
            wimg = MoveableImage(source='tools/theming/defaulttheme/slider_cursor.png')
            m = character()
            m.add_widget(wimg)
            return m
    
    
    if __name__ == '__main__':
        gameApp().run()