如何在Pygame中高效地按住键?

6
我找到了两个相关的问题: 但我想更具体地知道怎么做?
while not done:
    for e in event.get():
        if e.type == KEYDOWN:
            keys = key.get_pressed()
            if e.type == QUIT or keys[K_ESCAPE]:
                done = True
            if keys[K_DOWN]:
                print "DOWN"

当我按下箭头后,它会打印一次。如果我想再打印一次,我需要再次按下箭头。
如果我改用 while 关键字,
while keys[K_DOWN]:
    print "DOWN"

由于某些不明原因,我遇到了无限循环。

这个逻辑上的替代方法也是无用的:

if ((e.type == KEYDOWN) and keys[K_DOWN]):
    print "DOWN"

还有另外一个可以清除事件的方法,你可以在使用时调用:

while not done:
    for e in event.get():
        if e.type == KEYDOWN:
            keys = key.get_pressed()
            if e.type == QUIT or keys[K_ESCAPE]:
                done = True
            while keys[K_DOWN]:
                print "DOWN"
                event.get()
                keys = key.get_pressed()

但是你只按下向下键不到一秒钟就会打印出成千上万次。(移动玩家将是不可能的,调整时钟也似乎不是处理它的正确方法(我已经尝试过,但失败惨重))。
按下并执行成千上万次的块是无用的。我的需求是,在我不释放按键的情况下,继续进行该操作,并保持在定义的游戏时钟速度内。
8个回答

10
不要混淆event.get()key.get_pressed()
如果按下或释放键,则会将事件放入事件队列中,您可以使用event.get()查询该队列。如果您实际上对键是否被物理按下或释放感兴趣(这些是实际的键盘事件),请执行此操作。注意,根据键重复设置,KEYDOWN会多次添加到队列中。
另外,在处理KEYDOWN事件时,没有必要查询所有键的状态,因为通过检查event.key,您已经知道哪个键被按下。
如果您只关心键是否被保持按下(并忽略键重复,这可能是您想要的),那么您应该简单地使用key.get_pressed()。使用一堆标志是不必要的,并且会使代码变得杂乱无章。
因此,您的代码可以简化为:
while not done: 
    keys = key.get_pressed() 
    if keys[K_DOWN]: 
        print "DOWN" 
    for e in event.get(): 
        pass # proceed other events. 
             # always call event.get() or event.poll() in the main loop

2

我不熟悉Pygame,但是从我的理解来看,它的程序应该具有基于事件的架构。除非你获取并处理传入的事件,否则什么都不会发生。这就是为什么你的简单循环变得无限:因为它没有处理事件。

while keys[K_DOWN]: # Nobody updates the keys, no events are processed
    print "DOWN"

关于 get_pressed() 调用。它返回的是一个键列表。因此,你只能循环直到按键被释放,这是个问题。根据这里pygame.event.get() 即使队列中没有事件,也会立即返回。调用 get() 的意思是:我的代码仍有事要做,但我不想阻塞事件,所以在我继续之前请处理待处理的事件。如果你的代码只是等待事件,那就意味着它无事可做。
等待事件的函数(不阻塞 Pygame 的内部循环)是 pygame.event.wait() (逻辑是:在发生事件之前,我的代码无事可做)。然而,如果你使用它,就必须从事件本身获取有关按下或释放的键的信息,而不能从get_pressed() 中获取。
以下是文档页面评论中的示例:
for event in pygame.event.get() :
  if event.type == pygame.KEYDOWN :
    if event.key == pygame.K_SPACE :
      print "Space bar pressed down." #Here you should put you program in the mode associated with the pressed SPACE key
    elif event.key == pygame.K_ESCAPE :
      print "Escape key pressed down."
  elif event.type == pygame.KEYUP :
    if event.key == pygame.K_SPACE :
      print "Space bar released."
    elif event.key == pygame.K_ESCAPE :
      print "Escape key released." #Time to change the mode again

在主循环中,我刚刚添加了“print event.get()”,并开始看到和理解它的工作原理,你是对的。如果我在每个帧中循环遍历所有事件列表,它会获取按下的键,然后返回它,并稍后获取释放的键(它不会在按下键时保持按下状态。KEYDOWN只是RELEASED的相反,而不是“保持”)。无法保持按键。问题是使用wait()函数,结果完全相同。它等待按键按下,但是当我按住按钮时,就不再获得任何按键,直到我释放并再次按下它。 - Ericson Willians
它不会触发KEYUP事件吗?根据事件驱动应用程序的通用逻辑,您应该接收到KEYDOWN或KEYUP事件,并存储所需信息以使应用程序更改其行为。请查看我在答案中添加的示例代码。 - Ellioh
1
它提供了一个键按下和一个键释放事件。但是,"keydown" 仅在按下键时存在一次。因此,解决方案不在于事件,而在于类似于下面关于标志的帖子中的某些东西。可以检查是否有按键按下,并将某个标志设置为 true,当释放键出现时,将其标记为 false(这样,就可以使用 while 循环。只要标志为 true,它就会继续执行操作)。 - Ericson Willians
1
它应该只出现一次。当它出现时,当然你应该设置一个标志。你是在计时器中还是在循环中保持动作进行?通常,如果某个标志为真,您可能希望定期执行某些操作。 - Ellioh
“KEYDOWN”事件每次键盘驱动程序发出时都会出现;因此它通常只出现一次,然后在短时间内,您的事件队列就会被“KEYDOWN”事件淹没。这取决于您的键盘设置。 - sloth
当pygame被初始化时,键盘重复应该被禁用。除非您显式地调用pygame.key.set_repeat()来启用它。https://www.pygame.org/docs/ref/key.html - Ellioh

1
我倾向于采用一种稍微不同的方法来解决这个问题。不是在按下键时检查并执行某些操作,而是在按下键时设置标志,在松开键时取消标志。然后在更新玩家位置的函数中,检查标志并相应地更新。以下伪代码Python阐述了我的想法:
if down_key_pressed:
    down_flag = True
elif down_key_released:
    down_flag = False
elif right_key_pressed:
    etc...

这应该在一个单独的循环中完成,该循环接收玩家的输入。然后在update_player_position()函数中,您可以执行以下操作:

if down_flag:
    move_player_down()
elif right_flag:
    move_player_right()

这个例子假设可以进行四向移动,但是你可以通过仅使用if down_flag and right_flag轻松扩展为八向移动。

0

Dominik的解决方案是完美的(将event.get()与键盘事件分离)。它完美地工作!终于,不再有pygame输入方面的问题。

标志:

flag = False # The flag is essential.
while not done:
    for e in event.get(): # At each frame it loops through all possible events.
        keys = key.get_pressed() # It gets the states of all keyboard keys.
        if e.type == QUIT:
            done = True
        if e.type == KEYDOWN: # If the type is KEYDOWN (DIFFERENT FROM "HELD").
            if keys[K_DOWN]: # And if the key is K_DOWN:
                flag = True # The flag is set to true.
        elif e.type == KEYUP: # The very opposite.
            if keys[K_DOWN]:
                flag = False
    if flag == True: # DON'T USE "while flag == true", it'll crash everything. At every frame, it'll check if the flag is true up there. It's important to remember that the KEYDOWN worked ONLY ONCE, and it was enough to set that flag. So, at every frame, THE FLAG is checked, NOT THE KEYDOWN STATE. Every "player movement" while a key is being held MUST be done through flags.
        print "DOWN"

我建议根据初始的key.get_pressed()来设置标志的初始状态。如果在您启动时它已经按下怎么办? - Ellioh

0

如果使用pygame.key.set_repeat(#毫秒)设置每个键事件的时间限制,则可以重复获取keydown事件。引用:“启用键盘重复时,按住的按键将生成多个pygame.KEYDOWN事件。延迟是发送第一个重复的pygame.KEYDOWN之前的毫秒数。之后,每隔interval毫秒会发送另一个pygame.KEYDOWN。如果没有传递参数,则禁用键重复。当pygame初始化时,键重复被禁用。请参阅以下链接以获取详细信息http://www.pygame.org/docs/ref/key.html#pygame.key.set_repeat


1
我9个月前问过这个问题,现在我明白了。如果我想按下一个键并在按下键时不断获得“True”值,我需要使用“pygame.key.get_pressed()”而不是遍历所有事件列表。这就是为什么在我的引擎(Github上的Ergame)中,我创建了“press”和“push”之间的区别(其中PUSH表示“每次按下只返回一个True”)。在返回菜单等操作中使用“press”可能会变得令人沮丧,因为即使进行“快速按压”,你也会关闭多个菜单(所以与.set_repeat()无关)。 - Ericson Willians

0

我在使用一种不同的方法来按住一个键,以便在特定任务中将对象向左或向右移动。

我不关心计算机知道一个键实际上被按住了。

当我按下一个键时,我为该键定义的变量(例如:左箭头)被设置为True。直到该键被释放(使用pygame.KEYUP事件),才执行“向左移动”的操作。


0
根据pygame的文档,有一个名为的函数。
pygame.key.set_repeat()

这个设置决定了按住键盘时的行为,而默认行为是只触发一次事件。如果你想要多次触发事件,请在调用此函数之后再调用它。
pygame.init()

为了达到预期的效果,你可以传入一个参数来表示pygame将重复键盘事件的间隔。
不要像你那样使用while语句,因为只需要键盘事件为真一次,就会使你的程序陷入无尽的迭代。

-2

这基本上就是我是如何做的,首先打开www.cswonders.com,也许会对你有所帮助。跳转到 level 3,然后进入 3.6,并且跟着教程操作。我也是从那里学来的。下面是我的代码。

def update(self):
    self.speedx = 0
    self.speedy = 0
    # If left or right key is pressed, move left or right
    pressed_key = pygame.key.get_pressed()
    if pressed_key[pygame.K_LEFT]: 
        self.speedx = -10          
    if pressed_key[pygame.K_RIGHT]:
        self.speedx = 10
    if pressed_key[pygame.K_UP]:
        self.speedy = -10
    if pressed_key[pygame.K_DOWN]:
        self.speedy = 10

    self.rect.x += self.speedx
    self.rect.y += self.speedy

    # No further move if off screen
    if self.rect.right > SCREEN_WIDTH:
        self.rect.right = SCREEN_WIDTH
    if self.rect.left < 0:
        self.rect.left = 0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接