Python应用实例(一)外星人入侵(三)

1.驾驶飞船

下面来让玩家能够左右移动飞船。我们将编写代码,在用户按左或右箭头键时做出响应。我们将首先专注于向右移动,再使用同样的原理来控制向左移动。通过这样做,你将学会如何控制屏幕图像的移动。

1.1 响应按键

每当用户按键时,都将在Pygame中注册一个事件。事件都是通过方法pygame.event.get()获取的,因此需要在方法_check_events()中指定要检查哪些类型的事件。每次按键都被注册为一个KEYDOWN事件。

Pygame检测到KEYDOWN事件时,需要检查按下的是否是触发行动的键。例如,如果玩家按下的是右箭头键,就增大飞船的rect.centerx值,将飞船向右移动:

      def _check_events(self):
          """响应按键和鼠标事件。"""
          for event in pygame.event.get():
              if event.type == pygame.QUIT:
                  sys.exit()elif event.type == pygame.KEYDOWN:if event.key == pygame.K_RIGHT:
                      # 向右移动飞船。
❸                     self.ship.rect.x += 1

在方法_check_events()中,为事件循环添加一个elif代码块,以便在Pygame检测到KEYDOWN事件时做出响应(见❶)。我们检查按下键(event.key)是否是右箭头键(pygame.K_RIGHT)(见❷)。如果是,就将self.ship.rect.centerx的值加1,从而将飞船向右移动(见❸)。

如果现在运行alien_invasion.py,则每按右箭头键一次,飞船都将向右移动1像素。这是一个开端,但并非控制飞船的高效方式。下面来改进控制方式,允许持续移动。

1.2 允许持续移动

玩家按住右箭头键不放时,我们希望飞船不断向右移动,直到玩家松开为止。我们将让游戏检测pygame.KEYUP事件,以便知道玩家何时松开右箭头键。然后,结合使用KEYDOWN和KEYUP事件以及一个名为moving_right的标志来实现持续移动。

当标志moving_right为False时,飞船不会移动。玩家按下右箭头键时,我们将该标志设置为True,在玩家松开时将该标志重新设置为False。

飞船的属性都由Ship类控制,因此要给这个类添加一个名为moving_right的属性和一个名为update()的方法。方法update()检查标志moving_right的状态。如果该标志为True,就调整飞船的位置。我们将在while循环中调用这个方法,以调整飞船的位置。

下面是对Ship类所做的修改:

  class Ship:
      """管理飞船的类"""

      def __init__(self, ai_game):
          --snip--
          # 对于每艘新飞船,都将其放在屏幕底部的中央。
          self.rect.midbottom = self.screen_rect.midbottom

          # 移动标志。
❶         self.moving_right = Falsedef update(self):
          """根据移动标志调整飞船的位置。"""
          if self.moving_right:
              self.rect.x += 1

      def blitme(self):
          --snip--

在方法__init__()中,添加属性self.moving_right,并将其初始值设置为False(见❶)。接下来,添加方法update(),在前述标志为True时向右移动飞船(见❷)。方法update()将通过Ship实例来调用,因此不是辅助方法。

接下来,需要修改_check_events(),使其在玩家按下右箭头键时将moving_right设置为True,并在玩家松开时将moving_right设置为False:

      def _check_events(self):
          """响应按键和鼠标事件。"""
          for event in pygame.event.get():
              --snip--
              elif event.type == pygame.KEYDOWN:
                  if event.key == pygame.K_RIGHT:
❶                     self.ship.moving_right = Trueelif event.type == pygame.KEYUP:
                  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):
        """开始游戏主循环。"""
        while True:
            self._check_events()
            self.ship.update()
            self._update_screen()

飞船的位置将在检测到键盘事件后(但在更新屏幕前)更新。这样,玩家输入时,飞船的位置将更新,从而确保使用更新后的位置将飞船绘制到屏幕上。

如果现在运行alien_invasion.py并按住右箭头键,飞船将持续向右移动,直到松开为止。

1.3 左右移动

现在飞船能够持续向右移动了,添加向左移动的逻辑也很容易。我们将再次修改Ship类和方法_check_events()。下面显示了对Ship类的方法__init__()和update()所做的相关修改:

    def __init__(self, ai_game):
        --snip--
        # 移动标志
        self.moving_right = False
        self.moving_left = False

    def update(self):
        """根据移动标志调整飞船的位置。"""
        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代码块来处理向左移动的情况,右箭头键将始终处于优先地位。从向左移动切换到向右移动时,玩家可能同时按住左右箭头键,此时前面的做法让移动更准确。

还需对_check_events()做两方面的调整:

alien_invasion.py

    def _check_events(self):
        """响应按键和鼠标事件。"""
        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,将能够持续左右移动飞船。如果同时按下左右箭头键,飞船将纹丝不动。

下面来进一步优化飞船的移动方式:调整飞船的速度,以及限制飞船的移动距离,以免其消失在屏幕之外。

1.4 调整飞船的速度

当前,每次执行while循环时,飞船最多移动1像素,但可在Settings类中添加属性ship_speed,用于控制飞船的速度。我们将根据这个属性决定飞船在每次循环时最多移动多远。下面演示了如何在settings.py中添加这个新属性:

class Settings:
    """存储游戏《外星人入侵》中所有设置的类。"""

    def __init__(self):
        --snip--

        # 飞船设置
        self.ship_speed = 1.5

将ship_speed的初始值设置为1.5。现在需要移动飞船时,每次循环将移动1.5像素而不是1像素。

通过将速度设置指定为小数值,可在后面加快游戏节奏时更细致地控制飞船的速度。然而,rect的x等属性只能存储整数值,因此需要对Ship类做些修改:

  class Ship:
      """管理飞船的类"""def __init__(self, ai_game):
          """初始化飞船并设置其初始位置。"""
          self.screen = ai_game.screen
          self.settings = ai_game.settings
          --snip--

          # 对于每艘新飞船,都将其放在屏幕底部的中央。
          --snip--

          # 在飞船的属性x中存储小数值。
❷         self.x = float(self.rect.x)

          # 移动标志
          self.moving_right = False
          self.moving_left = False

      def update(self):
          """根据移动标志调整飞船的位置。"""
          # 更新飞船而不是rect对象的x值。
          if self.moving_right:
❸             self.x += self.settings.ship_speed
          if self.moving_left:
              self.x -= self.settings.ship_speed

          # 根据self.x更新rect对象。
❹         self.rect.x = self.x

      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.rect.x只存储self.x的整数部分,但对显示飞船而言,这问题不大。

现在可以修改ship_speed的值了。只要它的值大于1,飞船的移动速度就会比以前更快。这有助于让飞船的反应速度足够快,以便射杀外星人,还让我们能够随着游戏的进行加快游戏的节奏。

注意 如果你使用的是macOS,可能发现即便ship_speed的值很大,飞船的移动速度还是很慢。要修复这种问题,可在全屏模式下运行游戏,我们稍后就将实现这种功能。

1.5 限制飞船的活动范围

当前,如果玩家按住箭头键的时间足够长,飞船将飞到屏幕之外,消失得无影无踪。下面来修复这种问题,让飞船到达屏幕边缘后停止移动。为此,将修改Ship类的方法update():

      def update(self):
          """根据移动标志调整飞船的位置。"""
          # 更新飞船而不是rect对象的x值。if self.moving_right and self.rect.right < self.screen_rect.right:
              self.x += self.settings.ship_speed
❷         if self.moving_left and self.rect.left > 0:
              self.x -= self.settings.ship_speed

          # 根据self.x更新rect对象。
          self.rect.x = self.x

上述代码在修改self.x的值之前检查飞船的位置。self.rect.right返回飞船外接矩形右边缘的[插图]坐标。如果这个值小于self.screen_rect.right的值,就说明飞船未触及屏幕右边缘(见❶)。左边缘的情况与此类似:如果rect左边缘的[插图]坐标大于零,就说明飞船未触及屏幕左边缘(见❷)。这确保仅当飞船在屏幕内时,才调整self.x的值。

如果此时运行alien_invasion.py,飞船将在触及屏幕左边缘或右边缘后停止移动。真是太神奇了!只在if语句中添加一个条件测试,就让飞船在到达屏幕左右边缘时像被墙挡住了一样。

1.6 重构_check_events()

随着游戏的开发,方法_check_events()将越来越长。因此将其部分代码放在两个方法中,其中一个处理KEYDOWN事件,另一个处理KEYUP事件:

alien_invasion.py

    def _check_events(self):
        """响应鼠标和按键事件。"""
        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):
        """响应按键。"""
        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):
        """响应松开。"""
        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()中相应的代码替换成了对这两个新方法的调用。现在,方法_check_events()更简单,代码结构也更清晰,在其中响应玩家输入时将更容易。

1.7 按q键退出

能够高效地响应按键后,我们来添加另一种退出游戏的方式。当前,每次测试新功能时,都需要单击游戏窗口顶部的X按钮来结束游戏,实在是太麻烦了。因此,我们来添加一个结束游戏的键盘快捷键—— Q键:

alien_invasion.py

    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键来结束游戏,而无须使用鼠标将窗口关闭。

1.8 在全屏模式下运行游戏

Pygame支持全屏模式,你可能会更喜欢在这种模式下而非常规窗口中运行游戏。有些游戏在全屏模式下看起来更舒服,而在macOS系统中用全屏模式运行会提升性能。

要在全屏模式下运行该游戏,可在__init__()中做如下修改:

alien_invasion.py

      def __init__(self):
          """初始化游戏并创建游戏资源。"""
          pygame.init()
          self.settings = Settings()

❶         self.screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
❷         self.settings.screen_width = self.screen.get_rect().width
          self.settings.screen_height = self.screen.get_rect().height
          pygame.display.set_caption("Alien Invasion")

创建屏幕时,传入了尺寸(0, 0)以及参数pygame.FULLSCREEN(见❶)。这让Pygame生成一个覆盖整个显示器的屏幕。由于无法预先知道屏幕的宽度和高度,要在创建屏幕后更新这些设置(见❷):使用屏幕的rect的属性width和height来更新对象settings。

如果你喜欢这款游戏在全屏模式下的外观和行为,请保留这些设置。如果你更喜欢这款游戏在独立的窗口中运行,可恢复到原来采用的方法——将屏幕尺寸设置为特定的值。

注意 在全屏模式下运行这款游戏之前,请确认能够按Q键退出,因为Pygame默认不提供在全屏模式下退出游戏的方式。

posted @ 2023-07-04 21:17  小幽余生不加糖  阅读(20)  评论(0编辑  收藏  举报  来源