武装飞船
开发一个名为《外星人入侵》的游戏吧!为此将使用 Pygame,这是一组功能强大而有趣的模块,可用于管理图形、动画乃至声音, 让你能够更轻松地开发复杂的游戏。通过使用Pygame来处理在屏幕上绘制图像 等任务,可将重点放在程序的高级逻辑上。
你将安装Pygame,再创建一艘能够根据用户输入左右移动和射击的飞船。在接下来的两章,你将创建一群作为射杀目标的外星人,并改进该游戏:限制可供玩家使用的飞船数,并且添加记分牌。
玩家控制一艘最初出现在屏幕底部中央的飞船。玩 家可以使用箭头键左右移动飞船,还可使用空格键射击。游戏开始时,一群外 星人出现在天空中,并向屏幕下方移动。玩家的任务是射杀这些外星人。玩家 将所有外星人都消灭干净后,将出现一群新的外星人,其移动速度更快。只要 有外星人撞到玩家的飞船或到达屏幕底部,玩家就损失一艘飞船。玩家损失三艘飞船后,游戏结束。
一.第三阶段
本章将结束游戏《外星人入侵》的开发。我们会添加一个Play 按钮,用于根据需要启动游戏以及在游戏结束后重启游戏,还会修改这个游 戏,使其随玩家等级提高而加快节奏,并实现一个记分系统。阅读本章后,你 将掌握足够多的知识,能够开始编写随玩家等级提高而加大难度以及显示得分的游戏。
1.1添加Play按钮
添加一个Play按钮,它在游戏开始前出现,并在游戏结束后再次出现,让玩家能够开始新游戏。
python"> def __init__(self, ai_game): """初始化统计信息。""" self.settings = ai_game.settings self.reset_stats() # 让游戏一开始处于非活动状态。 self.game_active = False
1.1.1创建 Button 类
由于Pygame没有内置创建按钮的方法,编写一个Button 类,用于创建带标 签的实心矩形。
python">import pygame.font
class Button:def __init__(self,ai_game,msg): #1"""初始化按钮的属性。"""self.screen=ai_game.screenself.screen_rect=self.screen.get_rect()# 设置按钮的尺寸和其他属性。self.width,self.height=200,50 #2self.button_color=(0,255,0)self.text_color=(255,255,255)self.font=pygame.font.SysFont(None,48) #3# 创建按钮的rect对象,并使其居中。 self.rect=pygame.Rect(0,0,self.width,self.height)self.rect.center=self.screen_rect.center# 按钮的标签只创建一次。self._prep_msg(msg) #4
导入模块pygame.font ,让Pygame能够将文本渲染到屏幕上。方法 __init__() 接受参数self 、对象ai_game 和msg ,其中msg是要在按钮中显示的文本(#1)。设置按钮的尺寸(#2),再通过设置button_color ,让按钮的rect 对象为亮绿色,并通过设置text_color 让文本为白色。
在(#3)处,指定使用什么字体来渲染文本。实参None 让Pygame使用默认字体,而48 指定了文本的字号。为让按钮在屏幕上居中,创建一个表示按钮的rect 对象(#4),并将其center 属性设置为屏幕的center 属性。
Pygame处理文本的方式是,将要显示的字符串渲染为图像。在(#5)处,调用了 _prep_msg() 来处理这样的渲染。
button.py
python"> def _prep_msg(self,msg):"""将msg渲染为图像,并使其在按钮上居中。"""self.msg_image=self.font.render(msg,True,self.text_color, #1self.button_color)self.msg_image_rect=self.msg_image.get_rect() #2self.msg_image_rect.center=self.rect.center
方法_prep_msg() 接受实参self 以及要渲染为图像的文本(msg )。调用 font.render() 将存储在msg 中的文本转换为图像,再将该图像存储在 self.msg_image 中(#1)。方法font.render() 还接受一个布尔实参,该 实参指定开启还是关闭反锯齿功能(反锯齿让文本的边缘更平滑)。余下的两个实 参分别是文本颜色和背景色。我们启用了反锯齿功能,并将文本的背景色设置为按 钮的颜色。(如果没有指定背景色,Pygame渲染文本时将使用透明背景。)
在(#2)处,让文本图像在按钮上居中:根据文本图像创建一个rect ,并将其center 属性设置为按钮的center 属性。 最后,创建方法draw_button() ,用于将这个按钮显示到屏幕上:
创建方法draw_button() ,用于将这个按钮显示到屏幕上:
button.py
python">def draw_button(self): # 绘制一个用颜色填充的按钮,再绘制文本。 self.screen.fill(self.button_color, self.rect) self.screen.blit(self.msg_image, self.msg_image_rect)
调用screen.fill() 来绘制表示按钮的矩形,再调用screen.blit() 并向 它传递一幅图像以及与该图像相关联的rect ,从而在屏幕上绘制文本图像。
1.1.2在屏幕上绘制按钮
更新 import 语句:
alien_invasion.py
python">--snip--
from game_stats import GameStats
from button import Button
alien_invasion.py
python">def __init__(self): --snip-- self._create_fleet() #创建Play按钮。 self.play_button = Button(self, "Play")
alien_invasion.py
python"> def _update_screen(self): --snip-- self.aliens.draw(self.screen) # 如果游戏处于非活动状态,就绘制Play按钮。 if not self.stats.game_active: self.play_button.draw_button() pygame.display.flip()
为让Play按钮位于其他所有屏幕元素上面,在绘制其他所有游戏元素后再绘制这个 按钮,然后切换到新屏幕。将这些代码放在一个if 代码块中,让按钮仅在游戏出于 非活动状态时才出现。
1.1.3开始游戏
为在玩家单击Play按钮时开始新游戏,在_check_events() 末尾添加如下elif 代码块,以监视与该按钮相关的鼠标事件:
alien_invasion.py
python">def _check_events(self): """响应按键和鼠标事件。""" for event in pygame.event.get(): if event.type == pygame.QUIT: --snip-- elif event.type == pygame.MOUSEBUTTONDOWN: #1mouse_pos = pygame.mouse.get_pos() #2self._check_play_button(mouse_pos) #3
无论玩家单击屏幕的什么地方,Pygame都将检测到一个MOUSEBUTTONDOWN 事件 (#1),但我们只想让这个游戏在玩家用鼠标单击Play按钮时做出响应。为此,使 用了pygame.mouse.get_pos() ,它返回一个元组,其中包含玩家单击时鼠标的 坐标和 坐标(#2)。我们将这些值传递给新方法_check_play_button() (#3)
alien_invasion.py
python">def _check_play_button(self, mouse_pos): """在玩家单击Play按钮时开始新游戏。""" if self.play_button.rect.collidepoint(mouse_pos): self.stats.game_active = True
使用了rect 的方法collidepoint() 检查鼠标单击位置是否在Play按钮的 rect 内。如果是,就将game_active 设置为True ,让游戏开始!
1.1.4重置游戏
为在玩家每次单击Play按钮时都重置游戏,需要重置统计信息、删除现有的外星人 和子弹、创建一群新的外星人并让飞船居中
alien_invasion.py
python"> def _check_play_button(self,mouse_pos):"""在玩家单击Play按钮时开始新游戏。"""if self.play_button.rect.collidepoint(mouse_pos):#重置游戏统计信息。self.stats.reset_stats()self.stats.game_active= True#清空余下的外星人和子弹。self.aliens.empty()self.bullets.empty()#创建一群新的外星人并让飞船居中。self._create_fleet()self.ship.center_ship()
重置游戏统计信息,给玩家提供三艘新飞船。接下来,将game_active 设置为True 。这样,这个方法的代码执行完毕后,游戏就将开始。清空编组 aliens 和bullets ,然后创建一群新的外星人并将飞船居中。
1.1.5将Play按钮切换到非活动状态、
存在一个问题:即便Play按钮不可见,玩家单击其所在的区域时,游戏依然会 做出响应。游戏开始后,如果玩家不小心单击了Play按钮所处的区域,游戏将重新 开始! 为修复这个问题,可让游戏仅在game_active 为False 时才开始:
alien_invasion.py
python">def _check_play_button(self, mouse_pos): """玩家单击Play按钮时开始新游戏。""" button_clicked = self.play_button.rect.collidepoint(mouse_pos) #1if button_clicked and not self.stats.game_active: #2#重置游戏统计信息。 self.stats.
标志button_clicked 的值为True 或False (#1)。仅当玩家单击了Play按 钮 且 游戏当前处于非活动状态时,游戏才重新开始(#2)。
1.1.6隐藏鼠标光标
alien_invasion.py
python"> def _check_play_button(self, mouse_pos): """在玩家单击Play按钮时开始新游戏。""" button_clicked = self.play_button.rect.collidepoint(mouse_pos) if button_clicked and not self.stats.game_active: --snip-- #隐藏鼠标光标。 pygame.mouse.set_visible(False)
通过向set_visible() 传递False ,让Pygame在光标位于游戏窗口内时将其隐藏 起来。 游戏结束后,将重新显示光标,让玩家能够单击Play按钮来开始新游戏。
alien_invasion.py
python"> def _ship_hit(self): """响应飞船被外星人撞到。""" if self.stats.ships_left > 0: --snip-- else: self.stats.game_active = False pygame.mouse.set_visible(True)
1.2提高等级
当前,将整群外星人消灭干净后,玩家将提高一个等级,但游戏的难度没变。下面 来增加一点趣味性:每当玩家将屏幕上的外星人消灭干净后,都加快游戏的节奏, 让游戏玩起来更难。
1.2.1修改速度设置
首先重新组织Settings 类,将游戏设置划分成静态和动态两组。对于随着游戏进 行而变化的设置,还要确保在开始新游戏时进行重置。settings.py的方法 __init__() 如下:
settings.py
python">def __init__(self): """初始化游戏的静态设置。""" #屏幕设置 self.screen_width = 1200 self.screen_height = 800 self.bg_color = (230, 230, 230) #飞船设置 self.ship_limit = 3 #子弹设置 self.bullet_width = 3 self.bullet_height = 15 self.bullet_color = 60, 60, 60 self.bullets_allowed = 3 #外星人设置 self.fleet_drop_speed = 10 # 加快游戏节奏的速度。 self.speedup_scale = 1.1 #1self.initialize_dynamic_settings() #2
在(#1)处,添加设置speedup_scale ,用 于控制游戏节奏的加快速度:2表示玩家每提高一个等级,游戏的节奏就翻一倍;1 表示游戏节奏始终不变。将其设置为1.1能够将游戏节奏提高到足够快,让游戏既有 难度又并非不可完成。最后,调用initialize_dynamic_settings() 初始化 随游戏进行而变化的属性(#2)。
settings.py
python">def initialize_dynamic_settings(self): """初始化随游戏进行而变化的设置。""" self.ship_speed = 1.5 self.bullet_speed = 3.0 self.alien_speed = 1.0 # fleet_direction为1表示向右,为-1表示向左。 self.fleet_direction = 1
这个方法设置飞船、子弹和外星人的初始速度。随着游戏的进行,将提高这些速 度。每当玩家开始新游戏时,都将重置这些速度。在这个方法中,还设置了 fleet_direction ,使得游戏刚开始时,外星人总是向右移动。不需要增大 fleet_drop_speed 的值,因为外星人移动的速度越快,到达屏幕底端所需的时 间越短。
为在玩家的等级提高时提高飞船、子弹和外星人的速度,编写一个名为 increase_speed() 的新方法:settings.py
python"> def increase_speed(self): """提高速度设置""" self.ship_speed *= self.speedup_scale self.bullet_speed *= self.speedup_scale self.alien_speed *= self.speedup_scale
在_check_bullet_alien_collisions() 中,在整群外星人都被消灭后调用 increase_speed() 来加快游戏的节奏
alien_invasion.py
python"> def _check_bullet_alien_collisions(self): --snip-- if not self.aliens: # 删除现有的子弹并创建一群新的外星人。 self.bullets.empty() self._create_fleet() self.settings.increase_speed()
1.2.2重置速度
每当玩家开始新游戏时,都需要将发生了变化的设置重置为初始值,否则新游戏开 始时,速度设置将为前一次提高后的值:
alien_invasion.py
python">def _check_play_button(self, mouse_pos): """在玩家单击Play按钮时开始新游戏。""" button_clicked = self.play_button.rect.collidepoint(mouse_pos) if button_clicked and not self.stats.game_active: #重置游戏设置。 self.settings.initialize_dynamic_settings() --snip--
1.3记分
实现一个记分系统,以实时跟踪玩家的得分,并显示最高得分、等级和余下的飞船数。
得分是游戏的一项统计信息,因此在GameStats 中添加一个score 属性:
game_stats.py
python"> class GameStats: --snip-- def reset_stats(self): """初始化随游戏进行可能变化的统计信息。""" self.ships_left = self.ai_settings.ship_limit self.score = 0
为在每次开始游戏时都重置得分,我们在reset_stats() 而不是__init__() 中 初始化score 。
1.3.1显示得分
为在屏幕上显示得分,首先创建一个新类Scoreboard 。
scoreboard.py
python">import pygame.fontclass Scoreboard:"""显示得分信息的类。"""def __init__(self,ai_game): #1"""初始化得分涉及的属性。""" self.screen=ai_game.screenself.screen.rect=ai_game.screen.get_rect()self.settings=ai_game.settingsself.stats=ai_game.stats# 显示得分信息时使用的字体设置。self.text_color=(30,30,30) #2self.font=pygame.font.SysFont(None,48) #3# 准备初始得分图像。self.prep_score() #4
由于Scoreboard 在屏幕上显示文本,首先导入模块pygame.font 。接下来,在 __init__() 中包含形参ai_game ,以便访问报告跟踪的值所需的对象 settings 、screen 和stats (#1)。然后,设置文本颜色(#2)并实例化 一个字体对象(#3)。 为将要显示的文本转换为图像,调用prep_score() (#4),其定义如下:
scoreboard.py
python"> def prep_score(self):"""将得分转换为渲染的图像。"""score_str=str(self.stats.score) #1self.score_image=self.font.render(score_str,True,self.text_color,self.settings.bg_color)#2# 将得分放在屏幕右上角。self.score_rect=self.score_image.get_rect() #3self.score_rect.right=self.screen_rect.right-20 #4self.score_rect.top=20 #5
在prep_score() 中,将数值stats.score 转换为字符串(#1),再将这个字 符串传递给创建图像的render() (#2)。为在屏幕上清晰地显示得分,向 render() 传递屏幕背景色和文本颜色。 将得分放在屏幕右上角,并在得分增大导致数变宽时让其向左延伸。为确保得分始 终锚定在屏幕右边,创建一个名为score_rect 的rect (#3),让其右边缘与 屏幕右边缘相距20像素(#4),并让其上边缘与屏幕上边缘也相距20像素(#5)。 接下来,创建方法show_score() ,用于显示渲染好的得分图像:
scoreboard.py
python"> def show_score(self): """在屏幕上显示得分。""" self.screen.blit(self.score_image, self.score_rect)
1.3.2创建记分牌
为显示得分,在AlienInvasion 中创建一个Scoreboard 实例。先来更新 import 语句:
alien_invasion.py
python">--snip--
from game_stats import GameStats
from scoreboard import Scoreboard
--snip--
接下来,在方法__init__() 中创建一个Scoreboard 实例:
alien_invasion.py
python">def __init__(self): --snip-- pygame.display.set_caption("Alien Invasion") #创建存储游戏统计信息的实例, #并创建记分牌。 self.stats = GameStats(self) self.sb = Scoreboard(self) --snip--
然后,在_update_screen() 中将记分牌绘制到屏幕上:
python"> def _update_screen(self): --snip-- self.aliens.draw(self.screen) #显示得分。 self.sb.show_score() #如果游戏处于非活动状态,就显示Play按钮。 --snip--
alien_invasion.py
python">def _update_screen(self): --snip-- self.aliens.draw(self.screen) #显示得分。 self.sb.show_score() # 如果游戏处于非活动状态,就显示Play按钮。 --snip--
在显示Play按钮前调用show_score() 。
1.3.3 在外星人被消灭时更新得分
为在屏幕上实时显示得分,每当有外星人被击中时,都更新stats.score 的值, 再调用prep_score() 更新得分图像。但在此之前,需要指定玩家每击落一个外星 人将得到多少分:
settings.py
python"> def initialize_dynamic_settings(self): --snip-- # 记分 self.alien_points = 50
随着游戏的进行,将提高每个外星人的分数。为确保每次开始新游戏时这个值都会 被重置,我们在initialize_dynamic_settings() 中设置它。
在_check_bullet_alien_collisions() 中,每当有外星人被击落时,都更新得分:
alien_invasion.py
python"> def _check_bullet_alien_collisions(self): """响应子弹和外星人发生碰撞。""" #删除彼此碰撞的子弹和外星人。 collisions = pygame.sprite.groupcollide( self.bullets, self.aliens, True, True) if collisions: self.stats.score += self.settings.alien_points self.sb.prep_score() --snip--
有子弹击中外星人时,Pygame返回一个字典(collisions )。我们检查这个字典 是否存在,如果存在,就将得分加上一个外星人的分数。接下来,调用 prep_score() 来创建一幅包含最新得分的新图像。
1.3.4重置得分
当前,仅在有外星人被射杀 之后 生成得分。这在大多数情况下可行,但从开始新游 戏到有外星人被射杀之间,显示的是上一次的得分。 为修复这个问题,可在开始新游戏时生成得分:
alien_invasion.py
python"> def _check_play_button(self, mouse_pos): --snip-- if button_clicked and not self.stats.game_active: --snip-- # 重置游戏统计信息。 self.stats.reset_stats() self.stats.game_active = True self.sb.prep_score() --snip--
开始新游戏时,我们重置游戏统计信息再调用prep_score() 。此时生成的记分牌 上显示的得分为零。
1.3.5将消灭的每个外星人都计入得分
当前的代码可能会遗漏一些被消灭的外星人。例如,如果在一次循环中,有两颗子 弹击中了外星人,或者因子弹较宽而同时击中了多个外星人,玩家将只能得到一个 外星人的分数。为修复这种问题,我们来调整检测子弹和外星人碰撞的方式。
在_check_bullet_alien_collisions() 中,与外星人碰撞的子弹都是字典 collisions 中的一个键,而与每颗子弹相关的值都是一个列表,其中包含该子弹 击中的外星人。我们遍历字典collisions ,确保将消灭的每个外星人都计入得分:
alien_invasion.py
python">def _check_bullet_alien_collisions(self): --snip-- if collisions: for aliens in collisions.values(): self.stats.score += self.settings.alien_points * len(aliens) self.sb.prep_score()
如果字典collisions 存在,就遍历其中的所有值。别忘了,每个值都是一个列 表,包含被同一颗子弹击中的所有外星人。对于每个列表,都将其包含的外星人数 量乘以一个外星人的分数,并将结果加入当前得分。
1.3.6提高分数
鉴于玩家每提高一个等级,游戏都变得更难,因此处于较高的等级时,外星人的分 数应更高。
settings.py
python"> class Settings: """存储游戏《外星人入侵》的所有设置的类。""" def __init__(self): --snip-- # 加快游戏节奏的速度。 self.speedup_scale = 1.1 # 外星人分数的提高速度。 self.score_scale = 1.5 #1self.initialize_dynamic_settings() def initialize_dynamic_settings(self): --snip-- def increase_speed(self): """提高速度设置和外星人分数。""" self.ship_speed *= self.speedup_scale self.bullet_speed *= self.speedup_scale self.alien_speed *= self.speedup_scale self.alien_points = int(self.alien_points * self.score_scale) #2
定义了分数的提高速度,并称之为score_scale (#1)。较低的节奏加快 速度(1.1)让游戏很快变得极具挑战性,但为了让记分发生显著的变化,需要将分 数的提高速度设置为更大的值(1.5)。现在,在加快游戏节奏的同时,提高了每个 外星人的分数(#2)。为让分数为整数,使用了函数int() 。
1.3.7舍入得分
将得分显示为10的整数倍,下面让记分系统遵循这个原 则。我们还将设置得分的格式,在大数中添加用逗号表示的千位分隔符。在 Scoreboard 中执行这种修改:
scoreboard.py
python"> def prep_score(self): """将得分转换为渲染的图像。""" rounded_score = round(self.stats.score, -1) #1score_str = "{:,}".format(rounded_score) #2self.score_image = self.font.render(score_str, True, self.text_color, self.settings.bg_color) --snip--
函数round() 通常让小数精确到小数点后某一位,其中小数位数是由第二个实参指 定的。然而,如果将第二个实参指定为负数,round() 将舍入到最近的10的整数 倍,如10、100、1000等。(#1)处的代码让Python将stats.score 的值舍入到最近的 10的整数倍,并将结果存储到rounded_score 中。
1.3.8最高得分
每个玩家都想超过游戏的最高得分纪录。下面来跟踪并显示最高得分,给玩家提供 要超越的目标。我们将最高得分存储在GameStats 中:\
game_stats.py
python">def __init__(self, ai_game): --snip-- # 任何情况下都不应重置最高得分。 self.high_score = 0
scoreboard.py
python">def __init__(self, ai_game): --snip-- # 准备包含最高得分和当前得分的图像。 self.prep_score() self.prep_high_score()
最高得分将与当前得分分开显示,因此需要编写一个新方法prep_high_score() ,用于准备包含最高得分的图像.
方法prep_high_score() 的代码如下:
scoreboard.py
python"> def prep_high_score(self):"""将最高得分转换为图像。"""high_score = round(self.stats.high_score, -1) #1high_score_str = "{:,}".format(high_score)self.high_score_image = self.font.render(high_score_str, True, #2self.text_color, self.settings.bg_color)# 将最高得分放在屏幕顶部中央。self.high_score_rect = self.high_score_image.get_rect()self.high_score_rect.centerx = self.screen_rect.centerx #3self.high_score_rect.top = self.score_rect.top #4
将最高得分舍入到最近的10的整数倍,并添加用逗号表示的千分位分隔符(#1)。 然后,根据最高得分生成一幅图像(#2),使其水平居中(#3),并将其top 属性设置为当前得分图像的top 属性(#4)
现在,方法show_score() 需要在屏幕右上角显示当前得分,并在屏幕顶部中央显 示最高得分:
scoreboard.py
python"> def show_score(self): """在屏幕上显示得分。""" self.screen.blit(self.score_image, self.score_rect) self.screen.blit(self.high_score_image, self.high_score_rect)
为检查是否诞生了新的最高得分,在Scoreboard 中添加一个新方法 check_high_score() :
scoreboard.py
python"> def check_high_score(self): """检查是否诞生了新的最高得分。""" if self.stats.score > self.stats.high_score: self.stats.high_score = self.stats.score self.prep_high_score()
方法check_high_score() 比较当前得分和最高得分。如果当前得分更高,就更 新high_score 的值,并调用prep_high_score() 来更新包含最高得分的图像。
在_check_bullet_alien_collisions() 中,每当有外星人被消灭时,都需要 在更新得分后调用check_high_score() :
alien_invasion.py
python"> def _check_bullet_alien_collisions(self): --snip-- if collisions: for aliens in collisions.values(): self.stats.score += self.settings.alien_points * len(aliens) self.sb.prep_score() self.sb.check_high_score() --snip--
如果字典collisions 存在,就根据消灭了多少外星人更新得分,再调用 check_high_score() 。
1.3.9显示等级
为在游戏中显示玩家的等级,首先需要在GameStats 中添加一个表示当前等级的 属性。为确保每次开始新游戏时都重置等级,在reset_stats() 中初始化它:
game_stats.py
python"> def reset_stats(self): """初始化随游戏进行可能变化的统计信息。""" self.ships_left = self.settings.ship_limit self.score = 0 self.level = 1
为了让Scoreboard 显示当前等级,在__init__() 中调用一个新方法 prep_level() :
scoreboard.py
python"> def __init__(self, ai_game): --snip-- self.prep_high_score() self.prep_level()
prep_level() 的代码如下:
scoreboard.py
python"> def prep_level(self): """将等级转换为渲染的图像。""" level_str = str(self.stats.level) self.level_image = self.font.render(level_str, True, #1self.text_color, self.settings.bg_color) # 将等级放在得分下方。 self.level_rect = self.level_image.get_rect() self.level_rect.right = self.score_rect.right #2self.level_rect.top = self.score_rect.bottom + 10 #3
方法prep_level() 根据存储在stats.level 中的值创建一幅图像(#1),并 将其right 属性设置为得分的right 属性(#2)。然后,将top 属性设置为比 得分图像的bottom 属性大10像素,以便在得分和等级之间留出一定的空间(#3)。 还需要更新show_score() :
scoreboard.py
python"> def show_score(self): """在屏幕上显示得分和等级。""" self.screen.blit(self.score_image, self.score_rect) self.screen.blit(self.high_score_image, self.high_score_rect) self.screen.blit(self.level_image, self.level_rect)
新增的代码行在屏幕上显示等级图像。 我们在_check_bullet_alien_collisions() 中提高等级并更新等级图像:
alien_invasion.py
python"> def _check_bullet_alien_collisions(self): --snip-- if not self.aliens: # 删除现有的子弹并新建一群外星人。 self.bullets.empty() self._create_fleet() self.settings.increase_speed() # 提高等级。 self.stats.level += 1 self.sb.prep_level()
如果整群外星人都被消灭,就将stats.level 的值加1,并调用prep_level() 确保正确地显示了新等级。 为确保在开始新游戏时更新等级图像,还需在玩家单击按钮Play时调用 prep_level() : alien_invasion.py
python"> def _check_play_button(self, mouse_pos): --snip-- if button_clicked and not self.stats.game_active: --snip-- self.sb.prep_score() self.sb.prep_level() --snip--
1.3.10显示余下的飞船数
首先,需要让Ship 继承Sprite ,以便创建飞船编组:
ship.py
python"> import pygame from pygame.sprite import Sprite class Ship(Sprite): #1"""管理飞船的类。""" def __init__(self, ai_game): """初始化飞船并设置其起始位置。""" super().__init__() #2--snip--
这里导入了Sprite ,让Ship 继承Sprite (#1),并在__init__() 的开头 调用super() (#2)。
接下来,需要修改Scoreboard ,以创建可供显示的飞船编组。下面是其中的 import 语句: scoreboard.py
python">import pygame.font
from pygame.sprite import Group
from ship import Ship
鉴于需要创建飞船编组,导入Group 和Ship 类。 下面是方法__init__() : scoreboard.py
python">def __init__(self, ai_game): """初始化记录得分的属性。""" self.ai_game = ai_game self.screen = ai_game.screen --snip-- self.prep_level() self.prep_ships()
在调用 prep_level() 后调用了prep_ships() 。 prep_ships() 的代码如下:
scoreboard.py
python"> def prep_ships(self): """显示还余下多少艘飞船。""" self.ships = Group() #1 for ship_number in range(self.stats.ships_left): #2ship = Ship(self.ai_game) ship.rect.x = 10 + ship_number * ship.rect.width #3ship.rect.y = 10 #4self.ships.add(ship) #5
方法prep_ships() 创建一个空编组self.ships ,用于存储飞船实例(#1)。为填充这个编组,根据玩家还有多少艘飞船以相应的次数运行一个循环(#2)。在这个循环中,创建新飞船并设置其 坐标,让整个飞船编组都位于屏幕左 边,且每艘飞船的左边距都为10像素(#3)。还将 坐标设置为离屏幕上边缘10像 素,让所有飞船都出现在屏幕左上角(#4)。最后,将每艘新飞船都添加到编组 ships 中(#5)。
scoreboard.py
python"> def show_score(self): """在屏幕上绘制得分、等级和余下的飞船数。""" self.screen.blit(self.score_image, self.score_rect) self.screen.blit(self.high_score_image, self.high_score_rect) self.screen.blit(self.level_image, self.level_rect) self.ships.draw(self.screen)
为在屏幕上显示飞船,对编组调用draw() 。Pygame将绘制每艘飞船。 为在游戏开始时让玩家知道自己有多少艘飞船,在开始新游戏时调用 prep_ships() 。这是在AlienInvasion 的_check_play_button() 中进行 的:
alien_invasion.py
python"> def _check_play_button(self, mouse_pos): --snip-- if button_clicked and not self.stats.game_active: --snip-- self.sb.prep_score() self.sb.prep_level() self.sb.prep_ships() --snip--
还要在飞船被外星人撞到时调用prep_ships() ,从而在玩家损失飞船时更新飞船 图像: alien_invasion.py
python">def _ship_hit(self): """响应飞船被外星人撞到。""" if self.stats.ships_left > 0: #将ships_left减1并更新记分牌。 self.stats.ships_left -= 1 self.sb.prep_ships() --snip--
这里在将ships_left 的值减1后调用prep_ships() 。这样每次损失飞船后,显 示的飞船数都是正确的。