13.3.4 重构 create_fleet()
倘若我们创建了外星人群,也许应该让create_fleet()保持原样,但鉴于创建外星人的工作 还未完成,我们稍微清理一下这个函数。下面是create_fleet()和两个新函数,get_number_ aliens_x()和create_alien():
game_functions.py
1 def get_number_aliens_x(ai_settings, alien_width): """计算每行可容纳多少个外星人""" available_space_x = ai_settings.screen_width - 2 * alien_width number_aliens_x = int(available_space_x / (2 * alien_width)) return number_aliens_x def create_alien(ai_settings, screen, aliens, alien_number): """创建一个外星人并将其放在当前行""" alien = Alien(ai_settings, screen) 2 alien_width = alien.rect.width alien.x = alien_width + 2 * alien_width * alien_number alien.rect.x = alien.x aliens.add(alien) def create_fleet(ai_settings, screen, aliens): """创建外星人群""" # 创建一个外星人,并计算每行可容纳多少个外星人 alien = Alien(ai_settings, screen) 3 number_aliens_x = get_number_aliens_x(ai_settings, alien.rect.width) # 创建第一行外星人 for alien_number in range(number_aliens_x): 4 create_alien(ai_settings, screen, aliens, alien_number)
函数get_number_aliens_x()的代码都来自create_fleet(),且未做任何修改(见1)。函数 create_alien()的代码也都来自create_fleet(),且未做任何修改,只是使用刚创建的外星人来 获取外星人宽度(见2)。在3处,我们将计算可用水平空间的代码替换为对get_number_aliens_x() 的调用,并删除了引用alien_width的代码行,因为现在这是在create_alien()中处理的。在4处, 我们调用create_alien()。通过这样的重构,添加新行进而创建整群外星人将更容易。
13.3.5 添加行
要创建外星人群,需要计算屏幕可容纳多少行,并对创建一行外星人的循环重复相应的次数。 为计算可容纳的行数,我们这样计算可用垂直空间:将屏幕高度减去第一行外星人的上边距(外 星人高度)、飞船的高度以及最初外星人群与飞船的距离(外星人高度的两倍):
available_space_y = ai_settings.screen_height – 3 * alien_height – ship_height
这将在飞船上方留出一定的空白区域,给玩家留出射杀外星人的时间。
每行下方都要留出一定的空白区域,并将其设置为外星人的高度。为计算可容纳的行数,我 们将可用垂直空间除以外星人高度的两倍(同样,如果这样的计算不对,我们马上就能发现,继 而将间距调整为合理的值)。
number_rows = available_height_y / (2 * alien_height)
知道可容纳多少行后,便可重复执行创建一行外星人的代码:
game_functions.py
1 def get_number_rows(ai_settings, ship_height, alien_height): """计算屏幕可容纳多少行外星人""" 2 available_space_y = (ai_settings.screen_height - (3 * alien_height) - ship_height) number_rows = int(available_space_y / (2 * alien_height)) return number_rows def create_alien(ai_settings, screen, aliens, alien_number, row_number): --snip-- alien.x = alien_width + 2 * alien_width * alien_number alien.rect.x = alien.x 3 alien.rect.y = alien.rect.height + 2 * alien.rect.height * row_number aliens.add(alien) def create_fleet(ai_settings, screen, ship, aliens): --snip-- number_aliens_x = get_number_aliens_x(ai_settings, alien.rect.width) number_rows = get_number_rows(ai_settings, ship.rect.height, alien.rect.height) # 创建外星人群 4 for row_number in range(number_rows): for alien_number in range(number_aliens_x): create_alien(ai_settings, screen, aliens, alien_number, row_number)
为计算屏幕可容纳多少行外星人,我们在函数get_number_rows()中实现了前面计算 available_space_y和number_rows的公式(见1),这个函数与get_number_aliens_x()类似。计算 公式用括号括起来了,这样可将代码分成两行,以遵循每行不超过79字符的建议(见2)。这里 使用了int(),因为我们不想创建不完整的外星人行。
为创建多行,我们使用两个嵌套在一起的循环:一个外部循环和一个内部循环(见4)。其 中的内部循环创建一行外星人,而外部循环从零数到要创建的外星人行数。Python将重复执行创 建单行外星人的代码,重复次数为number_rows。 为嵌套循环,我们编写了一个新的for循环,并缩进了要重复执行的代码。(在大多数文本编 辑器中,缩进代码块和取消缩进都很容易,详情请参阅附录B。)我们调用create_alien()时,传 递了一个表示行号的实参,将每行都沿屏幕依次向下放置。 create_alien()的定义需要一个用于存储行号的形参。在create_alien()中,我们修改外星 人的y坐标(见3),并在第一行外星人上方留出与外星人等高的空白区域。相邻外星人行的y坐 标相差外星人高度的两倍,因此我们将外星人高度乘以2,再乘以行号。第一行的行号为0,因此 第一行的垂直位置不变,而其他行都沿屏幕依次向下放置。 在 create_fleet() 的定义中,还新增了一个用于存储 ship 对象的形参,因此在 alien_invasion.py中调用create_fleet()时,需要传递实参ship:
alien_invasion.py
# 创建外星人群 gf.create_fleet(ai_settings, screen, ship, aliens)
如果你现在运行这个游戏,将看到一群外星人,如图13-4所示
在下一节,我们将让外星人群动起来
13.4 让外星人群移动
下面来让外星人群在屏幕上向右移动,撞到屏幕边缘后下移一定的距离,再沿相反的方向移 动。我们将不断地移动所有的外星人,直到所有外星人都被消灭,有外星人撞上飞船,或有外星 人抵达屏幕底端。下面先来让外星人向右移动。
13.4.1 向右移动外星人
为移动外星人,我们将使用alien.py中的方法update(),且对外星人群中的每个外星人都调用 它。首先,添加一个控制外星人速度的设置:
settings.py
def __init__(self): --snip-- # 外星人设置 self.alien_speed_factor = 1
然后,使用这个设置来实现update():
alien.py
def update(self): """向右移动外星人""" 1 self.x += self.ai_settings.alien_speed_factor 2 self.rect.x = self.x
每次更新外星人位置时,都将它向右移动,移动量为alien_speed_factor的值。我们使用属 性self.x跟踪每个外星人的准确位置,这个属性可存储小数值(见Ø)。然后,我们使用self.x 的值来更新外星人的rect的位置(见)。 在主while循环中已调用了更新飞船和子弹的方法,但现在还需更新每个外星人的位置:
alien_invasion.py
en_invasion.py # 开始游戏主循环 while True: gf.check_events(ai_settings, screen, ship, bullets) ship.update() gf.update_bullets(bullets) gf.update_aliens(aliens) gf.update_screen(ai_settings, screen, ship, aliens, bullets)
我们在更新子弹后再更新外星人的位置,因为稍后要检查是否有子弹撞到了外星人。 最后,在文件game_functions.py末尾添加新函数update_aliens():
game_functions.py
def update_aliens(aliens): """更新外星人群中所有外星人的位置""" aliens.update()
我们对编组aliens调用方法update(),这将自动对每个外星人调用方法update()。如果你现 在运行这个游戏,会看到外星人群向右移,并逐渐在屏幕右边缘消失。
13.4.2 创建表示外星人移动方向的设置
下面来创建让外星人撞到屏幕右边缘后向下移动、再向左移动的设置。实现这种行为的代码 如下:
settings.py
# 外星人设置 self.alien_speed_factor = 1 self.fleet_drop_speed = 10 # fleet_direction为1表示向右移,为-1表示向左移 self.fleet_direction = 1
设置fleet_drop_speed指定了有外星人撞到屏幕边缘时,外星人群向下移动的速度。将这个 速度与水平速度分开是有好处的,这样你就可以分别调整这两种速度了。 要实现fleet_direction设置,可以将其设置为文本值,如'left'或'right',但这样就必须 编写if-elif语句来检查外星人群的移动方向。鉴于只有两个可能的方向,我们使用值1和1来表 示它们,并在外星人群改变方向时在这两个值之间切换。另外,鉴于向右移动时需要增大每个外 星人的x坐标,而向左移动时需要减小每个外星人的x坐标,使用数字来表示方向更合理。
13.4.3 检查外星人是否撞到了屏幕边缘
现在需要编写一个方法来检查是否有外星人撞到了屏幕边缘,还需修改update(),以让每个 外星人都沿正确的方向移动:
alien.py
def check_edges(self): """如果外星人位于屏幕边缘,就返回True""" screen_rect = self.screen.get_rect() 1 if self.rect.right >= screen_rect.right: return True 2 elif self.rect.left <= 0: return True def update(self): """向左或向右移动外星人""" 3 self.x += (self.ai_settings.alien_speed_factor * self.ai_settings.fleet_direction) self.rect.x = self.x
我们可对任何外星人调用新方法check_edges(),看看它是否位于屏幕左边缘或右边缘。如 果外星人的rect的right属性大于或等于屏幕的rect的right属性,就说明外星人位于屏幕右边缘 (见1)。如果外星人的rect的left属性小于或等于0,就说明外星人位于屏幕左边缘(见2)。 我们修改了方法update(),将移动量设置为外星人速度和fleet_direction的乘积,让外星人向左或向右移。如果fleet_direction为1,就将外星人当前的x坐标增大alien_speed_factor,从 而将外星人向右移;如果fleet_direction为1,就将外星人当前的x坐标减去alien_speed_ factor,从而将外星人向左移。