添加播放按钮

在本节中,我们将添加一个 "播放" 按钮,该按钮会在游戏开始前出现,并在游戏结束时再次出现,以便玩家再次进行游戏。

现在,只要运行 alien_invasion.py,游戏就会开始。让我们以非活动状态启动游戏,然后提示玩家点击播放按钮开始游戏。为此,请修改 AlienInvasion__init__() 方法:

alien_invasion.py
def __init__(self):
    """Initialize the game, and create game resources."""
    pygame.init()
    --snip--

    # Start Alien Invasion in an inactive state.
    self.game_active = False

现在,游戏开始时应该处于非活动状态,玩家无法启动游戏,直到我们制作了 "播放" 按钮。

创建按钮类

因为 Pygame 没有制作按钮的内置方法,我们将编写一个 Button 类来创建一个带标签的填充矩形。您可以用这段代码来制作游戏中的任何按钮。下面是 Button 类的第一部分;将其保存为 button.py

button.py
import pygame.font

class Button:
    """A class to build buttons for the game."""

    def __init__(self, ai_game, msg):
        """Initialize button attributes."""
        self.screen = ai_game.screen
        self.screen_rect = self.screen.get_rect()

        # Set the dimensions and properties of the button.
        self.width, self.height = 200, 50
        self.button_color = (0, 135, 0)
        self.text_color = (255, 255, 255)
        self.font = pygame.font.SysFont(None, 48)

        # Build the button's rect object and center it.
        self.rect = pygame.Rect(0, 0, self.width, self.height)
        self.rect.center = self.screen_rect.center

        # The button message needs to be prepped only once.
        self._prep_msg(msg)

首先,我们导入 pygame.font 模块,它可以让 Pygame 将文本呈现在屏幕上。__init__() 方法的参数是 selfai_game 对象和 msg,其中 msg 包含按钮的文本 ❶。我们设置按钮的尺寸为 ❷,设置 button_color 将按钮的矩形对象染成深绿色,设置 text_color 将文本渲染为白色。

接下来,我们为渲染文本 ❸ 准备字体属性。None 参数告诉 Pygame 使用默认字体,48 指定文本的大小。为了使按钮在屏幕上居中,我们为按钮 ❹ 创建一个矩形,并将其 center 属性设置为与屏幕相匹配。

Pygame 处理文本时,会将要显示的字符串渲染为图像。最后,我们调用 _prep_msg() 来处理这种渲染❺。

下面是 _prep_msg() 的代码:

button.py
def _prep_msg(self, msg):
    """Turn msg into a rendered image and center text on the button."""
    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() 方法还包含一个布尔值,用于打开或关闭抗锯齿(抗锯齿会使文本边缘更平滑)。其余参数是指定的字体颜色和背景颜色。我们将抗锯齿设置为 True,并将文本背景设置为与按钮相同的颜色。(如果不包含背景颜色,Pygame 将尝试以透明背景渲染字体)。

我们从图像中创建一个矩形,并将其中心属性设置为与按钮 ❷相匹配,从而使文本图像居中。

最后,我们创建了一个 draw_button() 方法,我们可以调用它在屏幕上显示按钮:

button.py
def draw_button(self):
    """Draw blank button and then draw message."""
    self.screen.fill(self.button_color, self.rect)
    self.screen.blit(self.msg_image, self.msg_image_rect)

我们调用 screen.fill() 绘制按钮的矩形部分。然后,我们调用 screen.blit() 将文本图像绘制到屏幕上,并将图像和与图像关联的矩形对象传给它。至此,按钮类就完成了。

将按钮绘制到屏幕上

我们将使用 Button 类在 AlienInvasion 中创建一个播放按钮。首先,我们将更新 import 语句:

alien_invasion.py
--snip--
from game_stats import GameStats
from button import Button

因为我们只需要一个播放按钮,所以我们将在 AlienInvasion__init__() 方法中创建该按钮。我们可以将这段代码放在 __init__() 的最后:

alien_invasion.py
def __init__(self):
    --snip--
    self.game_active = False

    # Make the Play button.
    self.play_button = Button(self, "Play")

这段代码创建了一个标签为 Play 的 Button 实例,但并没有将按钮绘制到屏幕上。为此,我们将在 _update_screen() 中调用按钮的 draw_button() 方法:

alien_invasion.py
def _update_screen(self):
    --snip--
    self.aliens.draw(self.screen)

    # Draw the play button if the game is inactive.
    if not self.game_active:
        self.play_button.draw_button()

    pygame.display.flip()

为了使 "播放" 按钮高于屏幕上的所有其他元素,我们在绘制完所有其他元素后,在翻转到新的屏幕前绘制该按钮。我们将其包含在一个 if 代码块中,因此只有当游戏处于非活动状态时,按钮才会出现。

现在,当你运行 外星入侵 时,应该会看到屏幕中央有一个播放按钮,如图 14-1 所示。

image 2023 12 04 11 33 18 065
Figure 1. Figure 14-1: A Play button appears when the game is inactive.

开始游戏

要在玩家单击 "播放" 时启动新游戏,请在 _check_events() 的末尾添加以下 elif 代码块,以监控按钮上的鼠标事件:

alien_invasion.py
def _check_events(self):
    """Respond to keypresses and mouse events."""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            --snip--
        elif event.type == pygame.MOUSEBUTTONDOWN: (1)
            mouse_pos = pygame.mouse.get_pos() (2)
            self._check_play_button(mouse_pos) (3)

当玩家点击屏幕❶ 上的任意位置时,Pygame 会检测到一个 MOUSEBUTTONDOWN 事件,但我们希望限制我们的游戏只响应鼠标点击 Play 按钮。为了实现这一目标,我们使用了 pygame.mouse.get_pos(),它会返回一个元组,其中包含点击鼠标按钮 ❷ 时鼠标光标的 x 坐标和 y 坐标。我们将这些值发送到新方法 _check_play_button() ❸。

下面是 _check_play_button(),我选择将其放在 _check_events() 之后:

alien_invasion.py
def _check_play_button(self, mouse_pos):
    """Start a new game when the player clicks Play."""
    if self.play_button.rect.collidepoint(mouse_pos): (1)
        self.game_active = True

我们使用矩形方法 collidepoint() 检查鼠标点击点是否与 Play 按钮的矩形❶ 所定义的区域重叠。如果是,我们就将 game_active 设置为 True,游戏就开始了!

至此,你应该可以开始并玩一个完整的游戏了。当游戏结束时,game_active 的值应该变为 False,而 Play 按钮应该重新出现。

重置游戏

我们刚刚编写的 "播放" 按钮代码在玩家第一次点击 "播放" 时起作用。但第一次游戏结束后就不起作用了,因为导致游戏结束的条件尚未重置。

为了在玩家每次点击 "播放" 时重置游戏,我们需要重置游戏统计信息,清除旧的外星人和子弹,建立新的舰队,并将飞船置于中心位置,如图所示:

def _check_play_button(self, mouse_pos):
    """Start a new game when the player clicks Play."""
    if self.play_button.rect.collidepoint(mouse_pos):
        # Reset the game statistics.
        self.stats.reset_stats()
        self.game_active = True

        # Get rid of any remaining bullets and aliens.
        self.bullets.empty()
        self.aliens.empty()

        # Create a new fleet and center the ship.
        self._create_fleet()
        self.ship.center_ship()

我们重置了游戏统计数据❶,这将为玩家提供三艘新战舰。然后我们将 game_active 设置为 True,这样一旦该函数中的代码运行完毕,游戏就会开始。我们清空外星人和子弹群❷,然后创建一个新的舰队并将飞船置于中心位置❸。

现在,每次点击 "播放" 时,游戏都会正确重置,你可以尽情地玩游戏!

停用播放按钮

我们的 "播放" 按钮有一个问题,那就是即使 "播放" 按钮不可见,屏幕上的按钮区域也会继续响应点击。如果您在游戏开始后不小心点击了 "播放" 按钮区域,游戏就会重新启动!

要解决这个问题,可以将游戏设置为只有在 game_activeFalse 时才开始:

alien_invasion.py
def _check_play_button(self, mouse_pos):
    """Start a new game when the player clicks Play."""
    button_clicked = self.play_button.rect.collidepoint(mouse_pos) (1)
    if button_clicked and not self.game_active: (2)
        # Reset the game statistics.
        self.stats.reset_stats()
        --snip--

button_clicked 标志存储的是 TrueFalse 值❶,只有在点击 "播放" 且游戏当前未激活的情况下❷,游戏才会重新启动。要测试这一行为,请启动一个新游戏并重复点击 "播放" 按钮的位置。如果一切正常,点击 "播放" 按钮不会对游戏产生任何影响。

隐藏鼠标光标

我们希望鼠标光标在游戏未激活时是可见的,但一旦游戏开始,它就会挡住去路。为了解决这个问题,我们要让鼠标指针在游戏开始时不可见。我们可以在 _check_play_button()if 代码块的末尾这样做:

def _check_play_button(self, mouse_pos):
    """Start a new game when the player clicks Play."""
    button_clicked = self.play_button.rect.collidepoint(mouse_pos)
    if button_clicked and not self.game_active:
        --snip--
        # Hide the mouse cursor.
        pygame.mouse.set_visible(False)

set_visible() 传递 False 会告诉 Pygame 当鼠标在游戏窗口上时隐藏光标。

一旦游戏结束,我们将让光标重新出现,这样玩家就可以再次点击 Play 开始新游戏。下面是实现这一目的的代码:

alien_invasion.py
def _ship_hit(self):
    """Respond to ship being hit by alien."""
    if self.stats.ships_left > 0:
        --snip--
    else:
        self.game_active = False
        pygame.mouse.set_visible(True)

我们会在 _ship_hit() 中让光标在游戏停止后立即恢复可见。注意这样的细节能让您的游戏看起来更专业,让玩家专注于游戏,而不是琢磨用户界面。

亲身体验

14-1. 按 P 键开始游戏:由于《异形入侵》使用键盘输入来控制飞船,因此通过按键来启动游戏将非常有用。添加让玩家按 P 键开始游戏的代码。将 _check_play_button() 中的一些代码移到 _start_game() 方法中可能会有帮助,该方法可以从 _check_play_button() 和 _check_keydown_events() 中调用。

14-2. 目标练习:在屏幕右边创建一个以稳定速度上下移动的矩形。然后在屏幕左侧创建一艘飞船,玩家可以上下移动飞船,同时向矩形目标发射子弹。添加一个开始游戏的 "播放" 按钮,当玩家三次未击中目标时,结束游戏并重新显示 "播放" 按钮。让玩家通过这个 "播放" 按钮重新开始游戏。