接下来学习怎么制作障碍物,也就是树和小旗。在这一部分,为了简化起见,我们再次从零开始——没有滑雪者,只有障碍物。我们会在最后将滑雪者的代码和障碍物的代码放到一起。
Skier 游戏的窗口大小是 640×640 像素。为了简化,也为了防止障碍物靠得太近,我们将窗口分割为一个 10×10 的网格。这样,一共有 100 个格子,每个格子的大小是 64×64 像素。因为我们的障碍物尺寸并不是太大,所以即使两个障碍物位于相邻的格子中,它们之间也有一些空隙。
创建单个障碍物
首先我们需要创建单个障碍物。为此,我们创建了一个名为 ObstacleClass
的类。和滑雪者一样,这也是一个 Pygame Sprite
类。
class ObstacleClass(pygame.sprite.Sprite): def __init__(self, image_file, location, type):pygame.sprite.Sprite.__init__(self)self.image_file = image_fileself.image = pygame.image.load(image_file)self.rect = self.image.get_rectself.rect.center = locationself.type = typeself.passed = False
创建障碍物地图
现在我们来创建多个障碍物,以便填充在 640×640 像素的窗口中。我们将 10 个障碍物(小旗和树)随机地分布在 100 个格子中,每个障碍物既可以是小旗也可以是树。最终结果可能是 2 面小旗 8 棵树,7 面小旗 3 棵树,或者是总和为 10 的任意组合。总之,小旗和树是随机选择的,它们的位置也是随机的。
我们唯一需要当心的是,不要尝试将两个障碍物放在同一位置,所以我们需要知道哪些位置是已经使用过的。变量 locations
是一个用于记录使用过的位置的列表。当想要在某个位置放置一个新的障碍物时,我们首先要查看这个位置是否已有一个障碍物。
好眼力!我们不希望游戏开始时满屏都是障碍物,而是希望它是空白的,然后障碍物从底部出现。所以,我们将障碍物的场景创建在窗口底部的“下方”。为此,我们需要为每个障碍物位置的 y 值增加 640 像素(窗口的高度)。
游戏开始时,我们希望障碍物从底部滚动上来。为此,我们修改每个障碍物位置的 y 值。改动的大小取决于滑雪者滑下小山的速度。我们将它放入一个名为 update
的方法中,它是 ObstacleClass
的一部分。
def update(self): global speed self.rect.centery -= speed[1]
变量 speed
是滑雪者的速度,它是一个全局变量,包含了 x 和 y 方向的速度,所以我们使用索引 [1] 来获取 y(垂直)方向的速度。
和创建的第一屏障碍物一样,我们还需要在窗口下方创建另外一屏的障碍物。那怎么知道应该在什么时候创建呢?我们可以创建一个名为 map_position
的变量,由它来告诉我们场景已经向上滚动了多少。我们在主循环中像下面这样处理。
我们用 animate
函数来重绘屏幕,就像在只有滑雪者的代码中那样。合在一起,只有障碍物的代码看起来像下面这样。
代码清单 25-2 创建 Skier 游戏——只有障碍物
如果运行以上代码,你应该可以看到树和小旗在屏幕上往上滚。
问得好。在当前的代码中,我们让它们继续在窗口上边界外向上滚动,这样其位置的 y 值(负值)绝对值会越来越大。如果游戏运行了比较长的时间,则会创建并积累大量的障碍物场景。这有可能会导致程序变慢或者在某个时间点发生内存不足的情况。所以我们需要做一点清理工作。
在障碍物类的 update
方法中,我们添加一个判断逻辑,看看障碍物是否已经移出屏幕。如果是的话,就移除它。Pygame 有一个名为 kill
的原生方法可用来做这件事。新的 update
方法看起来像下面这样。
现在我们可以将滑雪者和障碍物的代码放到一起了。
我们需要
SkierClass
和ObstacleClass
。我们的
animate
函数需要同时绘制滑雪者和障碍物。我们的初始化代码需要创建滑雪者和初始地图。
主循环需要同时包括滑雪者的键盘事件绑定和障碍物场景的创建。
基本上,这组合了代码清单 25-1 和代码清单 25-2,结果如下所示。
代码清单 25-3 滑雪者代码与障碍物代码相结合
如果运行以上代码,你就能操作滑雪者滑下小山,并且会看到障碍物向上滚过。你也会注意到滑雪者向左右滑和向下滑的速度取决于他的转向。至此,这个游戏已经快要完成了。
最后我们需要处理的两项是:
检测滑雪者是否碰到了树或者是否捡到了小旗
记录并显示分数
你已经在第 17 章中学到如何进行碰撞检测了。代码清单 25-3 已经将障碍物精灵放到了一个 sprite 组中,所以我们可以直接用 spritecollide
函数来检测滑雪者是否碰到树或者小旗。接下来我们需要知道这个障碍物是什么(树还是小旗),然后:
如果是树,则将滑雪者的图像切换为“碰撞”的图像,并将分数减去 100。
如果是小旗,则将分数加 10,然后将小旗从屏幕中移除。
为此所需的代码位于主循环中,看起来像下面这样:
变量 hit
告诉我们滑雪者撞到了哪个障碍物。它是一个列表,但在这里列表中只有一个条目,因为滑雪者一次只可能碰到一个障碍物,所以他碰到的障碍物是 hit[0]
。
passed
变量用于标记是否碰撞过树。这能保证当滑雪者在撞树之后继续往下滑时,不会立刻运行撞到同一棵树的逻辑。
现在我们需要显示分数。这只需要再写 3 行代码。在初始化代码中,我们创建一个 font
对象,它是 Pygame 的 Font
类的实例:
font = pygame.font.Font(None, 50)
在主循环中,我们使用一个新的分数文本来渲染 font 对象:
score_text = font.render(/"Score: /" +str(points), 1, (0, 0, 0))
在 animate
函数中,我们在左上角显示分数:
screen.blit(score_text, [10, 10])
这就是这个游戏的全部了。如果你把这些组合在一起,会发现这就是第 10 章的代码,只是现在你理解得更加深入了。理解 Skier 游戏的工作原理可以帮助你更好地策划和开发自己的游戏。
你学到了什么
在这一章,你学到了以下内容。
Skier 游戏的各个部分是如何工作的
怎样创建一个滚动的背景
动手试一试
1. 尝试修改 Skier 游戏,使游戏的难度随着游戏的进行逐渐增大。可以尝试以下建议:
使游戏的速度随着游戏的进行逐渐加快。
滑雪者滑下小山时树越来越多。
添加障碍物“冰”,使得滑雪者转向时更加困难。
2. Skier 游戏的灵感来自一个名为 SkiFree 的游戏,这个游戏中有一个讨厌的雪人会随机出现并追赶滑雪者。如果你想挑战一下,可以尝试在 Skier 游戏中添加一些类似的物体。你需要找到或者创建一个新的图片,并且修改代码以获得你想要的行为。