Pygame学习笔记(四):chimp源码解析
- Python
- 2016-01-27
- 128热度
- 0评论
导航
学习资料:《Chimp Tutorial, Line by Line》
介绍
Pygame自带的示例程序中有一个叫“chimp”的小程序。它展示了很多 Pygame 的特性,比如:创建图形窗口,加载图像和声音文件,渲染TTF文本,基本事件(如鼠标事件)处理。
导入模块
这段代码导入了所需的模块,并且检查可选模块是否可用。
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'
加载资源
我们分别用两个函数来加载图片和声音。下面逐个进行解析。
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()
我们把加载图片的语句封装在了 try/except 代码块中,这样一来,一旦出错,我们可以优雅地结束程序。
这段代码中,比较困扰我的是 colorkey 这个参数。经过查阅资料和动手实验,发现它是一个很重要的概念——色键,用于选定透明色。例如,给 colorkey 设定一个RGB值 (255, 255, 255),则加载后的图片中,原来像素值为 (255, 255, 255) 的点,都会变得透明。上面的代码中,如果给 colorkey 传的值为 -1,则会以原图最左上角的那个点的颜色值作为色键。
另外,参数 RLEACCEL 的作用:An RLEACCEL Surface will be slower to modify, but quicker to blit as a source.
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:', fullname raise SystemExit, message return sound
这段代码加载声音文件。我们首先检查 pygame.mixer 模块是否正确导入,如果没有,返回一个包含假的 play 方法的对象。在游戏中,它可以像正常的 Sound 对象那样进行调用,而不必做额外的错误检查。
游戏物体类
我们创建了两个类来表示游戏中的物体。
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.sprite 模块中的 Sprite 类派生而来。
我的心得:
- 初始化时,先调用父类的 __init__ 方法。
- Rect 类的 move 和 move_ip 方法,作用相似,但有差别。move 方法会返回一个新的Rect(Returns a new rectangle that is moved by the given offset),move_ip 方法则是对当前 Rect 进行移动(Same as the Rect.move() method, but operates in place)。
- inflate 方法返回一个改变了尺寸的新矩形(Returns a new rectangle with the size changed by the given offset.)。
- Rect 类使用 colliderect 方法进行矩形的碰撞检测。
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 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
Chimp 这个类让猩猩在屏幕上来回移动,当它被拳头击中时,会兴奋地转圈圈。同样是 Sprite 的子类,它的初始化与 Fist 类似。在初始化时,它还把整个屏幕的尺寸信息保存在了 self.area 中。
update 函数,判断猩猩是否处于“晕眩”状态,进而决定调用 _walk 还是 _spin 方法。注意这两个方法的命名方式,在 Python 的规范中,加上“_”的前缀,表示该方法仅供类的内部使用。更正式一点,可以加上两个下划线“__”,这时 Python 会认为它是类的私有方法,在类的外部不可见。
_walk 函数把猩猩移动到一个新的位置,并做了边界检查,如果到了左右边界,就改变方向,并翻转图片。pygame.transform.flip 函数的原型是 flip(Surface, xbool, ybool)
,返回一个 Surface 对象。
当猩猩处于“晕眩”状态时,会调用 _spin 函数。dizzy 属性存储图像旋转的度数,达到 360 时,将猩猩图像重置为原始状态。代码中对 transform.rotate 函数做了一个局部引用“rotate”,主要是为了尽量缩短后面的代码,并非必须。注意,在 rotate 函数中,我们总是对原始图像(original)进行旋转。因为旋转操作会对图片质量产生轻微的影响,如果对某一幅图像反复进行旋转,会让图片质量变得越来越差。另外,旋转时,图像的尺寸也会发生变化,图像的四个角跑到了原来的矩形区域之外,导致图像尺寸变大。我们要确保新图像的中心点始终与原图像的中心点重合,这样图像在旋转时就不会到处乱动了。
初始化
在 main 函数中,用以下代码对 Pygame 进行初始化,并创建一个图形窗口:
pygame.init() screen = pygame.display.set_mode((468, 60)) pygame.display.set_caption('Monkey Fever') pygame.mouse.set_visible(0)
pygame.display 模块控制着所有和显示有关的设置。
我们设置了窗口的标题,并让鼠标在经过窗口时不可见。
创建背景
我们的程序将在背景上显示文字信息,最好创建一个 surface 对象来表示背景,这样可以反复使用它。
background = pygame.Surface(screen.get_size()) background = background.convert() background.fill((250, 250, 250))
我们创建了一个与 display 窗口同样大小的 surface,并调用了 convert 函数。不带任何参数的 convert 可以确保我们的背景与 display 的格式相同,这让程序运行得更快。另外,还用了一个RGB值作为 fill 函数的参数,把背景填充为白色。
在背景上写字,并居中显示
下面,开始渲染字体。
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)
我们创建了一个 font 对象,用它渲染出一个新的 surface,然后,把它粘贴(blit)到背景上,并水平居中。
Font 函数的第一个参数用于指定要用的字体文件,例如 font = pygame.font.Font('data/hellofont.ttf', 36)
,如果传入None,则使用默认字体。第二个参数指定字体大小。
render 函数根据指定的文本渲染出一个大小合适的 surface 对象,它的函数原型是render(text, antialias, color, background=None)。
我们将 antialias 设为真(这样可以让文字看起来更平滑),然后用 (10, 10, 10) 把字体设为深灰色。background 参数用于指定文字的背景色,如果不设置或设为 None,则使用透明色。
Surface.get_rect 函数返回一个尺寸刚好能覆盖该 Surface 对象的矩形,并且默认情况下左上角的坐标总是 (0, 0)。我们可以传入键值对来指定此矩形的位置。
最后,我们把渲染好的字体 blit 到了已经居中的矩形区域上。
可以显示中文字符,但必须设置中文字体。我下载了一个“天真无邪体”,用法如下:
font = pygame.font.Font(os.path.join('data', '天真无邪体.ttf'), 36) text = font.render(u"打猩猩,赢美刀", 1, (10, 10, 10))
显示已设定好的背景
目前为止,我们只能看到一个黑色的窗口。用下面的代码,将背景显示出来。
screen.blit(background, (0, 0)) pygame.display.flip()
把整个背景 blit 到 display 窗口上,然后调用 flip 函数。
在 Pygame 中,对 display 的修改不是立即可见的。通常,display 必须先更新有变化的区域,然后才能对用户可见。由于 display 有两个缓存,为了使修改可见,必须交换(swap or flip)缓存。这就是 flip 函数的作用。
准备游戏对象
下面来创建游戏中用到的所有对象。
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()
我们把 chimp 和 fist 加入到了一个名为 RenderPlain 的特殊精灵组中,这个精灵组会把它包含的所有精灵绘制到屏幕上。pygame.sprite.RenderPlain 与 pygame.sprite.Group 没有任何区别,详见 Pygame 文档。
clock 对象用来控制游戏的帧率,我们将会在游戏的主循环中使用它,以确保游戏不会运行得太快。
主循环
就是一个无限循环。
while 1: clock.tick(60)
所有的游戏都运行在某个循环中,通常的执行顺序是:检查计算机的状态和用户输入,移动所有对象并更新它们的状态,然后,把它们在屏幕上画出来。我们这里的例子也是如此。
我们也调用了 clock 对象的方法,这可以确保我们的游戏运行速度不超过每秒 60 帧。
处理输入事件
以下是处理事件队列(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()
首先,我们获取了 Python 中所有可能的事件,并对它们进行迭代。
更新精灵
allsprites.update()
精灵组有一个 update 方法,它会调用精灵组内所有精灵的 update 方法。
绘制整个场景
screen.blit(background, (0, 0)) allsprites.draw(screen) pygame.display.flip()
第一行,把背景贴(blit)到整个屏幕上,这个步骤会擦除上一帧的所有内容(这样做,效率会轻微下降,但对目前这个游戏来说已经足够好了)。
第二行绘制出精灵组中所有的精灵。第三行将缓存中的内容显示到屏幕上。
游戏结束
用户退出游戏时,需要做清理工作。这在 Pygame 中很容易。事实上,由于所有变量都是自动析构(destructed)的,我们根本不需要做什么事情!