0
\$\begingroup\$

I want a single key press to result in the sending of one event in my game loop. As a toy example, if the player presses and holds LEFT, I want the sprite to move 1 pixel left and then stop until the player releases the key and presses it again.

Pygame allows you to set the repeat characteristics of key presses using

 set_repeat().

Not calling this method at all, calling it without parameters, or specifying 0 as its delay parameter are all supposed to result in the behavior I want.

From the documentation:

pygame.key.set_repeat()

control how held keys are repeated

set_repeat() -> None

set_repeat(delay, interval) -> None

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.

Unfortunately, I can't get this to work. I tried not calling the method at all, calling the method without parameters, and calling the method with a delay set to 0 (which is, strangely, supposed to disable key repeating -- see the comments in the documentation).

Here's a minimal example:

import pygame as pg
import sys

# colors
WHITE = (255, 255, 255)
GREY = (100, 100, 100)

class Game:

    def __init__(self):
        pg.init()
        self.screen = pg.display.set_mode((640, 640))
        self.clock = pg.time.Clock()
        pg.key.set_repeat()

    def new(self):
        self.all_sprites = pg.sprite.Group()
        self.player = Player(self, 100,100)

    def run(self):
        self.playing = True
        while self.playing:
            self.clock.tick(60)
            self.events()
            self.update()
            self.draw()

    def quit(self):
        pg.quit()
        sys.exit()

    def update(self):
        self.all_sprites.update()

    def draw(self):
        self.screen.fill(GREY)
        for sprite in self.all_sprites:
            self.screen.blit(sprite.image, sprite.rect)         
        pg.display.flip()

    def events(self):
        for self.event in pg.event.get():
            if self.event.type == pg.QUIT:
                self.quit()


class Player(pg.sprite.Sprite):
    def __init__(self, game, x, y):
        self.groups = game.all_sprites
        pg.sprite.Sprite.__init__(self, self.groups)
        self.game = game
        self.image = pg.Surface((32,32))
        self.image.fill(WHITE)
        self.rect = self.image.get_rect()
        self.pos = [x, y]

    def get_keys(self):
        if self.game.event.type == pg.KEYDOWN:
            if self.game.event.key == pg.K_LEFT:
                self.pos[0] -= 1
                print("This should only be printed once per key press")

            elif self.game.event.key == pg.K_RIGHT:
                self.game.player.pos[0] += 1
                print("This should only be printed once per key press")

    def update(self):
        self.get_keys()
        self.rect.centery = self.pos[1]             
        self.rect.centerx = self.pos[0]     


g = Game()
while True:
    g.new()
    g.run()

This does not result in the behavior I want. Pressing the LEFT key will continuously move the sprite left, and continuously print my test message. I have tried calling the method at various other spots in my code (for example, during the events() method of the game class and during the get_keys() method of the player class). No luck.

My question is simple: What am I doing wrong?


ADDITIONAL INFO

For what it's worth, when I call

pygame.key.get_repeat(), 

which is supposed to tell you the repeat characteristics for the current instance of pygame, the value returned is (0,0).

I am working on a MacBook 2010, running macOS Sierra 10.12.6.

I am interested to know if others can replicate this issue.

I know that I can create a workaround using booleans to track keydowns and keyups, but this would be a nuisance (the project relies on a lot of single key presses).

\$\endgroup\$
1
\$\begingroup\$

I tried your code on Linux, using the pygame_sdl2 module.

The set_repeat() works fine, you just have an issue with your code. You keep seeing the same old event, because you keep a reference to it.

After running Game.events() func, the game.event will still point to the last event.

Just iterate over the events as you retrieve them from the queue, and handle them right then and there, without keeping a reference to it.

You can use a secondary handler in Player class to do the actual work for keypresses if you want, as show below.

import pygame_sdl2 as pg

# colors
WHITE = (255, 255, 255)
GREY = (100, 100, 100)

class Game:

    def __init__(self):
        pg.init()
        self.screen = pg.display.set_mode((640, 640))
        self.clock = pg.time.Clock()
        pg.key.set_repeat()

    def new(self):
        self.all_sprites = pg.sprite.Group()
        self.player = Player(self, 100,100)

    def run(self):
        self.playing = True
        while self.playing:
            self.clock.tick(60)
            self.events()
            self.update()
            self.draw()

    def quit(self):
        pg.quit()

    def update(self):
        self.all_sprites.update()

    def draw(self):
        self.screen.fill(GREY)
        for sprite in self.all_sprites:
            self.screen.blit(sprite.image, sprite.rect)         
        pg.display.flip()

    def events(self):
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.quit()
            if event.type == pg.KEYDOWN:
                self.player.handle_keydown( event.key )


class Player(pg.sprite.Sprite):
    def __init__(self, game, x, y):
        self.groups = game.all_sprites
        pg.sprite.Sprite.__init__(self, self.groups)
        self.game = game
        self.image = pg.Surface((32,32))
        self.image.fill(WHITE)
        self.rect = self.image.get_rect()
        self.pos = [x, y]

    def handle_keydown( self, key ) :
        if key == pg.K_LEFT:
            self.pos[0] -= 1
            print("This should only be printed once per key press")
        elif key == pg.K_RIGHT:
            self.pos[0] += 1
            print("This should only be printed once per key press")

    def update(self):
        self.rect.centery = self.pos[1]             
        self.rect.centerx = self.pos[0]     


g = Game()
while True:
    g.new()
    g.run()

Note that I get a crash when I close the window, so your code needs more work than this.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the response! Any chance you could elaborate on the notion of a handler function and what that would look like? \$\endgroup\$ – GoldenGremlin Oct 27 '17 at 3:03
  • \$\begingroup\$ I tried creating a handler function which takes the event as input, returns the event, then clears the event queue using pg.event.clear(). I expected this to return the original keydown event then destroy the reference to it which is causing the issue, but it didn't work... \$\endgroup\$ – GoldenGremlin Oct 27 '17 at 4:08
  • 1
    \$\begingroup\$ New code shows how events are retrieved from queue without making them member vars. They get handled right then and there, but key presses are delegated to a fun in Player. \$\endgroup\$ – Bram Oct 27 '17 at 5:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.