13.6 结束游戏

如果玩家根本不会输,游戏还有什么趣味和挑战性可言?如果玩家没能在足够短的时间内将整群外星人消灭干净,导致有外星人撞到了飞船或抵达屏幕底端,飞船将被摧毁。与此同时,限制玩家可使用的飞船数,在玩家用光所有的飞船后,游戏将结束。

13.6.1 检测外星人和飞船碰撞

首先检查外星人和飞船之间的碰撞,以便在外星人撞上飞船时做出合适的响应。为此,在AlienInvasion 中更新每个外星人的位置后,立即检测外星人和飞船之间的碰撞:

alien_invasion.py

def updatealiens(self):
—snip—
self.aliens.update()

# 检测外星人和飞船之间的碰撞。
❶ if pygame.sprite.spritecollideany(self.ship, self.aliens):
❷ print("Ship hit!!!")


函数spritecollideany() 接受两个实参:一个精灵和一个编组。它检查编组是否有成员与精灵发生了碰撞,并在找到与精灵发生碰撞的成员后停止遍历编组。在这里,它遍历编组aliens ,并返回找到的第一个与飞船发生碰撞的外星人。

如果没有发生碰撞,spritecollideany() 将返回None ,因此❶处的if 代码块不会执行。如果找到了与飞船发生碰撞的外星人,它就返回这个外星人,因此if 代码块将执行:打印“Ship hit!!!”(见❷)。有外星人撞到飞船时,需要执行很多任务:删除余下的外星人和子弹,让飞船重新居中,以及创建一群新的外星人。编写完成这些任务的代码之前,需要确定检测外星人和飞船碰撞的方法是否可行。为此,最简单的方式就是调用函数print()

现在如果运行这个游戏,则每当有外星人撞到飞船时,终端窗口都将显示“Ship hit!!!”。测试这项功能时,请将alien_drop_speed 设置为较大的值,如50或100,这样外星人将更快地撞到飞船。

13.6.2 响应外星人和飞船碰撞

现在需要确定当外星人与飞船发生碰撞时该做些什么。我们不销毁Ship 实例并创建新的,而是通过跟踪游戏的统计信息来记录飞船被撞了多少次(跟踪统计信息还有助于记分)。

下面来编写一个用于跟踪游戏统计信息的新类GameStats ,并将其保存为文件game_stats.py:

game_stats.py

class GameStats:
"""跟踪游戏的统计信息。"""

def init(self, ai_game):
"""初始化统计信息。"""
self.settings = ai_game.settings
self.reset_stats()

def reset_stats(self):
"""初始化在游戏运行期间可能变化的统计信息。"""
self.ships_left = self.settings.ship_limit


在游戏运行期间,只创建一个GameStats 实例,但每当玩家开始新游戏时,需要重置一些统计信息。为此,在方法resetstats() 中初始化大部分统计信息,而不是在init() 中直接初始化。我们在_init() 中调用这个方法,这样创建GameStats 实例时将妥善地设置这些统计信息,在玩家开始新游戏时也能调用reset_stats()

当前,只有一项统计信息ships_left ,其值在游戏运行期间不断变化。一开始玩家拥有的飞船数存储在settings.py的ship_limit 中:

settings.py

# 飞船设置
self.ship_speed = 1.5
self.ship_limit = 3


还需对alien_invasion.py做些修改,以创建一个GameStats 实例。首先,更新这个文件开头的import 语句:

alien_invasion.py

import sys
from time import sleep

import pygame

from settings import Settings
from game_stats import GameStats
from ship import Ship
—snip—


从Python标准库的模块time 中导入函数sleep() ,以便在飞船被外星人撞到后让游戏暂停片刻。我们还导入了GameStats

接下来,在init() 中创建一个GameStats 实例:

alien_invasion.py

def init(self):
—snip—
self.screen = pygame.display.set_mode(
(self.settings.screen_width, self.settings.screen_height))
pygame.display.set_caption("Alien Invasion")

# 创建一个用于存储游戏统计信息的实例。
self.stats = GameStats(self)

self.ship = Ship(self)
—snip—


在创建游戏窗口后、定义诸如飞船等其他游戏元素前,创建一个GameStats 实例。

有外星人撞到飞船时,将余下的飞船数减1,创建一群新的外星人,并将飞船重新放到屏幕底端的中央。另外,让游戏暂停片刻,让玩家在新外星人群出现前注意到发生了碰撞并将重新创建外星人群。

下面将实现这些功能的大部分代码放到新方法shiphit() 中。在updatealiens() 中,将在有外星人撞到飞船时调用这个方法:

alien_invasion.py

def shiphit(self):
"""响应飞船被外星人撞到。"""

# 将ships_left减1。
❶ self.stats.ships_left -= 1

# 清空余下的外星人和子弹。
❷ self.aliens.empty()
self.bullets.empty()

# 创建一群新的外星人,并将飞船放到屏幕底端的中央。
❸ self.
createfleet()
self.ship.center_ship()

# 暂停。
❹ sleep(0.5)


新方法shiphit() 在飞船被外星人撞到时做出响应。在这个方法中,将余下的飞船数减1(见❶),再清空编组aliensbullets (见❷)。

接下来,创建一群新的外星人,并将飞船居中(见❸)。(稍后将在Ship 类中添加方法center_ship() 。)最后,在更新所有元素后(但在将修改显示到屏幕前)暂停,让玩家知道飞船被撞到了(见❹)。这里的函数调用sleep() 让游戏暂停半秒钟,让玩家能够看到外星人撞到了飞船。函数sleep() 执行完毕后,将接着执行方法updatescreen() ,将新的外星人群绘制到屏幕上。

updatealiens() 中,当有外星人撞到飞船时,不调用函数print() ,而调用shiphit()

alien_invasion.py

def updatealiens(self):
—snip—
if pygame.sprite.spritecollideany(self.ship, self.aliens):
self.
shiphit()


下面是新方法center_ship() ,请将其添加到ship.py的末尾:

ship.py

def center_ship(self):
"""让飞船在屏幕底端居中。"""
self.rect.midbottom = self.screen_rect.midbottom
self.x = float(self.rect.x)


这里像init() 中那样让飞船在屏幕底端居中。让飞船在屏幕底端居中后,重置用于跟踪飞船确切位置的属性self.x

注意  我们根本没有创建多艘飞船。在整个游戏运行期间,只创建了一个飞船实例,并在该飞船被撞到时将其居中。统计信息ships_left 指出玩家是否用完了所有的飞船。

请运行这个游戏,射杀几个外星人,并让一个外星人撞到飞船。游戏暂停片刻后,将出现一群新的外星人,而飞船将在屏幕底端居中。

13.6.3 有外星人到达屏幕底端

如果有外星人到达屏幕底端,我们将像有外星人撞到飞船那样做出响应。为检测这种情况,在alien_invasion.py中添加一个新方法:

alien_invasion.py

def checkaliens_bottom(self):
"""检查是否有外星人到达了屏幕底端。"""
screen_rect = self.screen.get_rect()
for alien in self.aliens.sprites():
❶ if alien.rect.bottom >= screen_rect.bottom:
# 像飞船被撞到一样处理。
self.
shiphit()
break


方法checkaliens_bottom() 检查是否有外星人到达了屏幕底端。到达屏幕底端后,外星人的属性rect.bottom 大于或等于屏幕的属性rect.bottom (见❶)。如果有外星人到达屏幕底端,就调用shiphit() 。只要检测到一个外星人到达屏幕底端,就无须检查其他外星人了,因此在调用shiphit() 后退出循环。

我们在updatealiens() 中调用checkaliens_bottom()

alien_invasion.py

def updatealiens(self):
—snip—
# 检查是否有外星人撞到飞船。
if pygame.sprite.spritecollideany(self.ship, self.aliens):
self.
shiphit()

# 检查是否有外星人到达了屏幕底端。
self.
checkaliens_bottom()


在更新所有外星人的位置并检测是否有外星人和飞船发生碰撞后调用checkaliens_bottom() 。现在,每当有外星人撞到飞船或抵达屏幕底端时,都将出现一群新的外星人。

13.6.4 游戏结束

现在这个游戏看起来更完整了,但它永远都不会结束,只是shipsleft 不断变成越来越小的负数。下面在GameStats 中添加一个作为标志的属性gameactive ,以便在玩家的飞船用完后结束游戏。首先,在GameStats 类的方法__init() 末尾设置这个标志:

game_stats.py

def init(self, ai_game):
—snip—
# 游戏刚启动时处于活动状态。
self.game_active = True


接下来在shiphit() 中添加代码,在玩家的飞船用完后将game_active 设置为False

alien_invasion.py

def shiphit(self):
"""响应飞船被外星人撞到。"""
if self.stats.ships_left > 0:
# 将ships_left减1。
self.stats.ships_left -= 1
—snip—
# 暂停。
sleep(0.5)
else:
self.stats.game_active = False


shiphit() 的大部分代码没有变。我们将原来的代码都移到了一个if 语句块中,它检查玩家是否至少还有一艘飞船。如果是,就创建一群新的外星人,暂停片刻,再接着往下执行。如果玩家没有了飞船,就将game_active 设置为False