结束游戏
玩一个不会输的游戏有什么乐趣和挑战?如果玩家没有足够快地击落舰队,我们会让外星人在接触时摧毁飞船。同时,我们会限制玩家使用的飞船数量,当外星人到达屏幕下方时,我们就会摧毁飞船。当玩家用完所有飞船后,游戏就会结束。
检测外星飞船碰撞
我们将首先检查外星人与飞船之间的碰撞,以便在外星人撞上飞船时作出适当反应。在更新 AlienInvasion
中每个外星人的位置后,我们将立即检查外星人与飞船的碰撞:
def _update_aliens(self):
--snip--
self.aliens.update()
# Look for alien-ship collisions.
if pygame.sprite.spritecollideany(self.ship, self.aliens): (1)
print("Ship hit!!!") (2)
python
spritecollideany()
函数有两个参数:一个精灵和一个组。该函数查找与精灵碰撞的组中的任何成员,并在找到与精灵碰撞的成员后立即停止在组中循环。 在这里,它循环遍历外星人组并返回它发现的第一个与飞船相撞的外星人。
如果没有发生碰撞,spritecollideany()
返回 None
并且 if
块不会执行 ❶。 如果它发现一个与飞船相撞的外星人,它会返回该外星人,并且执行 if
块:它会打印 Ship hit!!!
❷. 当外星人击中飞船时,我们需要执行许多任务:删除所有剩余的外星人和子弹,重新调整飞船的位置,并创建一支新的舰队。在我们编写代码来完成所有这些之前,我们想知道我们检测外星飞船碰撞的方法是否正确工作。编写 print()
调用是确保我们正确检测这些冲突的简单方法。
现在,当您运行《外星人入侵》时,会出现消息 Ship hit!!! 每当外星人闯入飞船时,航站楼就会出现。当您测试此功能时,请将 fleet_drop_speed
设置为更高的值,例如 50 或 100,以便外星人更快地到达您的飞船。
应对外星飞船碰撞
现在,我们需要弄清楚外星人与飞船相撞时会发生什么。我们不会销毁飞船实例并创建一个新实例,而是通过游戏中的跟踪统计来计算飞船被撞击的次数。跟踪统计对于计分也很有用。
让我们编写一个新类 GameStats
来跟踪游戏统计数据,并将其保存为 game_stats.py:
class GameStats:
"""Track statistics for Alien Invasion."""
def __init__(self, ai_game):
"""Initialize statistics."""
self.settings = ai_game.settings
self.reset_stats() (1)
def reset_stats(self):
"""Initialize statistics that can change during the game."""
self.ships_left = self.settings.ship_limit
python
我们将为《异形入侵》的整个运行过程创建一个 GameStats
实例,但每次玩家开始新游戏时,我们都需要重置一些统计数据。为此,我们将在 reset_stats()
方法中初始化大部分统计数据,而不是直接在 __init__()
中初始化。我们将在 __init__()
中调用该方法,这样在首次创建 GameStats
实例时,统计数据就会被正确设置❶。但我们也可以在玩家开始新游戏时随时调用 reset_stats()
。现在我们只有一个统计数据,即 ships_left
,其值会在整个游戏过程中发生变化。
玩家开始时拥有的战舰数量应作为 ship_limit
储存在 settings.py
中:
# Ship settings
self.ship_speed = 1.5
self.ship_limit = 3
python
我们还需要对 alien_invasion.py
进行一些修改,以创建 GameStats
实例。首先,我们要更新文件顶部的导入语句:
import sys
from time import sleep
import pygame
from settings import Settings
from game_stats import GameStats
from ship import Ship
--snip--
python
我们从 Python 标准库中的 time
模块导入 sleep()
函数,这样当飞船被击中时,我们就可以暂停游戏片刻。我们还导入了 GameStats
。
我们将在 init()
中创建一个 GameStats
实例:
def __init__(self):
--snip--
self.screen = pygame.display.set_mode((self.settings.screen_width, self.settings.screen_height))
pygame.display.set_caption("Alien Invasion")
# Create an instance to store game statistics.
self.stats = GameStats(self)
self.ship = Ship(self)
--snip--
python
我们在创建游戏窗口后,但在定义飞船等其他游戏元素前创建实例。
当外星人撞上飞船时,我们将从剩余的飞船数量中减去 1,摧毁所有现有的外星人和子弹,创建一支新的舰队,并将飞船重新定位在屏幕中央。我们还会暂停游戏片刻,让玩家注意到碰撞,并在新舰队出现之前重新集结。
让我们把大部分代码放在一个名为 _ship_hit()
的新方法中。当有外星人击中飞船时,我们将从 _update_aliens()
中调用这个方法:
def _ship_hit(self):
"""Respond to the ship being hit by an alien."""
# Decrement ships_left.
self.stats.ships_left -= 1 (1)
# Get rid of any remaining bullets and aliens.
self.bullets.empty() (2)
self.aliens.empty()
# Create a new fleet and center the ship.
self._create_fleet() (3)
self.ship.center_ship()
# Pause.
sleep(0.5) (4)
python
新方法 _ship_hit()
协调了外星人击中飞船时的反应。在 _ship_hit()
方法中,剩下的飞船数量会减少 1 ❶,然后我们清空 bullets
组和 aliens
组 ❷。
接下来,我们创建一个新的舰队,并将飞船❸ 置中。(稍后我们将为飞船添加 center_ship()
方法)。然后,我们在更新所有游戏元素后,但在屏幕上绘制出任何变化之前,添加一个暂停,这样玩家就可以看到他们的飞船被击中了❹。sleep()
调用会让程序暂停执行半秒,足够长的时间让玩家看到外星人击中了飞船。当 sleep()
函数结束后,代码执行将转入 _update_screen()
方法,该方法将新的舰队绘制到屏幕上。
在 _update_aliens()
方法中,当有异形击中飞船时,我们会调用 _ship_hit()
方法来代替 print()
方法:
def _update_aliens(self):
--snip--
if pygame.sprite.spritecollideany(self.ship, self.aliens):
self._ship_hit()
python
下面是新方法 center_ship()
,它属于 ship.py 中:
def center_ship(self):
"""Center the ship on the screen."""
self.rect.midbottom = self.screen_rect.midbottom
self.x = float(self.rect.x)
python
我们按照 __init__()
中的方法将飞船居中。居中后,我们重置 self.x
属性,这样就可以跟踪飞船的准确位置。
请注意,我们从不制造多于一艘的船只;我们在整个游戏中只制造一个船只实例,并在船只被击中时重新调整其位置。统计数据 |
运行游戏,射杀几个外星人,让外星人撞击飞船。游戏应该暂停,然后会出现一个新的舰队,飞船重新位于屏幕下方的中心位置。
到达屏幕底部的外星人
如果有外星人到达屏幕底部,我们会让游戏做出与外星人撞击飞船时相同的反应。要检查何时发生这种情况,请在 alien_invasion.py 中添加一个新方法:
def _check_aliens_bottom(self):
"""Check if any aliens have reached the bottom of the screen."""
for alien in self.aliens.sprites():
if alien.rect.bottom >= self.settings.screen_height: (1)
# Treat this the same as if the ship got hit.
self._ship_hit()
break
python
方法 _check_aliens_bottom()
检查是否有外星人到达屏幕底部。当异形的 rect.bottom
值大于或等于屏幕高度❶ 时,它就到达了底部。如果有外星人到达底部,我们就会调用 _ship_hit()
。如果有一个外星人到达底部,就没有必要检查其余的,因此我们在调用 _ship_hit()
后跳出循环。
我们将在 _update_aliens()
中调用该方法:
def _update_aliens(self):
--snip--
# Look for alien-ship collisions.
if pygame.sprite.spritecollideany(self.ship, self.aliens):
self._ship_hit()
# Look for aliens hitting the bottom of the screen.
self._check_aliens_bottom()
python
我们会在更新所有外星人的位置和查找外星人与飞船碰撞后调用 _check_aliens_bottom()
。现在,每当飞船被外星人撞击或外星人到达屏幕底部时,就会出现一个新的舰队。
游戏结束
外星入侵 现在感觉更完整了,但游戏永远不会结束。剩下的战舰值只会越来越负。让我们添加一个 game_active
标志,这样我们就能在玩家耗尽船只时结束游戏。我们将在 AlienInvasion
的 __init__()
方法末尾设置这个标志:
def __init__(self):
--snip--
# Start Alien Invasion in an active state.
self.game_active = True
python
现在,我们在 _ship_hit()
中添加代码,当玩家用完所有船只时,将 game_active
设置为 False
:
def _ship_hit(self):
"""Respond to ship being hit by alien."""
if self.stats.ships_left > 0:
# Decrement ships_left.
self.stats.ships_left -= 1
--snip--
# Pause.
sleep(0.5)
else:
self.game_active = False
python
_ship_hit()
的大部分内容没有变化。我们将所有现有代码移到了一个 if
代码块中,该代码块会进行测试,以确保玩家至少还有一艘飞船。如果是,我们会创建一支新舰队,暂停并继续前进。如果玩家没有剩余船只,我们会将 game_active
设置为 False
。
确定游戏的某些部分何时应该运行
我们需要确定游戏中哪些部分应该始终运行,哪些部分只有在游戏激活时才运行:
def run_game(self):
"""Start the main loop for the game."""
while True:
self._check_events()
if self.game_active:
self.ship.update()
self._update_bullets()
self._update_aliens()
self._update_screen()
self.clock.tick(60)
python
在主循环中,即使游戏处于非活动状态,我们也始终需要调用 _check_events()
。例如,我们仍然需要知道用户是否按 Q
键退出游戏或点击按钮关闭窗口。我们还要继续更新屏幕,以便在等待玩家是否选择开始新游戏的同时对屏幕进行更改。其余的函数调用只需在游戏处于活动状态时进行,因为游戏处于非活动状态时,我们不需要更新游戏元素的位置。
现在,当你玩 外星入侵 游戏时,游戏会在你用完所有飞船后冻结。
总结
在本章中,你将学习如何通过创建一支外星人舰队,为游戏添加大量相同的元素。你使用嵌套循环创建了一个元素网格,并通过调用每个元素的 update()
方法使大量游戏元素移动。你学会了控制屏幕上物体的方向,并对特定情况做出反应,例如当舰队到达屏幕边缘时。当子弹击中外星人和外星人击中飞船时,你们会检测到碰撞并做出反应。您还学会了如何跟踪游戏中的统计数据,并使用 game_active
标志来确定游戏何时结束。
在这个项目的下一章,也是最后一章,我们将添加一个 "播放" 按钮,这样玩家就可以选择何时开始他们的第一场游戏,以及游戏结束后是否再玩一次。每次玩家击落整个舰队时,我们都会加快游戏速度,并添加一个计分系统。最终的结果将是一个完全可玩的游戏!