How can I allow keyboard inputs to override each other in pygame?
Question:
I’m fairly new to Python (and programming in general), and brand-new to Pygame.
My desired outcome is: if I’m holding the A key, the character moves left. If I continue holding the A key and additionally hold down the W key, the character moves up. If I release the W key but not the A key, the character continues left.
This is the main loop:
direction = 0 #0 left, 1 right, 2 up, 3 down
move = False
running = True
while running:
#cap framerate
clock.tick(FPS)
#locational updates
dx = 0
dy = 0
if move:
if direction == 0:
dx = -SPEED
if direction == 1:
dx = SPEED
if direction == 2:
dy = -SPEED
if direction == 3:
dy = SPEED
#draw background
screen.fill(GREY)
player.move(dx, dy)
player.update()
player.draw(screen)
#handle keypresses
keys = pygame.key.get_pressed()
if keys[pygame.K_a] or keys[pygame.K_LEFT]:
direction = 0
move = True
elif keys[pygame.K_d] or keys[pygame.K_RIGHT]:
direction = 1
move = True
elif keys[pygame.K_w] or keys[pygame.K_UP]:
direction = 2
move = True
elif keys[pygame.K_s] or keys[pygame.K_DOWN]:
direction = 3
move = True
else:
move = False
#event handler
for event in pygame.event.get():
if event.type == QUIT:
running = False
#show all the things
pygame.display.update()
The actual outcome is: the S key can be overridden as desired, since it’s at the bottom of the if statements. The W key can be overridden by any key except S, D can only be overridden by A, and A cannot be overridden at all.
I tried using KEYDOWN and KEYUP, but that was even further from what I wanted. I also tried changing the elifs to ifs, but that just reversed the hierarchy, which makes sense. I’ve also searched for the answer, but I haven’t found anything which specifically relates to my problem.
I understand that the root of the problem is that Python reads the code line by line, but with my limited knowledge I don’t know how to get around that. Also, I’m specifically trying to avoid diagonal movement. Thanks for any help you guys can give.
Answers:
You have to use the KEYDOW
and KEYUP
event. The last key pressed defines the direction of the movement. You need to implement a queue of hit keys.
Create a dictionary that maps keys to direction vectors and an empty direction queue:
direction_map = {
pygame.K_a: (-1, 0), pygame.K_LEFT: (-1, 0), pygame.K_d: (1, 0), pygame.K_RIGHT: (1, 0),
pygame.K_w: (0, -1), pygame.K_UP: (0, -1), pygame.K_s: (0, 1), pygame.K_DOWN: (0, 1)
}
direction_queue = []
Add or move a direction to the end of the queue in the ‘KEYDOWN’ event:
while run:
for event in pygame.event.get():
# [...]
if event.type == pygame.KEYDOWN:
new_direction = direction_map.get(event.key)
if new_direction in direction_queue:
direction_queue.remove(new_direction)
if new_direction:
direction_queue.append(new_direction)
Removes a direction vector from the queue in the ‘KEYUP’ event:
while run:
for event in pygame.event.get():
# [...]
if event.type == pygame.KEYUP:
release_direction = direction_map.get(event.key)
if release_direction in direction_queue:
direction_queue.remove(release_direction)
Set the movement depending on the last item in the queue:
while run:
# [...]
dx, dy = direction_queue[-1] if direction_queue else (0, 0)
Minimal example:
import pygame
pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
rect = pygame.Rect(190, 190, 20, 20)
speed = 1
direction_map = {
pygame.K_a: (-1, 0), pygame.K_LEFT: (-1, 0), pygame.K_d: (1, 0), pygame.K_RIGHT: (1, 0),
pygame.K_w: (0, -1), pygame.K_UP: (0, -1), pygame.K_s: (0, 1), pygame.K_DOWN: (0, 1)
}
direction_queue = []
run = True
while run:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
new_direction = direction_map.get(event.key)
if new_direction in direction_queue:
direction_queue.remove(new_direction)
if new_direction:
direction_queue.append(new_direction)
if event.type == pygame.KEYUP:
release_direction = direction_map.get(event.key)
if release_direction in direction_queue:
direction_queue.remove(release_direction)
dx, dy = direction_queue[-1] if direction_queue else (0, 0)
rect.x = (rect.x + dx * speed) % 400
rect.y = (rect.y + dy * speed) % 400
window.fill(0)
pygame.draw.rect(window, "red", rect)
pygame.display.flip()
pygame.quit()
exit()
So with help from @Rabbid76, I found this solution, in case anyone else has this problem (though there’s probably a better way to do it):
First, instead of using the direction and move variables, I used these two:
#save the last pressed key
last_key = None
#create a list of key presses
key_list = []
Second, this section now uses the last_key variable to determine direction:
running = True
while running:
#[...]
#locational updates
dx, dy = 0, 0
#check which key should be controlling movement
if last_key in [pygame.K_a, pygame.K_LEFT]:
dx = -SPEED
elif last_key in [pygame.K_d, pygame.K_RIGHT]:
dx = SPEED
elif last_key in [pygame.K_w, pygame.K_UP]:
dy = -SPEED
elif last_key in [pygame.K_s, pygame.K_DOWN]:
dy = SPEED
else:
dx, dy = 0, 0
Finally, my event handler now adds KEYDOWN events to the list stored in the key_list variable, then uses that list during KEYUP events to see if it should revert to a previous direction:
running = True
while running:
#[...]
for event in pygame.event.get():
#handle keypresses
if event.type == pygame.KEYDOWN:
if event.key in [pygame.K_a, pygame.K_LEFT]:
last_key = event.key
#avoid redundancy in list of key presses
if last_key in key_list:
key_list.remove(last_key)
#add the key to the list
key_list.append(last_key)
if event.key in [pygame.K_d, pygame.K_RIGHT]:
last_key = event.key
if last_key in key_list:
key_list.remove(last_key)
key_list.append(last_key)
if event.key in [pygame.K_w, pygame.K_UP]:
last_key = event.key
if last_key in key_list:
key_list.remove(last_key)
key_list.append(last_key)
if event.key in [pygame.K_s, pygame.K_DOWN]:
last_key = event.key
if last_key in key_list:
key_list.remove(last_key)
key_list.append(last_key)
if event.type == pygame.KEYUP:
if event.key == last_key: #this is important or else the character stops moving if you, say, hold right, then left, then release right
try:
#check the second-to-last key to see if it is still pressed
if pygame.key.get_pressed()[key_list[len(key_list) - 2]]:
#if so, that is now the key to use
last_key = key_list[len(key_list) - 2]
#check if third-to-last key is still pressed
elif pygame.key.get_pressed()[key_list[len(key_list) - 3]]:
last_key = key_list[len(key_list) - 3]
#check if fourth-to-last key is still pressed
elif pygame.key.get_pressed()[key_list[len(key_list) - 4]]:
last_key = key_list[len(key_list) - 4]
#this breaks if you mix arrows and wasd, but you could add more of the above checks to fix that if it's an issue
else:
last_key = None
except:
last_key = None
if event.type == QUIT:
running = False
I’m fairly new to Python (and programming in general), and brand-new to Pygame.
My desired outcome is: if I’m holding the A key, the character moves left. If I continue holding the A key and additionally hold down the W key, the character moves up. If I release the W key but not the A key, the character continues left.
This is the main loop:
direction = 0 #0 left, 1 right, 2 up, 3 down
move = False
running = True
while running:
#cap framerate
clock.tick(FPS)
#locational updates
dx = 0
dy = 0
if move:
if direction == 0:
dx = -SPEED
if direction == 1:
dx = SPEED
if direction == 2:
dy = -SPEED
if direction == 3:
dy = SPEED
#draw background
screen.fill(GREY)
player.move(dx, dy)
player.update()
player.draw(screen)
#handle keypresses
keys = pygame.key.get_pressed()
if keys[pygame.K_a] or keys[pygame.K_LEFT]:
direction = 0
move = True
elif keys[pygame.K_d] or keys[pygame.K_RIGHT]:
direction = 1
move = True
elif keys[pygame.K_w] or keys[pygame.K_UP]:
direction = 2
move = True
elif keys[pygame.K_s] or keys[pygame.K_DOWN]:
direction = 3
move = True
else:
move = False
#event handler
for event in pygame.event.get():
if event.type == QUIT:
running = False
#show all the things
pygame.display.update()
The actual outcome is: the S key can be overridden as desired, since it’s at the bottom of the if statements. The W key can be overridden by any key except S, D can only be overridden by A, and A cannot be overridden at all.
I tried using KEYDOWN and KEYUP, but that was even further from what I wanted. I also tried changing the elifs to ifs, but that just reversed the hierarchy, which makes sense. I’ve also searched for the answer, but I haven’t found anything which specifically relates to my problem.
I understand that the root of the problem is that Python reads the code line by line, but with my limited knowledge I don’t know how to get around that. Also, I’m specifically trying to avoid diagonal movement. Thanks for any help you guys can give.
You have to use the KEYDOW
and KEYUP
event. The last key pressed defines the direction of the movement. You need to implement a queue of hit keys.
Create a dictionary that maps keys to direction vectors and an empty direction queue:
direction_map = {
pygame.K_a: (-1, 0), pygame.K_LEFT: (-1, 0), pygame.K_d: (1, 0), pygame.K_RIGHT: (1, 0),
pygame.K_w: (0, -1), pygame.K_UP: (0, -1), pygame.K_s: (0, 1), pygame.K_DOWN: (0, 1)
}
direction_queue = []
Add or move a direction to the end of the queue in the ‘KEYDOWN’ event:
while run:
for event in pygame.event.get():
# [...]
if event.type == pygame.KEYDOWN:
new_direction = direction_map.get(event.key)
if new_direction in direction_queue:
direction_queue.remove(new_direction)
if new_direction:
direction_queue.append(new_direction)
Removes a direction vector from the queue in the ‘KEYUP’ event:
while run:
for event in pygame.event.get():
# [...]
if event.type == pygame.KEYUP:
release_direction = direction_map.get(event.key)
if release_direction in direction_queue:
direction_queue.remove(release_direction)
Set the movement depending on the last item in the queue:
while run:
# [...]
dx, dy = direction_queue[-1] if direction_queue else (0, 0)
Minimal example:
import pygame
pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
rect = pygame.Rect(190, 190, 20, 20)
speed = 1
direction_map = {
pygame.K_a: (-1, 0), pygame.K_LEFT: (-1, 0), pygame.K_d: (1, 0), pygame.K_RIGHT: (1, 0),
pygame.K_w: (0, -1), pygame.K_UP: (0, -1), pygame.K_s: (0, 1), pygame.K_DOWN: (0, 1)
}
direction_queue = []
run = True
while run:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
new_direction = direction_map.get(event.key)
if new_direction in direction_queue:
direction_queue.remove(new_direction)
if new_direction:
direction_queue.append(new_direction)
if event.type == pygame.KEYUP:
release_direction = direction_map.get(event.key)
if release_direction in direction_queue:
direction_queue.remove(release_direction)
dx, dy = direction_queue[-1] if direction_queue else (0, 0)
rect.x = (rect.x + dx * speed) % 400
rect.y = (rect.y + dy * speed) % 400
window.fill(0)
pygame.draw.rect(window, "red", rect)
pygame.display.flip()
pygame.quit()
exit()
So with help from @Rabbid76, I found this solution, in case anyone else has this problem (though there’s probably a better way to do it):
First, instead of using the direction and move variables, I used these two:
#save the last pressed key
last_key = None
#create a list of key presses
key_list = []
Second, this section now uses the last_key variable to determine direction:
running = True
while running:
#[...]
#locational updates
dx, dy = 0, 0
#check which key should be controlling movement
if last_key in [pygame.K_a, pygame.K_LEFT]:
dx = -SPEED
elif last_key in [pygame.K_d, pygame.K_RIGHT]:
dx = SPEED
elif last_key in [pygame.K_w, pygame.K_UP]:
dy = -SPEED
elif last_key in [pygame.K_s, pygame.K_DOWN]:
dy = SPEED
else:
dx, dy = 0, 0
Finally, my event handler now adds KEYDOWN events to the list stored in the key_list variable, then uses that list during KEYUP events to see if it should revert to a previous direction:
running = True
while running:
#[...]
for event in pygame.event.get():
#handle keypresses
if event.type == pygame.KEYDOWN:
if event.key in [pygame.K_a, pygame.K_LEFT]:
last_key = event.key
#avoid redundancy in list of key presses
if last_key in key_list:
key_list.remove(last_key)
#add the key to the list
key_list.append(last_key)
if event.key in [pygame.K_d, pygame.K_RIGHT]:
last_key = event.key
if last_key in key_list:
key_list.remove(last_key)
key_list.append(last_key)
if event.key in [pygame.K_w, pygame.K_UP]:
last_key = event.key
if last_key in key_list:
key_list.remove(last_key)
key_list.append(last_key)
if event.key in [pygame.K_s, pygame.K_DOWN]:
last_key = event.key
if last_key in key_list:
key_list.remove(last_key)
key_list.append(last_key)
if event.type == pygame.KEYUP:
if event.key == last_key: #this is important or else the character stops moving if you, say, hold right, then left, then release right
try:
#check the second-to-last key to see if it is still pressed
if pygame.key.get_pressed()[key_list[len(key_list) - 2]]:
#if so, that is now the key to use
last_key = key_list[len(key_list) - 2]
#check if third-to-last key is still pressed
elif pygame.key.get_pressed()[key_list[len(key_list) - 3]]:
last_key = key_list[len(key_list) - 3]
#check if fourth-to-last key is still pressed
elif pygame.key.get_pressed()[key_list[len(key_list) - 4]]:
last_key = key_list[len(key_list) - 4]
#this breaks if you mix arrows and wasd, but you could add more of the above checks to fix that if it's an issue
else:
last_key = None
except:
last_key = None
if event.type == QUIT:
running = False