【问题标题】:How to efficiently hold a key in Pygame?如何在 Pygame 中有效地持有密钥?
【发布时间】:2014-04-01 08:48:04
【问题描述】:

我发现了两个相关的问题:

但我想具体一点。怎么做?

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:

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()

但是您按下向下键不到一秒钟,它就会打印数千次。 (移动玩家是不可能的,为此调整时钟似乎不是处理它的正确方法(我已经尝试过,但我失败了。)。

按数千次执行该块是没有用的。我想要的是在定义的游戏时钟速度内按下键并继续执行操作,而我不释放它。

【问题讨论】:

    标签: python input key pygame keydown


    【解决方案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 来相当轻松地将其扩展到八向。

    【讨论】:

      【解决方案2】:

      我对 Pygame 不熟悉,但正如我所见,其中的程序应该具有基于事件的架构。除非你得到传入的事件并处理它们,否则什么都不会发生。这就是为什么您的简单循环变得无限的原因:它只是不处理事件。

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

      然后关于get_pressed() 电话。它返回的是一个键列表。因此,您试图循环直到释放密钥。那是个问题。根据this,即使队列中没有事件,pygame.event.get() 也会立即返回。调用get() 的意思是:我的代码还有事要做,但我不想阻塞事件,所以请在我继续之前处理待处理的事件。如果你的代码只是在等待一个事件,那就意味着它什么都没有做。

      等待事件的函数(不阻塞 Pygame 的内部循环)是pygame.event.wait()(逻辑是:在发生某些事情之前,我的代码中无事可做)。但是,如果您使用它,则必须从事件本身而不是从 get_pressed() 获取有关按下或释放的键的信息。

      以下是从 cmets 到 doc 页面的示例:

      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
      

      【讨论】:

      • 在主循环中,我ve just added "print event.get()" and started to see and understand how it works, and you're right. If I loop through all the event list at each frame, it gets the pressed, and then returns it, and gets the released later on (It does not keep pressing while the key is down. The KEYDOWN is just an opposite for RELEASED, not "hold"). It's not possible to hold the key. The problem is that with the wait() function, the result is exactly the same. It waits for the keypressed, but I, with the button held, dont 得到了更多的按键,直到我释放然后再次按下它。
      • 它不会给你 KEYUP 事件吗?根据基于事件的应用程序的常见逻辑,您应该接收 KEYDOWN 或 KEYUP 事件并存储应用程序更改其行为所需的信息。查看我添加到答案中的示例代码。
      • 它给出了一个keydown和一个keyup。但是当按键被按下时,“keydown”只存在一次。因此,解决方案不在事件中,而是在类似于下面关于标志的帖子中。可以检查按键是否被按下,并将某个标志设置为真,当释放的标志出现时,它会标志为假(这样,可以使用 while 循环。当标志为真时,它保持行动)。
      • 它应该只出现一次。当它出现时,当然你应该设置一个标志。你是让动作在计时器中进行还是在循环中进行?通常,如果某些标志为真,您可能希望不时执行一些操作。
      • KEYDOWN 事件会在您的键盘驱动程序每次发出它时出现;所以它通常会出现一次,然后,在很短的时间之后,您的事件队列就会被KEYDOWN 事件发送到垃圾邮件中。这取决于您的键盘设置。
      【解决方案3】:

      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() 设置标志的初始状态。如果开始时已经按下了怎么办?
      【解决方案4】:

      不要混淆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
      

      【讨论】:

        【解决方案5】:

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

        【讨论】:

        • 9 个月前我问过这个问题,现在我明白了。如果我想按下一个键并在按下键时不断地获得“True”值,我需要使用“pygame.key.get_pressed()”而不是遍历所有事件的列表。这就是为什么在我的引擎(Github 上的 Ergame)中,我区分了术语“按下”和“推送”(其中 PUSH 的意思是“每次按下只有一次真正的回报”)。使用“按下”返回菜单等可能会令人沮丧,因为即使使用“快速按下”,您也会关闭多个菜单(因此,它与 .set_repeat() 无关)。
        【解决方案6】:

        这基本上就是我的做法,好吧,请访问 www.cswonders.com,它可能会对您有所帮助。转到第 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
        

        【讨论】:

          【解决方案7】:

          我正在使用不同的方法来按住我在向左或向右移动对象的特定任务中使用的键。

          我不关心计算机知道一个键实际上是被按下的。

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2016-12-08
            • 1970-01-01
            • 2011-11-14
            • 1970-01-01
            • 2013-05-23
            • 2012-12-25
            相关资源
            最近更新 更多