GryTrean
GryTrean

Reputation: 41

KIVY - Python Keep doing while button is pressed

I'm currently trying to make a platformer game for IOS/Android but I'm stuck on a problem. I've created two buttons and a character. I want the character to keep moving until the button is released. By that I mean: I can move the character once, when the button is pressed, but I want it to keep moving until the button is released.

I've tried multiple solution, for example I used pythons time module:

class Level1(Screen):
    posx = NumericProperty(0)
    posy = NumericProperty(0)
    moving = True
    i = 0
    def __init__(self, **kwargs):
        super(Level1, self).__init__(**kwargs)

    def rightmove(self):
        self.posx = self.posx+1
        time.sleep(10)

    def goright(self):
        while self.moving == True:
            self.rightmove()
            i += 1
            if i == 10:
                break


    def stopright(self):
        self.moving == False

but it doesn't work. It think that it somehow is put in an endless loop, because when I press the button the app stop working ("app stopped working..." error).

I have pretty much no idea how I can fix this. I've been trying for the last few hours and havn't found a solution yet. Here's my .py file:

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition,         SlideTransition
from kivy.config import Config
from kivy.core.window import Window
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty, NumericProperty
from kivy.clock import Clock
from kivy.uix.floatlayout import FloatLayout
import time
Config.set('graphics','resizable',0) #don't make the app re-sizeable
#Graphics fix
 #this fixes drawing issues on some phones
Window.clearcolor = (0,0,0,1.) 

language = "english"
curr1msg = 1

class HomeScreen(Screen):
    pass  

class OptionsScreen(Screen):
    pass

class GameScreen(Screen):
    pass

class LevelScreen(Screen):
    pass

class Level1intro(Screen):
    global language
    global curr1msg
    if language == "english" and curr1msg == 1:
        pName = "Pedro"
        msg1 = """Hello my friend!
My name is Pedro and I have a problem. Will you help me?
My spanish studens have a spanish test tomorrow, but I lost the exams!
You are the only one who can help me!"""
        cont = "Press anywhere to continue..."
    elif language == "swedish" and curr1msg == 1:
        pName = "Pedro"
        msg1 = """Hejsan!
Jag är Pedro och jag har ett problem. Kan du hjälpa mig?
Mina spanska-elever har ett spanskaprov imorgon men jag har tappat bort     proven!
Du är den enda som kan hjälpa mig!"""
        cont = "Tryck på skärmen för att fortsätta..."

class Level1(Screen):
        posx = NumericProperty(0)
        posy = NumericProperty(0)
        moving = True
        i = 0
        def __init__(self, **kwargs):
            super(Level1, self).__init__(**kwargs)

        def rightmove(self):
            self.posx = self.posx+1
            time.sleep(10)

        def goright(self):
            while self.moving == True:
                self.rightmove()
                i += 1
                if i == 10:
                    break


        def stopright(self):
            self.moving == False


class ScreenManagement(ScreenManager):
    pass


presentation = Builder.load_file("main.kv")

class MainApp(App):
    def build(self):
        return presentation

if __name__ == "__main__":
    MainApp().run()

And here is my .kv file:

#: import FadeTransition kivy.uix.screenmanager.FadeTransition
#: import SlideTransition kivy.uix.screenmanager.SlideTransition
ScreenManagement:
    transition: FadeTransition()
    HomeScreen:
    OptionsScreen:
    LevelScreen:
    Level1intro:
    Level1:

<HomeScreen>:
    name: 'home'

    FloatLayout:
        canvas:
            Rectangle:
                source:"images/home_background.jpg"
                size: self.size
        Image:
            source:"images/logo.png"
            allow_stretch: False
            keep_ratio: False
            opacity: 1.0
            size_hint: 0.7, 0.8
            pos_hint: {'center_x': 0.5, 'center_y': 0.9}
        Button:
            size_hint: 0.32,0.32
            pos_hint: {"x":0.34, "y":0.4}
            on_press:
                app.root.transition = SlideTransition(direction="left")
                app.root.current = "level"
            background_normal: "images/play_button.png"
            allow_stretch: False
        Button:
            size_hint: 0.25,0.25
            pos_hint: {"x":0.38, "y":0.15}
            on_press:
                app.root.transition = SlideTransition(direction="left")
                app.root.current = 'options'
            background_normal: "images/settings_button.png"

<OptionsScreen>:
    name: 'options'

<LevelScreen>
    name: "level"

    FloatLayout:
        canvas:
            Rectangle:
                source:"images/home_background.jpg"
                size: self.size
        Label:
            text: "[b]Choose Level[/b]"
            markup: 1
            font_size: 40
            color: 1,0.5,0,1
            pos: 0,250
        Button:
            size_hint: 0.1,0.1
            pos_hint: {"x": 0.1, "y": 0.8}
            on_press:
                app.root.current = "level1intro"
            Image:
                source:"images/level1.png"
                allow_stretch: True
                y: self.parent.y + self.parent.height - 70
                x: self.parent.x
                height: 80
                width: 80

        Button:
            background_normal: "images/menu_button.png"
            pos_hint: {"x": 0.4, "y": 0}
            size_hint: 0.3,0.3
            pos_hint: {"x": 0.35}
            on_press:
                app.root.transition = SlideTransition(direction="right")
                app.root.current = "home"

<Level1intro>
    name: "level1intro"

    canvas:
        Rectangle:
            source: "images/background.png"
            size: self.size
    Image:
        source: "images/dialog.png"
        pos_hint: {"y": -0.35}
        size_hint: 0.7,1.0
    Label:
        font_size: 20
        color: 1,1,1,1
        pos_hint: {"x": -0.385, "y": -0.285}
        text: root.pName
    Label:
        font_size: 15
        color: 1,1,1,1
        pos_hint: {"x": -0.15, "y": -0.4}
        text: root.msg1
    Label:
        font_size: 15
        color: 0.7,0.8,1,1
        pos_hint: {"x": 0.025, "y": -0.449}
        text: root.cont
        on_touch_down: 
            app.root.transition = FadeTransition()
            app.root.current = "level1"

<Level1>
    name: "level1"
    canvas:
        Rectangle:
            source: "images/background.png"
            size: self.size

    Button:
        text: ">"
        size_hint: 0.1,0.1
        pos_hint: {"x":0.9, "y":0.0}
        on_press:
            root.goright()
        on_release:
            root.stopright()
    Button:
        text: "<"
        size_hint: 0.1,0.1
        pos_hint: {"x": 0.0, "y": 0.0}
        on_press:
            root.posx = root.posx-1

    Image:
        id: char
        source: "images/idle1.png"
        size: self.size
        pos: root.posx,root.posy

Thank you for your time and help. GryTrean

//I changed "i" to "self.i" and it doesn't fix the problem.

Upvotes: 4

Views: 3605

Answers (4)

Raz Kheli
Raz Kheli

Reputation: 81

This took me a day to figure out:

Let say you want to have a button that has both short press (for event 1) and long press (for event 2).

# Button:
#                 id: btn6
#                 text:'Button6\nDown\nHold 3s'
#                 size_hint: (None,None)
#                 size:self.size
#                 on_press: root.hold_press(self)
#                 on_release: scrn_mnger.transition.direction = 'down'
#                 on_release: scrn_mnger.current = 'scrn_media'

In the python file:

from kivy.clock import Clock

def hold_press(self,inst):
    if inst.state == 'down':
        Clock.schedule_once(lambda x: self.long_press(),1)

def long_press(self):
    if self.ids.btn6.state == 'down':
        self.ids.scrn_mnger.transition.direction = 'left'
        self.ids.scrn_mnger.current = 'scrn_media'

Upvotes: 0

SomeMosa
SomeMosa

Reputation: 451

According to the Kivy API, once the button is pressed, you can create an event. For example,

my_event = Clock.schedule_interval(rightmove, 0.5)

This event will call rightmove() every 0.5 seconds. After creating the event, you can call it through -VariableName-(), so in this case we would put my_event().

Now, if we release the button, we need this to stop looping, so you can bind the button's on_release attribute to preform the cancel() function on the event. Something like this:

my_event.cancel()

or

Button(on_release=my_event.cancel)

Note: you will need my_event to be a global variable so that the start and end functions can access it.

Upvotes: 4

jligeza
jligeza

Reputation: 4693

I have created a simple example for you, featuring how to move a character (in this case, elf warrior level 1) with a button press:

#!/usr/bin/env python3.5
# -*- coding: utf-8 -*-
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.clock import mainthread, Clock

gui = '''
Root:
    orientation: 'vertical'

    arena: arena
    control_button: control_button

    Arena:
        id: arena

    Button
        id: control_button
        size_hint_y: None
        height: dp(50)
        text: 'move'


<Arena@FloatLayout>:
    player: player

    Button:
        id: player
        pos: 150, 300
        text: 'elf warrior\\nlevel 1'
        size_hint: None, None
        size: 100, 100
'''


class Root(BoxLayout):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        @mainthread
        def job():
            self.control_button.bind(on_press=self._on_press)
            self.control_button.bind(on_release=self._on_release)

        job()

    def _on_press(self, button):
        self.arena.start_movement()

    def _on_release(self, button):
        self.arena.stop_movement()


class Arena(FloatLayout):

    def start_movement(self):
        Clock.schedule_interval(self._move_right, 0.01)

    def stop_movement(self):
        Clock.unschedule(self._move_right)

    def _move_right(self, dt):
        self.player.x += 1


class Test(App):

    def build(self):
        return Builder.load_string(gui)


Test().run()

Upvotes: 4

TypeKazt
TypeKazt

Reputation: 318

Here is the button API in kivy. The two bindings that applicable to your problem are the on_press and on_release bindings. You would use these with the Button.bind() method. An example of binding a function to button binding is available here.

Upvotes: 0

Related Questions