目录
引言
直到上一篇博文为止,我们实现了《外星人入侵》这款有中飞船、外星人、子弹的绘制和移动,以及它们之间的碰撞检测。
在这篇博文中,将要实现为游戏增加“开始按钮”。
一、定义按钮基类
由于 pygame 不提供原生的按钮类供我们使用,因此我们需要自己来定义一个按钮类,以便创建“开始按钮”。
我们新建一个模块 button.py ,在其中定义 Button 类
1、按钮的构成
我们自己定义的按钮由这么两部分构成:
1、作为按钮整体的一个矩形
2、按钮上的文本
而再细分成类的属性的话,可以这么分:
1、按钮整体的尺寸
2、按钮整体的颜色
3、文本的字体
4、文本的颜色
5、按钮的位置
6、文本的内容
# 按钮的基类,我们可以通过实例化按钮类来创建各种按钮
class Button():
# 初始化按钮
def __init__(self, settings, screen, msg):
self.screen = screen
self.screen_rect = self.screen.get_rect()
self.settings = settings
# 设置按钮的尺寸
self.width = 200
self.height = 50
# 设置按钮的颜色
self.button_color = (0, 255, 0)
# 设置文本的颜色
self.text_color = (255, 255, 255)
# 设置文本的字体
self.font = pygame.font.Font(None, 48)
# 设置按钮的位置
self.rect = pygame.Rect(0, 0, self.width, self.height)
# 将按钮设置到屏幕中间
self.rect.center = self.screen_rect.center
# 将文本渲染成 Surface对象
self.prep_msg(msg)
2、在按钮上渲染文本
这里值得注意的是:
pygame 没有直接将字符串渲染到 Surface对象上的方法,所以我们必须将字符串转换成 Surface对象,然后将这个 Surface对象(文本)渲染到另一个 Surface对象(按钮)上。
这里是通过 prep_msg() 方法来实现将字符串转换成 Surface对象的
def prep_msg(self, msg):
# 将文本字符串渲染成一个Surface对象
self.msg_image = self.font.render(msg, True, self.text_color, self.button_color)
# 获取文本的坐标
self.msg_rect = self.msg_image.get_rect()
# 将文本位置放到按钮中间
self.msg_rect.center = self.rect.center
其中,pygame 包中的 font 模块的 render 函数将文本转换成 Sruface对象,第二个参数是反锯齿功能,第三个参数是文本颜色,第四个参数是背景颜色。
3、将按钮和文本“组合”
代码写到这里,其实已经可以发现,我们的按钮实际上是绘制出来的一个矩形图案,而按钮上的字实际上是一个由字符串渲染成的 Surface对象
现在,我们如何将两者“组合”成我们希望的完整按钮呢?
其实很简单,就是我们现在游戏屏幕的指定位置上绘制按钮矩形,然后再在矩形上绘制文本即可。
我们将这个方法定义在 Button 类中
def draw_button(self):
self.screen.fill(self.button_color, self.rect)
self.screen.blit(self.msg_image, self.msg_rect)
至此,我们就有了一个按钮基类,可以通过它来创建各种不同的按钮,但在《外星人入侵》中,我们只需要一个“开始按钮”。
二、绘制开始按钮
我们想要在运行游戏之初先显示“开始按钮”,然后游戏才开始正常进行。
因此,我们需要将游戏状态的初始值修改为 False,代表着运行之初游戏是处于非活跃状态的
1、修改游戏设置
class GameStats():
# 初始化游戏状态,设置玩家开局将有多少架飞船可以使用
def __init__(self, settings):
self.settings = settings
self.reset_stats()
def reset_stats(self):
self.ship_left = self.settings.ship_limit
# 游戏刚开始时,处于非活跃状态
self.game_active = False
2、创建开始按钮实例
我们在主模块中导入 Button 类,并且在游戏主逻辑前创建一个 Button实例,作为我们的开始按钮
# 从 按钮模块 中导入 Button 类
from button import Button
...
def run_game():
...
# 创建一个 启动游戏 的按钮
play_button = Button(my_settings, my_screen, 'Play')
...
3、修改绘制屏幕方法
此时,我们在绘制游戏元素之外,还需要绘制刚才创建的开始按钮,因此,需要修改 update_screen() 方法
def update_screen(screen, setting, ship, bullets, aliens, game_stats, button):
# 为纯黑的游戏屏幕填充上不一样的颜色
screen.fill(setting.background_color)
# 在背景之上绘制我们的飞船,注意这里的逻辑,必须是飞船在背景之后绘制,确保飞船在背景的上层
ship.blitme()
# 在屏幕和飞船之上,绘制子弹
for bullet in bullets.sprites():
bullet.draw_bullet()
# 在屏幕上绘制外星人
aliens.draw(screen)
# 如果游戏处于非活跃状态,那么就绘制按钮
if not game_stats.game_active:
button.draw_button()
# 刷新屏幕,使得元素能够不断刷新位置
pygame.display.flip()
效果如下:
三、游戏开始和游戏失败时开启游戏
现在,我们有了一个显示在屏幕中间的开始按钮。但它只是一个简单的矩形图案,没有任何实质性的作用。
1、检测玩家点击按钮的事件
pygame 的事件包括点击鼠标的行为,所以我们可以在事件检测方法中新增玩家标记鼠标的检测
# 当玩家 点击鼠标 发出事件时
elif event.type == pygame.MOUSEBUTTONDOWN:
# 获取玩家鼠标点击的位置
mouse_x, mouse_y = pygame.mouse.get_pos()
check_play_button(mouse_x, mouse_y, play_button, game_stats, aliens, bullets, settings, screen, ship)
当检测到玩家点击了鼠标后,我们需要获取玩家点击的位置信息,并且判断这个位置是否在开始按钮内。
通过 pygame 包的 mouse 模块的 get_pos() 方法来获取玩家鼠标的位置。
2、点击开始按钮后的业务
当玩家点击的是开始按钮时,我们需要考虑游戏需要做什么
1、如果游戏处于非活跃状态,那么就转换成活跃
2、重置游戏设置,主要是让玩家可以重启游戏
3、清空外星人和子弹、生成一群新的外星人、将飞船置于屏幕底部中间。
这些业务统一封装到一个函数中,在检测到玩家点击开始按钮后调用
# 玩家点击Play按钮后开始新游戏
def check_play_button(mouse_x, mouse_y, play_button, game_stats, aliens, bullets, settings, screen, ship):
button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y)
# 当玩家在游戏处于非活跃状态下点击Play按钮时
if button_clicked and not game_stats.game_active:
# 游戏活跃时隐藏光标
pygame.mouse.set_visible(False)
# 重置游戏设置
game_stats.reset_stats()
# 将游戏切换回活跃状态
game_stats.game_active = True
# 清空子弹和外星人
aliens.empty()
bullets.empty()
# 创建一群新的外星人,并让飞船居中
create_fleet(settings, screen, aliens, ship)
ship.center_ship()
其中,我们还设置了当游戏活跃性为 True 时,玩家的光标不可见的效果。
只有当活跃性为 False 时,玩家才可以看到自己的光标。
3、修改主模块逻辑
由于我们将游戏设置的活跃性初始值改成了 False
所以当游戏开始时,有些事情就不需要做了,这些事包括:
1、飞船的绘制
2、子弹的绘制
3、外星人群的生成
即使游戏没开始也要一直做的事有:
1、事件检测
2、绘制游戏屏幕和开始按钮
因此,我们需要修改一下游戏主循环
# 游戏主循环,使得循环中的代码可以不断循环执行
while True:
# 事件检测,现在我们已经将事件循环的代码封装到函数中了,直接运行即可
check_events(my_ship, my_screen, my_settings, bullets, play_button, game_stats, aliens)
# 绘制游戏画面,包括屏幕背景、飞船,并将内容显示到游戏屏幕上
update_screen(my_screen, my_settings, my_ship, bullets, aliens, game_stats, play_button)
# 当游戏处于活跃状态时,才渲染屏幕元素
if game_stats.game_active:
# 每次执行完事件检测循环后,都更新飞船的位置
my_ship.update()
# 每次执行完事件检测循环后,都更新子弹的位置、并删除出界的子弹,检测子弹和外星人之间的碰撞
update_bullet(bullets, my_settings, aliens, my_screen, my_ship)
# 每次执行完事件检测后,都更新外星人的位置,检测外星人和飞船之间的碰撞
update_aliens(aliens, my_settings, my_ship, game_stats, bullets, my_screen)
此时,我们运行游戏,可以先看到一个开始按钮,当玩家点击按钮后,才会开始游戏。
当玩家的3架飞船都用完时,会重新跳出开始按钮,玩家再次点击可以重开游戏。
四、小结
至此,我们实现了开始按钮的绘制,并通过玩家点击开始按钮来实现游戏流程的控制。
这里学到了 pygame 字体的使用方法,并且掌握了如何通过现有的模块来组织成需要的东西(类似造轮子?)
通过对前面知识的巩固,再加新功能时,就不会感到迷茫,可以马上猜到需要修改的位置,基本上八九不离十。