理解事件
在上一个程序中,我们处理了QUIT事件,你想退出程序时它是必定会产生的消息。Pygame也产生其他的消息来通知你发生了什么事情比如鼠标移动和按键。事件可能在任何时候产生,无论你的程序正在做什么。例如,你的代码正在画一个坦克在桌面上,这个时候用户按下了面板上的fire 键。这个时候你无法对事件作出反应,pygame把这些消息存储在队列之中,知道你准备好了去处理它们。(特别是在游戏循环开始的时候)你可以将消息队列认为是很多人在排队,这些排队的人都携带着特别的信息。当游戏玩家按下fire键,事件产生,事件包含的信息告诉游戏哪些键被按下。同样当玩家释放这个键的时候,同样有消息产生告诉程序那个键被释放了。他们可以伴随这鼠标事件和按键事件。
检索事件
在早先的例子中我们使用pygame.event.get()来检索所有的事件和把它们从队列中移除。就好像打开门让每一个人都进来。这个可能是处理事件的最好的方式。因为它确保我们已经处理了所有的事情在我们在屏幕上画一些东西之前。但是还有其他的方式来处理事件。如果你调用pygame.event.wait(),pygame将等待事件的发生在它返回之前。,就好像在门口等待客人到达一样。这个函数并不经常用在游戏中因为它将会是程序挂起知道某些事件的发生,但是这个函数对与其他程序通信的游戏程序有用,像媒体播放器。另外一个可以选择的方法是使用pygame.event.poll(),它将返回一个单一事件如果那里确实有一个消息等待,或者一个虚拟的事件NOEVENT如果那里没有事件在队列中。
无论你使用哪种方法,不允许这些建立是很重要的,因为这些事件队列在大小上是受到限制的。事件将被丢失如果队列溢出的话。. Whatever method you use, it is important to
not allow them to build up, because the event queue is limited in size and events will be lost if
the queue overflows.所以有必要至少使用一个事件处理函数,这样pygame才能正常的处理事件。如果你不使用任何事件处理函数,你可以调用pygame.event.pump()代替事件循环。每一个对象都含有一些成员变量来描叙事件的发生。这些信息根据事件的不同而不同。它们唯一的相同点就是事件的类型。这个类型是一个值来指示事件的类型。知道这些值,你可以决定怎么处理它。表3-4列出了标准的事件你可能接收到的。你将看到这些事件中的一部分的使用在本章中。
Table 3-4. 标准事件
事件目的参数
QUIT 用户点击关闭按钮
ACTIVEEVENT pygame被激活或隐藏Pygame has been activated or hidden. gain, state
KEYDOWN 键盘按键被按下
KEYUP 按键被释放
MOUSEMOTION 鼠标移动
MOUSEBUTTONDOWN 鼠标被点击
MOUSEBUTTONUP 鼠标点击释放
JOYAXISMOTION 游戏手柄移动Joystick or pad was moved. joy, axis, value
JOYBALLMOTION 手柄滑球系统
JOYHATMOTION Joystick hat was moved. joy, hat, value
JOYBUTTONDOWN 游戏手柄按键
JOYBUTTONUP 游戏手柄按键释放Joystick or pad button was released. joy, button
VIDEORESIZE 窗口重画
VIDEOEXPOSE 部分或者所有的游戏窗口被显示
USEREVENT 用户事件
下面让我们写一个简单的程序来显示所有产生的时间。列表3-2使用pygame.event.wait()来等待一个事件。只要它得到这个消息,它将将消息内容送到str,并且将它添加到列表。剩余的代码将尽可能的在屏幕上显示新的事件。Pygame将使用字体模板来显示文本(这个我们将稍后讨论)。
■提醒 如果你改变列表的填充颜色到(0,0,0)和字体颜色到(0,255,0),列表看起来将象矩阵。你的使用你的想象能力。
Listing 3-2. Displaying the Message Queue
import pygame
from pygame.locals import *
from sys import exit
pygame.init()
SCREEN_SIZE = (800, 600)
screen = pygame.display.set_mode(SCREEN_SIZE, 0, 32)
font = pygame.font.SysFont("arial", 16);
font_height = font.get_linesize()
event_text = []
while True:
event = pygame.event.wait()
event_text.append(str(event))
event_text = event_text[-SCREEN_SIZE[1]/font_height:]
if event.type == QUIT:
exit()
screen.fill((255, 255, 255))
y = SCREEN_SIZE[1]-font_height
for text in reversed(event_text):
screen.blit( font.render(text, True, (0, 0, 0)), (0, y) )
y-=font_height
pygame.display.update()
如果你运行程序,你将看到一个简单的白色窗口移动鼠标到窗口,它将开始捕获鼠标移动的消息,这些消息只要鼠标移动就会产生。这些事件详细说明当前鼠标的位置,无论鼠标移动的位置和上次移动的位置有多远也不管当前那个按钮被按下。你都能够得到鼠标的当前位置通过使用pygame.mouse。正如我们在上个hello world例子中做的一样。但是你可能会丢失玩家在干什么的信息。这是一个特别的问题对在后台运行大量工作的桌面电脑来说。它可能在短时间内停止你的游戏。对一个鼠标贯标来说,你只需要知道鼠标在每一帧开始的时候的位置,所以只需使用pygame.nouse.get_pos().如果你使用鼠标移动来控制一辆坦克和按钮来控制开火,最好使用事件来管理游戏,因为游戏能更好的掌握玩家正在做什么。
Figure 3-2. Output from the events script
处理鼠标移动事件
正如你前面看到的,MOUSEMOTION 事件将产生,无论什么时候你将鼠标划过游戏窗口。这些事件含有以下值:
• buttons—一个有三个值的元组与鼠标上的按键对应。 buttons[0]是鼠标左键buttons[1] 鼠标中间键, buttons[2]是鼠标右键. 如果被按下,相应的值被置1,如果没有按下则为
Multiple buttons can be pressed at once.
• pos—一个元组含有当鼠标被按下时的位置
• rel—一个元组含有鼠标从上次鼠标事件到本次鼠标事件的距离。
处理鼠标按钮事件
另外鼠标也会产生MOUSEBUTTONDOWN and MOUSEBUTTONUP事件
如果你点击鼠标,在消息队列中,你将先看到鼠标按下的事件,接下来是按键释放的事件当你的手指松开鼠标按键时。为什么会有两个事件呢?如果你把鼠标按键当作发射火箭的触发装置,你将只需要其中的一个事件,但是你可能有不同类型的武器,例如机关连续发射当鼠标按键被一直按下的时候。那样的话,只要鼠标按键是按下的就会一直发射,知道鼠标按键被释放。两种类型的鼠标按键事件包含下面的两个属性值。
• button—按下的键的数字,1代表左键,2代表中建,3代表右键。
• pos—包含事件发生时,鼠标位置的元组。
处理键盘事件
键盘和游戏手柄有类似的按键按下和按键释放事件;KEYUP在按键释放时产生,程序3-3展示了怎么样去响应KEYUP和KEYDOWN事件来通过光标键来移动屏幕上的东西。如果你运行这个程序,你将看到一个包含一个简单背景图像的窗口。按向前、向后、向左和向右的话,背景将在那个方向滑动。当你把手指移开按键的时候,背景将停止移动。
Listing 3-3.使用键盘事件来移动背景
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:
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:
if event.key == K_LEFT:
move_x = 0
elif event.key == K_RIGHT:
move_x = 0
elif event.key == K_UP:
move_y = 0
elif event.key == K_DOWN:
move_y = 0
x+= move_x
y+= move_y
screen.fill((0, 0, 0))
screen.blit(background, (x, y))
pygame.display.update()
Listing 3-3 象Hello World程序一样运行,它导入和初始化pygame模板,然后在载入背景图片。只有事件循环是不同的,在这里有两个事件,一个是KEYDOWN,另外一个是KEYUP。这些事件含有同样的三个属性值:
• key—这个代表按下或释放的键。每一个按键都一K_开始。例如字母键就是从K_a到K_z,同样也有如空格键K_SPACE和返回键K_RETURN。想知道完整的键盘代码,请访问www.pygame.org/docs/ref/key.html.
• mod—这个值告诉我们与其他键一起使用的组合键,如Shift, Alt, and Ctrl.每一个这样的键都以KMOD开始,如KMOD_SHIFT, KMOD_ALT, and KMOD_CTRL. 你可以检测这些值通过与操作。For example, mod & KMOD_CTRL 将会检测CTRL键是否按下。www.pygame.org/docs/ref/key.html 提供了所有的这些键的键值。
• unicode—这个参数告诉我们按下的键值 Unicode 。它是由按下的键和组合键一起产生的。
每一个英文字母和其他语言符号都有一个 Unicode value 。你不会经常使用到这些键值,因为按键在游戏中一般当作键入文本的工具。唯一的例外就是在英雄榜你想输入非英文字母的时候。
在处理KEYDOWN事件的时候,我们.检测四个光标键。如果光标左键按下,那么move_x的值被设置成-1;如果光标右键被按下,那么move_x将是1;这些值将加到x坐标上,这样才能使背景左右移动。同样,y方向上也是这样的操作。在这里我们同样处理KEYUP事件,因为我们想让背景停止移动,当我们释放光标键的时候。处理按键释放的代码与处理按键按下的代码是类似的,但是它将move_x和 move_y设置成0来停止背景的移动。
在事件循环之后,我们就应该将move_x和move_y的值分别加到x和y坐标轴上。下面的事情就是天成背景颜色,这里我们通过screen.fill((0, 0, 0))来将背景设置成黑色。(颜色的内容将在第四章讲解)。这一行语句是必要的,因为如果我们移动背景图像,它将不能再覆盖整个显示区域,也就以为这它将不再是背景,所以我们需要重新设置背景。
过滤消息
并不是所有的消息在游戏中都需要处理,同样我们有不同的方法来取得事件消息的内容。例如,如果你用pygame.mouse.get_pos() ,你就没有必要再响应MOUSEMOTION event.有时你可能需要终止处理一些消息。如果你正在播放一个视频,你可能想忽略输入事件知道它播放完毕。
Pygame的事件模板有大量的函数来帮助你处理事件。你可以组织事件进入事件队列通过使用set_block函数。例如下面的这一行语句将忽略鼠标移动的消息。
pygame.event.set_blocked(MOUSEMOTION)
如果你传递一系列的事件,所有的这些时间将会被阻止。下面的这行语句将阻止所有的来自键盘消息包括KEYDOWN和KEYUP事件。
pygame.event.set_blocked([KEYDOWN, KEYUP])
如果你不想阻止所有的时间,那么就将参数None付给set_blocked().这个语句将允许所有的消息进入事件队列。
pygame.event.set_blocked(None)
与 set_blocked相反的函数是 set_allowed, 这个函数将可以选择事件进入队列。它可以是单一的事件类型或者一系列的事件类型。但是如果你传递none参数,那么它将有效的阻止所有的事件。你可以使用通过pygame.event.get_block来获得被阻止的单一事件。
投递事件
一般是pygame来产生所有的事件,但是你也可以创建自己的用户事件。你可以利用这个能力来通过复制用户输入来演示文档或者模拟一只猫走路。
为了发送事件。你必须先构造一个事件对象通过pygame.event.Event ,然后通过pygame.event.post. 来投递这个消息。这个事件将会被放在队列的尾部。等待被事件循环检索到。这里我们模拟玩家按下了spacebar:
my_event = pygame.event.Event(KEYDOWN, key=K_SPACE, mod=0, unicode=u' ')
pgame.event.post(my_event)
事件构造有多种事件类型,表3-4中的事件类型都可以,事件的值必须包含在里面。一旦我们模拟了KEYDOWN事件,我们需要提供事件处理的所有的值。这行语句将创造出同样的事件对象:
my_event = pygame.event.Event(KEYDOWN, {"key":K_SPACE, "mod":0, "unicode":u' '})
同样你可以创造自己的新事件。所有你需要做的只是提供这个事件的属性值
这一点将十分有用,如果你想在事件循环里做一些事情在窗口被绘制之前。这里有一个用户事件响应通过键盘来控制猫走路的例子。
CATONKEYBOARD = USEREVENT+1
my_event = pygame.event.Event(CATONKEYBOARD, message="Bad cat!")
pgame.event.post(my_event)
处理用户自定义事件的方法同pygame的普通事件一样。你需要做的就是检测事件类型是否与你定制的事件相匹配。这里有一个处理自定义事件的例子: CATONKEYBOARD event:
for event in pygame.event.get():
if event.type == CATONKEYBOARD:
print event.message
打开显示
我很慎重的掩盖了打开显示在HELLO WORLD这个例子中,因为我们只需要一个简单的显示就够了。但是pygame有许多种显示。具体怎么显示,依赖于你怎么实现游戏。一般我们采用固定的分辨率,因为它能简化我们编写程序。同样也取决与你想达到的游戏效果。在游戏中你同时移动的东西越多,游戏运行的速度就越慢。所以你应该选择一个低分辨率的方案来加速你的游戏。
最好的解决办法就是让游戏玩家自己决定采用哪种分辨率,只有那样才能更好的利用系统的资源在显示效果和运行速度之间。如果你这样选择,那么你必须确定在任何潜在的解决方法中,你的游戏将运行正常。
全屏显示
在 Hello World这个程序中,我们使用了下面的语句来建立一个窗口:
screen = pygame.display.set_mode((640, 480), 0, 32)
第一个参数就是窗口的大小。这个参数能够覆盖大部分的桌面,但不是全部,你可以选择你自己想要的大小。在一个较小的窗口中可以方便调试,但是大部分的游戏都运行在整个屏幕下。这个屏幕将没有边界和标题栏。全屏模式运行速度将会快一点,因为你的程序不会与其他的窗口打交道。
要设置全屏模式只需要将set_mode第二个参数设置成FULLSCREEN
screen = pygame.display.set_mode((640, 480), FULLSCREEN, 32)
■警告如果你的程序有错误在全屏模式下,那么有时候很难让你回到桌面上。因此你最好先在窗口模式下测试它。同样你最好提供另外的退出游戏的方法,因为在全屏模式下关闭按钮是不可见的。
当你选择全屏模式的时候,你的视频卡将切换到另外的视频模式,这个模式将改变显示的高度和宽度。并且决定一次显示多少色彩。视频卡仅仅支持少量的大小模式和一定数量的色彩数,但是pygame将帮助你如果你选择的一个视频模式视频卡不支持。如果你选择的大小视频卡不支持,那么pygame 将把画面复制到屏幕的中央。这样将导致画面的边界是黑色的。为了避免这样的边界,选择一个标准的分辨率所有的视频卡都支持的。:(640,480),(800,600),(1024,768)。为了准确的知道你的显示卡支持的分辨率,你只好使用
>>> import pygame
>>> pygame.init()
>>> pygame.display.list_modes()
[(800, 600), (1280, 1024), (1280, 960), (1280, 800), (1280, 768), (1280, 720),
(1152, 864), (1088, 612), (1024, 768), (960, 600), (848, 480), (800, 600),
(720, 576), (720, 480), (640, 480), (640, 400), (512, 384), (480, 360), (400, 300),
(320, 240), (320, 200), (640, 480)]
如果视频卡不能不能给你需要的颜色数,pygame将自动的转换颜色来适应显示。(图像的质量会下降)
下面的例子将告诉我们怎么样从窗口模式到全屏模式。如果你按下F键,将从窗口模式切换到全屏模式。第二次按下F键,将返回到窗口模式。
Listing 3-4. Full-Screen Example
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()
Fullscreen = False
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
if event.type == KEYDOWN:
if event.key == K_f:
Fullscreen = not Fullscreen
if Fullscreen:
screen = pygame.display.set_mode((640, 480), FULLSCREEN, 32)
else:
screen = pygame.display.set_mode((640, 480), 0, 32)
screen.blit(background, (0,0))
pygame.display.update()
Resizable Pygame Windows
如果你想改变窗口的大小,那么只需要把鼠标移动到窗口边界,然后用鼠标滑动,就可以改变大小。我们可以很容易的在set_mode函数中使用RESIZABLE标志来达到我们的目的。Pygame将通知你的程序,如果窗口大小改变,将发送VIDEORESIZE时间来告知高度和宽度。
当你收到这样的消息,你需要调用display.set_mode()来适应窗口大小的改变。例子3-5告诉我们怎么样响应窗口大小的改变。
Listing 3-5. Using a Resizable Window
background_image_filename = 'sushiplate.jpg'
import pygame
from pygame.locals import *
from sys import exit
60 CHAPTER 3 ■ INTRODUCING PYGAME
SCREEN_SIZE = (640, 480)
pygame.init()
screen = pygame.display.set_mode(SCREEN_SIZE, RESIZABLE, 32)
background = pygame.image.load(background_image_filename).convert()
while True:
event = pygame.event.wait()
if event.type == QUIT:
exit()
if event.type == VIDEORESIZE:
SCREEN_SIZE = event.size
screen = pygame.display.set_mode(SCREEN_SIZE, RESIZABLE, 32)
pygame.display.set_caption("Window resized to "+str(event.size))
screen_width, screen_height = SCREEN_SIZE
for y in range(0, screen_height, background.get_height()):
for x in range(0, screen_width, background.get_width()):
screen.blit(background, (x, y))
pygame.display.update()
当程序运行的时候,将显示一个图像背景的简单的窗口。如果你点击窗口边界的角,再滑动鼠标,程序将获得VIDEORESIZE事件。然后就会调用display_set_mode()函数来改变窗口的大小。
窗口大小改变的消息有以下的属性值:
• size—这个元组说明原来窗口的高度的宽度。size[0] is the width and size[1] is the height.
• w—这个值是新窗口的宽度T
• h—新窗口的高度
因为窗口显示的大小是改变的,我们画的背景将有一些不同,因为我们的背景是图片,它必须适应大小来覆盖显示区域。两次调用将使我们的图片在坐标之间。
大多数的游戏在全屏模式下运行,所以改变大小的功能你可能用不到。但是它也有它一定的用处。