How can I add a delay between keyboard button presses?

Viewed 154

I'm trying to add a "shoot" function to my game, where the player -a drone- would shoot a bullet upon pressing the space bar on the keyboard.

I've done that successfully but I also want the function to be on delay after executing it once. For example the player could press spacebar once then have to wait 2-3 seconds before being able to execute the function again. Hence my question, how could I make that work?

This is the current functional code.

main.py

    def shoot(self):
        self.get_drone_coordinates()

        x = self.drone_coordinates[0][0]
        y = self.drone_coordinates[0][1] - 20

        self.drone_bullet = Image(source="images/drone_bullet.png",
                                  pos=(x, y))

        self.add_widget(self.drone_bullet)
        self.bullets.append(self.drone_bullet)

controls.py

def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
    elif keycode[1] == 'spacebar':
        self.shoot()

I tried importing the time library and adding a time.sleep() function in the function in controls.py, but that froze the entire program.

import time

def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
    elif keycode[1] == 'spacebar':
        self.shoot()
        time.sleep(3)

Any help would be appreciated! Thanks in advance!

2 Answers

The best choice for async events in kivy is to use asyncio. First at all you have to be sure your app runs from asyncio.run() instead of App.run(). To do this, you have to import asyncio and also you have to add a method to your App class. See the example below:

import asyncio

######## MAIN APP ######## 
class ExampleApp(App):
    def build(self):
        #Your  app stuff here

    async def kivyCoro(self):  #This is the method that's gonna launch your kivy app
        await self.async_run(async_lib='asyncio')
        print('Kivy async app finished...')

    # This func will start all the "tasks", in this case the only task is the kivy app
    async def base(self):
        (done, pending) = await asyncio.wait({self.kivyCoro()}, 
    return_when='FIRST_COMPLETED')

if __name__ == '__main__':
    instanceApp = ExampleApp() #You have to instanciate your App class
    asyncio.run(instanciaApp.base()) # Run in async mode

The code above will make your kivyApp to run as a "Task" (a task it's something that's gonna be concurrently executed). A task or coroutine can call another task within itself, so you can have another async task running during the execution of your kivyApp.

# Here you create a coroutine (global scope)
async def delayWithoutFreeze():
    print('Wait 3 segs...')
    await asyncio.sleep(3)
    print('3 segs elapsed...')

Finally you just have to call that async function as a task, to do that from your _on_keyboard_down function:

def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
    j = 0 #This is a counter to know if a delay task is running
    elif keycode[1] == 'spacebar':
        for task in asyncio.all_tasks(): #This loop checks if there's a delay already running
            if task.get_name()=='shootTask':
                j+=1
                print('Theres already {} shootTask'.format(j))
        if j==0: #If j == 0 means that no delay is running so you can press and action again
            # This is how you call the async func
            asyncio.create_task(delayWithoutFreeze(), name='shootTask') 
            self.shoot()
        

NOTE: Idk why u use elif instead if in the _on_keyboard_down. You only have 1 condition. no elif needed

For more info about coroutines and tasks: https://docs.python.org/3/library/asyncio-task.html

Try this:

import time
def _on_keyboard_down(self, keyboard, keycode, text, modifiers,cooldown==3):
  elif keycode[1] == 'spacebar':
      try:
            LastShot
      except:
            LastShot=0
      if time.time()-LastShot>=cooldown:
            self.shoot()
            LastShot=time.time()

Basically, it checks whether the time between the last shot and now is greater or equal to the cooldown, then records the time of the last shot.

time.time() doesn't sleep, so it doesn't stop your program. This makes sure that your program keeps running, even when waiting for the cooldown to finish.

Related