本章将结束《外星人入侵》这个游戏的开发。我们会添加一个play按钮,用于根据需要启动游戏以及重启游戏,还会修改这个预习,使其根据玩家等级提高难度,并实现一个计分系统。
14.1 添加play按钮
本节将添加一个play按钮,它在游戏开始前出现,并在游戏结束后再次出现,让玩家能够开始新游戏。
下面让游戏一开始处于非活动状态,并提示玩家单机play按钮来开始游戏。
def run_game():
pygame.init()
ai_settings = Text_settings()
stats = GameStats(ai_settings)
screen = pygame.display.set_mode(
(ai_settings.screen_width, ai_settings.screen_height))
pygame.display.set_caption("My Game")
ship = Ship(ai_settings, screen)
bullets = Group()
aliens = Group()
test_game_function.create_fleet(ai_settings, screen, ship, aliens)
#让游戏一开始处于非活动状态
stats.game_active = False
14.1.1 创建button类(胭惜雨:button原意是纽扣、扣子,互联网语境下表示按钮)
由于pygame没有内置创建按钮的方法,我们将编写一个button类,用于创建带标签的实心矩形。
import pygame.font
class Button:
def __init__(self, screen, msg):
self.screen = screen
self.screen_rect = self.screen.get_rect()
# 设置按钮的尺寸和其他属性
self.width, self.height = 200, 50
self.button_color = (255,153,0)
self.text_color = (0, 0, 0)
self.font = pygame.font.SysFont(None, 48)
# 创建按钮的rect对象,并使其居中
self.rect = pygame.Rect(0, 0, self.width, self.height)
self.rect.center = self.screen_rect.center
# 按钮的标签只需创建一次
self._prep_msg(msg)
首先导入模块pygame.font,它让pygame能够将文本渲染到屏幕上。方法__inint__()接受参数self、对象ai_game和msg,其中msg是要在按钮中显示的文本。设置按钮的尺寸,再通过设置button_color,让按钮的rect对象变为对应的颜色,并通过设置text_color让文本为黑色。
在font.SysFont处制定用什么字体来渲染文本。实参None让pygame使用默认字体,而48指定了文本的自豪。为让按钮在屏幕上居中,创建一个表示按钮的rect对象,并将其center属性设置为屏幕的center属性。
pygame处理文本的方式是,将要显示的字符串渲染为图像。self._prep_msg(msg)处来处理这样的渲染。
def _prep_msg(self, msg):
"""将msg渲染为图像,并使其在按钮上居中"""
self.msg_image = self.font.render(
msg, True, self.text_color, self.button_color)
self.msg_image_rect = self.msg_image.get_rect()
self.msg_image_rect.center = self.rect.center
方法_prep_msg()接受实参self以及要渲染为图像的文本(msg)。调用font.render()将存储在msg中的文本转换为图像,再将该图像存储在self.msg_image中。方法font.render()还接受一个布尔实参,该实参指定开启还是关闭反锯齿功能(反锯齿让文本的边缘更平滑)。余下两个实参分别是文本颜色和背景色。
最后,创建方法draw_button(),用于将这个按钮显示到屏幕上:
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,从而在屏幕上绘制文本图像。至此,Button便创建好了。
14.1.2 在屏幕上绘制按钮
首先,在主文本上更新import语句:
from test_button import Button
其次,在AlienInvasion类的方法__init__()中创建它。可将这些代码放到方法__init__()的末尾:
def run_game():
pygame.init()
ai_settings = Text_settings()
stats = GameStats(ai_settings)
screen = pygame.display.set_mode(
(ai_settings.screen_width, ai_settings.screen_height))
# 创建play按钮
play_button = Button(screen,"play game")
这些代码创建一个标签为play的Button实例,但没有将它显示到屏幕上。为显示该按钮,在_update_screen()中对齐调用方法draw_button():
def update_screen(ai_settings, screen, ship, bullets, aliens,stats,play_button):
"""更新屏幕上的图像,并切换到新屏幕"""
screen.fill(ai_settings.bg_color)
# 重绘所有子弹
for bullet in bullets.sprites():
bullet.draw_bullet()
aliens.draw(screen)
# 如果游戏处于非活动状态,就绘制play按钮
if not stats.game_active:
play_button.draw_button()
ship.blitme()
pygame.display.flip()
为让play按钮位于其他所有屏幕元素上面,在绘制其他所有游戏元素后再绘制这个按钮,然后切换到新屏幕。将这些代码放在一个if代码块中,让按钮仅在游戏处于非活动状态时才出现。
14.1.3 开始游戏
为在玩家单机Play按钮时开始新游戏,在_check_events()末尾添加如下elif代码块,以见识与该按钮相关的鼠标事件:
def check_events(ai_settings, screen, ship, bullets):
"""响应按键和鼠标事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown_events(event, ai_settings, screen, ship, bullets)
elif event.type == pygame.KEYUP:
check_keyup_events(event, ship)
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = pygame.mouse.get_pos()
check_play_button(mouse_pos)
无论玩家单机屏幕的什么地方,pygame都将检测到一个MOUSEBUTTONDOWN事件,但我们只想让这个游戏在玩家用鼠标单击play时做出响应。为此,使用了pygame.mouse.get_pos(),它返回一个元组,其中包含玩家单击时鼠标的x坐标和y坐标。我们将这些值传递给新方法check_play_button()。
方法check_play_button()的代码如下,将它放在check_events()后面,同时check_events()也要进行修改:
def check_events(stats,play_button,aliens,bullets,ai_settings,screen,ship):
"""响应按键和鼠标事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown_events(event, ai_settings, screen, ship, bullets)
elif event.type == pygame.KEYUP:
check_keyup_events(event, ship)
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = pygame.mouse.get_pos()
check_play_button(stats,play_button,aliens,bullets,ai_settings,screen,ship,mouse_pos)
def check_play_button(stats,play_button,aliens,bullets,ai_settings,screen,ship,mouse_pos):
"""玩家单击play时开始游戏"""
if play_button.rect.collidepoint(mouse_pos):
# 重置游戏统计信息
stats.reset_stats()
stats.game_active = True
# 清空余下的外星人和子弹
aliens.empty()
bullets.empty()
# 创建一群新的外星人并让飞船居中
create_fleet(ai_settings, screen, ship, aliens)
ship.center_ship()
这里使用了rect的方法collidepoint()检查鼠标单机位置是否在Play按钮的rect内。如果是,就将game_active设置为True,让游戏开始!
至此,现在应该能够开始这个游戏了。游戏结束时,应该game_active设置为False,并重新显示Play按钮。
14.1.5 将Play按钮切换到非活动状态
当前存在一个问题:即便play按钮不可见,玩家单击其所在的区域时,游戏依然会做出响应。游戏开始后,如果玩家不小心单击了Play按钮所在的区域,游戏将重新开始。
为修复这个问题,可让游戏仅在game_active为False时才开始:
def check_play_button(
stats,
play_button,
aliens,
bullets,
ai_settings,
screen,
ship,
mouse_pos):
"""玩家单击play时开始游戏"""
button_clicked = play_button.rect.collidepoint(mouse_pos)
if button_clicked and not stats.game_active:
# 重置游戏统计信息
stats.reset_stats()
stats.game_active = True
# 清空余下的外星人和子弹
aliens.empty()
bullets.empty()
# 创建一群新的外星人并让飞船居中
create_fleet(ai_settings, screen, ship, aliens)
ship.center_ship()
标志button_clicked的值为True或False。仅当玩家单机了play按钮且游戏当前处于非活动状态时,游戏才重新开始。
14.1.6 隐藏鼠标光标
为让玩家开始游戏,要让鼠标光标可见,但是游戏开始后,光标只会添乱。为修复这种问题,需要在游戏处于活动状态时使其不可见。可在方法check_play_button()末尾的if代码块中完成这项任务:
if button_clicked and not stats.game_active:
# 重置游戏统计信息
stats.reset_stats()
stats.game_active = True
# 清空余下的外星人和子弹
aliens.empty()
bullets.empty()
# 创建一群新的外星人并让飞船居中
create_fleet(ai_settings, screen, ship, aliens)
ship.center_ship()
#隐藏光标
pygame.mouse.set_visible(False)
通过想set_visible()传递False,让pygame在光标位于游戏窗口内将其隐藏起来。
游戏结束后,将重新显示光标,让玩家能够单击Play按钮来开始新游戏。相关的代码如下:
def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):
"""响应被外星人撞到的飞船"""
if stats.ships_left > 0:
# 将ship_left 减1
stats.ships_left -= 1
# 清空外星人列表和子弹列表
aliens.empty()
bullets.empty()
# 创建一群新的外星人,并将飞船放到屏幕低端中央
create_fleet(ai_settings, screen, ship, aliens)
ship.center_ship()
# 暂停
sleep(0.5)
else:
print("给你机会你不中用啊!")
stats.game_active = False
pygame.mouse.set_visible(True)
在ship_hit()中,在游戏进入非活动状态后,立即让光标可见。关注这样的细节让游戏显得更专业,也让玩家能够专注于玩游戏而不是费力理解用户界面。
14.2 提高等级
将整群外星人小妹干净后,玩家将提高一个等级,但游戏的难度没变。下面来增加一点趣味性,每当玩家将屏幕上的外星人消灭干净后,都将加快游戏的节奏。
14.2.1 修改速度设置
import pygame
from pygame.sprite import Group
class Text_settings():
"""所有设置"""
def __init__(self):
"""初始化游戏的静态设置"""
self.bg_color = (230, 230, 230)
self.screen_width = 1200
self.screen_height = 800
self.screen = pygame.display.set_mode((self.screen_width, self.screen_height))
self.ship_speed_factor = 1
self.bullet_speed_factor = 2
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = (60, 60, 60)
self.bullet_allowed = 100
self.alien_speed = float(0.5)
self.fleet_drop_speed = 50
#fleet_direction 为1 表示向右移,为-1表示向左移
self.fleet_direction = 1
self.ship_limit = 3
# 加快游戏节奏的速度
self.speedup_scale = 1.1
self.initialize_dynamic_settings()
def initialize_dynamic_settings(self):
"""初始化随游戏进行变化的设置"""
self.ship_speed_factor = 1.5
self.bullet_speed_factor = 3.0
self.alien_speed = 1.0
#fleet_direction 为1 表示向右移,为-1表示向左移
self.fleet_direction = 1
依然在__init__()中初始化静态设置,添加设置speedup_scale,用于控制游戏节奏的加快速度:2表示玩家每提高一个等级,游戏的节奏就翻一倍;1表示游戏节奏始终不变。将其设置为1.1能够将游戏节奏提高到足够快,让游戏既有难度又并非不可完成。最后调用initialize_dynamic_settings()初始化随游戏进行而变化的属性。
这个方法设置飞船、子弹和外星人的初始速度。随着游戏的进行,将提高这些速度。每当玩家开始新游戏时,都将重置这些速度。在这个方法中,还设置了fleet_speed的值,因为外星人移动的速度越快,到达屏幕底端所需的时间越短。
为在玩家等级提高时提高飞船、子弹和外星人的速度,编写一个名叫increase_speed()的新方法:
def increase_speed(self):
"""提高速度设置"""
self.ship_speed_factor *= self.speedup_scale
self.bullet_speed_factor *= self.speedup_scale
self.alien_speed *= self.speedup_scale
为提高这些游戏元素的速度,将每个速度设置都乘以speedup_scale的值。
在check_bullet_alien_collisions()中,在整群外星人都被消灭后调用increase_speed()来加快游戏的节奏:
def check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets):
"""响应子弹和外星人碰撞"""
# 检查是否有子弹击中了外星人
# 删除发生碰撞的子弹和外星人
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if len(aliens) == 0:
# 删除现有的子弹并新建一群外星人
bullets.empty()
create_fleet(ai_settings, screen, ship, aliens)
ai_settings.increase_speed()
通过修改速度设置ship_speed、alien_speed和bullet_speed的值,足以加快整个游戏的节奏!
14.2.2 重置速度
每当玩家开始新游戏时,都需要将发生了变化的设置重置为初始值,否则新游戏开始时,速度设置将为前一次提高后的值:
def check_play_button(
stats,
play_button,
aliens,
bullets,
ai_settings,
screen,
ship,
mouse_pos):
"""玩家单击play时开始游戏"""
button_clicked = play_button.rect.collidepoint(mouse_pos)
if button_clicked and not stats.game_active:
# 重置游戏统计信息
ai_settings.initialize_dynamic_settings()
stats.reset_stats()
stats.game_active = True
14.3 记分
下面来实现一个记分系统,以实时跟踪玩家的得分,并显示最高得分、等级和余下的飞船数。
得分是游戏的一向统计信息,因此在GameStats中添加一个score属性:
def reset_stats(self):
"""初始化在游戏运行期间可能变化的统计信息"""
self.ships_left = self.ai_settings.ship_limit
self.score = 0
为在每次开始游戏时都重置得分,我们在reset_stats()而不是__init__()中初始化score。
14.3.1 显示得分
为在屏幕上显示得分,首先创建一个新类Scoreboard。当前,这个类只显示当前的分,但后面也将使用它来显示最高得分、等级和余下的飞船数。下面是这个类的前半部分。
import pygame.font
class Scoreboard:
"""显示得分信息的类"""
def __init__(self, screen, ai_settings, stats):
"""初始化显示得分涉及的属性"""
self.screen = screen
self.screen_rect = self.screen.get_rect()
self.settings = ai_settings
self.stats = stats
# 显示得分信息是使用的字体设置
self.text_color = (30, 30, 30)
self.font = pygame.font.SysFont(None, 48)
# 准备初始化得分图像
self.prep_score()
由于Scoreboard在屏幕上显示文本,首先要导入模块pygame.font。接下来,在__init__()中包含形参screen,ai_settings,stats。然后,设置文本颜色,并实例化一个字体对象。
为将要显示的文本转换为图像,调用prep_score(),其定义如下:
def prep_score(self):
"""将得分转换为一副渲染的图像"""
score_str = str(self.stats.score)
self.score_image = self.font.render(score_str,True,self.text_color,self.settings.bg_color)
# 在屏幕右上角显示得分
self.score_rect = self.score_image.get_rect()
self.score_rect.right = self.screen_rect.right - 20
self.score_rect.top = 20
在prep_score()中,将数值stats.score转换为字符串,再将这个字符串传递给创建图像的render()。为在屏幕上清晰地显示得分,向render传递屏幕背景色和文本颜色。
将得分放在屏幕右上角,并在得分增大导致数变宽时让其向左延伸。为确保得分始终锚定在屏幕右边,创建一个名叫score_rect的rect,让其右边缘与屏幕右边缘相距20像素,并让其上边缘与屏幕上边缘也相距20像素。
接下来,创建方法show_score(),用于显示渲染好的得分图像。
def show_score(self):
"""在屏幕上显示得分"""
self.screen.blit(self.score_image,self.score_rect)
14.3.2 创建记分牌
为显示得分,在AlienInvasion中创建一个Scoreboard实例。先来更新import语句:
from test_scoreboard import Scoreboard
接下来,在方法__init__()中创建一个Scoreboard实例:
sb = Scoreboard(screen,ai_settings,stats)def run_game():
pygame.init()
ai_settings = Text_settings()
# 创建存储游戏统计信息的实例,并创建记分牌
stats = GameStats(ai_settings)
sb = Scoreboard(screen,ai_settings,stats)
然后,在update_screen中奖记分牌绘制到屏幕上:
def update_screen(
ai_settings,
screen,
ship,
bullets,
aliens,
stats,
play_button,sb):
"""更新屏幕上的图像,并切换到新屏幕"""
screen.fill(ai_settings.bg_color)
# 重绘所有子弹
for bullet in bullets.sprites():
bullet.draw_bullet()
aliens.draw(screen)
# 显示得分
sb.show_score()
14.3.3 在外星人被消灭时更新得分
为在屏幕上实时显示得分,每当有外星人被击中时,都将更新stats.score的值,在调用prep_score()更新得分图像。但在此之前,需要制定玩家每击落一个外星人将得到多少分:
def initialize_dynamic_settings(self):
"""初始化随游戏进行变化的设置"""
self.ship_speed_factor = 1.5
self.bullet_speed_factor = 3.0
self.alien_speed = 1.0
#fleet_direction 为1 表示向右移,为-1表示向左移
self.fleet_direction = 1
# 记分
self.alien_points = 50
随着游戏的进行,将提高每个外星人的分数。为确保每次开始新游戏时,这个值都会被重置,我们将在initialize_dynamic_settings()中设置它。
在check_bullet_alien_collisions()中,每当有外星人被击落时,都更新得分:
def update_bullet(ai_settings, screen, ship, aliens, bullets,stats):
# 检查子弹位置,并删除多余子弹
for bullet in bullets.copy():
if bullet.rect.bottom <= 20:
bullets.remove(bullet)
check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets,stats)
def check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets,stats):
"""响应子弹和外星人碰撞"""
# 检查是否有子弹击中了外星人
# 删除发生碰撞的子弹和外星人
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if collisions:
stats.score += ai_settings.alien_points
if len(aliens) == 0:
# 删除现有的子弹并新建一群外星人
bullets.empty()
create_fleet(ai_settings, screen, ship, aliens)
ai_settings.increase_speed()
有子弹击中外星人时,Python将返回一个字典(collisions)。我们检查这个字典是否存在,如果存在,就将得分加上一个外星人的分数。接下来,调用prep_score()来创建一副包含最新得分的新图像。
14.3.4 重置得分
当前,仅在有外星人被射杀之后生成得分。这在大多数情况下可行,但从开始新游戏到有外星人被射杀之间,显示的是上一次的得分。
为修复这个问题,可在开始新游戏时生成得分:
def check_play_button(
stats,
play_button,
aliens,
bullets,
ai_settings,
screen,
ship,
mouse_pos,sb):
"""玩家单击play时开始游戏"""
button_clicked = play_button.rect.collidepoint(mouse_pos)
if button_clicked and not stats.game_active:
# 重置游戏统计信息
ai_settings.initialize_dynamic_settings()
stats.reset_stats()
stats.game_active = True
sb.prep_score()
开始新游戏时,我们重置游戏统计信息再调用prep_score()。此时生成的记分牌上显示的得分为0.
14.3.5 将消灭的每个外星人都计入得分
在check_bullet_alien_collisions()中,与外星人碰撞的子弹都是字典collisions中的一个键,而与每颗子弹相关的值都是一个列表,其中包含该子弹集中的的外星人。我们遍历字典collisions,确保将消灭的每个外星人都计入得分:
def check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets,stats,sb):
"""响应子弹和外星人碰撞"""
# 检查是否有子弹击中了外星人
# 删除发生碰撞的子弹和外星人
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if collisions:
for aliens in collisions.values():
stats.score += ai_settings.alien_points * len(aliens)
sb.prep_score
如果字典collisions存在,就遍历所有值。别忘了,每个值都是一个列表,包含被同一颗子弹击中的所有外星人。
14.3.6 提高分数
鉴于玩家每提高一个等级,游戏都变得更难,因此处于较高的等级时,外星人的分数应更高。为实现这种功能,需要编写在游戏节奏加快时提高分数的代码:
# 加快游戏节奏的速度
self.speedup_scale = 1.1
# 外星人分数的提高速度
self.score_scale = 1.5
self.initialize_dynamic_settings()
def initialize_dynamic_settings(self):
"""初始化随游戏进行变化的设置"""
self.ship_speed_factor = 1.5
self.bullet_speed_factor = 3.0
self.alien_speed = 1.0
#fleet_direction 为1 表示向右移,为-1表示向左移
self.fleet_direction = 1
# 记分
self.alien_points = 50
def increase_speed(self):
"""提高速度设置和外星人分数"""
self.ship_speed_factor *= self.speedup_scale
self.bullet_speed_factor *= self.speedup_scale
self.alien_speed *= self.speedup_scale
alien_points = int(self.alien_points * self.score_scale)
我们定义了分数的提高速度,并称之为score_scale。较低的节奏加快速度让游戏很快变得极具挑战性,但未了让记分发生显著的变化,需要将分数的提高速度设置为更大的值。为让分数为整数,使用了函数int()。
为显示外星人的分数,在Settings的方法increase_speed()中调用函数print():
def increase_speed(self):
"""提高速度设置和外星人分数"""
self.ship_speed_factor *= self.speedup_scale
self.bullet_speed_factor *= self.speedup_scale
self.alien_speed *= self.speedup_scale
alien_points = int(self.alien_points * self.score_scale)
print(self.alien_points)
现在每当提高一个等级时,你都将在终端窗口看到新的分数值。
14.3.7 舍入得分
大多数阶级风格的设计游戏将得分显示为10的整数倍,下面让记分系统遵循这个原则。我们还将设置得分的格式,在大多数中用逗号标识千位分隔符。在Scoreboard中执行这种修改:
def prep_score(self):
"""将得分转换为一副渲染的图像"""
rounded_score = round(self.stats.score, -1)
score_str = "{:,}",format(rounded_score)
self.score_image = self.font.render(
score_str, True, self.text_color, self.settings.bg_color)
函数round()通常让小数精确到小数点后某一位。其中小数位数是由第二个实参指定的,然而如果将第二个实参指定为复数,round()将舍入到最近的10的整数倍,如10、100、1000等。
在score_str处,使用一个字符串格式设置指令,让Python将数值转换为字符串时在其中插入逗号。
14.3.8 最高得分
我们将最高得分存储在GameStats中:
class GameStats():
"""跟踪游戏的统计信息"""
def __init__(self, ai_settings):
self.ai_settings = ai_settings
self.reset_stats()
self.game_active = True
# 任何情况下都不应重置最高得分
self.high_score = 0
因为在任何情况下都不会重置最高得分,所以__init__()而不是reset_stats()中初始化 high_score。
下面来修改Scoreboard以显示最高得分。先来修改方法__init__():
def __init__(self, screen,ai_settings,stats):
"""初始化显示得分涉及的属性"""
self.screen = screen
self.screen_rect = screen.get_rect()
self.settings = ai_settings
self.stats = stats
# 显示得分信息是使用的字体设置
self.text_color = (30, 30, 30)
self.font = pygame.font.SysFont(None, 48)
# 准备包含最高得分和当前得分的图像
self.prep_score()
self.prep_high_score()
最高得分疆域当前得分分开显示,因此需要编写一个新方法prep_high_score(),用于准备包含最高得分的图像。
方法prep_high_score()的代码如下:
def prep_high_score(self):
"""将最高得分转换为渲染的图像"""
high_score = round(self.stats.high_score,-1)
high_score_str = "{;,}".format(high_score)
self.high_score_image = self.font.render(high_score_str,True,self.text_color,self.settings.bg_color)
# 将最高得分放在皮屏幕顶部中央
self.high_score_rect = self.high_score_image.get_rect()
self.high_score_centerx = self.screen_rect.centerx
self.high_score_rect.top = self.score_rect.top
将最高得分舍入到最近的10的整数倍,并添加用逗号标识的千分位分隔符。然后,根据最高得分生成一幅图像,并使其水平居中,并将其top属性设置为当前得分图像的top属性。
现在,方法show_score()需要在屏幕右上角显示当前得分,并在屏幕顶部中央显示最高得分:
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():
def check_high_score(self):
"""检查是否诞生了新的最高得分"""
if self.stats.score > self.stats.high_score:
self.stats.high_score = self.stats.score
self.prep_score()
方法check_high_score比较当前分和最高分,如果当前分比记录的最高分高,就替换历史最高分。
在check_bullet_alien_collisions()中,每当有外星人被消灭时,都需要在更新得分后调用check_hiht_score():
def check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets,stats,sb):
"""响应子弹和外星人碰撞"""
# 检查是否有子弹击中了外星人
# 删除发生碰撞的子弹和外星人
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if collisions:
for aliens in collisions.values():
stats.score += ai_settings.alien_points * len(aliens)
sb.prep_score()
sb.check_high_score()
如果字典collisons存在,就根据消灭了多少外星人更新的分,在调用check_high_score()。
14.3.9 显示等级
为在游戏中显示玩家的等级,需要在GameStats中添加一个表示当前等级的属性。为确保每次开始新游戏时都重置等级,在reset_stats()中初始化它:
def reset_stats(self):
"""初始化在游戏运行期间可能变化的统计信息"""
self.ships_left = self.ai_settings.ship_limit
self.score = 0
self.level = 1
为了让Scoreboard显示当前等级,在__init__()调用一个新方法prep_level():
def prep_level(self):
"""将等级转换为渲染的图像"""
level_str = str(self.stats.level)
self.level_image = self.font.render(
level_str, True, self.text_color, self.settings.bg_color)
# 将等级放到得分下方
self.level_rect = self.level_image.get_rect()
self.level_rect.right = self.score_rect.right
self.level_rect.top = self.score_rect.bottom + 10
方法prep_level()根据存储在stats.level中的值创建一幅图像,并将其right属性设置为得分的right属性。然后,将top属性设置为比得分图像的bottom属性大10像素,以便在得分和等级之间留出一定的空间。
还需要更新show_score():
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()中提高等级并更新等级图像:
def check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets,stats,sb):
"""响应子弹和外星人碰撞"""
# 检查是否有子弹击中了外星人
# 删除发生碰撞的子弹和外星人
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if collisions:
for aliens in collisions.values():
stats.score += ai_settings.alien_points * len(aliens)
sb.prep_score()
sb.check_high_score()
if len(aliens) == 0:
# 删除现有的子弹并新建一群外星人
bullets.empty()
create_fleet(ai_settings, screen, ship, aliens)
ai_settings.increase_speed()
# 提高等级
stats.level += 1
sb.prep_level()
如果整群外星人都被消灭,就将stats.level的值加1,并调用prep_level()确保正确地显示新等级。
为确保在开始新游戏时更新等级图像,还需在玩家单机按钮play时调用prep_level()。
if button_clicked and not stats.game_active:
# 重置游戏统计信息
ai_settings.initialize_dynamic_settings()
stats.reset_stats()
stats.game_active = True
sb.prep_score()
sb.prep_level()
14.3.10 显示余下的飞船数
最后来显示玩家还有多少艘飞船,但是用图形而不是数字。为此,在屏幕左上角绘制飞船图像来支出还余下多少艘飞船,就像众多经典游戏那样。
首先,需要让Ship继承Sprite,以便创建飞船编组:
import pygame
from pygame.sprite import Sprite
class Ship(Sprite):
def __init__(self, ai_settings, screen):
super().__init__()
这里导入了Sprite,让Ship继承Sprite,并在__init__()的开头调用super()。
接下来,需要修改Scoreboard,以创建可供显示的飞船编组。下面是其中的import语句:
import pygame.font
from pygame.sprite import Group
from test_ship import Ship
鉴于需要创建飞船编组,导入Group和Ship类。
下面是方法__init__():
class Scoreboard:
"""显示得分信息的类"""
def __init__(self, screen,ai_settings,stats):
"""初始化显示得分涉及的属性"""
self.screen = screen
self.screen_rect = screen.get_rect()
self.ai_settings = ai_settings
self.stats = stats
# 显示得分信息是使用的字体设置
self.text_color = (30, 30, 30)
self.font = pygame.font.SysFont(None, 48)
# 准备包含最高得分和当前得分的图像
self.prep_score()
self.prep_high_score()
self.prep_level()
self.prep_ships()
我们将游戏实例赋给一个属性,因为创建飞船时需要用到它。在调用prep_level()后调用了prep_ship().
prep_ship()的代码如下:
def prep_ships(self):
"""显示还余下多少艘飞船"""
self.ships = Group
for ship_number in range(self.stats.ship_left):
ship = Ship(ai_settings,screen)
ship.rect.x = 10 + ship_number * ship.rect.width
ship.rect.y = 10
self.ships.add(ship)
方法prep_ships创建了一个空编组self.ships,用于存储飞船实例。为填充这个编组,根据玩家还有多少艘飞船以响应的次数运行一个循环。在这个循环中,创建新飞船并设置其X坐标,让整个飞船编组都位于屏幕左边,且每艘飞船的左边距都为10像素。还将y坐标设置为离屏幕上边缘10像素,让所有飞船都出现在屏幕左上角。最后,将每艘新飞船都添加到编组ships中。
现在需要在屏幕上绘制飞船了:
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()。这是在check_play_button()中进行的:
if button_clicked and not stats.game_active:
# 重置游戏统计信息
ai_settings.initialize_dynamic_settings()
stats.reset_stats()
stats.game_active = True
sb.prep_score()
sb.prep_level()
sb.prep_ships()
还要在飞船被外星人撞到时调用prep_ships(),从而在玩家损失飞船时更新飞船图像:
def ship_hit(ai_settings, stats, screen, ship, aliens, bullets,sb):
"""响应被外星人撞到的飞船"""
if stats.ships_left > 0:
# 将ship_left 减1 并更新记分牌
stats.ships_left -= 1
sb.prep_ships()
这里在将ships_left的值减1后调用prep_ships()。这样每次损失飞船后,显示的飞船数都是正确的。
历经了不知道多少天。总算是把第一个项目:外星人入侵搞定了,纸上得来终觉浅,绝知此事要躬行。别说是上手了,自己跟着练一遍都还觉得自己跟弱智的区别在于他不会觉得自己笨。
不容易啊。
下一篇内容我会把外星人入侵所有代码贴上来,不敢说都对,但起码能运行。
下一个项目再见~
胭惜雨
2021年02月26日