This question already has answers here:
What all things happens inside pygame when I press a key? When to use pygame.event==KEYDOWN
(1 answer)
How to get keyboard input in pygame?
(11 answers)
Closed 2 years ago.
I am struggling how I can make it so that when a key is pressed in Pygame, you aren't able to keep on holding the key.
I am working on player jumps, but it doesn't work unless the key is only pressed once.
Here is my keypress code:
keys = pygame.key.get_pressed()
if keys[pygame.K_UP] and not self.jumping:
self.jumping = True
And here is my jumping method (which also includes gravity)
def gravity(self):
# if the player isn't jumping
if not self.jumping:
# set acceleration constant and accelerate
self.accel_y = PLAYER_ACCELERATION
self.y_change += self.accel_y
# terminal velocity
if abs(self.y_change) >= PLAYER_MAX_SPEED_Y:
# set the y change to the max speed
self.y_change = PLAYER_MAX_SPEED_Y
self.y += self.y_change
else: # jumping
self.accel_y = -PLAYER_ACCELERATION_JUMPING
self.y_change += self.accel_y
if abs(self.y_change) >= PLAYER_MAX_SPEED_JUMPING:
self.jumping = False
self.y += self.y_change
print(self.y_change)
The player can just keep on holding the up arrow key, and the player will fly - which is not good. I would like it so that you can only press once, and then self.jumping is set to False when it has reached it's top speed (which I have already implemented)
Don't use pygame.key.get_pressed() in this case, but use the keyboard events (see pygame.event).
pygame.key.get_pressed() returns a list with the state of each key. If a key is held down, the state for the key is True, otherwise False. Use pygame.key.get_pressed() to evaluate the current state of a button and get continuous movement:
The keyboard events occur only once when the state of a key changes. The KEYDOWN event occurs once every time a key is pressed. KEYUP occurs once every time a key is released. Use the keyboard events for a single action or a step-by-step movement.
For instance:
while True:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP and not self.jumping:
self.jumping = True
Related
I was recently coding a platformer game, when I faced into a problem: The user could spam space to jump infinitely.
Here is my code:
def __init__(self, pos):
super().__init__()
self.image = pygame.Surface((32, 64))
self.image.fill("green")
self.rect = self.image.get_rect(topleft = pos)
self.direction = pygame.math.Vector2(0, 0)
self.speed = speed
self.gravity = gravity
self.jump_height = jump_height
def get_input(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and keys[pygame.K_RIGHT]: pass
elif keys[pygame.K_LEFT]: self.direction.x = -1
elif keys[pygame.K_RIGHT]: self.direction.x = 1
else: self.direction.x = 0
if keys[pygame.K_SPACE]:
self.jump()
I tried several things, such as implementing a self.antispam boolean, set to True, at the __init__ method that turns into False when the space key is pressed, and then turns True again after the jump method, or turning the jump method into a coroutine to make an asyncio loop, but none of them worked.
To jump you have to use KEYDOWN instead of pygame.key.get_pressed(). Use pygame.key.get_pressed() to move but not to jump.
pygame.key.get_pressed() returns a sequence with the state of each key. If a key is held down, the state for the key is True, otherwise False. Use pygame.key.get_pressed() to evaluate the current state of a button and get continuous movement.
The keyboard events (see pygame.event module) occur only once when the state of a key changes. The KEYDOWN event occurs once every time a key is pressed. KEYUP occurs once every time a key is released. Use the keyboard events for a single action like jumping or spawning a bullet.
Make sure you only call pygame.get.event() once (see Faster version of 'pygame.event.get()'. Why are events being missed and why are the events delayed?):
def get_input(self, event_list):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and keys[pygame.K_RIGHT]: pass
elif keys[pygame.K_LEFT]: self.direction.x = -1
elif keys[pygame.K_RIGHT]: self.direction.x = 1
else: self.direction.x = 0
for event in event_list:
if event.type == pygame.KEYDONW and event.key == pygame.K_SPACE:
self.jump()
# application loop
while run:
# event loop
event_list = pygame.get.event()
for event in event_list:
if event.type == pygame.QUIT:
# [...]
player.get_input(event_list)
The other option is to state if SPACE was pressed in previous frames. So you can detect that the SPACE is hold down:
def __init__(self, pos):
super().__init__()
# [...]
self.space_was_pressed = False
def get_input(self):
keys = pygame.key.get_pressed()
# [...]
space_is_pressed = keys[pygame.K_SPACE]
if space_is_pressed and not self.space_was_pressed:
self.jump()
self.space_was_pressed = space_is_pressed
See also How to make a character jump in Pygame?
so I was programming again this morning and I wanted to write that the player in my small game can shoot bullets. That worked fine but theres an issue: I wrote for the x and the y coordinates of the 'bullet spawner' player.x and player.y and I thought that the bullets would shoot from the player's position. but they don't. They shoot from the position, where the player was in the beginning of the game and the spawner doesn't move. So I tried to do this with a while loop and the bool isMoving, that only is True if the player moves:
...
isMoving = False
...
bullets = []
position = (player.x, player.y)
while isMoving:
position = (player.x, player.y)
...
if keys[pygame.K_d] or keys[pygame.K_a] or keys[pygame.K_w] or keys[pygame.K_s] or keys[pygame.K_UP] or keys[pygame.K_DOWN] or keys[pygame.K_LEFT] or keys[pygame.K_RIGHT]:
isMoving = True
else:
isMoving = False
But if I run pygame now, the window just freezes. If I remove the while loop again, it works but it shoots from the player's first position again.
Oh, and I get the error " while isMoving:
UnboundLocalError: local variable 'isMoving' referenced before assignment
" Any ideas how to fix that?
Pygame should run in a main while loop which has all the main actions inside it.
Try setting the position at the start, then inside the while loop check for pygame events that trigger the isMoving change. Nested while loops will cause issues with pygame. Use if functions inside the while loop instead of another while loop. For example,
position = (player.x, player.y) # initial position
while isRunning:
isMoving = False
# PyGame event interaction
for event in pygame.event.get():
# Exits loop
if event.type == pygame.QUIT:
isRunning = False
# Check if key is pressed
if event.type == pygame.KEYDOWN:
keys = [pygame.K_a, pygame.K_w, pygame.K_s, pygame.K_d, pygame.K_UP, pygame.K_DOWN, pygame.K_LEFT, pygame.K_RIGHT]
if event.key in keys:
isMoving = True
if isMoving:
position = (player.x, player.y)
# do other stuff
These two pieces of code output the same thing. What's the difference between them and when should we prefer using one over the other?
if event.type==pygame.KEYDOWN:
if event.key==pygame.K_LEFT:
xc=-4
if event.key==pygame.K_RIGHT:
xc=+4
if event.key==pygame.K_UP:
yc=-4
if event.key==pygame.K_DOWN:
yc=+4
if event.type==pygame.KEYUP:
if event.key==pygame.K_LEFT or event.key==pygame.K_RIGHT:
xc=0
if event.key == pygame.K_UP or event.key == pygame.K_DOWN:
yc=0
pressed=pygame.key.get_pressed()
if pressed[pygame.K_LEFT]:
xc=-4
elif pressed[pygame.K_RIGHT]:
xc=+4
else:
xc=0
if pressed[pygame.K_UP]:
yc=-4
elif pressed[pygame.K_DOWN]:
yc=+4
else:
yc=0
The keyboard events (see pygame.event module) occur only once when the state of a key changes. The KEYDOWN event occurs once every time a key is pressed. KEYUP occurs once every time a key is released. Use the keyboard events for a single action or a step-by-step movement.
pygame.key.get_pressed() returns a list with the state of each key. If a key is held down, the state for the key is True, otherwise False. Use pygame.key.get_pressed() to evaluate the current state of a button and get continuous movement.
See also:
How to get keyboard input in pygame?
How can I make a sprite move when key is held down
pygame.KEYDOWN events are only fired when the key is first pressed. You can customize this behavior with pygame.key.set_repeat() in order to make it fire a fixed rate when the key is pressed, though you can't make it match your framerate exactly using this method. This should generally be used for text input, or if an action should happen once when the key is pressed first.
Using pressed will be true as long as the key is pressed, regardless of the setting of set_repeat. This is generally used if something should happen every frame while the key is pressed.
pygame.key.set_repeat(1, 1)
def game():
game_running = True
while game_running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_running = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
player.move_left(3)
if keys[pygame.K_RIGHT]:
player.move_right(3)
if keys[pygame.K_UP]:
player.move_up(3)
if keys[pygame.K_DOWN]:
player.move_down(3)
if keys[pygame.K_SPACE]:
create_bullet()
The pygame.key.set_repeat() value here allows me to move the player sprite smoothly, but also causes way more bullets than I'd want to be created if the player were to hold space. To prevent this, I'd want space key to have a set_repeat value of (1, 500). Would it be possible to accomplish this without creating a solution of my own?
I think you would be better served setting a timer on when a bullet was created, and not allow another bullet to be created for BULLET_COOLDOWN milliseconds. This is fairly easy to implement with the function pygame.time.get_ticks() which returns an ever-increasing number of milliseconds.
BULLET_COOLDOWN = 500 # milliseconds between bullets
bullet_time = 0 # time when bullet fired
...
if keys[pygame.K_SPACE]:
time_now = pygame.time.get_ticks() # what time is it?
if ( bullet_time + BULLET_COOLDOWN < time_now ): # has cooldown past?
create_bullet()
bullet_time = time_now # remember for next firing
I found a solution to make a sprite move when you hold a key down. The problem is that it forces writing ugly duplicated code. The current solution I found is:
for event in pygame.event.get():
if event.type == KEYDOWN:
keystate = pygame.key.get_pressed()
while keystate[K_RIGHT]:
screen.fill((255,255,255))
pygame.event.get()
for sprite in sprites:
rimage = sprite[1].getimage()
if sprite[2] is None:
x+=3
sprite[1].update(time)
screen.blit(rimage, (x,y))
if sprite[1].isfinished() == True:
sprite[1].reset()
last_dir = "right"
if x >= screen_width - rimage.get_width():
x = screen_width - rimage.get_width()
#update player sprite movement
#update player sprite animation
#update rest of game map
keystate = pygame.key.get_pressed()
time = pygame.time.get_ticks()
pygame.display.update()
The problem is that the while keystate block. It has to be repeated for each direction and the game world needs to be updated in each while block. That is five places where the same code needs to be duplicated....4 directions plus if a key is not pressed. I could wrap it in a function but I was wondering if there was a better way to handle holding down a button in pygame.
The usual way program in pygame is use the events to update the direction, then write the update position code outside events, that way you don't need replicated code.
clock = pygame.time.Clock()
direction = (0,0)
while True: # main loop
for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == K_RIGHT:
direction = (3, 0)
elif event.key == K_LEFT:
direction = (-3, 0)
elif event.key == K_UP:
direction = (0, 3)
elif event.key == K_DOWN:
direction = (0, -3)
else:
print "Unrecognized key"
if event.type == KEYUP:
direction = (0, 0)
screen.fill((255,255,255))
for sprite in sprites:
rimage = sprite[1].getimage()
if sprite[2] is None:
# Check if new position is inside the screen
new_pos = x + direction[0], y + direction[1]
if new_pos[0] + rimage.get_width() < screen_width:
x = new_pos[0]
if new_pos[1] + rimage.get_height() < screen_height:
y = new_pos[1]
# Draw the sprite
sprite[1].update(time)
screen.blit(rimage, (x,y))
if sprite[1].isfinished() == True:
sprite[1].reset()
last_dir = direction
#update player sprite movement
#update player sprite animation
#update rest of game map
time = pygame.time.get_ticks()
pygame.display.update()
clock.tick(40) # Keep game running at 40 fps
If you want you can achieve the same with keystate, in such case you don't need to process key events.
Pygame suggests or implies the division of programs into 3 parts:
The event handling, updating and drawing.
As pmoleri already said, you simply change the direction of the movement.
In the update function, you should pass in a delta time parameter, to move all the sprites according to the time passed. It is quite important, since the other technique doesn't take into account the variable speed of the processor. Games in DOS have been made this way, so now we need emulators to artificially slow down the processor. The draw part simply draws all the sprites.
This way you have a clear division between these 3 distinc parts in games: player input, game logic(movement, collision, actions etc.) and drawing.
Additionally, pygame.key.get_pressed() can be used.
This returns a list of all the keys currently being held down.
keys = pygame.key.get_pressed()
if keys[pygame.K_w]:
# Move Up
if keys[pygame.K_a]:
# Move Down
... etc ...
This does not need to be in the event section, but probably where the player updates.
This can make the code less clunky and (possible) be more efficient
-Hope I helped!