驾驶飞船
接下来,我们将赋予玩家左右移动飞船的能力。我们将编写在玩家按下向右或向左箭头键时响应的代码。我们将首先关注向右移动,然后我们将应用相同的原则来控制向左移动。当我们添加这段代码时,您将学习如何控制屏幕上图像的移动以及如何响应用户输入。
响应按键
每当玩家按下某个键时,该按键就会在 Pygame 中注册为一个事件。每个事件都由 pygame.event.get() 方法获取。我们需要在我们的 _check_events() 方法中指定我们希望游戏检查的事件类型。每次按键都被注册为 KEYDOWN 事件。
当 Pygame 检测到 KEYDOWN 事件时,我们需要检查按下的键是否是触发某个动作的键。 例如,如果玩家按下右箭头键,我们想增加船的 rect.x 值以将船向右移动:
def _check_events(self):
"""Respond to keypresses and mouse events."""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN: (1)
if event.key == pygame.K_RIGHT: (2)
# Move the ship to the right.
self.ship.rect.x += 1 (3)
在 _check_events() 内部,我们向事件循环添加一个 elif 块,以在 Pygame 检测到 KEYDOWN 事件时做出响应❶。 我们检查按下的键 event.key 是否是右箭头键❷。 右箭头键由 pygame.K_RIGHT 表示。 如果按下右箭头键,我们将 self.ship.rect.x 的值增加 1 ❸,从而将船向右移动。
当你现在运行 alien_invasion.py 时,每次按下右箭头键时,飞船应该向右移动一个像素。这是一个开始,但这并不是控制船只的有效方法。让我们通过允许连续移动来改进这种控制。
允许持续移动
当玩家按住右箭头键时,我们希望飞船继续向右移动,直到玩家松开按键。我们会让游戏检测到 pygame.KEYUP 事件,这样我们就会知道何时释放右箭头键;然后我们将使用 KEYDOWN 和 KEYUP 事件以及一个名为 moving_right 的标志来实现连续运动。
当 moving_right 标志为 False 时,船将静止不动。当玩家按下右箭头键时,我们会将标志设置为 True,当玩家释放该键时,我们将再次将标志设置为 False。
Ship 类控制船的所有属性,因此我们将为其提供一个名为 moving_right 的属性和一个 update() 方法来检查 moving_right 标志的状态。 如果该标志设置为 True,则 update() 方法将更改船舶的位置。 我们将在每次通过 while 循环时调用此方法一次,以更新船舶的位置。
以下是对 Ship 的更改:
class Ship:
"""A class to manage the ship."""
def __init__(self, ai_game):
--snip--
# Start each new ship at the bottom center of the screen.
self.rect.midbottom = self.screen_rect.midbottom
# Movement flag; start with a ship that's not moving.
self.moving_right = False (1)
def update(self): (2)
"""Update the ship's position based on the movement flag."""
if self.moving_right:
self.rect.x += 1
def blitme(self):
--snip--
我们在 __init__()
方法中添加一个 self.moving_right 属性,并将其初始设置为 False ❶。 然后我们添加 update(),如果 flag 为 True 则将船向右移动 ❷。 update() 方法将从类外部调用,因此它不被视为辅助方法。
现在我们需要修改 _check_events() 以便 moving_right 在按下右箭头键时设置为 True,在释放键时设置为 False:
def _check_events(self):
"""Respond to keypresses and mouse events."""
for event in pygame.event.get():
--snip--
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
self.ship.moving_right = True (1)
elif event.type == pygame.KEYUP: (2)
if event.key == pygame.K_RIGHT:
self.ship.moving_right = False
在这里,我们修改了当玩家按下右箭头键时游戏的响应方式:我们只是将 moving_right 设置为 True ❶,而不是直接更改船舶的位置。 然后我们添加一个新的 elif 块,它响应 KEYUP 事件❷。 当玩家释放右箭头键 (K_RIGHT) 时,我们将 moving_right 设置为 False。
接下来,我们修改 run_game() 中的 while 循环,以便它在每次循环时调用船舶的 update() 方法:
def run_game(self):
"""Start the main loop for the game."""
while True:
self._check_events()
self.ship.update() (1)
self._update_screen()
self.clock.tick(60)
在我们检查键盘事件之后和更新屏幕之前,船舶的位置将被更新。 这允许船舶的位置根据玩家输入进行更新,并确保在将船舶绘制到屏幕时使用更新后的位置。
当你运行 alien_invasion.py 并按住右箭头键时,飞船应该不断向右移动,直到你释放该键。
左右移动
既然船可以连续向右移动,那么向左添加移动就很简单了。同样,我们将修改 Ship 类和 _check_events()
方法。 以下是 Ship 中 __init__()
和 update()
的相关更改:
def __init__(self, ai_game):
--snip--
# Movement flags; start with a ship that's not moving.
self.moving_right = False
self.moving_left = False
def update(self):
"""Update the ship's position based on movement flags."""
if self.moving_right:
self.rect.x += 1
if self.moving_left:
self.rect.x -= 1
在 __init__()
中,我们添加了 self.moving_left 标志。在 update() 中,我们使用两个单独的 if 块,而不是 elif,以允许在按住两个箭头键时增加然后减少船舶的 rect.x 值。这导致船静止不动。如果我们使用 elif 向左移动,则右箭头键始终具有优先权。当玩家在改变方向时可能暂时按住两个键时,使用两个 if 块可以使移动更加准确。
我们必须对 _check_events() 添加两处内容:
def _check_events(self):
"""Respond to keypresses and mouse events."""
for event in pygame.event.get():
--snip--
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
self.ship.moving_right = True
elif event.key == pygame.K_LEFT:
self.ship.moving_left = True
elif event.type == pygame.KEYUP:
if event.key == pygame.K_RIGHT:
self.ship.moving_right = False
elif event.key == pygame.K_LEFT:
self.ship.moving_left = False
如果 K_LEFT 键发生 KEYDOWN 事件,我们将 moving_left 设置为 True。 如果 K_LEFT 键发生 KEYUP 事件,我们将 moving_left 设置为 False。 我们可以在这里使用 elif 块,因为每个事件只连接到一个键。如果玩家同时按下两个键,则会检测到两个独立的事件。
当您现在运行 alien_invasion.py 时,您应该能够连续向右和向左移动飞船。如果你同时按住两个键,船应该停止移动。
接下来,我们将进一步完善船舶的运动。 让我们调整船的速度并限制船可以移动的距离,这样它就不会消失在屏幕的两侧。
调整飞船的速度
目前,船在 while 循环中每个周期移动一个像素,但我们可以通过将 ship_speed 属性添加到 Settings 类来更好地控制船的速度。 我们将使用此属性来确定每次通过循环时将船移动多远。 这是 settings.py 中的新属性:
class Settings:
"""A class to store all settings for Alien Invasion."""
def __init__(self):
--snip--
# Ship settings
self.ship_speed = 1.5
我们将 ship_speed 的初始值设置为1.5。 当船现在移动时,它的位置在每次通过循环时调整 1.5 像素(而不是 1 像素)。
我们使用浮点数来设置速度,以便在我们稍后提高游戏节奏时更好地控制船速。但是,x 等 rect 属性只存储整数值,因此我们需要对 Ship 进行一些修改:
class Ship:
"""A class to manage the ship."""
def __init__(self, ai_game):
"""Initialize the ship and set its starting position."""
self.screen = ai_game.screen
self.settings = ai_game.settings (1)
--snip--
# Start each new ship at the bottom center of the screen.
self.rect.midbottom = self.screen_rect.midbottom
# Store a float for the ship's exact horizontal position.
self.x = float(self.rect.x) (2)
# Movement flags; start with a ship that's not moving.
self.moving_right = False
self.moving_left = False
def update(self):
"""Update the ship's position based on movement flags."""
# Update the ship's x value, not the rect.
if self.moving_right:
self.x += self.settings.ship_speed (3)
if self.moving_left:
self.x -= self.settings.ship_speed
# Update rect object from self.x.
self.rect.x = self.x (4)
def blitme(self):
--snip--
我们为 Ship 创建一个 settings 属性,因此我们可以在 update() ❶ 中使用它。 因为我们正在通过像素的分数调整船的位置,所以我们需要将位置分配给一个变量,该变量可以分配一个浮点数。 您可以使用浮点来设置矩形(rect)的属性,但矩形(rect)只会保留该值的整数部分。为了准确跟踪船舶的位置,我们定义了一个新的 self.x ❷。我们使用 float() 函数将 self.rect.x 的值转换为浮点数,并将该值赋给 self.x。
现在,当我们在 update() 中更改船舶位置时,self.x 的值会根据 settings.ship_speed 中存储的数量进行调整❸。self.x 更新后,我们使用新值来更新self.rect.x,它控制船❹的位置。只有 self.x 的整数部分会被分配给 self.rect.x,但这对于显示船舶来说很好。
现在我们可以改变 ship_speed 的值,任何大于1的值都会使船移动得更快。这将有助于使飞船做出足够快的反应以击落外星人,并且可以让我们随着玩家在游戏中的进行而改变游戏的节奏。
限制飞船的活动范围
此时,如果您按住箭头键足够长的时间,飞船就会从屏幕的任一边缘消失。让我们纠正这个问题,以便飞船在到达屏幕边缘时停止移动。我们通过修改 Ship 中的 update() 方法来做到这一点:
def update(self):
"""Update the ship's position based on movement flags."""
# Update the ship's x value, not the rect.
if self.moving_right and self.rect.right < self.screen_rect.right: (1)
self.x += self.settings.ship_speed
if self.moving_left and self.rect.left > 0: (2)
self.x -= self.settings.ship_speed
# Update rect object from self.x.
self.rect.x = self.x
此代码在更改 self.x 的值之前检查船舶的位置。代码 self.rect.right 返回船舶矩形右边缘的 x 坐标。如果这个值小于 self.screen_rect.right 返回的值,则船还没有到达屏幕的右边缘❶。左边缘也是如此:如果矩形左侧的值大于0,则船还没有到达屏幕的左边缘❷。这可以确保在调整 self.x 的值之前船舶处于这些范围内。
当你现在运行 alien_invasion.py 时,飞船应该在屏幕的任一边缘停止移动。这很酷;我们所做的只是在 if 语句中添加一个条件测试,但感觉就像船撞到了屏幕任一边缘的墙壁或力场!
重构_check_events()
随着我们继续开发游戏,_check_events() 方法的长度会增加,所以让我们将 _check_events() 分成两个独立的方法:一个处理 KEYDOWN 事件,另一个处理 KEYUP 事件:
def _check_events(self):
"""Respond to keypresses and mouse events."""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
self._check_keydown_events(event)
elif event.type == pygame.KEYUP:
self._check_keyup_events(event)
def _check_keydown_events(self, event):
"""Respond to keypresses."""
if event.key == pygame.K_RIGHT:
self.ship.moving_right = True
elif event.key == pygame.K_LEFT:
self.ship.moving_left = True
def _check_keyup_events(self, event):
"""Respond to key releases."""
if event.key == pygame.K_RIGHT:
self.ship.moving_right = False
elif event.key == pygame.K_LEFT:
self.ship.moving_left = False
我们制作了两个新的辅助方法:_check_keydown_events() 和 _check_keyup_events()。 每个都需要一个 self 参数和一个 event 参数。这两个方法的主体是从 _check_events() 复制而来的,我们用对新方法的调用替换了旧代码。 现在,有了更清晰的代码结构,_check_events() 方法变得更简单,这将使开发对玩家输入的进一步响应变得更容易。
按Q键退出
现在我们可以有效地响应按键,我们可以添加另一种退出游戏的方法。 每次测试新功能时,单击游戏窗口顶部的 X 来结束游戏会很乏味,因此我们将添加一个键盘快捷键,以在玩家按 Q 时结束游戏:
def _check_keydown_events(self, event):
--snip--
elif event.key == pygame.K_LEFT:
self.ship.moving_left = True
elif event.key == pygame.K_q:
sys.exit()
在 _check_keydown_events() 中,我们添加了一个新块,当玩家按 Q 时结束游戏。现在,在测试时,您可以按 Q 关闭游戏,而不是使用光标关闭窗口。
在全屏模式下运行游戏
Pygame 具有全屏模式,与在常规窗口中运行游戏相比,您可能更喜欢这种模式。有些游戏在全屏模式下看起来更好,而在某些系统上,游戏在全屏模式下的整体表现可能更好。
要以全屏模式运行游戏,请在 __init__()
中进行以下更改:
def __init__(self):
"""Initialize the game, and create game resources."""
pygame.init()
self.settings = Settings()
self.screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN) (1)
self.settings.screen_width = self.screen.get_rect().width (2)
self.settings.screen_height = self.screen.get_rect().height
pygame.display.set_caption("Alien Invasion")
创建屏幕表面(surface)时,我们传递尺寸为 (0, 0) 和参数 pygame.FULLSCREEN ❶。这告诉 Pygame 计算出填满屏幕的窗口大小。因为我们提前不知道屏幕的宽度和高度,所以我们在屏幕创建后更新这些设置❷。 我们使用屏幕矩形(rect)的宽度(width)和高度(height)属性来更新设置(settings)对象。
如果您喜欢游戏在全屏模式下的外观或行为,请保留这些设置。如果您更喜欢游戏自己的窗口,您可以恢复到我们为游戏设置特定屏幕尺寸的原始方法。
在全屏模式下运行游戏之前,请确保您可以按 Q 退出; Pygame 不提供在全屏模式下退出游戏的默认方法。 |