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:代表当前坐标距离上次鼠标事件的距离,表示形式也是一个二维元组。
MOUSEBUTTONDOWN和MOUSEBUTTONUP
- button:这个参数,跟上面比,少了一个s,这个代表了那个按键被操作
- pos:还是位置
处理键盘事件
KEYDOWN和KEYUP的参数描述:
- 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