14.3.2 创建记分牌
为显示得分,我们在alien_invasion.py中创建一个Scoreboard实例:
alien_invasion.py
--snip-- from game_stats import GameStats from scoreboard import Scoreboard --snip-- def run_game(): --snip-- # 创建存储游戏统计信息的实例,并创建记分牌 stats = GameStats(ai_settings) 1 sb = Scoreboard(ai_settings, screen, stats) --snip-- # 开始游戏主循环 while True: --snip-- 2 gf.update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button) run_game()
我们导入新创建的类Scoreboard,并在创建实例stats后创建了一个名为sb的Scoreboard实例 (见1)。接下来,我们将sb传递给update_screen(),让它能够在屏幕上显示得分(见2)。 为显示得分,将update_screen()修改成下面这样:
game_functions.py
def update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button): --snip-- # 显示得分 sb.show_score() # 如果游戏处于非活动状态,就显示Play按钮 if not stats.game_active: play_button.draw_button() # 让最近绘制的屏幕可见 pygame.display.flip()
我们在update_screen()的形参列表中添加了sb,并在绘制Play按钮前调用show_score。 如果现在运行这个游戏,你将在屏幕右上角看到0(当前,我们只想在进一步开发记分系统 前确认得分出现在正确的地方)。图14-2显示了游戏开始前的得分。
下面来指定每个外星人值多少点!
14.3.3 在外星人被消灭时更新得分
为在屏幕上实时地显示得分,每当有外星人被击中时,我们都更新stats.score的值,再调 用prep_score()更新得分图像。但在此之前,我们需要指定玩家每击落一个外星人都将得到多少 个点:
settings.py
def initialize_dynamic_settings(self): --snip-- # 记分 self.alien_points = 50
随着游戏的进行,我们将提高每个外星人值的点数。为确保每次开始新游戏时这个值都会被 重置,我们在initialize_dynamic_settings()中设置它。 在check_bullet_alien_collisions()中,每当有外星人被击落时,都更新得分:
game_functions.py
def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets): """响应子弹和外星人发生碰撞""" # 删除发生碰撞的子弹和外星人 collisions = pygame.sprite.groupcollide(bullets, aliens, True, True) if collisions: 1 stats.score += ai_settings.alien_points sb.prep_score() --snip--
我们更新check_bullet_alien_collisions()的定义,在其中包含了形参stats和sb,让它能够 更新得分和记分牌。有子弹撞到外星人时,Pygame返回一个字典(collisions)。我们检查这个 字典是否存在,如果存在,就将得分加上一个外星人值的点数(见)。接下来,我们调用 prep_score()来创建一幅显示最新得分的新图像。 我们需要修改update_bullets(),确保在函数之间传递合适的实参:
game_functions.py
def update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets): """更新子弹的位置,并删除已消失的子弹""" --snip-- check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets)
在update_bullets()的定义中,需要新增形参stats和sb,而调用check_bullet_alien_collisions()时,也需要传递实参stats和sb。 我们还需要修改主while循环中调用update_bullets()的代码:
alien_invasion.py
# 开始游戏主循环 while True: gf.check_events(ai_settings, screen, stats, play_button, ship, aliens, bullets) if stats.game_active: ship.update() gf.update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets) --snip--
调用update_bullets()时,需要传递实参stats和sb。 如果你现在运行这个游戏,得分将不断增加!
14.3.4 将消灭的每个外星人的点数都计入得分
当前,我们的代码可能遗漏了一些被消灭的外星人。例如,如果在一次循环中有两颗子弹射 中了外星人,或者因子弹更宽而同时击中了多个外星人,玩家将只能得到一个被消灭的外星人的 点数。为修复这种问题,我们来调整检测子弹和外星人碰撞的方式。 在check_bullet_alien_collisions()中,与外星人碰撞的子弹都是字典collisions中的一个 键;而与每颗子弹相关的值都是一个列表,其中包含该子弹撞到的外星人。我们遍历字典 collisions,确保将消灭的每个外星人的点数都记入得分:
game_functions.py
def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets): --snip-- if collisions: 1 for aliens in collisions.values(): stats.score += ai_settings.alien_points * len(aliens) sb.prep_score() --snip--
如果字典collisions存在,我们就遍历其中的所有值。别忘了,每个值都是一个列表,包含 被同一颗子弹击中的所有外星人。对于每个列表,都将一个外星人的点数乘以其中包含的外星人 数量,并将结果加入到当前得分中。为测试这一点,请将子弹宽度改为300像素,并核实你得到 了更宽的子弹击中的每个外星人的点数,再将子弹宽度恢复到正常值。
14.3.5 提高点数
玩家每提高一个等级,游戏都变得更难,因此处于较高的等级时,外星人的点数应更高。为实现这种功能,我们添加一些代码,以在游戏节奏加快时提高点数:
settings.py
class Settings(): """存储游戏《外星人入侵》的所有设置的类""" def __init__(self): --snip-- # 加快游戏节奏的速度 self.speedup_scale = 1.1 # 外星人点数的提高速度 1 self.score_scale = 1.5 self.initialize_dynamic_settings() def increase_speed(self): """提高速度设置和外星人点数""" self.ship_speed_factor *= self.speedup_scale self.bullet_speed_factor *= self.speedup_scale self.alien_speed_factor *= self.speedup_scale 2 self.alien_points = int(self.alien_points * self.score_scale)
我们定义了点数提高的速度,并称之为score_scale(见)。很小的节奏加快速度(1.1)让 游戏很快就变得极具挑战性,但为让记分发生显著的变化,需要将点数的提高速度设置为更大的 值(1.5)。现在,我们在加快游戏节奏的同时,提高了每个外星人的点数。为让点数为整数,我 们使用了函数int()。 为显示外星人的点数,我们在Settings的方法increase_speed()中添加了一条print语句:
settings.py
def increase_speed(self): --snip-- self.alien_points = int(self.alien_points * self.score_scale) print(self.alien_points)
现在每当提高一个等级时,你都会在终端窗口看到新的点数值.
注意
确认点数在不断增加后,一定要删除这条print语句,否则它可能会影响游戏的性能以及 分散玩家的注意力。
14.3.6 将得分圆整
大多数街机风格的射击游戏都将得分显示为10的整数倍,下面让我们的记分系统遵循这个原 则。我们还将设置得分的格式,在大数字中添加用逗号表示的千位分隔符。我们在Scoreboard中执行这种修改:
scoreboard.py
def prep_score(self): """将得分转换为渲染的图像""" 1 rounded_score = int(round(self.stats.score, -1)) 2 score_str = "{:,}".format(rounded_score) self.score_image = self.font.render(score_str, True, self.text_color, self.ai_settings.bg_color) --snip--
函数round()通常让小数精确到小数点后多少位,其中小数位数是由第二个实参指定的。然 而,如果将第二个实参指定为负数,round()将圆整到最近的10、100、1000等整数倍。1处的代 码让Python将stats.score的值圆整到最近的10的整数倍,并将结果存储到rounded_score中。
注意
在Python 2.7中,round()总是返回一个小数值,因此我们使用int()来确保报告的得分为 整数。如果你使用的是Python 3,可省略对int()的调用。
2处使用了一个字符串格式设置指令,它让Python将数值转换为字符串时在其中插入逗号, 例如,输出1,000,000而不是1000000。如果你现在运行这个游戏,看到的将是10的整数倍的整洁 得分,即便得分很高亦如此,如图14-3所示。
14.3.7 最高得分
每个玩家都想超过游戏的最高得分记录。下面来跟踪并显示最高得分,给玩家提供要超越的 目标。我们将最高得分存储在GameStats中:
game_stats.py
def __init__(self, ai_settings): --snip-- # 在任何情况下都不应重置最高得分 self.high_score = 0
鉴于在任何情况下都不会重置最高得分,我们在__init__()中而不是reset_stats()中初始化 high_score。 下面来修改Scoreboard以显示最高得分。先来修改方法__init__():
scoreboard.py
def __init__(self, ai_settings, screen, stats): --snip-- # 准备包含最高得分和当前得分的图像 self.prep_score() 1 self.prep_high_score()
最高得分将与当前得分分开显示,因此我们需要编写一个新方法prep_high_score(),用于准 备包含最高得分的图像(见)。 方法prep_high_score()的代码如下:
scoreboard.py
def prep_high_score(self): """将最高得分转换为渲染的图像""" 1 high_score = int(round(self.stats.high_score, -1)) 2 high_score_str = "{:,}".format(high_score) 3 self.high_score_image = self.font.render(high_score_str, True, self.text_color, self.ai_settings.bg_color) #将最高得分放在屏幕顶部中央 self.high_score_rect = self.high_score_image.get_rect() 4 self.high_score_rect.centerx = self.screen_rect.centerx 5 self.high_score_rect.top = self.score_rect.top