Pygame游戏开发之四
初学乍练
Pygame中除了Group这个Sprite的容器类,还有一些继承自Group的容器类,它们分别是RenderUpdates、OrderedUpdates、LayeredUpdates、LayeredDirty。
pygame.sprite.RenderUpdates继承自pygame.sprite.Group,它相对于Group的不同点就是重写了Group的draw函数,原型是:RenderUpdates.draw(surface) : return Rect_list它将所有它包含的Sprites绘制到surface上,和Group.draw一样。但是这个函数返回了一系列的屏幕上发生改变的矩形列表,这个矩形列表应该被传递到pygame.display.update中去。
pygame.sprite.OrderedUpdates继承自pygame.sprite.RenderUpdates,它绘制Sprite的时候是以Sprite被添加时的顺序来绘制的。
pygame.sprite. LayeredUpdates绘制的方式和pygame.sprite.OrderedUpdates类似,只是它引入了图层的概念。你可以通过'default_layer'设置默认图层,或者一个整数来表示图层。如果添加的Sprite自己有一个layer的图层属性那么就是用这个,否则在添加Sprite的时候,在pygame.sprite. LayeredUpdates(*sprites, **kwarges)中的**kwarges参数中对layer属性进行设置,如果两者都没有设置,那么系统将采用默认图层。
pygame.sprite. LayeredDirty继承自pygame.sprite.LayeredUpdates,它是DirtySprites的专用容器类,继承了Group的所有操作。
因为我们需要用到DirtySprite,所以之前我们用到的所有Group类都替换成LayeredDirty,而且为了便于扩展,我们将LayeredDirty再封装一层,以便添加新的成员函数。
class gameManager(pygame.sprite.LayeredDirty) :
def __init__(self, selfdata) :
pygame.sprite.LayeredDirty.__init__(self)
def keyevent(self, keypressed) :
for son in self.sprites() :
son.keyevent(keypressed)
定义一个gameManager类,它继承了pygame.sprite.LayeredDirty,并且添加一个keyevent的成员函数,用于对键盘事件进行处理,它的操作很简单,调用它容器里的元素的keyevent函数,它容器里的元素有可能是另一个gameManager、或者是一个单纯的RenderObject。如果是gameManager的话就会进行递归调用,直到是RenderObject,所以我们还要给RenderObject定义一个keyevent函数,像这样:
class RenderObject(pygame.sprite.DirtySprite) :
… …
def keyevent(self, keypressed) :
pass
我们把keyevent定义在基类RenderObject中,并且用pass表示它什么都不做,然后等着子类去实现它。
class Player(RenderObject) :
… …
def keyevent(self, keypressed) :
left_or_right = keypressed[K_RIGHT] - keypressed[K_LEFT]
up_or_down = keypressed[K_DOWN] - keypressed[K_UP]
self.move(left_or_right, up_or_down)
Player是RenderObject的一个子类,我们可以用以上的语句实现Player的行走,keypressed其实是pygame.key.get_pressed(),用于获取当前键盘的某个键是否按下的映射表。
接下来我们用之前的模块来写一个游戏的背景,利用图4-1的资源拼接出图4-2的图像,并且让它在屏幕Y轴方向进行滚屏操作。
图 4-1
图 4-2
首先要明确的一点就是图片的四个方向必须是可拼接的,也就是说用9张相同的图片拼成一个九宫格,用肉眼是分辨不出它们是九张图片的,看到的是一个完整的图像。
我们定义一个RenderBack类:
class RenderBack(RenderObject) :
def __init__(self, selfdata) :
RenderObject.__init__(self, selfdata)
do_init()
def update(self) :
RenderObject.update(self)
do_update()
首先确定做这么一个背景需要用到的数据,背景的宽高是肯定要的,而且要求它在Y方向做滚屏操作,所以这个背景的实际的高肯定要比屏幕上显示高还要长(我们假设他是图4-1图片高的整数倍),那么我们用一个四元组(x, y, z, w)来表示需要知道的数据:
x : 背景图资源在images[]的索引号
y : 生成图的宽
z : 生成图的高
w: 生成图在竖直方向的原图图块的数量
这个四元组是作为selfdata被传到__init__函数中进行初始化的,并且可以在配置文件中修改它的值。
因为最后的图像要进行Y方向的滚屏操作,所以我们还需要定义一个Y方向的偏移量,以便每次绘制的时候进行偏移操作。这样初始化就可以这么写:
self.image = pygame.Surface( (int(selfdata[1]), int(selfdata[2])) )
self.rect = self.image.get_rect()
self.source_rect = self.image.get_rect()
self.backlen = int(selfdata[3])
self.backimage = RenderObject( [ selfdata[0] ] )
self.top_offset = self.backimage.image.get_height() * self.backlen - self.rect.height
这里需要注意的是,LayeredDirty在对DirtySprite进行绘制的时候总是对sprite.image的内容进行绘制的,所以在RenderBack里我们需要额外定义一个backimage来对原图进行缓存,然后通过循环将它blit到image上。这样RenderBack的拥有者才能找到image将它绘制出来。
对于update函数,每一帧我们需要更新self.top_offset这个Y方向的偏移量,并且根据它更新image图像。具体实现如下:
def update(self) :
RenderObject.update(self)
xstep = self.backimage.image.get_width()
ystep = self.backimage.image.get_height()
self.top_offset -= 3
y_offset = self.top_offset % ystep
for x in range(0, self.rect.width + xstep, xstep) :
self.image.blit( self.backimage.image, (x, 0), ((0, y_offset), (xstep, ystep - y_offset) ) )
for y in range(ystep - y_offset, self.rect.height + ystep, ystep) :
for x in range(0, self.rect.width + xstep, xstep) :
self.image.blit( self.backimage.image, (x, y), ((0,0), (xstep, ystep) ) )
(xstep, ystep)是原图的尺寸,每一帧我们对self.top_offset进行自减操作(效果就是让最后的图像有一种向上延伸的感觉,也就是滚屏的效果)。
然后我们分x方向和y方向进行讨论,因为x方向没有滚屏操作,所以只要用现有的图片填充整个宽度即可,比如原图是W=100个像素,需要填充的背景是D=250个像素,那么水平方向至少需要用到[ (D + W – 1) / W ] = 3张原图([x]表示比x小的最大整数,即取下整)。对于竖直方向,因为有滚屏操作,所以有两部分组成:第一排(从原图的y方向某个位置粘贴过来的)以及非第一排(总是粘贴原图)。将self.top_offset 对 ystep 取模,得到的是需要绘制图像的最上方在原图的偏移量,通过它可以绘制第一排,然后再调用两层for循环绘制后面几排。这样随着时间的推移,一个Y方向的滚屏效果就出来了。(未完待续)