如何擷取處理遊戲事件?以及建立和引發自訂事件等問題
訂閱 375
本節提到的事件佇列中儲存的事件,實際上是指event
模組的Event
物件。
Pygame 中的 event 模組
在 Pygame 中,事件訊息是通過一個事件佇列來傳遞的,該事件佇列相依於display
模組,如果display
模組沒有初始化,那麽事件佇列可能無法正常工作。
如果事件佇列所擁有的事件的數量達到了最大,那麽新的事件將被丟棄,並且不會發出任何通知。為了防止這種情況發生,你應該及時的對事件進行處理,以減少佇列中的事件,比如,在遊戲迴圈中取出事件佇列中的所有事件。
Pygame 的event
模組,可用於擷取處理遊戲中的各種事件,當然,這需要通過操作事件佇列來完成。
Pygame 中的事件類型
pygame
模組使用一系列的整數變數來表示不同的事件類型,這些變數可以在判斷事件類型,以及呼叫與事件相關的函式時使用。當然,存在一些特殊的事件類型,用於表達某些特殊含義,以下為他們的說明。
- QUIT 變數
QUIT
變數表示遊戲程式要求結束,比如,點選了視窗的關閉按鈕。- NOEVENT 變數
NOEVENT
變數表示沒有事件,任何其他事件類型對應的整數應大於NOEVENT
變數。- NUMEVENTS 變數
NUMEVENTS
變數表示事件類型所對應的整數的上限,任何其他事件類型對應的整數應小於NUMEVENTS
變數。- USEREVENT 變數
USEREVENT
變數用於界定自訂事件的開始,任何自訂事件對應的整數應該大於USEREVENT
變數。當然,你可以使用custom_type
函式來建置自訂事件對應的整數,具體請檢視建置自訂事件類型一段。
以下是與 Windows 系統事件有關的變數。所有 Windows 事件所對應的Event
物件均擁有名稱為window
的變數。
- WINDOWSHOWN 變數
WINDOWSHOWN
變數表示遊戲視窗已經顯示。- WINDOWHIDDEN 變數
WINDOWHIDDEN
變數表示遊戲視窗已經隱藏。- WINDOWEXPOSED 變數
WINDOWEXPOSED
變數表示遊戲視窗被外部事件更新。- WINDOWMOVED 變數
WINDOWMOVED
變數表示遊戲視窗已經被移動。表示該事件的Event
物件擁有表示視窗左上角座標的變數x
和y
,其中x
為 X 座標,y
為 Y 座標。- WINDOWRESIZED 變數
WINDOWRESIZED
變數表示遊戲視窗被調正了大小。表示該事件的Event
物件擁有表示視窗大小的變數x
和y
,其中x
為寬度,y
為高度。- WINDOWSIZECHANGED 變數
WINDOWSIZECHANGED
變數表示遊戲視窗大小發生了改變。表示該事件的Event
物件擁有表示視窗大小的變數x
和y
,其中x
為寬度,y
為高度。- WINDOWMINIMIZED 變數
WINDOWMINIMIZED
變數表示遊戲視窗已經最小化。- WINDOWMAXIMIZED 變數
WINDOWMAXIMIZED
變數表示遊戲視窗已經最大化。- WINDOWRESTORED 變數
WINDOWRESTORED
變數表示遊戲視窗已經被還原。- WINDOWENTER 變數
WINDOWENTER
變數表示滑鼠已經進入了遊戲視窗。- WINDOWLEAVE 變數
WINDOWLEAVE
變數表示滑鼠已經離開了遊戲視窗。- WINDOWFOCUSGAINED 變數
WINDOWFOCUSGAINED
變數表示遊戲視窗已經獲得了焦點。- WINDOWFOCUSLOST 變數
WINDOWFOCUSLOST
變數表示遊戲視窗已經失去了焦點。- WINDOWCLOSE 變數
WINDOWCLOSE
變數表示遊戲視窗已經關閉。- WINDOWTAKEFOCUS 變數
WINDOWTAKEFOCUS
變數表示遊戲視窗被給予了焦點(需要 SDL 版本為 2.0.5 或以上)。- WINDOWHITTEST 變數
WINDOWHITTEST
變數表示遊戲視窗有一個特殊的命中測試(需要 SDL 版本為 2.0.5 或以上)。- WINDOWICCPROFCHANGED 變數
WINDOWICCPROFCHANGED
變數表示遊戲視窗的 ICC 設定檔案已變更(需要 SDL 版本為 2.0.18 或以上)。- WINDOWDISPLAYCHANGED 變數
WINDOWDISPLAYCHANGED
變數表示遊戲視窗被移動到了一個新的熒幕中(需要 SDL 版本為 2.0.18 或以上),表示該事件的Event
物件擁有一個整數變數display_index
,他表示了視窗目前所在的熒幕的索引(主熒幕的索引為0
,其他熒幕的索引依次遞增)。
以下是與 Android 系統事件有關的變數。
- APP_TERMINATING 變數
APP_TERMINATING
變數表示遊戲正在被系統關閉。- APP_LOWMEMORY 變數
APP_LOWMEMORY
變數表示系統記憶體不足,需要盡可能的釋放記憶體。- APP_WILLENTERBACKGROUND 變數
APP_WILLENTERBACKGROUND
變數表示遊戲正在進入背景。- APP_DIDENTERBACKGROUND 變數
APP_DIDENTERBACKGROUND
變數表示遊戲已經進入背景。- APP_WILLENTERFOREGROUND 變數
APP_WILLENTERFOREGROUND
變數表示遊戲正在進入前景。- APP_DIDENTERFOREGROUND 變數
APP_DIDENTERFOREGROUND
變數表示遊戲已經進入前景。
以下是與音訊裝置有關的變數。所有音訊裝置事件所對應的Event
物件均擁有名稱為which
和iscapture
的變數,其中,which
是表示音訊裝置索引的整數。
- AUDIODEVICEADDED 變數
AUDIODEVICEADDED
變數表示音訊裝置被新增(需要 SDL 版本為 2.0.4 或以上),如果在遊戲啟動前音訊裝置已經存在,那麽該事件會在遊戲啟動時引發。- AUDIODEVICEREMOVED 變數
AUDIODEVICEREMOVED
變數表示音訊裝置被移除(需要 SDL 版本為 2.0.4 或以上)。
以下是與拖放事件有關的變數。
- DROPFILE 變數
DROPFILE
變數表示拖放檔案。- DROPTEXT 變數
DROPTEXT
變數表示拖放文字。如果不支援對文字進行拖放,那麽該事件不會引發。- DROPBEGIN 變數
DROPBEGIN
變數表示拖放開始。- DROPCOMPLETE 變數
DROPCOMPLETE
變數表示拖放完成。
以下是與某些不常使用事件相關的變數。
- RENDER_TARGETS_RESET 變數
RENDER_TARGETS_RESET
變數表示算繪目標重設(需要 SDL 版本為 2.0.2 或以上)。- RENDER_DEVICE_RESET 變數
RENDER_DEVICE_RESET
變數表示算繪裝置重設(需要 SDL 版本為 2.0.4 或以上)。- LOCALECHANGED 變數
LOCALECHANGED
變數表示區域發生了變化(需要 SDL 版本為 2.0.14 或以上)。
至於其他的事件類型,及他們相關的變數,請檢視內容分類一段列出的章節,或其他具體章節。
讓 Pygame 自行處理事件佇列中的事件
event
模組的pump
函式,可以讓 Pygame 自行處理事件,處理不會導致事件佇列中的事件被移除。
pump()
防止作業系統認為遊戲視窗處於未回應狀態
在一般情況下,你不必呼叫event
模組pump
函式,因為他會被其他相關函式隱含呼叫,比如get
函式。但如果隱含呼叫沒有發生,那麽你應該明確的在遊戲迴圈中呼叫pump
,以防止一些意外情況,因為,長時間不對事件進行處理,可能導致作業系統認為遊戲視窗無法進行回應。
在下面的範例中,由於遊戲迴圈並沒有隱含呼叫,因此我們明確的呼叫了pump
函式。
# 匯入模組 display 和 event,並建立遊戲視窗
from pygame import display, event
w = display.set_mode([800, 600])
count = 0
while True:
# 讓 Pygame 自動處理事件
event.pump()
# 不斷加 1,使視窗顯示一段時間
count += 1
if count == 10000000:
# 跳出迴圈會使程式結束,視窗也將關閉
break
取得事件佇列中的事件
Pygame 擷取、過濾、處理事件影片示範 YouTube
event
模組的get
函式,可用於取得事件佇列中的事件,並將這些事件從事件佇列中移除。
get(eventtype=None, pump=True, exclude=None)
- eventtype 參數
eventtype
參數用於指定需要取得的事件的類型,他可以是某個事件類型對應的整數,或一個包含事件類型對應整數的 Python 序列物件。如果該參數為空值None
,則表示將取得任何類型的事件。- pump 參數
如果
pump
參數為True
,那麽get
函式將呼叫pump
方法。- exclude 參數
exclude
參數用於指定需要排除的事件的類型,他可以是某個事件類型對應的整數,或一個包含事件類型對應整數的 Python 序列物件。如果你為該參數賦予了有效的值,那麽get
參數將傳回事件佇列中所有未被排除的事件。- 傳回值
get
函式的傳回值是一個包含Event
物件的 Python 串列。
在下面的範例中,第一次get
函式取得了事件佇列中的結束和視窗移動事件(如果存在的話),第二次get
函式取得了事件佇列中的除了視窗最大化之外的事件(如果存在的話)。
# 匯入模組 display 和 event,並建立遊戲視窗
from pygame import display, event
w = display.set_mode([800, 600])
# 匯入表示結束,視窗移動,視窗最大化的變數
from pygame import QUIT, WINDOWMOVED, WINDOWMAXIMIZED
running = True
while running:
# 取得佇列中的結束,視窗移動事件
for e in event.get((QUIT, WINDOWMOVED)):
# 根據事件的類型進行不同的處理
if e.type == QUIT:
# 結束遊戲迴圈
running = False
print(f'要求結束遊戲')
elif e.type == WINDOWMOVED:
print(f'視窗位置:{e.x} {e.y}')
# 取得佇列中的其他事件,除了視窗最大化
es = event.get(exclude=WINDOWMAXIMIZED)
print(es)
event
模組的poll
函式,可用於取得事件佇列中的一個事件,並將該事件從事件佇列中移除。
poll()
- 傳回值
poll
函式的傳回值是表示事件的Event
物件。如果事件佇列中沒有任何事件,則poll
函式將傳回一個事件類型為NOEVENT
的Event
物件。
在下面的範例中,我們在迴圈中不斷呼叫poll
函式,以取得佇列中的所有事件。
# 匯入模組 display 和 event,並建立遊戲視窗
from pygame import display, event
w = display.set_mode([800, 600])
# 匯入表示結束和沒有事件的變數
from pygame import QUIT, NOEVENT
running = True
while running:
# 使用迴圈取出佇列中的所有事件
while True:
e = event.poll()
# 根據事件的類型進行不同的處理
if e.type == QUIT:
print('QUIT')
# 結束遊戲迴圈
running = False
break
elif e.type == NOEVENT:
# 佇列中已經沒有事件進入下一個遊戲迴圈
print('NOEVENT')
break
與poll
函式類似,event
模組的wait
函式,同樣用於取得事件佇列中的一個事件,並將該事件從事件佇列中移除。但不同的是,如果事件佇列中沒有事件,wait
函式會等候,在事件佇列中出現一個新的事件後,將傳回該事件。
wait(timeout=0)
- timeout 參數
timeout
參數是表示逾時時間的整數(以毫秒為單位)。如果將該參數忽略或設定為0
,那麽在事件佇列沒有事件時,可以一直等候。- 傳回值
wait
函式的傳回值是表示事件的Event
物件。如果等候時間逾時,那麽wait
函式將傳回一個事件類型為NOEVENT
的Event
物件。
在下面的範例中,我們在取得事件佇列中的所有事件之後,使用wait
函式等候一個新的事件。
# 匯入模組 display 和 event,並建立遊戲視窗
from pygame import display, event
w = display.set_mode([800, 600])
# 匯入表示沒有事件的變數
from pygame import NOEVENT
while True:
# 取得佇列中的所有事件
event.get()
# 等候一個新的事件,逾時時間為 5 秒
e = event.wait(5000)
# 如果佇列中沒有事件,則結束
if e.type == NOEVENT:
break
判斷事件佇列中是否包含某種類型的事件
event
模組的peek
函式,可用於判斷事件佇列中是否包含某種類型的事件。
peek(eventtype=None, pump=True)
- eventtype 參數
如果
eventtype
參數是一個整數,則判斷該整數所對應的事件類型是否存在於事件佇列中,如果eventtype
參數一個 Python 整數序列物件,則判斷這些整數所對應的事件類型中的任意一個是否存在於事件佇列中。- pump 參數
如果
pump
參數為True
,那麽peek
函式將呼叫pump
方法。- 傳回值
如果
peek
函式的傳回值為True
,那麽表示成功在事件佇列中找到了指定類型的事件。如果eventtype
參數為空值None
,那麽peek
傳回事件佇列中的第一個事件(第一個Event
物件)。
請不要使用 peek 函式來判斷是否發生了某種類型的事件
雖然peek
函式可以判斷某種類型的事件是否存在,但與其他函式(比如get
函式)一同使用時,這種判斷可能不會達到預期效果。
在下面的範例中,由於我們使用了get
函式,因此peek
函式的判斷可能會出現一些看上去非常奇怪的問題,比如,「似乎佇列中存在的事件被認為不存在」。
# 匯入模組 display 和 event,並建立遊戲視窗
from pygame import display, event
w = display.set_mode([800, 600])
# 匯入表示結束,滑鼠進入,滑鼠離開的變數
from pygame import QUIT, WINDOWENTER, WINDOWLEAVE
running = True
while running:
# 判斷事件滑鼠進入視窗或滑鼠離開視窗是否存在
if event.peek([WINDOWENTER, WINDOWLEAVE]):
print('滑鼠進入或滑鼠離開')
# 判斷結束事件是否存在
if event.peek(QUIT):
running = False
# 取得並顯示所有的事件
es = event.get()
if len(es):
print(es)
清除事件佇列中的事件
event
模組的clear
函式,可用於清除事件佇列中的事件,該效果類似於get
函式對事件的移除,只不過clear
函式不會傳回串列,因此,他可能會比get
函式快一些。
clear(eventtype=None, pump=True)
- eventtype 參數
eventtype
參數用於指定需要清除的事件的類型,他可以是某個事件類型對應的整數,或一個包含事件類型對應整數的 Python 序列物件。如果該參數為空值None
,則表示將清除任何類型的事件。- pump 參數
如果
pump
參數為True
,那麽clear
函式將呼叫pump
方法。
使用 clear 函式可能導致一些事件無法被擷取
如果你在遊戲迴圈中使用clear
函式清除了所有的事件,那麽一些事件可能無法被正常擷取。比如,當按下滑鼠按鍵時,其相應的程式碼並沒有執行,因為clear
函式已將滑鼠按鍵事件清除。
# 匯入模組 display 和 event,並建立遊戲視窗
from pygame import display, event
w = display.set_mode([800, 600])
# 匯入表示結束的變數
from pygame import QUIT
while True:
# 如果存在結束事件,則結束遊戲迴圈
es = event.get(QUIT)
if len(es):
break
# 清除佇列中剩余的事件
event.clear()
封鎖某種事件類型
如果你不希望某些類型的事件出現在事件佇列中,那麽可以使用event
模組的set_blocked
函式,將指定的事件類型排除。這意味著,當某種事件類型被排除時,即便作業系統傳送了該類型的事件,Pygame 也不會將其放入事件佇列中。
set_blocked(type)
- type 參數
type
參數用於指定需要封鎖的事件類型,他可以是某個事件類型對應的整數,或一個包含事件類型對應整數的 Python 序列物件。如果該參數為空值None
,則表示封鎖所有的事件類型。
在下面的範例中,由於滑鼠進入事件被封鎖,因此他不會出現在事件佇列中。
# 匯入模組 display 和 event,並建立遊戲視窗
from pygame import display, event
w = display.set_mode([800, 600])
# 匯入表示結束,滑鼠進入的變數
from pygame import QUIT, WINDOWENTER
# 封鎖滑鼠進入事件
event.set_blocked(WINDOWENTER)
while True:
# 檢查佇列中是否出現了滑鼠進入事件
if event.peek(WINDOWENTER):
print('居然存在滑鼠進入?!')
# 一次取出一個事件,並判斷是否需要結束迴圈
if event.poll().type == QUIT:
break
判斷事件類型是否被封鎖
event
模組的get_blocked
函式,可用於判斷指定的事件類型是否已經被封鎖。
get_blocked(type)
- type 參數
如果
type
參數是一個整數,則判斷該整數所對應的事件類型是否被封鎖,如果eventtype
參數一個 Python 整數序列物件,則判斷這些整數所對應的事件類型中的任意一個是否被封鎖。- 傳回值
如果
get_blocked
函式的傳回值為True
,那麽表示指定的事件類型(或其中的一個)已經被封鎖。
取消封鎖某種事件類型
如果某種事件類型被封鎖,那麽你可以使用event
模組的set_allowed
函式,來取消封鎖該事件類型。
set_allowed(type)
- type 參數
type
參數用於指定取消封鎖的事件類型,他可以是某個事件類型對應的整數,或一個包含事件類型對應整數的 Python 序列物件。如果該參數為空值None
,則表示取消封鎖所有的事件類型。
在下面的範例中,我們封鎖了結束和視窗關閉事件,當點選視窗的關閉按鈕時,視窗可能不會關閉,除非你之前將滑鼠移入過視窗。
# 匯入模組 display 和 event,並建立遊戲視窗
from pygame import display, event
w = display.set_mode([800, 600])
# 匯入表示結束,視窗關閉,滑鼠進入的變數
from pygame import QUIT, WINDOWCLOSE, WINDOWENTER
running = True
# 封鎖結束和視窗關閉事件
event.set_blocked([QUIT, WINDOWCLOSE])
while running:
for e in event.get():
# 當滑鼠進入視窗並且結束事件被封鎖時,解除對結束和視窗關閉事件的封鎖
if e.type == WINDOWENTER and event.get_blocked(QUIT):
event.set_allowed((QUIT, WINDOWCLOSE))
# 根據事件的類型進行不同的處理
if e.type == WINDOWCLOSE:
print('視窗關閉')
elif e.type == QUIT:
print('結束')
running = False
取得事件的名稱
event
模組的event_name
函式,可用於取得事件類型對應的事件名稱。
event_name(type)
- type 參數
type
參數是一個表示事件類型的整數。- 傳回值
event_name
函式傳回表示事件名稱的字串(請註意字母的大小寫問題)。如果是自訂事件,則統一傳回'UserEvent'
,如果是事件的類型未知,則統一傳回'Unknown'
。
在下面的範例中,我們嘗試通過event_name
函式顯示所有系統事件的名稱。
# 匯入模組 event 和一些與事件相關的變數
from pygame import event, NOEVENT, USEREVENT
# 取得與系統相關的事件的名稱
for i in range(NOEVENT + 1, USEREVENT):
name = event.event_name(i)
# 不顯示未知事件
if name != 'Unknown':
print(name)
建立自訂事件
如果你希望建立自訂事件,那麽可以使用event
模組的Event
類別(別名為EventType
)。事實上,Event
類別即可表示由作業系統引發的事件,也可表示由開發人員引發的自訂事件。
Event(type, dict, **kwargs)
- type 參數
type
參數是表示事件類型的整數,對於自訂事件,可通過函式custom_type
來產生新的自訂事件類型,具體請檢視建置自訂事件類型一段。- dict 參數
dict
參數是一個包含事件特性的 Python 字典,其鍵值組的鍵是表示特性名稱的字串,鍵值組的值是特性的值。- kwargs 參數
可變參數
kwargs
所包含的關鍵字參數,同樣用於設定事件的特性,參數的名稱為特性的名稱,參數的值為特性的值。如果
kwargs
參數與dict
參數設定了相同的特性,則以kwargs
為準。
Event
物件的唯讀變數type
,是表示事件類型的整數,其值與建構子的type
參數相同。
event.type
與其他 Python 物件類似,你可以通過.
運算子來存取Event
物件的特性,這些特性通常是通過Event
物件建構子的dict
與kwargs
參數建立的。而Event
物件的唯讀變數dict
,是一個包含了事件特性的字典,他提供了另外一種存取事件特性的方式,其鍵值組的鍵是表示特性名稱的字串,鍵值組的值是特性的值。
event.dict
可以修改 Event 物件的特性
雖然Event
物件擁有唯讀變數dict
,但通過建構子的dict
與kwargs
參數所建立的特性是可以被修改的,這可以通過.
運算子以及Event
物件的dict
變數來實作。
在下面的範例中,事件物件e2
的id
為2
而不是1
,因為,需要以kwargs
參數所指定的為準。
# 匯入模組 event 和一些與事件相關的變數
from pygame import event, QUIT, USEREVENT
# 建立系統事件物件
e1 = event.Event(QUIT)
print(e1)
# 建立自訂事件物件,其 id 為 2
e2 = event.Event(USEREVENT + 100, {'id': 1, 'message': '這是自訂事件!'}, id=2)
print(e2)
<Event(256-Quit {})>
<Event(32966-UserEvent {'id': 2, 'message': '這是自訂事件!'})>
建置自訂事件類型
event
模組的custom_type
函式,可用於建置一個新的整數,用於表示自訂事件的類型,而每一次呼叫custom_type
所得到整數均不同。
custom_type()
- 傳回值
custom_type
函式傳回一個新的整數,用於表示自訂事件的類型。
呼叫 custom_type 函式到達一定次數後將導致例外狀況
你不能無限製的使用custom_type
函式,當對其的呼叫到達一定次數之後,將導致例外狀況pygame.error: pygame.event.custom_type made too many event types
。
# 匯入模組 event
from pygame import event
# 建立兩個自訂事件
e1 = event.Event(event.custom_type())
print(e1.type)
e2 = event.Event(event.custom_type())
print(e2.type)
32867
32868
判斷兩個事件是否相等
對於兩個Event
物件,你可以通過==
或!=
來判斷他們是否相等或不相等,當他們的事件類型以及dict
變數對應的字典相等時,則認為兩個事件物件相等,否則認為不相等。
# 匯入模組 event,USEREVENT
from pygame import event, USEREVENT
# 建立一些事件物件
e1 = event.Event(USEREVENT + 1)
e2 = event.Event(USEREVENT + 1, message='一個自訂事件')
e3 = event.Event(USEREVENT + 1, {'message': '一個自訂事件'})
e4 = event.Event(USEREVENT + 2, {'message': '一個自訂事件'})
e5 = event.Event(USEREVENT + 2, {'message': '一個自訂事件'}, id=1)
# 判斷他們是否相等
print(f'e1 == e2?{e1 == e2}')
print(f'e2 == e3?{e2 == e3}')
print(f'e3 == e4?{e3 == e4}')
print(f'e4 == e5?{e4 == e5}')
e1 == e2?False
e2 == e3?True
e3 == e4?False
e4 == e5?False
引發事件
event
模組的post
函式可將一個事件新增至事件佇列,這類似於引發了新的事件,你可以通過get
之類的函式來取得被新增的事件。通常情況下,呼叫post
是為了新增自訂事件。
post(event)
- event 參數
event
參數是被新增的Event
事件物件。- 傳回值
如果
post
函式傳回True
,表示事件被成功新增至事件佇列末尾,否則表示未能成功新增。
post 函式不能將被封鎖的事件新增至事件佇列
如果被新增的事件的類型已經被封鎖,那麽post
函式將傳回False
,這表示事件未能被成功新增至事件佇列。此時,你需要首先使用set_allowed
函式來解除被封鎖定的事件類型,具體請檢視取消封鎖某種事件類型一段。
在下面的範例中,按下滑鼠將引發結束事件,但這並不會成功,除非你將滑鼠移出視窗一次,因為我們在開始時封鎖了結束事件。
# 匯入相關內容,並建立遊戲視窗
from pygame import display, event, QUIT, WINDOWLEAVE, MOUSEBUTTONDOWN
w = display.set_mode([800, 600])
# 封鎖結束事件
event.set_blocked(QUIT)
running = True
while running:
for e in event.get():
if e.type == WINDOWLEAVE:
# 當滑鼠離開視窗時,取消封鎖結束事件
event.set_allowed(QUIT)
elif e.type == MOUSEBUTTONDOWN:
# 按下滑鼠時,引發結束事件
event.post(event.Event(QUIT))
elif e.type == QUIT:
# 結束遊戲迴圈
running = False