简体   繁体   English

Pygame:key.get_pressed() 与事件队列不一致

[英]Pygame: key.get_pressed() does not coincide with the event queue

I'm attempting to work out simple controls for an application using pygame in Python.我正在尝试使用 Python 中的 pygame 为应用程序制定简单的控件。 I have got the basics working, but I'm hitting a weird wall: I am using the arrow keys to control my character.我已经掌握了基础知识,但遇到了奇怪的问题:我正在使用箭头键来控制我的角色。 If I hold down one arrow key, then hold down another arrow key (to move diagonally), the character moves as expected.如果我按住一个箭头键,然后按住另一个箭头键(对角移动),角色会按预期移动。 However, if I release the second key that I pressed (while still holding down the first key), the character stops moving, even though I am still holding down that first key.但是,如果我松开我按下的第二个键(同时仍然按住第一个键),即使我仍然按住第一个键,角色也会停止移动。 Here is my simple movement code:这是我的简单移动代码:

for event in pygame.event.get():
    if event.type == QUIT:
        pygame.quit()
        sys.exit()
    elif event.type == KEYDOWN:
        if pygame.key.get_pressed()[K_LEFT]:
            player.pos = (player.pos[0] - 2, player.pos[1])
        if pygame.key.get_pressed()[K_RIGHT]:
            player.pos = (player.pos[0] + 2, player.pos[1])
        if pygame.key.get_pressed()[K_UP]:
            player.pos = (player.pos[0], player.pos[1] - 2)
        if pygame.key.get_pressed()[K_DOWN]:
            player.pos = (player.pos[0], player.pos[1] + 2)

Now, I was naturally very confused by this.现在,我自然对此感到非常困惑。 So I tried to print some lines to debug.所以我尝试打印一些行进行调试。 In the top of the main control loop, I wrote:在主控制循环的顶部,我写道:

print (pygame.key.get_pressed()[K_DOWN], pygame.key.get_pressed()[K_RIGHT])
print pygame.event.get()

...to output a tuple displaying the state of the down and right arrow keys, and then display the pygame event queue. ...输出一个显示向下和向右箭头键状态的元组,然后显示pygame事件队列。 My results baffled me even more.我的结果更让我困惑。 If I move the character diagonally down and right, pressing the down key first and then the right key, then release the right key to make it move simply downward, the character stops moving as before... but this is printed to the shell:如果我沿对角线向下和向右移动字符,先按向下键,然后按右键,然后松开右键使其简单向下移动,字符会像以前一样停止移动……但这会打印到外壳上:

(1, 0)
[]

That is, when I release the right arrow key and still hold down the down arrow key, pygame.key.get_pressed() understands that the down arrow key is still being held down, but there is nothing in the event queue.也就是说,当我释放右箭头键并仍然按住向下箭头键时,pygame.key.get_pressed() 知道向下箭头键仍然被按住,但事件队列中没有任何内容。

Also, earlier in the code (before the control loop) I am invoking另外,在代码的前面(在控制循环之前)我正在调用

pygame.key.set_repeat(1, 2)

to make the character continue to move while the key is held down.使角色在按住键的同时继续移动。

Any help will be appreciated!任何帮助将不胜感激! Thanks :)谢谢 :)

For things like movement, you should not check for events (like KEYDOWN or KEYUP ), but check every iteration of your mainloop if your movement keys are pressed (using get_pressed ).对于诸如移动之类的事情,您不应该检查事件(如KEYDOWNKEYUP ),但如果您的移动键被按下(使用get_pressed ),则应检查主循环的每次迭代。

In your code, you check the pressed keys only if there's also a KEYDOWN event.在您的代码中,仅当还有KEYDOWN事件时才检查按下的键。


There are also some other things to consider:还有一些其他的事情需要考虑:

  • You should seperate the key-mapping and the speed of your player, so it will be easier later on to change either of this.您应该将键映射和播放器的速度分开,以便以后更改其中任何一个会更容易。

  • You should determine a movement vector and normalize it first, since otherwise, if your vertical and horizontal movement speed is 10 , your diagonal movement speed would be ~ 14 .您应该先确定一个移动向量并对其进行标准化,否则,如果您的垂直和水平移动速度为10 ,则您的对角线移动速度将为 ~ 14

Working example:工作示例:

import pygame

pygame.init()

screen = pygame.display.set_mode((200, 200))
run = True
pos = pygame.Vector2(100, 100)
clock = pygame.time.Clock()

# speed of your player
speed = 2

# key bindings
move_map = {pygame.K_LEFT: pygame.Vector2(-1, 0),
            pygame.K_RIGHT: pygame.Vector2(1, 0),
            pygame.K_UP: pygame.Vector2(0, -1),
            pygame.K_DOWN: pygame.Vector2(0, 1)}

while run:
  for e in pygame.event.get():
    if e.type == pygame.QUIT: run = False

  screen.fill((30, 30, 30))
  # draw player, but convert position to integers first
  pygame.draw.circle(screen, pygame.Color('dodgerblue'), [int(x) for x in pos], 10)
  pygame.display.flip()

  # determine movement vector
  pressed = pygame.key.get_pressed()
  move_vector = pygame.Vector2(0, 0)
  for m in (move_map[key] for key in move_map if pressed[key]):
    move_vector += m

  # normalize movement vector if necessary
  if move_vector.length() > 0:
    move_vector.normalize_ip()

  # apply speed to movement vector
  move_vector *= speed

  # update position of player
  pos += move_vector

  clock.tick(60)

just use the events return data, instead of trying to poll, you're already checking if its a keydown event TYPE, now just interrogate the KEY index, like so:只需使用事件返回数据,而不是尝试轮询,您已经在检查它是否是 keydown 事件类型,现在只需询问 KEY 索引,如下所示:

for event in pygame.event.get():
    if event.type == QUIT:
        pygame.quit()
        sys.exit()
    elif event.type == KEYDOWN:
        if event.key == K_LEFT:
            player.pos = (player.pos[0] - 2, player.pos[1])

rest of code.....其余代码.....

also consider using a separate data structure to store the state of your controls, then just use the events to update that data structure.还可以考虑使用单独的数据结构来存储控件的状态,然后只使用事件来更新该数据结构。 That will help make the controls a bit more flexible as you wont be relying on the event queue to cause your character to move, which in my experience causes problems like: not being able to push more than two buttons at a time, and odd delay or timing issues with character movements.这将有助于使控件更加灵活,因为您不会依赖事件队列来导致角色移动,根据我的经验,这会导致以下问题:一次无法按下两个以上的按钮,以及奇怪的延迟或角色移动的时间问题。 so something like:所以像:

keystates={'up':False, 'down':False, 'left':False, 'right':False}
running=True

#start main pygame event processing loop here
while running:
    for event in pygame.event.get():
        if event.type == QUIT:
            running=False

        #check for key down events
        if event.type == KEYDOWN:
            if event.key == K_UP:
                keystates['up']=True
            if event.key == K_DOWN:
                keystates['down']=True
            if event.key == K_LEFT:
                keystates['left']=True
            if event.key == K_RIGHT:
                keystates['right']=True

        #check for key up events
        if event.type == KEYUP:
            if event.key == K_UP:
                keystates['up']=False
            if event.key == K_DOWN:
                keystates['down']=False
            if event.key == K_LEFT:
                keystates['left']=False
            if event.key == K_RIGHT:
                keystates['right']=False

    #do something about the key states here, now that the event queue has been processed
    if keystates['up']:
        character.moveUp()  #or whatever your call for these are...
    if keystates['down']:
        character.moveDown()
    if keystates['left']:
        character.moveLeft()
    if keystates['right']:
        character.moveRight()

#gracefully exit pygame here
pygame.quit()

You are using event-based input , but in this case you want polling-based input.您正在使用基于事件的输入,但在这种情况下,您需要基于轮询的输入。 Then you don't mess with key-repeats.这样你就不会被重复的键弄乱了。

import pygame
from pygame.locals import *

done = False    
player.pos = Rect(0,0,10,10)

while not done:
    for event in pygame.event.get():
        # any other key event input
        if event.type == QUIT:
            done = True        
        elif event.type == KEYDOWN:
            if event.key == K_ESC:
                done = True
            elif event.key == K_F1:
                print "hi world mode"

    # get key current state
    keys = pygame.key.get_pressed()
    if keys[K_LEFT]:
        player.pos.left -= 10
    if keys[K_RIGHT]:
        player.pos.left += 10
    if keys[K_UP]:
        player.pos.top -= 10
    if keys[K_DOWN]:
        player.pos.left += 10
    if keys[K_SPACE]: 
        print 'firing repeated gun'

My guess is that set repeat doesn't work the way that you think it will.我的猜测是 set repeat 不会像你认为的那样工作。 Basically, after your second key goes up, the repeat doesn't happen.基本上,在您的第二个键上升后,不会发生重复。 This would seem to make sense to me: open up a text editor and hold down the "A" key.这对我来说似乎很有意义:打开文本编辑器并按住“A”键。 "A"s will spill out across the screen. “A”将溢出屏幕。 Then, press the "J" key with the "A" key still held down.然后,在仍按住“A”键的情况下按“J”键。 The "A"s stop. “A”停止。 That is a typical key repeat system.那是典型的密钥重复系统。

I'm not sure using this "set_repeat" method is going to work out in the end anyway.我不确定使用这种“set_repeat”方法最终会奏效。 Basically, any key that the player presses will now "repeat", even if they click "fire" or "jump".基本上,玩家按下的任何键现在都会“重复”,即使他们点击“开火”或“跳跃”。

As an alternative, try saving the state when the user presses or releases.或者,尝试在用户按下或释放时保存状态。 Don't use the set_repeat, but do something like the following:不要使用 set_repeat,而是执行以下操作:

for event in pygame.event.get():
    if event.type == QUIT:
        pygame.quit()
        sys.exit()
    elif event.type == KEYDOWN:
        if pygame.key.get_pressed()[K_LEFT]:
            player.moving_left = True
        if pygame.key.get_pressed()[K_RIGHT]:
            player.moving_right = True
        if pygame.key.get_pressed()[K_UP]:
            player.moving_up = True
        if pygame.key.get_pressed()[K_DOWN]:
            player.moving_down = True
    elif event.type == KEYUP:
        if pygame.key.get_pressed()[K_LEFT]:
            player.moving_left = False
        if pygame.key.get_pressed()[K_RIGHT]:
            player.moving_right = False
        if pygame.key.get_pressed()[K_UP]:
            player.moving_up = False
        if pygame.key.get_pressed()[K_DOWN]:
            player.moving_down = False

# Somewhere else in your game loop...
if player.moving_left:
    player.pos[0] -= 2
if player.moving_right:
    player.pos[0] += 2
if player.moving_up:
    player.pos[1] -= 2
if player.moving_right:
    player.pos[1] += 2

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM