如何捕获处理游戏事件?以及创建和引发自定义事件等问题
关注 1260
本节提到的事件队列中存储的事件,实际上是指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如何在 Pygame 中捕获、过滤、处理事件视频演示 Bilibili
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
内容分类
源码
讲解视频
如何在 Pygame 中捕获、过滤、处理事件·YouTube如何在 Pygame 中捕获、过滤、处理事件·Bilibili