Smooth Keyboard Movement in Pygame
Asked Answered
T

4

7

I'm using this code to have a player sprite move around a screen when the arrow keys are pressed:

import pygame, sys, time
from pygame.locals import *

pygame.init()

FPS=30
fpsClock=pygame.time.Clock()

width=400
height=300
DISPLAYSURF=pygame.display.set_mode((width,height),0,32)
pygame.display.set_caption('Animation')
background=pygame.image.load('bg.png')


UP='up'
LEFT='left'
RIGHT='right'
DOWN='down'

sprite=pygame.image.load('down.png')
spritex=200
spritey=130
direction=DOWN


pygame.mixer.music.load('bgm.mp3')
pygame.mixer.music.play(-1, 0.0)
while True:
    DISPLAYSURF.blit(background,(0,0))

    DISPLAYSURF.blit(sprite,(spritex,spritey))

    for event in pygame.event.get():
        if event.type==QUIT:
            pygame.quit()
            sys.exit()

        if event.type == KEYDOWN:
            if (event.key == K_LEFT):
                spritex-=5
                sprite=pygame.image.load('left.png')
            elif (event.key == K_RIGHT):
                spritex+=5
                sprite=pygame.image.load('right.png')
            elif (event.key == K_UP):
                spritey-=5
                sprite=pygame.image.load('up.png')
            elif (event.key == K_DOWN):
                spritey+=5
                sprite=pygame.image.load('down.png')

    pygame.display.update()
    fpsClock.tick(FPS)

The image is able to actually move, but only 5 pixels when the key is pressed. I want for the image to keep moving while the key is held down (and to add basic collision detection with the window, but that's a different issue). What would make the image keep moving while the key is pressed down?

Thickset answered 30/12, 2012 at 1:47 Comment(1)
I recommend 4 space indents rather than tabs.Husbandman
B
7

I suggest using variables to keep track of which arrow keys are pressed and which are not. You can use the KEYDOWN and KEYUP events to update the variables. Then you can adjust the position of the sprite each frame based on which keys are pressed. This also means you can easily set the speed of the sprite in different directions by changing how far it moves each frame.

EDIT:

Or as @monkey suggested, you can use key.get_pressed() instead.

Here's an untested example:

while True:
    DISPLAYSURF.blit(background,(0,0))

    DISPLAYSURF.blit(sprite,(spritex,spritey))

    for event in pygame.event.get():
        if event.type==QUIT:
            pygame.quit()
            sys.exit()

        if event.type == KEYDOWN:
            if (event.key == pygame.K_LEFT):
                sprite=pygame.image.load('left.png')
            elif (event.key == pygame.K_RIGHT):
                sprite=pygame.image.load('right.png')
            elif (event.key == pygame.K_UP):
                sprite=pygame.image.load('up.png')
            elif (event.key == pygame.K_DOWN):
                sprite=pygame.image.load('down.png')

    keys_pressed = pygame.key.get_pressed()

    if keys_pressed[pygame.K_LEFT]:
        spritex -= 5

    if keys_pressed[pygame.K_RIGHT]:
        spritex += 5

    if keys_pressed[pygame.K_UP]:
        spritey -= 5

    if keys_pressed[pygame.K_DOWN]:
        spritey += 5

    pygame.display.update()
    fpsClock.tick(FPS)
Baldridge answered 30/12, 2012 at 2:17 Comment(4)
If you use key.get_pressed() You don't need an extra variable for every key. Your logic will be if pressed[K_LEFT]: do stuff.Marenmarena
OP will only move in one of the cardinal directions at a time, but your solution will move in multiple directions if multiple buttons are pushed.Cutlass
@Sam Mussmann: I assumed multiple directions were wanted, but it can easily be changed by using a single if statement to adjust the sprite's position (elif keys_pressed... rather than if keys_pressed...).Baldridge
I like this solution better than my own. I didn't even consider moving diagonally.Husbandman
L
2

I would suggest using the set_repeat function. Held keys generate multiple events, periodically(which is set by the function's parameters).This allows you to use your code unmodified (no need for extra variables).

The function prototype:

set_repeat(delay, interval)

The first parameter 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.

Simply use this function before the main loop.

 pygame.key.set_repeat(10,10)

Source: http://www.pygame.org/docs/ref/key.html#pygame.key.set_repeat

Liberec answered 20/9, 2013 at 11:35 Comment(1)
Thank you - this solution is much simpler to implementAuramine
H
1

I did it like this:

import pygame, sys, time
from pygame.locals import *

pygame.init()

FPS=30
fpsClock=pygame.time.Clock()

width=400
height=300
DISPLAYSURF=pygame.display.set_mode((width,height),0,32)
pygame.display.set_caption('Animation')
background=pygame.image.load('bg.png')


UP='up'
LEFT='left'
RIGHT='right'
DOWN='down'

sprite=pygame.image.load('down.png')
spritex=200
spritey=130
direction=None

def move(direction, sprite, spritex, spritey):
    if direction:
        if direction == K_UP:
            spritey-=5
            sprite=pygame.image.load('up.png')
        elif direction == K_DOWN:
            spritey+=5
            sprite=pygame.image.load('down.png')
        if direction == K_LEFT:
            spritex-=5
            sprite=pygame.image.load('left.png')
        elif direction == K_RIGHT:
            spritex+=5
            sprite=pygame.image.load('right.png')
    return sprite, spritex, spritey

pygame.mixer.music.load('bgm.mp3')
pygame.mixer.music.play(-1, 0.0)
while True:
    DISPLAYSURF.blit(background,(0,0))

    DISPLAYSURF.blit(sprite,(spritex,spritey))

    for event in pygame.event.get():
        if event.type==QUIT:
            pygame.quit()
            sys.exit()

        if event.type == KEYDOWN:
            direction = event.key
        if event.type == KEYUP:
            if (event.key == direction):
                direction = None
    sprite, spritex, spritey = move(direction, sprite, spritex, spritey)

    pygame.display.update()
    fpsClock.tick(FPS)
Husbandman answered 30/12, 2012 at 2:21 Comment(6)
I think you only need one if (event.key == direction).Cutlass
Also, couldn't you just set direction = event.key if direction is one of the ones we want? Check membership with a set maybe?Cutlass
Not even necessary, because the move function won't do anything in the other cases.Husbandman
Although if you press and hold the left arrow and then press and release the 'a' key, that might behave strangely.Cutlass
Ya, if you press any other key, it stops the movement.Husbandman
I wouldn't recommend reloading the images each time. It would be much more efficient to load and store them once, then use them accordingly.Boding
C
0

I would do this by taking a slightly different course of action inside the loop.

To update the sprite, I would first check for a KEYDOWN event and then set direction based on that. Then, I would update sprite, spritex, and spritey based on direction. Then, I would check for KEYUP events and set direction based on that, if appropriate.

Here's how I might code it:

sprite=pygame.image.load('down.png')
spritex=200
spritey=130
direction='down'

dir_from_key = {
  K_LEFT: 'left',
  K_RIGHT: 'right',
  K_UP: 'up',
  K_DOWN: 'down'
}

pygame.mixer.music.load('bgm.mp3')
pygame.mixer.music.play(-1, 0.0)
while True:
    DISPLAYSURF.blit(background,(0,0))

    DISPLAYSURF.blit(sprite,(spritex,spritey))

    # Get all the events for this tick into a list
    events = list(pygame.event.get()) 

    quit_events = [e for e in events if e.type == QUIT]
    keydown_events = [e for e in events if e.type == KEYDOWN 
                                           and e.key in dir_from_key]
    keyup_events = [e for e in events if e.type == KEYUP 
                                         and e.key in dir_from_key]

    # If there's no quit event, then the empty list acts like false
    if quit_events:
        pygame.quit()
        sys.exit()

    # Non-last key down events will be overridden anyway
    if keydown_events:
      direction = dir_from_key[keydown_events[-1].key]

    # Change location and image based on direction
    if direction == 'left':
      spritex-=5
      sprite=pygame.image.load('left.png')
    elif direction == 'right':
      spritex+=5
      sprite=pygame.image.load('right.png')
    elif direction == 'up':
      spritey-=5
      sprite=pygame.image.load('up.png')
    elif direction == 'down':
      spritey+=5
      sprite=pygame.image.load('down.png')

   # If there's a keyup event for the current direction.
   if [e for e in keyup_events if dir_from_key[e.key] == direction]:
      direction = None

   pygame.display.update()
   fpsClock.tick(FPS)
Cutlass answered 30/12, 2012 at 2:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.