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
