目标:添加一个Play按钮,让玩家能够开始游戏,以及游戏结束后再玩。每当玩家消灭一群外星人后,加快游戏的节奏,并添加一个记分系统,让游戏更有挑战性和趣味性。
Play按钮:它在游戏开始前出现,并在游戏结束后再次出现,让玩家能够开始新游戏。
让游戏一开始处于非活动状态,并提示玩家单击Play按钮来开始游戏。在game_stats.py中输入如下代码:
class GameStats():
# 跟踪游戏的统计信息
def __init__(self, ai_settings):
# 初始化统计信息
self.ai_settings = ai_settings
self.reset_status()
# 游戏刚启动时处于非活动状态
self.game_active = False
def reset_status(self):
# skip
现在游戏一开始将处于非活动状态,等我们创建Play按钮后,玩家才能开始游戏。
【1】创建Button类
由于Pygame没有内置创建按钮的方法,创建一个Button 类,用于创建带标签的实心矩形。下面是Button 类的第一部分, 请将这个类保存为文件button.py:
import pygame.font # 将文本渲染到屏幕上
class Button():
def __init__(self, ai_settings, screen, msg):
# 初始化按钮的属性
self.screen =screen
self.screen_rect = screen.get_rect()
# 设置按钮的尺寸和其他属性
self.width, self.height = 200, 50
self.button_color = (67, 205, 128)
self.text_color = (255, 255, 255)
self.font = pygame.font.SysFont(None, 48) # 实参None 让Pygame 使用默认字体,48指定文本的字号。
# 创建按钮的rect对象,并使其居中
self.rect = pygame.Rect(0, 0, self.width, self.height)
self.rect.center = self.screen_rect.center
# 按钮的标签只需创建一次
self.prep_msg(msg)
Pygame通过将你要显示的字符串渲染为图像来处理文本。在❺处,我们调用prep_msg() 来处理这样的渲染。 prep_msg() 的代码如下:
button.py
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 中的文本转换为图像,然后将该图像存储在msg_image 中。方法font.render() 还接受一个布尔实参,该实参指定开启还是关闭反锯齿功能(反锯齿让文本的边缘更平滑)。余下的两个实参分别是文本颜色和背景色。我们启用 了反锯齿功能,并将文本的背景色设置为按钮的颜色(如果没有指定背景色,Pygame将以透明背景的方式渲染文本)。
创建方法draw_button() ,通过调用它可将这个按钮显示到屏幕上:
button.py
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 对象,从而在屏幕上绘制文本图像。
【2】在屏幕上绘制按钮
alien_invasion.py
#skip
from button import Button
def run_game():
# skip
pygame.display.set_caption("Alien Invasion")
# 创建Play按钮
play_button = Button(ai_settings,screen,"Play")
# skip
# 开始游戏的主循环
while True:
# skip
gf.update_screen(ai_settings, screen,stats, ship, aliens, bullets, play_button)
run_game()
修改update_screen() ,以便在游戏处于非活动状态时显示Play按钮:
game_function.py
def update_screen(ai_settings, screen,stats, ship, aliens, bullets, play_button):
# skip
# 如果游戏处于非活动状态,绘制Play按钮
if not stats.game_active:
play_button.draw_button()
# 不断更新屏幕,让最近绘制的屏幕可见
pygame.display.flip()
运行游戏:
【3】开始游戏
需在game_functions.py中添加如下代码,以监视与这个按钮相关的鼠标事件:
def check_events(ai_settings, screen, stats, play_button, ship, bullets):
# 响应按键和鼠标事件
# skip
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_x, mouse_y = pygame.mouse.get_pos()
check_play_button(stats, play_button, mouse_x,mouse_y)
def check_play_button(stats, play_button, mouse_x, mouse_y):
# 在玩家单击Play按钮时开始游戏
if play_button.rect.collidepoint(mouse_x,mouse_y):
stats.game_active = True
# # 向右移动飞船
# ship.rect.centerx += 1
了pygame.mouse.get_pos() ,它返回一个元组,其中包含玩家单击时鼠标的x 和y 坐标。将这些值传递给函数check_play_button() ,而函 数使用collidepoint() 检查鼠标单击位置是否在Play按钮的rect 内。如果是这样的,将game_active 设置为True ,让游戏就此开始!
在alien_invasion.py更新check_events()调用:
# 开始游戏的主循环
while True:
# 监视键盘和鼠标
gf.check_events(ai_settings, screen, stats, play_button, ship, bullets)
现在运行程序,点击按钮Play开始游戏,游戏结束时,game_active 应为False ,并重新显示Play按钮。
【4】重置代码
为在玩家每次单击Play按钮时都重置游戏,需要重置统计信息、删除现有的外星人和子弹、创建一群新的外星人,并让飞船居中,如下所示:
game_function.py
def check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y):
# 在玩家单击Play按钮时开始游戏
if play_button.rect.collidepoint(mouse_x,mouse_y):
# 重置游戏统计信息
stats.reset_status()
stats.game_active = True
# 清空外星人列表和子弹列表
aliens.empty()
bullets.empty()
# 创建一群新外星人,并让飞船居中
create_fleet(ai_settings, screen, ship, aliens)
ship.center_ship()
更新check_events() 方法,及调用check_play_button() 的代码。
现在玩家单击Play按钮时,这个游戏都将正确地重置,玩家想玩多少次就玩多少次!
【5】将Play按钮切换到非活动状态
当前,Play按钮存在一个问题:即便Play按钮不可见,玩家单击其原来所在的区域时,游戏依然会作出响应。游戏开始后,如果玩家不小心单击了Play按钮原来所处的区域,游戏将重新开始!
为修复这个问题,可让游戏仅在game_active 为False 时才开始:
def check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y):
# 在玩家单击Play按钮时开始游戏
button_clicked = play_button.rect.collidepoint(mouse_x,mouse_y)
if button_clicked and not stats.game_active:
# 重置游戏统计信息
# skip
【6】隐藏光标
玩家能够开始游戏,让光标可见,游戏开始后,让光标不可见:
game_function.py
def check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y):
# 在玩家单击Play按钮时开始游戏
button_clicked = play_button.rect.collidepoint(mouse_x,mouse_y)
if button_clicked and not stats.game_active:
# 隐藏光标
pygame.mouse.set_visible(False)
# SKIP
游戏结束后,重新显示光标,让玩家能够单击Play按钮来开始新游戏:
def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):
# 响应被外星人撞到的飞船
if stats.ship_left > 0:
# SKIP
else:
stats.game_active = False
pygame.mouse.set_visible(True)
增加一点趣味性:每当玩家将屏幕上的外星人都消灭干净后,加快游戏的节奏,让游戏玩起来更难。
【1】修改速度设置
先重新组织Settings 类,将游戏设置划分成静态的和动态的两组。对于随着游戏进行而变化的设置,确保它们在开始新游戏时被重置。
settings.py
class Setting():
# 存储该项目所有设置的类
def __init__(self):
# 初始化游戏的静态设置
# 屏幕设置
self.screen_width = 1100
self.screen_height = 620
self.bg_color = (65,65,65)
#飞船的设置
self.ship_limit = 3
# 子弹设置
self.bullet_width = 3 # 些设置创建宽3像素、高15像素的深灰色子弹
self.bullet_height = 15
self.bullet_color = 178,34,34
self.bullet_allowed = 3 # 将未消失的子弹数限制为3颗
#外星人设置
self.fleet_drop_speed = 10 # 外星人群向下移动的速度
# 以什么样的速度加快游戏节奏
self.speedup_scale = 1.1
self.initialize_dynamic_settings()
def initialize_dynamic_settings(self):
# 初始化随游戏进行而变化的设置
self.ship_speed_factor = 1.5 # 移动1.5px
self.bullet_speed_factor = 3
self.alien_speed_factor = 1 # 外星人移动速度设置
# fleet_direction为1表示右移,-1表示为左移
self.fleet_direction = 1
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
在check_bullet_alien_collisions() 中,在整群外星人都被消灭后调用increase_speed() 来加快游戏的节奏,再创建一群新的外星人:
game_function.py
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()
ai_settings.increase_speed()
create_fleet(ai_settings, screen, ship, aliens)
【2】重置速度
每当玩家开始新游戏时,需要将发生了变化的设置重置为初始值,否则新游戏开始时,速度设置将是前一次游戏增加了的值:
def check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y):
# 在玩家单击Play按钮时开始游戏
button_clicked = play_button.rect.collidepoint(mouse_x,mouse_y)
if button_clicked and not stats.game_active:
# 重置游戏设置
ai_settings.initialize_dynamic_settings()
# skip
现在,玩家将屏幕上的外星人消灭干净后,游戏都将加快节奏,因此难度会更大些。如果游戏的难度提高得太快,可降低settings.speedup_scale 的值;如果游戏的挑战性不足,可稍微提高这个设置的值。
实现一个记分系统,以实时地跟踪玩家的得分,并显示最高得分、当前等级和余下的飞船数。
得分是游戏的一项统计信息,们在GameStats 中添加一个score 属性:
class GameStats():
# 跟踪游戏的统计信息
# skip
def reset_status(self):
# 初始化在游戏运行期间可能变化的统计信息
self.ship_left = self.ai_settings.ship_limit
self.score = 0
【1】显示得分
为在屏幕上显示得分,先创建一个新类Scoreboard 。就当前而言,这个类只显示当前得分,后面我们也将使用它来显示最高得分、等级和余下的飞船数。
scoreboard.py:
import pygame.font
class Scoreboard():
# 显示得分信息的类
def __init__(self, ai_settings, screen, stats):
# 初试化显示得分涉及的属性
self.screen = screen
self.screen_rect = screen.get_rect()
self.ai_settings = ai_settings
self.stats = stats
# 显示得分信息时显示的字体设置
self.text_color = (255, 255, 255)
self.font = pygame.font.SysFont(None, 40)
# 准备初始得分图像
self.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.ai_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
def show_score(self):
# 在屏幕上显示得分
self.screen.blit(self.score_image, self.score_rect)
【2】创建记分牌
为显示得分,在alien_invasion.py中创建一个Scoreboard 实例:
# skip
from scoreboard import Scoreboard
def run_game():
# skip
# 创建存储游戏统计信息的实例,并创建记分牌
sb = Scoreboard(ai_settings,screen,stats)
# 开始游戏的主循环
while True:
# skip
gf.update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button)
更新方法update_scree():
def update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button):
# 更新屏幕上的图像,并切换到新屏幕
# skip
# 显示得分
sb.show_score()
# 如果游戏处于非活动状态,绘制Play按钮
运行游戏,可在屏幕右上角看到0:
【3】在外星人被消灭时更新得分
指定玩家每击落一个外星人都将得到多少个点:
def initialize_dynamic_settings(self):
# skip
# 记分
self.alien_points = 50
在check_bullet_alien_collisions() 中,每当有外星人被击落时,都更新得分:
def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets):
# 响应子弹和外星人的碰撞
# 删除发生碰撞的子弹和外星人
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if collisions:
stats.score += ai_settings.alien_points
sb.prep_score() # 创建一幅显示最新得分的新图像
# skip
更新改update_bullets() 形参,以及改主while 循环中调用update_bullets() 的代码。
运行游戏,得分不断增加:
【4】将消灭的每个外星人的点数都计入得分
代码可能遗漏了一些被消灭的外星人。例如,如果在一次循环中有两颗子弹射中了外星人,或者因子弹更宽而同时击中了多个外星人,玩家将只能得到一个被消灭 的外星人的点数。
为修复这种问题,调整检测子弹和外星人碰撞的方式。 在check_bullet_alien_collisions() 中,与外星人碰撞的子弹都是字典collisions 中的一个键;而与每颗子弹相关的值都是一个列表,其中包含该子弹撞到的外星 人。遍历字典collisions ,确保将消灭的每个外星人的点数都记入得分:
def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets):
# 响应子弹和外星人的碰撞
# 删除发生碰撞的子弹和外星人
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() # 创建一幅显示最新得分的新图像
可放大子弹尺寸测试结果。
【5】提高点数
玩家每提高一个等级,游戏都变得更难,因此处于较高的等级时,外星人的点数应更高。为实现这种功能,添加一些代码,以在游戏节奏加快时提高点数:
setting.py
class Setting():
# 存储该项目所有设置的类
def __init__(self):
# skip
# 以什么样的速度加快游戏节奏
self.speedup_scale = 1.1
# 外星人点数的提高速度
self.score_scale = 1.5
self.initialize_dynamic_settings()
#skip
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
self.alien_points = int(self.alien_points * self.score_scale)
在测试效果时可在Settings 的方法increase_speed() 中添加了一条print 语句:
def increase_speed(self):
# skip
self.alien_points = int(self.alien_points * self.score_scale)
print(self.alien_points)
列如在得分如下时:
测试完成后删除该print语句。
【6】将得分圆整
大多数街机风格的射击游戏都将得分显示为10的整数倍,下面让记分系统遵循这个原则。将设置得分的格式,在大数字中添加用逗号表示的千位分隔符。在Scoreboard 中执行这种修改:
def prep_score(self):
# 将得分转化为一幅渲染的图像
rounded_score = int(round(self.stats.score, -1))
score_str = "{:,}".format(rounded_score)
self.score_image = self.font.render(score_str, True, self.text_color, self.ai_settings.bg_color)
函数round() 通常让小数精确到小数点后多少位,其中小数位数是由第二个实参指定的。将第二个实参指定为负数,round() 将圆整到最近的10、100、1000等整数倍。这里代码让Python将stats.score 的值圆整到最近的10的整数倍,并将结果存储到rounded_score 中。(在Python 2.7中,round() 总是返回一个小数值,因此使用int() 来确保报告的得分为整数。如果使用的是Python 3,可省略对int() 的调用。)
score_str使用了一个字符串格式设置指令,它让Python将数值转换为字符串时在其中插入逗号,例如,输出1,000,000 而不是1000000 。
得分为10的整数倍,并将逗号用作千分位分隔符:
【7】最高得分
每个玩家都想超过游戏的最高得分记录。下面跟踪并显示最高得分,给玩家提供要超越的目标。将最高得分存储在GameStats 中:
class GameStats():
# 跟踪游戏的统计信息
def __init__(self, ai_settings):
# skip
# 任何情况下都不应该重置最高分
self.high_score = 0
来修改Scoreboard 以显示最高得分:
class Scoreboard():
# 显示得分信息的类
def __init__(self, ai_settings, screen, stats):
# skip
# 准备包含最高得分和当前得分的图像
self.prep_score()
self.prep_high_score()
#skip
def prep_high_score(self):
# 将最高得分转换为渲染图像
high_score = int(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.ai_settings.bg_color)
# 将最高得分放在屏幕顶部中央
self.high_score_rect = self.high_score_image.get_rect()
self.high_score_rect.centerx = self.score_rect.centerx
self.high_score_rect.top = self.score_rect.top
def show_score(self):
# 在屏幕上显示当前得分和最高
self.screen.blit(self.score_image, self.score_rect)
self.screen.blit(self.high_score_image, self.high_score_rect)
为检查是否诞生了新的最高得分,在game_functions.py中添加一个新函数check_high_score() :
def check_high_score(stats, sb):
# 检查是否诞生了最高分数
if stats.score > stats.high_score:
stats.high_score = stats.score
sb.prep_high_score()
在check_bullet_alien_collisions() 中,每当有外星人被消灭,都需要在更新得分后调用check_high_score() :
def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets):
# 响应子弹和外星人的碰撞
# 删除发生碰撞的子弹和外星人
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() # 创建一幅显示最新得分的新图像
check_high_score(stats ,sb)
第一次玩这款游戏时,当前得分就是最高得分。再次开始这个游戏时,最高得分出现在中央,而当前得分出现在右边。
学习了
后面还会继续改进该游戏,将该项目全部代码打包放到了:外星人入侵项目(github地址)