2.13 Sprite
Sprite是游戏编程中一个非常常见的概念,Pygame中自然也有Sprite一说,本节就详细介绍一下Sprite及其用法。
Sprite的相关功能是在pygame.sprite模块中定义的。
1. 什么是Sprite
图2-16 游戏中的一只小狗
Sprite翻译为“精灵”,其实就是指游戏中各种被赋有生命的可视化对象,如一个小人、一只怪兽、一个道具等,它们普遍都有其对应的图片信息和位置信息。例如游戏中的一只小狗,它在画面中不断地做各种动作,会吃掉“骨头”等道具,如图2-16所示。
那么就可以说这只小狗是一个Sprite。
pygame.sprite模块提供了Sprite、Group等类以管理Sprite对象。
在定义自己的Sprite时,只需让它继承自Sprite类,即可使用pygame.sprite模块中预定义的众多功能。不过Sprite类的使用与Group类的使用密切相关。在Pygame中,只有使用到Group时,才必须要让自己的Sprite继承自Sprite类,否则并没有必要。这是由于pygame.sprite模块的Sprite类本身并没有提供什么独特的功能,它的大部分功能都是与Group相关的(这点与Cocos2d非常不同,Cocos2d中的Sprite类提供了非常强大的功能)。这里的Group可以理解为一个群组,我们把具有相似特征的一批Sprite添加进群组统一进行管理,所以Group其实就是Sprite的容器。
下面重点介绍Sprite和Group的用法。
2. Sprite类
Sprite类的常用属性如下。
属性image表示Sprite的图像surface,通常是来自于pygame.image.load()所加载的图片。
属性rect表示Sprite所在的位置矩形,通常来自image.get_rect()所返回的Rect对象。
在定义自己的Sprite类时,image和rect这两个属性都是必须指定的,同时,它们也都是Sprite最基本的两类信息。
Sprite类的常用方法如下。
该方法的函数体为空,它其实什么都没有做,放在这里只是为了用户在自定义时方便进行重写,我们可以把Sprite的状态更新都放在这里实现,它最终会被Group类的update()方法所调用。对于用户来说,在通过继承Sprite类定义自己的Sprite时,只需重写该update()方法即可,执行Group类的update()方法时会调用该Group中全部Sprite的update()方法。
把当前Sprite添加到参数所指定的Group。
把当前Sprite从参数所指定的Group中删除。
该方法把当前Sprite从它所属的所有Group中删除。
该方法返回当前Sprite所属的所有Group列表。
注意:一个Sprite可以同时被添加进多个Group管理。
判断当前Sprite是否属于任意一个Group,如果是,则返回True,否则返回False。
稍做总结:当通过继承pygame.sprite.Sprite自定义Sprite类时,通常情况下,我们要做的事情就是指定self.image和self.rect属性、重写self.update()方法,至于其他方法,它们都是作为功能函数调用而使用的。
3. Group类
前面讲过,Pygame中的Sprite类和Group类密切相关,所以现在让我们再来了解一下Group类,下面是它的一些常用方法。
前面讲过,Group类的update()方法将执行它所包含的所有Sprite的update()方法,也就是说,执行了该方法即更新了该Group下所有的Sprite的状态。
把指定的Sprite添加到当前Group。
把指定的Sprite从当前Group中删除。
清空当前Group,把其中所包含的Sprite全部删除。
返回当前Group所包含的所有Sprite的列表。
判断当前Group是否包含指定的全部Sprite,如果是,则返回True;如果否,则返回False。
该方法用来绘制当前Group中的所有Sprite,即把它们都绘制到参数所指定的surface上。在函数体内部进行绘制操作其实就是执行surface的blit()函数,它将使用Sprite的image和rect属性作为参数。由于Group内部的Sprite是没有存放顺序的,所以绘制的顺序也是随机的。
除了以上方法,Group类还支持一些Python中的标准操作符,如len,它用来计算Group中的Sprite的数目;如in,它用来判断Group是否包含某个Sprite。
4. 具体示例
最后看一个完整的示例,不过这次先看看它的执行效果。
如图2-17所示,窗口中有一只小狗,五块骨头。其中,小狗不断左右走动,遇到窗口边缘则调头往回走;五块骨头则以同样的速度从上至下移动,超出窗口底部时再重新出现在最顶部。
图2-17 Sprite示例程序
整体代码如下。
下面解释这段代码。
定义了两个Sprite类,一个是Dog类,另一个是Bone类,它们都继承自pygame.sprite.Sprite。
关于Dog类,也就是小狗类。由于屏幕上只有一只小狗,所以没有必要把它添加到Group进行管理,因此这里我们可以让它继承自pygame.sprite.Sprite,也可以不继承直接定义。在__init__()函数中初始化了一系列的属性:其中,self.images表示小狗所用到的图片surface列表,它包含两个图片,一个是原始图片,另一个是翻转后的图片。这里,图片翻转用到了pygame.transform.flip()函数,它的第二个参数为1,第三个参数为0,表示把图片在x方向上进行垂直翻转;self.image_idx表示当前图片surface在self.images列表中的索引,可选值为0或1;self.image为当前所使用的图片surface;self.rect为小狗的位置矩形,把它的中心点初始值指定为(50,270);self.speed为小狗的移动速度。由于Sprite类并没有draw()函数,所以需要我们自己定义,draw()的作用主要是绘制Sprite,它的函数体很简单,只是调用参数surface的blit()函数,把Sprite的self.image绘制到self.rect的位置。另外,重写了update()函数以更新小狗的状态,包括小狗的位置、速度、图片等。
关于Bone类,即骨头类。由于屏幕上存在着许多的骨头,所以我们有必要把它添加到Group进行管理,因此Bone类必须继承自pygame.sprite.Sprite。又由于Group类本身就有draw()函数,调用Group的draw()函数即可绘制其所包含的全部Sprite,所以这里就没有必要再为Bone类定义draw()函数了。在Bone类中,我们只指定了self.image和self.rect属性,重写了update()函数。
再来看main()函数。dog是Dog类的对象,它是一个独立的Sprite;bones是Group类的对象,它用来管理所有的骨头Sprite,通过Group类的add()函数为之添加五个Bone类的对象作为其Sprite;然后在while循环中不断调用dog.update()以更新小狗的状态,调用bones.update()更新各个骨头的状态,调用dog.draw()把小狗图片绘制到屏幕surface,调用bones.draw()把各个骨头图片绘制到屏幕surface。
这段程序展示了Sprite类与Group类的基本用法。由此可见,使用Group管理Sprite可以很方便地进行绘制和更新操作:执行Group的draw()函数可以绘制其所包含的所有Sprite,执行Group的update()函数可以执行其所包含的每个Sprite的update()函数。
不过在这段程序中,小狗和骨头之间是没有交互的,那么,如果想让小狗遇到骨头时把它吃掉又该怎么实现呢?这就涉及碰撞检测。下一节将介绍如何在Sprite之间实现碰撞检测。