2. pygame事件

pygame之事件

什么是事件

和事件关联的动词,是“发生”,所以当我们在关注事件的时候,我们其实就是在关注当前正在发生什么……
举一个非常扯淡的例子,在我们口语表达中,有这样一种嫌弃:“你事情怎么这么多的?”。这是一种南方的口语表达,可能还不是非常明显,换成北方的表达方式,就很直接了:“你怎么这么事儿?”
这个“事儿”我们就可以理解为,操作。我们的程序是会一直一直运行下去的,直到我关闭窗口的操作产生了一个QUIT事件。事件随时可能发生,而且量也可能会很大,pygame的做法是把一系列的事情存放在一个队列里,逐个处理。

事件检索

通常,在我目前写到的程序中,我都使用pygame.event.get()来处理所有的事件。如果我们使用pygame.event.wait(),pygame就会等到发生下一个事件才继续下去。
在一些动态游戏中,游戏往往是要动态运作的,而另外一个方法pygame.event.poll()就好一些,一旦调用,他会根据现在的情形返回一个真实的事件,或者一个“什么都没有”。

下表为一个常用事件集:

事件 产生途径 参数
QUIT 用户按下关闭按钮 none
ATIVEEVENT pygame被激活或者隐藏 gain,state
KEYDOWN 键盘被按下 unicode,key,mod
KEYUP 键盘被放开 key,mod
MOUSEMOTION 鼠标移动 pos,rel,buttons
MOUSEBUTTONDOWN 鼠标按下 pos,button
MOUSEBUTTONUP 鼠标放开 pos,button
JOYAXISMOTION 游戏手柄(joystick or pad)移动 joy,axis,value
JOYBALLMOTION 游戏手柄(joystick ball)移动 joy,axis,value
JOYHATMOTION 游戏手柄(joystick)移动 joy,axis,value
JOYBUTTONDOWN 游戏手柄按下 joy,button
JOYBUTTONUP 游戏手柄放开 joy,button
VIDEORESIZE pygame窗口缩放 size,w,h
VIDEOEXPOSE pygame窗口部分公开(expose) none
USEREVENT 触发用户事件 code

一个用来测试查看事件的小脚本

import pygame
from pygame.locals import *
from sys import exit

pygame.init()
screen_size = (640, 480)
screen = pygame.display.set_mode(screen_size, 0, 32)

font = pygame.font.SysFont("arial", 17)
font_height = font.get_linesize()
event_text = []

while 1:
    event = pygame.event.wait()
    event_text.append(str(event))
    event_text = event_text[(-screen_size[1]//font_height):]
    # 这个切片操作保证了event_text里面只保留一个屏幕的文字

    if event.type == QUIT:
        exit()
    screen.fill((0, 0, 0))
    # screen.blit()
    y = screen_size[1]-font_height
    # 找一个合适的起笔位置,最下面开始但是要留一行空

    for text in reversed(event_text):
        screen.blit(font.render(text,True,(0,255,0)),(0,y))
        y -= font_height

    pygame.display.update()

这个程序非常适合分步去了解各个操作在pygame内的响应效果,但是有一个小小的弊端就是,他和一般的pygame.event.get()不同,它只有在新的事件发生的时候才会有反馈到屏幕上,这就让我们造成某种错觉,就是pygame只能知道我们某瞬间的状态,如果这个状态不改变,pygame就无法察觉。
显然这是不对的,例如我们按下某个键的时候,我们可以用for语句搭配pygame.event.get去进行一个轮询,这个轮询会不断的检测事件,pygame始终能觉察到我们的操作和状态。
下面的程序就是一个典型的例子。在长按方向键的时候,pygame并不会移动一次就结束,而是会一直移动,直到我们松开按键返回原始状态。

background_image_filename = 'sushiplate.jpg'

import pygame
from pygame.locals import *
from sys import exit

pygame.init()
screen = pygame.display.set_mode((640, 480), 0, 32)
background = pygame.image.load(background_image_filename).convert()

x, y = 0, 0
move_x, move_y = 0, 0

while True:
    for event in pygame.event.get():
        if event.type == QUIT:
           exit()
        if event.type == KEYDOWN:
            #键盘有按下?
            if event.key == K_LEFT:
                #按下的是左方向键的话,把x坐标减一
                move_x = -1
            elif event.key == K_RIGHT:
                #右方向键则加一
                move_x = 1
            elif event.key == K_UP:
                #类似了
                move_y = -1
            elif event.key == K_DOWN:
                move_y = 1
        elif event.type == KEYUP:
            #如果用户放开了键盘,图就不要动了
            move_x = 0
            move_y = 0

    #计算出新的坐标
    x+= move_x
    y+= move_y

    screen.fill((0,0,0))
    screen.blit(background, (x,y))
    #在新的位置上画图
    pygame.display.update()

举个常用的栗子

通过上面的动作检测脚本我们可以发现,出现频率最高的,就是鼠标事件和键盘事件。所以在这里我们特别地来讨论一下这两个事件的典型用法。

处理鼠标事件

MOUSEMOTION事件会在鼠标动作的时候发生,他有三个参数:

  • buttons:一个含有三个数字的元组,三个值分别表示左键、中键、右键。1表示按下,0表示空放状态。
  • pos:position,表示位置
  • rel:代表当前坐标距离上次鼠标事件的距离,表示形式也是一个二维元组。

MOUSEBUTTONDOWNMOUSEBUTTONUP

  • button:这个参数,跟上面比,少了一个s,这个代表了那个按键被操作
  • pos:还是位置

处理键盘事件

KEYDOWNKEYUP的参数描述:

  • key:按下或者放开的键值,是一个数字,估计地球上很少有人可以完全记得住,所以pygame中你可以使用K_xxx来表示,比如字母a就是K_a,还有K_SPACE和K_RETURN等。
  • mod:包含了组合键信息,如果mod&KMOD_CTRL的值为真,表示用户同时按下了ctrl键。类似的还有KMOD_SHIFT,KMOD_ALT
  • unicode:代表了按下键的unicode值

案例在上面已经放了,就是通过方向键移动图片的小脚本。但是这个脚本有一个小小的bug:“在快速切换方向时,有时候会出现无法持续移动的情况”,这种bug的原因其实跟pygame.event.get()机制有关。event.get只能监听接收一个事件,在我们快速操作的时候,比如我们按下左键,突然按下上键并且松开左键,这种时候bug就会出现。
我们把上述动作分解一下:

  • KEYDOWN-------K_LEFT
  • KEYDOWN-------K_UP
  • KEYUP-----------K_LEFT

显而易见,最后的终结操作并不是KEYDOWN,而是KEYUP,在我们的代码中KEYUP会把移动位给归零。这时候即便K_UP还是在一个KEYDOWN的状态,但是他不再被event.get到了,所以自然,这个图像就停下来了。

改良版:

background_image_filename = 'sushiplate.jpg'

import pygame
from pygame.locals import *
from sys import exit

pygame.init()
screen = pygame.display.set_mode((640, 480), 0, 32)
background = pygame.image.load(background_image_filename).convert()

x, y = 0, 0
move_x, move_y = 0, 0

move={K_LEFT:0,K_RIGHT:0,K_UP:0,K_DOWN:0}


while True:
    for event in pygame.event.get():
        if event.type == QUIT:
           exit()
        if event.type == KEYDOWN:
            #键盘有按下?
            if event.key in move.keys():
                #按下的是左方向键的话,把x坐标减一
                move[event.key]=1

        elif event.type == KEYUP:
            if event.key in move.keys():
                move[event.key]=0
    #计算出新的坐标
    x-= move[K_LEFT]
    x+= move[K_RIGHT]
    y-= move[K_UP]
    y+= move[K_DOWN]


    screen.fill((0,0,0))
    screen.blit(background, (x,y))
    #在新的位置上画图
    pygame.display.update()

事件过滤

并不是所有的事件都是需要处理的,比如在游戏场景切换的时候,你按什么都没有用。我们应该有一个方法来过滤掉一些我们不感兴趣的事件(当然我们可以不处理,不给他们设置响应的事件,但是最好的方法还是让他们根本不进入我们的事件队列)。
我们使用pygame.event.set_blocked(事件名)来完成。如果有好多事件需要过滤,可以传递一个列表pygame.event.set_blocked([list]),如果是设置参数为None,那么所有的事件就被打开了。
与之相对的,我们使用pygame.event.set_allowed()来设定允许的事件

产生事件

通常玩家做什么,pygame产生对应的事件就可以了,不过有的时候我们需要模拟出一些事件来,比如录像回放的时候,我们就要把用户的操作再现一遍。或者说我们在做外挂的时候,我们就要把一些用户的操作自动完成。

my_event = pygame.event.Event(KEYDOWN, key=K_SPACE, mod=0, unicode=u' ')
#你也可以像下面这样写,看起来比较清晰(但字变多了……)
my_event = pygame.event.Event(KEYDOWN, {"key":K_SPACE, "mod":0, "unicode":u' '})
pygame.event.post(my_event)

有时候我们甚至可以产生一个完全自定义的全新事件。范例代码如下,但是我自己并没有敲成功,因为我不知道它的这个USEREVENT在哪里什么时候如何定义的

CATONKEYBOARD = USEREVENT+1
my_event = pygame.event.Event(CATONKEYBOARD, message="Bad cat!")
pgame.event.post(my_event)

#然后获得它
for event in pygame.event.get():
    if event.type == CATONKEYBOARD:
        print event.message
posted @ 2017-11-09 14:05  sc0T7_ly  阅读(2940)  评论(0编辑  收藏  举报