Python应用实例(一)外星人入侵(五)
外星人入侵(五)
在游戏《外星人入侵》中添加外星人。我们将首先在屏幕上边缘附近添加一个外星人,再生成一群外星人。然后让这群外星人向两边和下面移动,并删除被子弹击中的外星人。最后,显示玩家拥有的飞船数量,并在玩家的飞船用完后结束游戏。
1.项目回顾
开发大型项目时,要在进入每个开发阶段之前回顾一下开发计划,搞清楚接下来要通过编写代码来完成哪些任务。本章涉及以下内容。
▲ 研究既有代码,确定实现新功能前是否要重构。
▲ 在屏幕左上角添加一个外星人,并指定合适的边距。
▲ 根据第一个外星人的边距和屏幕尺寸计算屏幕上可容纳多少个外星人。编写一个循环来创建一系列外星人,使其填满屏幕的上半部分。
▲ 让外星人群向两边和下方移动,直到外星人被全部击落、有外星人撞到飞船或有外星人抵达屏幕底端。如果整群外星人都被击落,将再创建一群外星人。如果有外星人撞到了飞船或抵达屏幕底端,将销毁飞船并再创建一群外星人。
▲ 限制玩家可用的飞船数量。当配给的飞船用完之后,游戏将结束。
我们将在实现功能的同时完善这个计划,但就目前而言,该计划已足够详尽。
在项目中添加新功能前,还应审核既有代码。每进入一个新阶段,项目通常会更复杂,因此最好对混乱或低效的代码进行清理。我们一直在不断重构,因此当前没有需要重构的代码。
2.创建第一个外星人
在屏幕上放置外星人与放置飞船类似。每个外星人的行为都由Alien类控制,我们将像创建Ship类那样创建这个类。出于简化考虑,也将使用位图来表示外星人。你可以自己寻找表示外星人的图像。这幅图像的背景为灰色,与屏幕背景色一致。请务必将选择的图像文件保存到文件夹images中。
2.1 创建Alien类
下面来编写Alien类并将其保存为文件alien.py:
alien.py
import pygame
from pygame.sprite import Sprite
class Alien(Sprite):
"""表示单个外星人的类。"""
def __init__(self, ai_game):
"""初始化外星人并设置其起始位置。"""
super().__init__()
self.screen = ai_game.screen
# 加载外星人图像并设置其rect属性
self.image = pygame.image.load('images/alien.bmp')
self.rect = self.image.get_rect()
# 每个外星人最初都在屏幕左上角附近。
❶ self.rect.x = self.rect.width
self.rect.y = self.rect.height
# 存储外星人的精确水平位置。
❷ self.x = float(self.rect.x)
除位置不同外,这个类的大部分代码与Ship类相似。每个外星人最初都位于屏幕左上角附近。将每个外星人的左边距都设置为外星人的宽度,并将上边距设置为外星人的高度(见❶),这样更容易看清。我们主要关心的是外星人的水平速度,因此精确地记录了每个外星人的水平位置(见❷)。
Alien类不需要一个在屏幕上绘制外星人的方法,因为我们将使用一个Pygame编组方法,自动在屏幕上绘制编组中的所有元素。
2.2 创建Alien实例
要让第一个外星人在屏幕上现身,需要创建一个Alien实例。这属于设置工作,因此将把这些代码放在AlienInvasion类的方法__init__()末尾。我们最终会创建一群外星人,涉及的工作量不少,因此将新建一个名为_create_fleet()的辅助方法。
在类中,方法的排列顺序无关紧要,只要按统一的标准排列就行。我们将把_create_fleet()放在_update_screen()前面,不过放在AlienInvasion类的任何地方其实都可行。首先,需要导入Alien类。
下面是alien_invasion.py中修改后的import语句:
alien_invasion.py
--snip--
from bullet import Bullet
from alien import Alien
下面是修改后的方法__init__():
alien_invasion.py
def __init__(self):
--snip--
self.ship = Ship(self)
self.bullets = pygame.sprite.Group()
self.aliens = pygame.sprite.Group()
self._create_fleet()
创建了一个用于存储外星人群的编组,还调用了接下来将编写的方法_create_fleet()。
下面是新编写的方法_create_fleet():
alien_invasion.py
def _create_fleet(self):
"""创建外星人群。"""
# 创建一个外星人。
alien = Alien(self)
self.aliens.add(alien)
在这个方法中,创建了一个Alien实例,再将其添加到用于存储外星人群的编组中。外星人默认放在屏幕左上角附近,对第一个外星人来说,这样的位置非常合适。
要让外星人现身,需要在_update_screen()中对外星人编组调用方法draw():
alien_invasion.py
def _update_screen(self):
--snip--
for bullet in self.bullets.sprites():
bullet.draw_bullet()
self.aliens.draw(self.screen)
pygame.display.flip()
对编组调用draw()时,Pygame将把编组中的每个元素绘制到属性rect指定的位置。方法draw()接受一个参数,这个参数指定了要将编组中的元素绘制到哪个surface上。图显示了在屏幕上现身的第一个外星人。
第一个外星人正确地现身了,下面来编写绘制一群外星人的代码。
3.创建一群外星人
要绘制一群外星人,需要确定一行能容纳多少个外星人以及要绘制多少行。我们将首先计算外星人的水平间距并创建一行外星人,再确定可用的垂直空间并创建整群外星人。
3.1 确定一行可容纳多少个外星人
为确定一行可容纳多少个外星人,来看看可用的水平空间有多大。屏幕宽度存储在settings.screen_width中,但需要在屏幕两边都留下一定的边距(将其设置为外星人的宽度)。因为有两个边距,所以可用于放置外星人的水平空间为屏幕宽度减去外星人宽度的两倍:
available_space_x = settings.screen_width – (2 * alien_width)
还需要在外星人之间留出一定的空间,不妨将其定为外星人的宽度。因此,显示一个外星人所需的水平空间为外星人宽度的两倍:一个宽度用于放置外星人,另一个宽度为外星人右边的空白区域。为确定一行可容纳多少个外星人,将可用空间除以外星人宽度的两倍。我们使用整除(floor division)运算符//,它将两个数相除并丢弃余数,让我们得到一个表示外星人个数的整数。
number_aliens_x = available_space_x // (2 * alien_width)
我们将在创建外星人群时使用这些公式。
注意 令人欣慰的是,在程序中执行计算时,无须在一开始确定公式是正确的,而是可以尝试运行程序,看看结果是否符合预期。即便是在最坏的情况下,也只是屏幕上显示的外星人太多或太少。随后可根据在屏幕上看到的情况调整计算公式。
3.2 创建一行外星人
现在可以创建整行外星人了。由于创建单个外星人的代码管用,我们重写_create_fleet()使其创建一行外星人:
alien_invasion.py
def _create_fleet(self):
"""创建外星人群。"""
# 创建一个外星人并计算一行可容纳多少个外星人。
# 外星人的间距为外星人宽度。
❶ alien = Alien(self)
❷ alien_width = alien.rect.width
❸ available_space_x = self.settings.screen_width - (2 * alien_width)
number_aliens_x = available_space_x // (2 * alien_width)
# 创建第一行外星人。
❹ for alien_number in range(number_aliens_x):
# 创建一个外星人并将其加入当前行。
alien = Alien(self)
❺ alien.x = alien_width + 2 * alien_width * alien_number
alien.rect.x = alien.x
self.aliens.add(alien)
这些代码大多在前面详细介绍过。为放置外星人,需要知道外星人的宽度和高度,因此在执行计算前,创建一个外星人(见❶)。这个外星人不是外星人群的成员,因此没有将其加入编组aliens中。在❷处,从外星人的rect属性中获取外星人宽度,并将这个值存储到alien_width中,以免反复访问属性rect。在❸处,计算可用于放置外星人的水平空间以及其中可容纳多少个外星人。
接下来,编写一个循环,从零数到要创建的外星人数(见❹)。在这个循环中,创建一个新的外星人,并通过设置[插图]坐标将其加入当前行(见❺)。将每个外星人都往右推一个外星人宽度。接下来,将外星人宽度乘以2,得到每个外星人占据的空间(其中包括右边的空白区域),再据此计算当前外星人在当前行的位置。我们使用外星人的属性x来设置其rect的位置。最后,将每个新创建的外星人都添加到编组aliens中。
如果现在运行这个游戏,将看到第一行外星人,如图所示。
这行外星人在屏幕上稍微偏向了左边、这实际上是有好处的,因为后面将让外星人群往右移,触及屏幕边缘后稍微往下移,再往左移,依此类推。就像经典游戏《太空入侵者》,相比于只往下移,这种移动方式更为有趣。我们将让外星人群不断这样移动,直到所有外星人都被击落,或者有外星人撞上飞船或抵达屏幕底端。
3.3 重构_create_fleet()
倘若只需使用前面的代码就能创建外星人群,也许应该让_create_fleet()保持原样,但鉴于创建外星人群的工作还未完成,我们稍微整理一下这个方法。为此,添加辅助方法_create_alien(),并在_create_fleet()中调用它:
alien_invasion.py
def _create_fleet(self):
--snip--
# 创建第一行外星人。
for alien_number in range(number_aliens_x):
self._create_alien(alien_number)
def _create_alien(self, alien_number):
"""创建一个外星人并将其放在当前行。"""
alien = Alien(self)
alien_width = alien.rect.width
alien.x = alien_width + 2 * alien_width * alien_number
alien.rect.x = alien.x
self.aliens.add(alien)
除self外,方法_create_alien()还接受另一个参数,即要创建的外星人的编号。该方法的代码与_create_fleet()相同,但在内部获取外星人宽度,而不是将其作为参数传入。这样重构后,添加新行进而创建整群外星人将更容易。
3.4 添加行
要创建外星人群,需要计算屏幕可容纳多少行,并将创建一行外星人的循环重复执行相应的次数。为计算可容纳的行数,要先计算可用的垂直空间:用屏幕高度减去第一行外星人的上边距(外星人高度)、飞船的高度以及外星人群最初与飞船之间的距离(外星人高度的两倍):
available_space_y = settings.screen_height – (3 * alien_height) – ship_height
这将在飞船上方留出一定的空白区域,给玩家留出射杀外星人的时间。
每行下方都要留出一定的空白区域,不妨将其设置为外星人的高度。为计算可容纳的行数,将可用的垂直空间除以外星人高度的两倍。我们使用整除,因为行数只能是整数。(同样,如果这样的计算不对,我们马上就能发现,继而将间距调整为合理的值。)
number_rows = available_space_y // (2 * alien_height)
知道可容纳多少行之后,便可重复执行创建一行外星人的代码了:
alien_invasion.py
def _create_fleet(self):
--snip--
alien = Alien(self)
❶ alien_width, alien_height = alien.rect.size
available_space_x = self.settings.screen_width - (2 * alien_width)
number_aliens_x = available_space_x // (2 * alien_width)
#计算屏幕可容纳多少行外星人。
ship_height = self.ship.rect.height
❷ available_space_y = (self.settings.screen_height -
(3 * alien_height) - ship_height)
number_rows = available_space_y // (2 * alien_height)
# 创建外星人群。
❸ for row_number in range(number_rows):
for alien_number in range(number_aliens_x):
self._create_alien(alien_number, row_number)
def _create_alien(self, alien_number, row_number):
"""创建一个外星人,并将其放在当前行。"""
alien = Alien(self)
alien_width, alien_height = alien.rect.size
alien.x = alien_width + 2 * alien_width * alien_number
alien.rect.x = alien.x
❹ alien.rect.y = alien.rect.height + 2 * alien.rect.height * row_number
self.aliens.add(alien)
需要知道外星人的宽度和高度,因此在❶处使用了属性size。该属性是一个元组,包含rect对象的宽度和高度。为计算屏幕可容纳多少行外星人,在计算available_space_x的代码后面添加了计算available_space_y的代码(见❷)。此处将计算公式用圆括号括起来,以便将代码分成两行,遵循每行不超过79字符的建议。
为创建多行外星人,使用了两个嵌套在一起的循环:一个外部循环和一个内部循环(见❸)。内部循环创建一行外星人,而外部循环从零数到要创建的外星人行数:Python将重复执行创建单行外星人的代码,重复次数为number_rows。
为嵌套循环,编写了一个新的for循环,并缩进了要重复执行的代码。(在大多数文本编辑器中,缩进代码块和取消缩进都很容易,详情请参阅附录B)。现在调用_create_alien()时,传递了一个表示行号的实参,将每行都沿屏幕依次向下放置。
在_create_alien()的定义中,需要一个用于存储行号的形参。在_create_alien()中,修改外星人的[插图]坐标(见❹)并在第一行外星人上方留出与外星人等高的空白区域。相邻外星人行的[插图]坐标相差外星人高度的两倍,因此将外星人高度乘以2,再乘以行号。第一行的行号为0,因此第一行的垂直位置不变,而其他行都沿屏幕依次向下放置。
如果现在运行这个游戏,将看到一群外星人,如图: