从上一章我们已经了解到,看似简单的动画实际上并不简单。如果有大量图像在四处移动,要想跟踪每个图像“底下”有些什么,以便在移动图像时能够重绘,这可能要费很大的功夫。在我们的第一个沙滩球例子中,由于背景是白色的,所以更容易一些。不过你也可以想象,倘若背景上有一些图形,这肯定会复杂得多。
幸运的是,Pygame 可以为我们提供额外的帮助。四处移动的单个图像或图像部分称为动画精灵(sprite),Pygame 有一个特殊的模块来处理动画精灵。利用这个模块,我们可以更容易地移动图形对象。
在上一章中,我们让一个沙滩球在屏幕上反弹。如果希望一堆沙滩球都反弹呢?当然可以编写代码来单独地管理各个球,不过我们不会去这样做,而是使用 Pygame 的 sprite
模块,这样更简单一些。
术语箱
动画精灵表示作为一个单位来移动和显示的一组像素,这是一种图形对象。
“‘动画精灵’(sprite)这个词是从老式的计算机和游戏机流传下来的。这些老式的游戏机不能很快地绘制和擦除图形来保证游戏正常工作。这些游戏机有一些特殊的硬件,专门用来处理需要快速移动的游戏类对象。这些对象就称为‘动画精灵’。它们有一些特殊的限制,不过可以非常快地绘制和更新……如今,一般来讲,计算机的速度已经足够快了,不需要专门的硬件也可以很好地处理类似动画精灵的对象。不过‘动画精灵’这个词仍用来表示二维(2D)游戏中的所有动画对象。”
( 摘自 Pete Shinners 的“Pygame 教程——Sprite 模块介绍”,http://www.pygame.org/docs/tut/SpriteIntro.html。)
什么是动画精灵
可以把动画精灵想成一个小图片——一种可以在屏幕上移动的图形对象,并且可以与其他图形对象交互。
大多数动画精灵都有以下两个基本属性。
图像(image):为动画精灵显示的图片。
矩形区(rect):包含动画精灵的矩形区域。
动画精灵的图像可以是使用 Pygame 绘制函数绘制的图像(如上一章看到的图像),也可以是原来就有的图像文件。
Sprite
类
Pygame 的 sprite
模块提供了一个动画精灵基类,名为 Sprite
。(还记得几章前讨论过的对象和类吗?)正常情况下,我们不会直接使用基类,而是基于 pygame.sprite.Sprite
来创建自己的子类。下面将完成这样一个例子,并把我们的类命名为 MyBallClass
。创建这个类的代码如下:
要仔细分析这些代码的最后一行。location
是一个 [x, y]
位置,这是个包含两个元素的列表。因为 =
号的一边是包含两个元素的列表(x
和 y
),所以可以在另一边赋两个值。这里我们为动画精灵矩形的 left
和 top
属性赋值。
既然已经定义了 MyBallClass
,接下来必须创建它的一些实例。(要记住,类定义只是一个蓝图;现在必须动手盖房子。)我们仍然需要和上一章同样的代码来创建 Pygame 窗口,另外还要在屏幕上创建一些球,按行列摆放。这要利用一个嵌套循环来完成:
我们还需要把球块移到显示表面。(还记得那个好玩的词“块移”吗?我们在上一章讨论过的。)
for ball in balls:screen.blit(ball.image, ball.rect)pygame.display.flip
把所有这些代码放在一起,就构成了我们的程序,见代码清单 17-1。
代码清单 17-1 使用动画精灵在屏幕上放多个沙滩球图像
如果运行这个程序,会看到 9 个沙滩球出现在 Pygame 窗口中,就像右图显示的这样:
稍后,我们就会让它们动起来。
有没有注意到第 10 行和第 11 行有一个小小的变化?(就是设置 Pygame 窗口大小的那两行代码。)我们将代码
screen = pygame.display.set_mode([640,480])
替换为
size = width, height = 640, 480screen = pygame.display.set_mode(size)
这个代码不仅设置了窗口的大小(像前面一样),还定义了两个变量,width
和 height
,后面将会用到这两个变量。这里有一点很棒,我们不仅定义了一个列表 size
(其中包含两个元素),还定义了两个整型变量 width
和 height
,而且所有这些都在一个语句中完成。另外,我们的列表两边没有加中括号,在 Python 中这是允许的。
我这样做只是想告诉你:在 Python 中有时做同样的事情可以有多种不同的方法。这些方法不存在绝对的优劣(只要它们都能工作),不能说哪种方法一定比另一种方法强。你得遵循 Python 的语法(语言规则),这是必须保证的,尽管如此,自由表述的空间还是有的。如果你让 10 个程序员编写同样的程序,可能得不到两个完全相同的代码。
move
方法
因为我们把球创建为 MyBallClass
的实例,应该可以使用一个类方法来移动这些球。下面就来创建一个新的类方法,名为 move
:
动画精灵(实际上是其中的 rect
)有一个内置方法 move
。这个方法需要一个 speed
参数来告诉它对象要移动多远(也就是移动多快)。因为我们处理的是二维(2D)图形,而 speed
是一个包含两个数的列表,一个对应 x-speed,另一个对应 y-speed。我们还要检查球是否碰到窗口的边界,使球能够在屏幕上“反弹”。
下面修改 MyBallClass
定义,增加 speed
属性和 move
方法:
注意第 2 行(def__init__(self,image_file,location,speed):
)中的修改,这里增加了第 7 行(self.speed=speed
),另外第 9 行到第 15 行增加了新的 move
方法。
现在创建球的各个实例时,需要告诉它速度,还要指出图像文件以及位置:
speed = [2, 2]ball = MyBallClass(img_file, location, speed)
前面的代码把所有球都创建为相同的速度(相同方向),不过如果球的移动有些随机性可能更有意思。下面使用 random.choice
函数来设置速度,如下:
from random import *speed = [choice([-2, 2]), choice([-2, 2])]
这会为 x 和 y 速度选择 -2 或 2。
代码清单 17-2 给出了完整的程序。
代码清单 17-2 使用动画精灵移动球的程序
这个程序使用一个列表来跟踪所有球。第 32 行(balls.append(ball)
)上,创建每个球时会把球增加到这个列表。
最后 5 行代码会重绘屏幕。这里我们走了一条捷径,并不是单独地“擦除”(覆盖)各个球,我们直接用白色填充窗口,然后重绘所有球。
可以试验一下这些代码,看看有更多(或更少)球时会怎么样,可以改变它们的速度,还可以改变它们移动和“反弹”的方式,等等。你会注意到,球会四处移动,而且在窗口四周会反弹,不过它们相互之间还不能反弹!