在本章中,我们将在游戏《外星人入侵》中添加外星人。首先,我们在屏幕上边缘附近添加一个外星人,然后生成一群外星人。我们让这群外星人向两边和下面移 动,并删除被子弹击中的外星人。最后,我们将显示玩家拥有的飞船数量,并在玩家的飞船用完后结束游戏。
通过阅读本章,你将更深入地了解Pygame和大型项目的管理。你还将学习如何检测游戏对象之间的碰撞,如子弹和外星人之间的碰撞。检测碰撞有助于你定义游戏元 素之间的交互:可以将角色限定在迷宫墙壁之内或在两个角色之间传球。我们将时不时地查看游戏开发计划,以确保编程工作不偏离轨道。
着手编写在屏幕上添加一群外星人的代码前,先来回顾一下这个项目,并更新开发计划。
1 回顾项目
开发较大的项目时,进入每个开发阶段前回顾一下开发计划,搞清楚接下来要通过编写代码来完成哪些任务都是不错的主意。本章涉及以下内容。
1.研究既有代码,确定实现新功能前是否要进行重构。
2.在屏幕左上角添加一个外星人,并指定合适的边距。
3.根据第一个外星人的边距和屏幕尺寸计算屏幕上可容纳多少个外星人。我们将编写一个循环来创建一系列外星人,这些外星人填满了屏幕的上半部分。
4.让外星人群向两边和下方移动,直到外星人被全部击落,有外星人撞到飞船,或有外星人抵达屏幕底端。如果整群外星人都被击落,我们将再创建一群外星人。如果有外星 人撞到了飞船或抵达屏幕底端,我们将销毁飞船并再创建一群外星人。
5.限制玩家可用的飞船数量,配给的飞船用完后,游戏结束。
我们将在实现功能的同时完善这个计划,但就目前而言,该计划已足够详尽。
在给项目添加新功能前,还应审核既有代码。每进入一个新阶段,通常项目都会更复杂,因此最好对混乱或低效的代码进行清理。
我们在开发的同时一直不断地重构,因此当前需要做的清理工作不多,但每次为测试新功能而运行这个游戏时,都必须使用鼠标来关闭它,这太讨厌了。下面来添加一个结束游 戏的快捷键Q:
game_functions.py
def check_keydown_events(event, ai_settings, screen, ship, bullets):--snip--
elif event.key == pygame.K_q:
sys.exit()
在check_keydown_events() 中,我们添加了一个代码块,以便在玩家按Q时结束游戏。这样的修改很安全,因为Q键离箭头键和空格键很远,玩家不小心按Q键而导致游戏 结束的可能性不大。现在测试时可按Q关闭游戏,而无需使用鼠标来关闭窗口了。
2 创建第一个外星人 创建第一个外星人
在屏幕上放置外星人与放置飞船类似。每个外星人的行为都由Alien 类控制,我们将像创建Ship 类那样创建这个类。出于简化考虑,我们也使用位图来表示外星人。你可以自 己寻找表示外星人的图像,这幅图像的背景为灰色,与屏幕背景色一致。请 务必将你选择的图像文件保存到文件夹images中。
2.1 创建Alien 类
下面来编写Alien 类:
alien.py
import pygame
from pygame.sprite import Sprite
class Alien(Sprite):
"""表示单个外星人的类"""
def __init__(self, ai_settings, screen):
"""初始化外星人并设置其起始位置"""
super(Alien, self).__init__()
self.screen = screen
self.ai_settings = ai_settings
# 加载外星人图像,并设置其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)
def blitme(self):
"""在指定位置绘制外星人"""
self.screen.blit(self.image, self.rect)
除位置不同外,这个类的大部分代码都与Ship 类相似。每个外星人最初都位于屏幕左上角附近,我们将每个外星人的左边距都设置为外星人的宽度,并将上边距设置为外星人 的高度(见❶)。
2.2 创建Alien 实例
下面在alien_invasion.py中创建一个Alien 实例:
alien_invasion.py
--snip--
from ship import Ship
from alien import Alien
import game_functions as gf
def run_game():
--snip--
# 创建一个外星人
alien = Alien(ai_settings, screen)
# 开始游戏主循环
while True:
gf.check_events(ai_settings, screen, ship, bullets)
ship.update()
gf.update_bullets(bullets)
gf.update_screen(ai_settings, screen, ship, alien, bullets)
run_game()
在这里,我们导入了新创建的Alien 类,并在进入主while 循环前创建了一个Alien 实例。我们没有修改外星人的位置,因此该while 循环没有任何新东西,但我们修改了 对update_screen() 的调用,传递了一个外星人实例。
2.3 让外星人出现在屏幕上
为让外星人出现在屏幕上,我们在update_screen() 中调用其方法blitme() :
game_functions.py
def update_screen(ai_settings, screen, ship, alien, bullets):
--snip--
# 在飞船和外星人后面重绘所有的子弹
for bullet in bullets:
bullet.draw_bullet()
ship.blitme()
alien.blitme()
# 让最近绘制的屏幕可见
pygame.display.flip()
我们先绘制飞船和子弹,再绘制外星人,让外星人在屏幕上位于最前面。图13-2显示了屏幕上的第一个外星人。
第一个外星人正确地现身后,下面来编写绘制一群外星人的代码。
3 创建一群外星人
要绘制一群外星人,需要确定一行能容纳多少个外星人以及要绘制多少行外星人。我们将首先计算外星人之间的水平间距,并创建一行外星人,再确定可用的垂直空间,并创建 整群外星人。
3.1 确定一行可容纳多少个外星人
为确定一行可容纳多少个外星人,我们来看看可用的水平空间有多大。屏幕宽度存储在ai_settings.screen_width 中,但需要在屏幕两边都留下一定的边距,把它设置为 外星人的宽度。由于有两个边距,因此可用于放置外星人的水平空间为屏幕宽度减去外星人宽度的两倍:
available_space_x = ai_settings.screen_width – (2 * alien_width)
我们还需要在外星人之间留出一定的空间,即外星人宽度。因此,显示一个外星人所需的水平空间为外星人宽度的两倍:一个宽度用于放置外星人,另一个宽度为外星人右边的 空白区域。为确定一行可容纳多少个外星人,我们将可用空间除以外星人宽度的两倍:
number_aliens_x = available_space_x / (2 * alien_width)
我们将在创建外星人群时使用这些公式。
注意 令人欣慰的是,在程序中执行计算时,一开始你无需确定公式是正确的,而可以尝试直接运行程序,看看结果是否符合预期。即便是在最糟糕的情况下,也只 是屏幕上显示的外星人太多或太少。你可以根据在屏幕上看到的情况调整计算公式。
3.2 创建多行外星人
为创建一行外星人,首先在alien_invasion.py中创建一个名为aliens 的空编组,用于存储全部外星人,再调用game_functions.py中创建外星人群的函数:
alien_invasion.py
import pygame
from pygame.sprite import Group
from settings import Settings
from ship import Ship
import game_functions as gf
def run_game():
--snip--
# 创建一艘飞船、一个子弹编组和一个外星人编组
ship = Ship(ai_settings, screen)
bullets = Group()
❶ aliens = Group()
# 创建外星人群
❷ gf.create_fleet(ai_settings, screen, aliens)
# 开始游戏主循环
while True:
--snip—
❸ gf.update_screen(ai_settings, screen, ship, aliens, bullets)
run_game()
由于我们不再在alien_invasion.py中直接创建外星人,因此无需在这个文件中导入Alien 类。
❶处创建了一个空编组,用于存储所有的外星人。接下来,调用稍后将编写的函数create_fleet() (见❷),并将ai_settings 、对象screen 和空编组aliens 传递给 它。然后,修改对update_screen() 的调用,让它能够访问外星人编组(见❸)。
我们还需要修改update_screen() :
game_functions.py
def update_screen(ai_settings, screen, ship, aliens, bullets):
--snip--
ship.blitme()
aliens.draw(screen)
# 让最近绘制的屏幕可见
pygame.display.flip()
对编组调用draw() 时,Pygame自动绘制编组的每个元素,绘制位置由元素的属性rect 决定。在这里,aliens.draw(screen) 在屏幕上绘制编组中的每个外星人。
3.3 创建外星人群
现在可以创建外星人群了。下面是新函数create_fleet() ,我们将它放在game_functions.py的末尾。我们还需要导入Alien 类,因此务必在文件game_functions.py开头添加相应 的import 语句:
game_functions.py
--snip--
from bullet import Bullet
from alien import Alien
--snip--
def create_fleet(ai_settings, screen, aliens):