Smooth Keyboard Movement in Pygame

2019-02-07 08:03发布

问题:

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?

回答1:

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)


回答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



回答3:

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)


回答4:

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)