pygame编程入门之二:手把手教你——拳击猴子
作者: Pete Shinners(pete@shinners.org)
翻译: 杨晓宏(561071777@qq.com)
Introduction
在pygame的例子中有一个简单的例子,叫做“黑猩猩”。
这个例子模拟一个可以在小屏幕上可移动的小猴子,攻攻击它承诺着财富和回报。
这个例子本身非常简单,而且在错误检查代码上也有点不完备。
这个示例程序演示了pygame的许多功能,比如创建图形窗口、加载图像和声音文件、渲染TTF文本、基本事件和鼠标处理。
对于pygame的1.3版本后,本例子被完全重写,以添加更多的特性和错误检查。 这大约是原始示例的两倍,现在的内容,是为你的项目推荐的重用代码。
本教程将逐块遍历代码块,解释代码是如何工作的。
并涉及到如何改进代码,以及错误检查,可以帮助解决兼容问题。
这是一个适合第一次接触到pygame代码的个人学习和观察。
一旦pygame被完全安装,就可以在示例目录中找到并运行黑猩猩程序。
(不,这不是一个横幅广告,是它的截图)
Full Source
Import Modules
这是将所有需要的模块导入程序的代码。
它还检查了一些可选的pygame模块的可用性。
import os, sys
import pygame
from pygame.locals import *
if not pygame.font: print 'Warning, fonts disabled'
if not pygame.mixer: print 'Warning, sound disabled'
首先,我们导入标准的“os”和“sys”python模块。这些允许我们做一些事情,比如创建平台独立的文件路径。
在接下来的一行中,我们导入pygame包。当pygame被导入时,它将导入pygame的所有模块。一些pygame模块是可选的,如果没有找到它们,它们的值就会被设置为“None”。
有一个特别的pygame模块,名为“locals”。这个模块包含pygame的一个子集。这个模块的成员是常用的常量和函数,它们已经被证明是有用的,可以放到程序的全局名称空间中。
这个局部模块包括“Rect”这样的函数来创建一个矩形对象,还有许多常量,比如“QUIT,HWSURFACE”,这些常量用于与pygame的其他部分交互。将本地模块导入到全局命名空间中是完全可选的。
如果您选择不导入它,所有的本地成员也可以在pygame模块中使用。
最后,如果在pygame中没有可用的字体或声音模块,我们决定打印一条 适合的警告消息。
载入资源
这里有两个函数,可以用来加载图像和声音。我们将在本节中单独查看每个函数。
def load_image(name, colorkey=None):
fullname = os.path.join('data', name)
try:
image = pygame.image.load(fullname)
except pygame.error, message:
print 'Cannot load image:', name
raise SystemExit, message
image = image.convert()
if colorkey is not None:
if colorkey is -1:
colorkey = image.get_at((0,0))
image.set_colorkey(colorkey, RLEACCEL)
return image, image.get_rect()
这个函数接受一个图像名称来加载。它还有一个可选参数,用来为图像设置一个colorkey。在图形中使用一个colorkey来表示透明图像的颜色。
这个函数所做的第一件事就是为文件创建一个完整的路径名。在这个例子中,所有的资源都在一个“data”子目录中。通过使用os.path.join函数,创建一个路径名,它使游戏适合于运行在任何平台。
接下来,我们使用pygame.image.load 函数。
将这个函数封装在一个try/except块中,因此如果加载图像有问题,我们可以优雅地退出。
在载入映像之后,对convert()函数进行调用。这将生成一个新的surface副本,并转换其颜色的格式和深度以匹配显示。这意味着将图像blit()到屏幕将会尽可能快。
最后,可以设置图像的colorkey。如果用户为colorkey提供了一个参数,我们将该值用作图像的colorkey。这通常是一个颜色RGB值,比如(255,255,255)为白色。
也可以把-1的值作为颜色键。在这种情况下,函数将在图像的左上像素中查找颜色,让colorkey使用该颜色。
def load_sound(name):
class NoneSound:
def play(self): pass
if not pygame.mixer:
return NoneSound()
fullname = os.path.join('data', name)
try:
sound = pygame.mixer.Sound(fullname)
except pygame.error, message:
print 'Cannot load sound:', wav
raise SystemExit, message
return sound
接下来是加载声音文件的函数。这个函数的第一件事就是检查pygame.mixer模块被正确导入。
如果没有,它返回一个小型的类实例,它有一个虚拟的播放方法。这将足够产生一个普通的声音对象,让游戏在没有任何额外的错误检查的情况下运行。
这个函数类似于图像加载函数,但是处理一些不同的问题。
首先创建一个完整的声音图像路径,并将声音文件加载到 try/except 块中。然后简单地返回加载的声音对象。
游戏对象类
这里,创建了两个类来表示游戏中的对象。几乎所有的游戏逻辑在这两个类里都有表达。我们要在这里多看一看了。
class Fist(pygame.sprite.Sprite):
"""moves a clenched fist on the screen, following the mouse"""
def __init__(self):
pygame.sprite.Sprite.__init__(self) #call Sprite initializer
self.image, self.rect = load_image('fist.bmp', -1)
self.punching = 0
def update(self):
"move the fist based on the mouse position"
pos = pygame.mouse.get_pos()
self.rect.midtop = pos
if self.punching:
self.rect.move_ip(5, 10)
def punch(self, target):
"returns true if the fist collides with the target"
if not self.punching:
self.punching = 1
hitbox = self.rect.inflate(-5, -5)
return hitbox.colliderect(target.rect)
def unpunch(self):
"called to pull the fist back"
self.punching = 0
这里我们创建一个类来表示玩家的拳头。它继承自于pygame模块中的精灵类。
当这个类的新实例被创建时,就会调用init函数。我们要做的第一件事是确保基类调用init函数。
这允许Sprite's init函数准备为精灵对象使用。这个游戏使用一个 sprite drawing Group类。这些类可以绘制具有“image”和“rect”属性的精灵。
通过简单地改变这两个属性,渲染器将在当前位置绘制当前图像。
所有sprites都有一个update()方法。
这个函数通常在每个帧中调用一次。在这里,您应该放置代码来移动和更新精灵的变量。
拳头的update()方法将拳头移动到鼠标的位置。如果拳头在“punching”状态时,它也会轻微地偏移拳头位置。
下面两个函数punch()和unpunch()改变了拳头的punching状态。如果拳头与给定的目标精灵发生碰撞时,punch()方法也返回一个true的值。
class Chimp(pygame.sprite.Sprite):
"""moves a monkey critter across the screen. it can spin the
monkey when it is punched."""
def __init__(self):
pygame.sprite.Sprite.__init__(self) #call Sprite intializer
self.image, self.rect = load_image('chimp.bmp', -1)
screen = pygame.display.get_surface()
self.area = screen.get_rect()
self.rect.topleft = 10, 10
self.move = 9
self.dizzy = 0
def update(self):
"walk or spin, depending on the monkeys state"
if self.dizzy:
self._spin()
else:
self._walk()
def _walk(self):
"move the monkey across the screen, and turn at the ends"
newpos = self.rect.move((self.move, 0))
if not self.area.contains(newpos):
if self.rect.left < self.area.left or \
self.rect.right > self.area.right:
self.move = -self.move
newpos = self.rect.move((self.move, 0))
self.image = pygame.transform.flip(self.image, 1, 0)
self.rect = newpos
def _spin(self):
"spin the monkey image"
center = self.rect.center
self.dizzy += 12
if self.dizzy >= 360:
self.dizzy = 0
self.image = self.original
else:
rotate = pygame.transform.rotate
self.image = rotate(self.original, self.dizzy)
self.rect = self.image.get_rect(center=center)
def punched(self):
"this will cause the monkey to start spinning"
if not self.dizzy:
self.dizzy = 1
self.original = self.image
黑猩猩类的内容比拳头多一点,但没有什么复杂的。此类将在屏幕上来回移动黑猩猩。当黑猩猩被punched,就会有一个令人激动的旋晕效果。
这个类也从Sprite基类派生出来,并且初始化方法与拳头相同。在初始化时,该类还将属性“area”设置为屏幕大小。
这只黑猩猩的update()功能仅仅是观察当前“dizzy”的状态,当猴子从中拳而旋转时,dizzy获得true。
由此,它可以调用_spin()或_walk()方法。这些函数有下划线预先定义好的。
这是个标准的python习惯用法,表明这些方法只能被黑猩猩类私有。我们可以给他们一个双下划线,这将告诉python真正的私有方法,但在此不需要这样的保护。:)
通过偏移当前的rect,通过一个给定的偏移量,为猴子创建了一个新的位置。如果这个新位置超过屏幕的显示区域,它就会反转移动方向。它还使用了pygame.transform.flip函数。这是种粗糙的效果,使猴子看起来像是在转动他的方向。
当猴子目前“dizzy”时,就会调用_spin()方法。dizzy属性用于存储当前的旋转量。
当猴子旋转360度后,它将猴子图像重新设置为原始的,非旋转的版本。在调用transform.rotate 函数前,您将看到代码对名为“rotate”的函数进行了局部引用。这个例子没有必要这样做,只是为了让下面一行的长度稍微短一点。
注意,当调用rotate函数时,总是从原始的猴子图像旋转。因为当旋转时,图像质量会有轻微的下降。如果反复旋转相同的图像,图像质量会变得更糟。
此外,当旋转一个图像时,图像的大小将会改变。这是因为图像的角将被旋转出去,使图像更大。应确保新图像的中心与旧图像的中心相匹配,让它在不移动的情况下旋转。
最后一个方法是punched(),它让精灵进入dizzy状态。这将导致图像旋转。它还复制了当前图像,并改名为“original”。
初始化所有对象
在我们对pygame做更多之前,需要确保所有模块被初始化。
在本例中,我们将打开一个简单的图形窗口。现在我们开始介绍程序的main()函数,它实际上运行着程序的所有内容。
pygame.init()
screen = pygame.display.set_mode((468, 60))
pygame.display.set_caption('Monkey Fever')
pygame.mouse.set_visible(0)
初始化“pygame”的第一行,为我们处理了一些工作。它会检查导入的pygame模块,并尝试初始化它们中的每一个模块。可以从返回值检查模块是否失败,但是我们不会在这里多做耽误。
当然可以通过手工进行更多的控制和初始化每个特定的模块。这种类型控制通常是不需要的,但是如果你愿意的话也可以使用。
接下来,我们设置显示图形模式。注意,pygame的.display模块用于控制所有的显示设置。
当前情况下,我们的要求是一个简单的窗口。关于设置图形模式有一个完整的教程,但是如果我们不关心,pygame会很好地为我们提供一些有用的东西。
既然在参数里我们还没有提供,Pygame会选择最好的颜色深度。
最后,设置了窗口标题,并关闭了窗口的鼠标光标。这当然是非常基本的,现在我们有一个小的黑色窗口准备好了。通常光标默认为可见的,因此不需要设置状态,除非我们确实想隐藏它。
创建背景
程序在背景上有文本信息。对我们来说,创建一个单独的surface来表示背景,并反复使用它是很好的。
第一步是创建surface。
background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill((250, 250, 250))
这创建了一个与显示窗口大小相同的新surface。
注意在创建Surface之后的额外调用convert()。
没有参数的转换确保我们的背景与显示窗口的格式相同,这将带来最快的结果。
用一种纯白的颜色填充整个背景。填充用RGB三元组作为颜色参数。
把文本放在背景上,居中
如果pygame.font模块已经正确导入了,既然有了一个背景surface,让我们把文本放上去。
如果没有,我们就跳过这一节。
if pygame.font:
font = pygame.font.Font(None, 36)
text = font.render("Pummel The Chimp, And Win $$$", 1, (10, 10, 10))
textpos = text.get_rect(centerx=background.get_width()/2)
background.blit(text, textpos)
如您所见,几个步骤可以完成这项工作。
首先,我们必须创建字体对象并将其渲染到一个新的surface。
然后我们找到这个新surface的中心,并将它blit(粘贴)到背景中。
字体是用Font模块的Font()构造器创建的。通常是将TrueType字体文件的名称传递给这个函数,但是也可以传递None,它将使用默认字体。
字体构造器还需要知道创建的字体大小。
然后把字体渲染到一个新的surface。渲染函数创建一个新的表面,正好适合文本大小。还告诉渲染打开反锯齿功能(为了平滑的外观)和使用深灰色的颜色。
接下来,需要在显示屏上找到文本的中心位置。从文本维度创建一个“Rect”对象,这可以轻松地将其布置垤屏幕中心。
最后,将文本(blit类似于复制或粘贴)到背景图像上。
在配置完成时显示背景
屏幕上仍然有一个黑色的窗口。当等待其他资源加载时,让程序展示背景。
screen.blit(background, (0, 0))
pygame.display.flip()
这将把整个背景blit到显示窗口。blit不言自明的,但是这个flip程序是做什么的呢?
在pygame中,显示内容的更改不会立即可见。通常情况下,显示必须在已经改变的区域中update,用户才可见。
有了双缓冲显示,显示必须交换(或flipped),以便使改变可见。
这种情况下,flip()函数可以很好地工作,因为它只处理整个窗口区域,并处理单个和双缓冲surface。
准备游戏对象
这里,我们创建了游戏所需的所有对象。
whiff_sound = load_sound('whiff.wav')
punch_sound = load_sound('punch.wav')
chimp = Chimp()
fist = Fist()
allsprites = pygame.sprite.RenderPlain((fist, chimp))
clock = pygame.time.Clock()
首先,使用上面定义的load_sound函数加载两个音效。然后为每个精灵类创建一个实例。
最后,我们创建一个精灵组,它将包含所有的精灵。
实际上使用了一个名为RenderPlain的特殊sprite group。这个sprite group可以把它包含的所有精灵都画在屏幕上。它被称为RenderPlain,实际上它是更高级的Render groups。对于我们的游戏,我们只需要简单的绘画。
我们创建了一个名为“allsprites”的group,它包含了所有属于该组的精灵,形成了一个列表。稍后也可以添加或删除该组中的精灵,但是在这个游戏中用不到了。
创建的clock对象,被用来帮助控制游戏的framerate。在游戏的主循环中使用它,以确保不会跑得太快。
主循环
这里没有什么,只是一个无限循环。
while 1:
clock.tick(60)
All games run in some sort of loop. The usual order of things is to check on the state of the computer and user input, move and update the state of all the objects, and then draw them to the screen. You'll see that this example is no different.
We also make a call to our clock object, which will make sure our game doesn't run faster than 60 frames per second.
Handle All Input Events
This is an extremely simple case of working the event queue.
for event in pygame.event.get():
if event.type == QUIT:
return
elif event.type == KEYDOWN and event.key == K_ESCAPE:
return
elif event.type == MOUSEBUTTONDOWN:
if fist.punch(chimp):
punch_sound.play() #punch
chimp.punched()
else:
whiff_sound.play() #miss
elif event.type == MOUSEBUTTONUP:
fist.unpunch()
首先,从pygame中获取所有可用的事件,并循环遍历它们。前两个if看看用户是否退出了我们的游戏,或者按下了escape键。
所有情况下,我们都是从main()函数返回,程序干净利落地结束。
接下来,我们将检查鼠标按钮是否被按下或释放。如果按下按钮,我们会问拳头物体是否与黑猩猩相撞。如果猴子被击中,我们播放适当的声音效果,让猴子开始旋转(通过调用他的punched()方法)。
更新精灵
::
allsprites.update()
Sprite groups有一个update()方法,它简单地调用它所包含的所有精灵的update方法。每个对象都会动起来,当然这取决于它们的状态。
这是让黑猩猩一步步从一边跑向另一边的地方,也者他最近被punched之后后,旋转起来的理论所在。
画出整个场景
现在,所有物体到了正确位置,是时候画出来了。
screen.blit(background, (0, 0))
allsprites.draw(screen)
pygame.display.flip()
第一个blit调用将把背景画在整个屏幕上。这消除了我们在前一帧中看到的所有内容(稍微低效率,但对于这个游戏来说已经足够好了)。
接下来,我们调用sprite容器的draw()方法。
由于这个sprite容器实际上是“DrawPlain”sprite组的一个实例,所以它知道如何绘制精灵。
最后,我们 用flip()把pygame双缓冲区的内容加载到屏幕上。这使得所有东西都看到了。
游戏结束
用户已经退出了,是时候清理了。
pygame清理运行中游戏是非常简单的。事实上,由于所有的变量都是自动销毁的,我们真的不需要做任何事情。
网友评论