How to efficiently hold a key in Pygame?
Asked Answered
B

8

6

I've found two related questions:

But I want to be specific. How to?

while not done:
    for e in event.get():
        if e.type == KEYDOWN:
            keys = key.get_pressed()
            if e.type == QUIT or keys[K_ESCAPE]:
                done = True
            if keys[K_DOWN]:
                print "DOWN"

When I press the down arrow, it prints, but it prints just once. If I want to print it another time, I need to press it again.

If I use the while keyword instead,

while keys[K_DOWN]:
    print "DOWN"

I get an infinite loop for some obscure reason.

This logical alternative is also useless:

if ((e.type == KEYDOWN) and keys[K_DOWN]):
    print "DOWN"

And there is this other one that somehow cleans the events and you can use while:

while not done:
    for e in event.get():
        if e.type == KEYDOWN:
            keys = key.get_pressed()
            if e.type == QUIT or keys[K_ESCAPE]:
                done = True
            while keys[K_DOWN]:
                print "DOWN"
                event.get()
                keys = key.get_pressed()

But you press the down key for less than one second and it prints thousands of times. (Moving a player would be impossible, and adjusting clock for this does not seem to be the right way to deal with it (And I've tried and I've failed miserably.)).

To press and execute the block thousands of times is useless. What I want, is to press the key and keep going with the action while I don't release it, within the defined game clock speed.

Baccalaureate answered 28/2, 2014 at 11:17 Comment(0)
B
10

Don't mix up event.get() and key.get_pressed().


If you press or release a key, and event is put into the event queue, which you query with event.get(). Do this if you're actually interested if a key was pressed down physically or released (these are the actual keyboard events. Note that KEYDOWN get's added multiple time to the queue depending on the key-repeat settings).

Also, there's no need to query the state of all keys while handling a KEYDOWN event, since you already know which key is pressed down by checking event.key


If you're interested in if a key is hold down (and ignoring the key-repeat, which you probably want), then you should simply use key.get_pressed(). Using a bunch of flags is just unnecessary and will clutter up your code.

So your code could simplified to:

while not done: 
    keys = key.get_pressed() 
    if keys[K_DOWN]: 
        print "DOWN" 
    for e in event.get(): 
        pass # proceed other events. 
             # always call event.get() or event.poll() in the main loop
Borborygmus answered 28/2, 2014 at 15:48 Comment(0)
L
2

I am not familiar with Pygame, but, as I see, the program in it should have an event-based architecture. Unless you get the incoming events and process them, nothing happens. That's why your simple loop becomes infinite: it just does not process events.

while keys[K_DOWN]: # Nobody updates the keys, no events are processed
    print "DOWN"

Then concerning the get_pressed() call. What it returns is a list of keys. So, you are trying to just loop until the key is released. That's a problem. According to this, pygame.event.get() returns immediately even if there are no events in the queue. The call to get() means: my code still has what to do, but I don't want to block the events, so please process the pending events before I continue. If your code is just waiting for an event, that means it has nothing to do.

The function to WAIT (without blocking the inner loop of Pygame) for an event is pygame.event.wait() (the logic is: I have nothing to do in my code until something happens). However, if you use it, you will have to get information about keys pressed or released from the event itself, not from get_pressed().

Here is an example from the comments to the doc page:

for event in pygame.event.get() :
  if event.type == pygame.KEYDOWN :
    if event.key == pygame.K_SPACE :
      print "Space bar pressed down." #Here you should put you program in the mode associated with the pressed SPACE key
    elif event.key == pygame.K_ESCAPE :
      print "Escape key pressed down."
  elif event.type == pygame.KEYUP :
    if event.key == pygame.K_SPACE :
      print "Space bar released."
    elif event.key == pygame.K_ESCAPE :
      print "Escape key released." #Time to change the mode again
Lowenstern answered 28/2, 2014 at 11:35 Comment(6)
In the main loop, Ive just added "print event.get()" and started to see and understand how it works, and you're right. If I loop through all the event list at each frame, it gets the pressed, and then returns it, and gets the released later on (It does not keep pressing while the key is down. The KEYDOWN is just an opposite for RELEASED, not "hold"). It's not possible to hold the key. The problem is that with the wait() function, the result is exactly the same. It waits for the keypressed, but I, with the button held, dont get anymore keypresses until I RELEASE and then press it again.Baccalaureate
Doesn't it give you KEYUP events? According to a logic that is common to event-based apps, you should receive a KEYDOWN or KEYUP event and store the information needed for your app to change its behavior. Look at a sample code I added to the answer.Lowenstern
It gives a keydown and a keyup. But the "keydown" exists only once when the key is pressed. So, the solution is not in the events, but in something similar to the post down here about flags. It would be possible to check for a keypressed, and turn some flag to true, and when the released one appears, it flags to false (In that way, it would be possible to use a while loop. While the flag is true, it keeps the action going).Baccalaureate
It SHOULD appear only once. When it appears, of course you should set a flag. Do you keep the action going in a timer, or in the loop? Usually you may want to perform some actions from time to time if some flag is true.Lowenstern
The KEYDOWN events appears everytime your keyboard driver issues it; so it generally appears once, then, after a short amount of time, your event queue is spammed with KEYDOWN events. It depends on your keyboard settings.Borborygmus
When pygame is initialized the key repeat should be disabled. Unless you explicitly call pygame.key.set_repeat() to enable it. pygame.org/docs/ref/key.htmlLowenstern
C
1

I like to take a slightly different approach to this problem. Instead of checking if the key is pressed and taking some action when it is, set a flag on key down and unset it on key up. Then in the function to update the player's position, check the flag and update accordingly. The following pseudo-Python explains what I'm getting at:

if down_key_pressed:
    down_flag = True
elif down_key_released:
    down_flag = False
elif right_key_pressed:
    etc...

This should be done in a separate loop that takes the player's input. Then in update_player_position() you can do:

if down_flag:
    move_player_down()
elif right_flag:
    move_player_right()

This example assumes four-directional movement, but you could extend it to eight-directional fairly easily by just using if down_flag and right_flag instead.

Cashew answered 28/2, 2014 at 11:34 Comment(0)
B
0

Dominik's solution is the perfect one (separating event.get() from the keyboard ones). It works perfectly! Finally, no more problems with pygame's input.

Flags:

flag = False # The flag is essential.
while not done:
    for e in event.get(): # At each frame it loops through all possible events.
        keys = key.get_pressed() # It gets the states of all keyboard keys.
        if e.type == QUIT:
            done = True
        if e.type == KEYDOWN: # If the type is KEYDOWN (DIFFERENT FROM "HELD").
            if keys[K_DOWN]: # And if the key is K_DOWN:
                flag = True # The flag is set to true.
        elif e.type == KEYUP: # The very opposite.
            if keys[K_DOWN]:
                flag = False
    if flag == True: # DON'T USE "while flag == true", it'll crash everything. At every frame, it'll check if the flag is true up there. It's important to remember that the KEYDOWN worked ONLY ONCE, and it was enough to set that flag. So, at every frame, THE FLAG is checked, NOT THE KEYDOWN STATE. Every "player movement" while a key is being held MUST be done through flags.
        print "DOWN"
Baccalaureate answered 28/2, 2014 at 12:28 Comment(1)
I would recommend to set the initial state of flag based on the initial key.get_pressed(). What if it already is pressed when you start?Lowenstern
X
0

You can get keydown event repeatedly if you use pygame.key.set_repeat(# millisecond) to set the time limitation for each key event. Quote: when the keyboard repeat is enabled, keys that are held down will generate multiple pygame.KEYDOWN events. The delay is the number of milliseconds before the first repeated pygame.KEYDOWN will be sent. After that another pygame.KEYDOWN will be sent every interval milliseconds. If no arguments are passed the key repeat is disabled. When pygame is initialized the key repeat is disabled. please see following link for detail http://www.pygame.org/docs/ref/key.html#pygame.key.set_repeat

Xochitlxp answered 5/12, 2014 at 1:21 Comment(1)
I've asked this question 9 months ago, and now I understand it. If I want to press a key and keep getting "True" values endlessly while the key is pressed, I need to use "pygame.key.get_pressed()" instead of iterating over the list of all events. That's why in my engine (Ergame on Github) I've created a distinction between the terms "press" and "push" (Where PUSH means "only one True return per press"). Using "press" for going back menus, etc, can become frustrating since you're going to close more than one menu even with a "fast press" (So, it has nothing to do with .set_repeat()).Baccalaureate
M
0

I am using a different approach on holding down a key that I am using in the specific task of moving an object left or right.

I do not care about the computer knowing that a key is actually held down.

When I press a key, a variable that I define for that key (EG: left arrow) is set to True. Until that key is unpressed (with the event pygame.KEYUP) the "movement to left" is performed.

Morion answered 8/1, 2018 at 20:48 Comment(0)
P
0

If we refer to the pygame documentation, there's a function called:

pygame.key.set_repeat()

This sets the behavior for when you hold down keys, and the default behavior is to only trigger one event. If you want to trigger events more then once, call this function after you call

pygame.init()

To get the desired effect, you can pass in a parameter that represents the interval that pygame will repeat key events.

Don't use the while statement like you were, because all it takes is the key event to be true once to lock your program in endless iteration.

Prichard answered 19/6, 2023 at 19:17 Comment(0)
H
-2

This is basically How I did it, well Go to www.cswonders.com, and it might help you . Go to level 3, then 3.6, and go through the tutorial. I learned it from there. Here is my code.

def update(self):
    self.speedx = 0
    self.speedy = 0
    # If left or right key is pressed, move left or right
    pressed_key = pygame.key.get_pressed()
    if pressed_key[pygame.K_LEFT]: 
        self.speedx = -10          
    if pressed_key[pygame.K_RIGHT]:
        self.speedx = 10
    if pressed_key[pygame.K_UP]:
        self.speedy = -10
    if pressed_key[pygame.K_DOWN]:
        self.speedy = 10

    self.rect.x += self.speedx
    self.rect.y += self.speedy

    # No further move if off screen
    if self.rect.right > SCREEN_WIDTH:
        self.rect.right = SCREEN_WIDTH
    if self.rect.left < 0:
        self.rect.left = 0
Heartrending answered 26/2, 2017 at 16:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.