Arcade:Python 游戏框架入门

举报
Yuchuan 发表于 2021/12/22 13:47:29 2021/12/22
【摘要】 电脑游戏是对编码的一个很好的介绍,arcade图书馆是一个很好的第一步。设计为用于制作游戏的现代 Python 框架,您可以创建具有出色图形和声音的引人入胜的 Python 游戏体验。

目录

电脑游戏是向人们介绍编码和计算机科学的好方法。由于我年轻时是一名玩家,编写电子游戏的诱惑是我学习编码的原因。当然,当我学习 Python 时,我的第一反应就是写一个 Python 游戏。

虽然 Python 使每个人都可以更轻松地学习编码,但视频游戏编写的选择可能有限,特别是如果您想编写具有出色图形和引人入胜的音效的街机游戏。多年来,Python 游戏程序员仅限于该pygame框架。现在,还有另一个选择。

arcade是一个现代 Python 框架,用于制作具有引人注目的图形和声音的游戏。面向对象并为 Python 3.6 及更高版本构建,arcade为程序员提供了一套现代工具来打造出色的 Python 游戏体验。

在本教程中,您将学习如何:

  • 安装arcade
  • 在屏幕上绘制项目
  • 工作arcadePython的游戏循环
  • 管理屏幕上的图形元素
  • 处理用户输入
  • 播放音效和音乐
  • 描述如何Python的游戏编程与arcade来自不同pygame

背景和设置

arcade库由美国爱荷华州辛普森学院的计算机科学教授Paul Vincent Craven编写。由于它建立在窗口和多媒体库之上,因此具有各种改进、现代化和增强功能:pygletarcadepygame

  • 拥有现代 OpenGL 图形
  • 支持 Python 3类型提示
  • 更好地支持动画精灵
  • 包含一致的命令、函数和参数名称
  • 鼓励将游戏逻辑与显示代码分离
  • 需要更少的样板代码
  • 维护更多文档,包括完整的 Python 游戏示例
  • 具有用于平台游戏的内置物理引擎

要安装arcade及其依赖项,请使用适当的pip命令:

$ python -m pip install arcade

在 Mac 上,您还需要安装PyObjC

$ python -m pip install PyObjC arcade

基于您的平台的完整安装说明可用于WindowsMacLinux甚至Raspberry Pi。如果您愿意,您甚至可以arcade直接从源代码安装。

注意:最新版本的arcade利用数据类,它们仅包含在 Python 3.7 及更高版本中。

但是,PyPI for Python 3.6 上提供了一个反向移植,您可以使用pip以下命令进行安装:

$ python -m pip install dataclasses

有关更多信息,请参阅Python 3.7 中的数据类终极指南

本教程假设您arcade始终使用2.1 和 Python 3.7。

基础arcade课程

在深入研究之前,让我们先看看一个arcade程序,它会打开一个窗口,用白色填充它,并在中间画一个蓝色圆圈:

 1# Basic arcade program
 2# Displays a white window with a blue circle in the middle
 3
 4# Imports
 5import arcade
 6
 7# Constants
 8SCREEN_WIDTH = 600
 9SCREEN_HEIGHT = 800
10SCREEN_TITLE = "Welcome to Arcade"
11RADIUS = 150
12
13# Open the window
14arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
15
16# Set the background color
17arcade.set_background_color(arcade.color.WHITE)
18
19# Clear the screen and start drawing
20arcade.start_render()
21
22# Draw a blue circle
23arcade.draw_circle_filled(
24    SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, RADIUS, arcade.color.BLUE
25)
26
27# Finish drawing
28arcade.finish_render()
29
30# Display everything
31arcade.run()

当你运行这个程序时,你会看到一个看起来像这样的窗口:

使用街机库的基本程序。

让我们逐行分解:

  • 第 5 行导入arcade库。没有这个,其他的都不起作用。
  • 为清楚起见,第 8 行到第 11 行定义了一些稍后将使用的常量。
  • 第 14 行打开主窗口。您提供宽度、高度和标题栏文本,然后arcade完成其余的工作。
  • 第 17 行使用arcade.color包中的常量设置背景颜色。您还可以使用列表或元组指定 RGB 颜色。
  • 第 20 行设置arcade为绘图模式。在此线之后绘制的任何内容都将显示在屏幕上。
  • 第 23 到 25 行通过提供中心 X 和 Y 坐标、半径和要使用的颜色来绘制圆。
  • 第 28 行结束绘图模式。
  • 第 31 行显示您的窗口供您查看。

如果您熟悉pygame,那么您会注意到一些不同之处:

  • 没有pygame.init()。当您运行import arcade.
  • 没有明确定义的显示循环。它在arcade.run().
  • 这里也没有事件循环。同样,arcade.run()处理事件并提供一些默认行为,例如关闭窗口的能力。
  • 您可以使用预定义的颜色进行绘图,而不是自己定义所有颜色。
  • 您必须使用start_render()和开始和完成街机中的绘图finish_render()

让我们仔细看看arcade这个程序背后的基本概念。

arcade 概念

就像pygamearcade代码运行在几乎所有支持 Python 的平台上。这需要arcade处理这些平台上各种硬件差异的抽象。理解这些概念和抽象将帮助你设计和开发你自己的游戏,同时理解它们之间的arcade不同pygame将帮助你适应其独特的观点。

初始化

由于它涉及多种平台,因此arcade必须执行初始化步骤才能使用它。此步骤是自动的,并且在您导入时发生arcade,因此您无需编写额外的代码。导入时,arcade执行以下操作:

  • 验证是否在 Python 3.6 或更高版本上运行。
  • 导入pyglet_ffmeg2用于声音处理的库(如果可用)。
  • 导入pyglet用于窗口和多媒体处理的库。
  • 为颜色和键映射设置常量。
  • 导入剩余的arcade库。

将此与 相比pygame,它需要为每个模块进行单独的初始化步骤。

窗口和坐标

中的所有内容都arcade发生在一个窗口中,您可以使用open_window(). 目前,arcade仅支持单个显示窗口。您可以在打开窗口时调整其大小。

arcade使用您可能在代数课中学到的相同笛卡尔坐标系。窗口位于象限 I 中,原点 (0, 0) 位于屏幕的左下角。向右移动 x 坐标增加,向上移动 y 坐标增加:

街机窗口的布局。

需要注意的是,这种行为pygame与许多其他 Python 游戏框架相反。您可能需要一些时间来适应这种差异。

画画

开箱即用,arcade具有绘制各种几何形状的功能,包括:

  • Arcs
  • Circles
  • Ellipses
  • Lines
  • Parabolas
  • Points
  • Polygons
  • Rectangles
  • Triangles

所有绘图功能draw_都以一致的命名和参数模式开始并遵循。绘制填充和轮廓形状有不同的功能:

使用 Arcade 绘制的一些示例形状

因为矩形很常见,所以有三个独立的函数可以以不同的方式绘制它们:

  • draw_rectangle() 期望矩形中心的 x 和 y 坐标、宽度和高度。
  • draw_lrtb_rectangle() 期望左右 x 坐标,然后是顶部和底部 y 坐标。
  • draw_xywh_rectangle() 使用左下角的 x 和 y 坐标,然后是宽度和高度。

请注意,每个函数都需要四个参数。您还可以使用缓冲绘图函数绘制每个形状,该函数利用顶点缓冲区将所有内容直接推送到显卡,以实现令人难以置信的性能改进。所有缓冲绘图函数都以create_一致的命名和参数模式开始并遵循。

面向对象设计

它的核心arcade是一个面向对象的库。就像pygame,您可以按arcade程序编写代码,就像在上面的示例中所做的那样。然而,arcade当您创建完全面向对象的程序时,它的真正威力就会显现出来

当您arcade.open_window()在上面的示例中调用时,代码会arcade.Window在幕后创建一个对象来管理该窗口。稍后,您将创建自己的类,arcade.Window以编写完整的 Python 游戏。

首先,看一下现在使用面向对象概念的原始示例代码,以突出主要区别:

# Basic arcade program using objects
# Displays a white window with a blue circle in the middle

# Imports
import arcade

# Constants
SCREEN_WIDTH = 600
SCREEN_HEIGHT = 800
SCREEN_TITLE = "Welcome to Arcade"
RADIUS = 150

# Classes
class Welcome(arcade.Window):
    """Main welcome window
    """
    def __init__(self):
        """Initialize the window
        """

        # Call the parent class constructor
        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)

        # Set the background window
        arcade.set_background_color(arcade.color.WHITE)

    def on_draw(self):
        """Called whenever you need to draw your window
        """

        # Clear the screen and start drawing
        arcade.start_render()

        # Draw a blue circle
        arcade.draw_circle_filled(
            SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, RADIUS, arcade.color.BLUE
        )

# Main code entry point
if __name__ == "__main__":
    app = Welcome()
    arcade.run()

让我们一行一行地看一下这段代码:

  • 第 1 到 11 行与前面的过程示例相同。

  • 第 15 行是差异开始的地方。您定义一个Welcome基于父类调用的类arcade.Window。这允许您根据需要覆盖父类中的方法。

  • 第 18 到 26 行定义了该.__init__()方法。在调用用于设置窗口的父.__init__()方法之后super(),您可以像以前一样设置其背景颜色。

  • 第 28 到 38 行定义了.on_draw(). 这是Window您可以重写以自定义arcade程序行为的几种方法之一。每次arcade想要在窗口上绘制时都会调用此方法。它首先调用arcade.start_render(),然后是所有绘图代码。arcade.finish_render()但是,您不需要调用,因为arcade会在.on_draw()结束时隐式调用它。

  • 第 41 到 43 行是代码的主要入口点。在您首先创建一个Welcome名为的新对象后app,您可以调用arcade.run()以显示该窗口。

这个面向对象的例子是从arcade. 您可能已经注意到的一件事是.on_draw()arcade每次要在窗口上绘制时都会调用它。那么,如何arcade知道何时绘制任何东西呢?让我们来看看这意味着什么。

游戏循环

几乎每场比赛中的所有动作都发生在一个中央游戏循环中。您甚至可以在跳棋、老女仆或棒球等实体游戏中看到游戏循环的示例。游戏循环在游戏设置和初始化后开始,并在游戏完成时结束。在这个循环中,有几件事会依次发生。一个游戏循环至少需要执行以下四个动作:

  1. 该程序确定游戏是否结束。如果是,则循环结束。
  2. 处理用户输入
  3. 游戏对象的状态根据用户输入或时间等因素进行更新。
  4. 游戏根据新状态显示视觉效果并播放声音效果。

在 中pygame,您必须明确设置和控制此循环。在 中arcade,为您提供了 Python 游戏循环,封装在arcade.run()调用中。

在内置游戏循环期间,arcade调用一组Window方法来实现上面列出的所有功能。这些方法的名称都on_task 或 event handlers开头,可以将其视为任务或事件处理程序。当arcade游戏循环需要更新所有 Python 游戏对象的状态时,它会调用.on_update(). 当它需要检查鼠标移动时,它会调用.on_mouse_motion().

默认情况下,这些方法都没有做任何有用的事情。当您基于 来创建您自己的类时arcade.Window,您可以根据需要覆盖它们以提供您自己的游戏功能。提供的一些方法包括:

  • 键盘输入: .on_key_press() ,.on_key_release()
  • 鼠标输入: .on_mouse_press() , .on_mouse_release(),.on_mouse_motion()
  • 更新游戏对象: .on_update()
  • 画画: .on_draw()

您不需要覆盖所有这些方法,只需覆盖您想要为其提供不同行为的方法。您也无需担心他们何时被调用,只需担心他们被调用时该怎么做。接下来,您将探索如何将所有这些概念组合在一起来创建游戏。

Python 游戏设计基础

在您开始编写任何代码之前,先做好设计总是一个好主意。由于您将在本教程中创建一个 Python 游戏,因此您还将为其设计一些游戏玩法:

  • 该游戏是一个横向滚动的敌人回避游戏。
    • 播放器从屏幕左侧开始。
    • 敌人以固定的间隔和右侧的随机位置进入。
    • 敌人沿直线向左移动,直到他们离开屏幕。
  • 玩家可以向左、向右、向上或向下移动来躲避敌人。
  • 玩家不能离开屏幕。
  • 当玩家被敌人击中或用户关闭窗口时游戏结束。

当他描述软件项目时,我的一位前同事曾经说过:“直到你知道你不知道什么,你才知道你做了什么。” 考虑到这一点,以下是本教程中不会涉及的一些内容:

  • 没有多重生命
  • 没有记分
  • 无玩家攻击能力
  • 没有进阶
  • 没有“老板”角色

您可以随意尝试将这些和其他功能添加到您自己的程序中。

导入和常量

与任何arcade程序一样,您将从导入库开始:

# Basic arcade shooter

# Imports
import arcade
import random

# Constants
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
SCREEN_TITLE = "Arcade Space Shooter"
SCALING = 2.0

除了arcade,您还导入random,因为稍后您将使用随机数。常量设置窗口大小和标题,但什么是SCALING?此常量用于使窗口和其中的游戏对象变大以补偿高 DPI 屏幕。随着教程的继续,您将看到它在两个地方使用。您可以更改此值以适合您的屏幕大小。

窗口类

要充分利用arcadePython 游戏循环和事件处理程序,请基于以下内容创建一个新类arcade.Window

35class SpaceShooter(arcade.Window):
36    """Space Shooter side scroller game
37    Player starts on the left, enemies appear on the right
38    Player can move anywhere, but not off screen
39    Enemies fly to the left at variable speed
40    Collisions end the game
41    """
42
43    def __init__(self, width, height, title):
44        """Initialize the game
45        """
46        super().__init__(width, height, title)
47
48        # Set up the empty sprite lists
49        self.enemies_list = arcade.SpriteList()
50        self.clouds_list = arcade.SpriteList()
51        self.all_sprites = arcade.SpriteList()

您的新类就像上面的面向对象示例一样开始。在第 43 行,您定义了构造函数,它获取游戏窗口的宽度、高度和标题,并将super()它们传递给父级。然后在第 49 行到第 51 行初始化一些空的精灵列表。在下一节中,您将了解有关精灵和精灵列表的更多信息。

精灵和精灵列表

您的 Python 游戏设计要求从左侧开始并可以在窗口中自由移动的单个玩家。它还需要随机出现在右侧并向左侧移动的敌人(换句话说,不止一个)。虽然您可以使用draw_命令来绘制玩家和每个敌人,但很快就会很难保持笔直。

相反,大多数现代游戏使用精灵来表示屏幕上的对象。从本质上讲,精灵是在屏幕上的特定位置绘制的具有定义大小的游戏对象的二维图片。在 中arcade,精灵是 class 的对象arcade.Sprite,您将使用它们来代表您的玩家和敌人。你甚至会加入一些云来使背景更有趣。

管理所有这些精灵可能是一个挑战。您将创建一个单人游戏精灵,但您还将创建大量敌人和云精灵。跟踪所有这些是精灵列表的工作。如果您了解Python 列表的工作原理,那么您就有了使用arcade的精灵列表的工具。精灵列表的作用不仅仅是保存所有的精​​灵。它们实现了三个重要的行为:

  1. 您可以通过一次调用来更新列表中的所有精灵SpriteList.update()
  2. 您可以通过一次调用来绘制列表中的所有精灵SpriteList.draw()
  3. 您可以检查单个精灵是否与列表中的任何精灵发生碰撞。

如果您只需要管理多个敌人和云,您可能想知道为什么需要三个不同的精灵列表。原因是三个不同的精灵列表中的每一个都存在,因为您将它们用于三个不同的目的:

  1. .enemies_list用来更新敌人的位置并检查碰撞。
  2. 您用于.clouds_list更新云位置。
  3. 最后,你.all_sprites用来绘制一切。

现在,列表仅与其包含的数据一样有用。以下是填充精灵列表的方法:

53def setup(self):
54    """Get the game ready to play
55    """
56
57    # Set the background color
58    arcade.set_background_color(arcade.color.SKY_BLUE)
59
60    # Set up the player
61    self.player = arcade.Sprite("images/jet.png", SCALING)
62    self.player.center_y = self.height / 2
63    self.player.left = 10
64    self.all_sprites.append(self.player)

您定义.setup()将游戏初始化为已知起点。虽然您可以在 中执行此操作.__init__(),但使用单独的.setup()方法很有用。

想象一下,您希望您的 Python 游戏具有多个级别,或者您的玩家拥有多个生命。不是通过调用 重新启动整个游戏.__init__(),而是调用.setup()将游戏重新初始化到已知起点或设置新级别。即使这个 Python 游戏没有这些功能,设置结构可以让以后更快地添加它们。

在第 58 行设置背景颜色后,您可以定义玩家精灵:

  • 第 61 行arcade.Sprite通过指定要显示的图像和缩放因子来创建一个新对象。将图像组织到单个子文件夹中是个好主意,尤其是在较大的项目中。

  • 第 62行将精灵的 y 位置设置为窗口高度的一半。

  • 第 63 行通过将左边缘放置在距窗口左边缘几个像素的位置来设置精灵的 x 位置。

  • 第 64 行最后用于.append()将精灵添加到.all_sprites您将用于绘图的列表中。

第 62 行和第 63 行显示了两种不同的定位精灵的方法。让我们仔细看看所有可用的精灵定位选项。

精灵定位

所有精灵arcade窗口中都有特定的大小和位置:

  • Sprite.width和指定的大小Sprite.height创建精灵时使用的图形决定。
  • 该位置最初设置为精灵的中心,由Sprite.center_x和指定Sprite.center_y,在窗口中的 (0,0) 处。

一旦.center_x.center_y坐标是已知的,arcade可以使用尺寸来计算Sprite.leftSprite.rightSprite.top,和Sprite.bottom边缘,以及。

这也适用于相反的情况。例如,如果您设置Sprite.left为给定值,那么arcade也会重新计算剩余的位置属性。您可以使用它们中的任何一个来定位精灵或在窗口中移动它。这是arcade精灵的一个非常有用和强大的特性。如果您使用它们,那么您的 Python 游戏将需要比以下更少的代码pygame

只有一个玩家的街机教程游戏

现在您已经定义了玩家精灵,您可以处理敌人的精灵。该设计要求您让敌人的精灵定期出现。你怎么能这样做?

调度功能

arcade.schedule()正是为此目的而设计的。它需要两个参数:

  1. 要调用的函数的名称
  2. 每次调用之间等待的时间间隔,以秒为单位

由于您希望在整个游戏中同时出现敌人和云,因此您设置了一个计划函数来创建新的敌人,第二个函数来创建新的云。该代码进入.setup(). 下面是代码的样子:

66# Spawn a new enemy every 0.25 seconds
67arcade.schedule(self.add_enemy, 0.25)
68
69# Spawn a new cloud every second
70arcade.schedule(self.add_cloud, 1.0)

现在您所要做的就是定义self.add_enemy()self.add_cloud()

添加敌人

从您的 Python 游戏设计来看,敌人具有三个关键属性:

  1. 它们出现在窗口右侧的随机位置。
  2. 它们沿直线向左移动
  3. 当它们离开屏幕时它们就会消失

创建敌人精灵的代码与创建玩家精灵的代码非常相似:

 93def add_enemy(self, delta_time: float):
 94    """Adds a new enemy to the screen
 95
 96    Arguments:
 97        delta_time {float} -- How much time has passed since the last call
 98    """
 99
100    # First, create the new enemy sprite
101    enemy = arcade.Sprite("images/missile.png", SCALING)
102
103    # Set its position to a random height and off screen right
104    enemy.left = random.randint(self.width, self.width + 80)
105    enemy.top = random.randint(10, self.height - 10)

.add_enemy()采用单个参数 ,delta_time表示自上次调用以来已经过去了多长时间。这是 需要的arcade.schedule(),虽然您不会在这里使用它,但它对于需要高级计时的应用程序很有用。

与播放器精灵一样,您首先创建一个arcade.Sprite带有图片和缩放因子的新精灵。您使用.left.top将位置设置为屏幕右侧某处的随机位置:

几个敌人出现在屏幕上

这使得敌人可以顺利地移动到屏幕上,而不仅仅是出现在屏幕上。现在,你如何让它移动?

移动精灵

移动精灵需要您在游戏循环的更新阶段更改其位置。虽然您可以自己完成此操作,但arcade有一些内置功能可以减少您的工作量。每个arcade.Sprite不仅有一组位置属性,而且还有一组运动属性。每次更新精灵时,arcade都会使用运动属性来更新位置,从而为精灵赋予相对运动。

Sprite.velocity属性是一个由 x 和 y 位置变化组成的元组。您也可以直接访问Sprite.change_xSprite.change_y。如上所述,每次更新精灵时,它.position都会根据.velocity. 您需要做的.add_enemy()就是设置速度:

107# Set its speed to a random speed heading left
108enemy.velocity = (random.randint(-20, -5), 0)
109
110# Add it to the enemies list
111self.enemies_list.append(enemy)
112self.all_sprites.append(enemy)

在第 108 行将速度设置为向左移动的随机速度后,将新敌人添加到相应的列表中。当您稍后调用时sprite.update()arcade将处理其余的:

在教程游戏中飞过的敌人

在您的 Python 游戏设计中,敌人从右到左沿直线移动。因为你的敌人总是向左移动,一旦他们离开屏幕,他们就不会回来。如果您可以摆脱屏幕外的敌人精灵以释放内存并加快更新速度,那就太好了。幸运的是,arcade你覆盖了。

移除精灵

因为你的敌人总是向左移动,所以他们的 x 位置总是越来越小,而他们的 y 位置总是不变的。因此,当enemy.right小于零时,您可以确定敌人在屏幕外,即窗口的左边缘。一旦你确定敌人不在屏幕上,你调用enemy.remove_from_sprite_lists()从它所属的所有列表中删除它并从内存中释放该对象

if enemy.right < 0:
    enemy.remove_from_sprite_lists()

但是你什么时候执行这个检查?通常,这会在精灵移动后立即发生。但是,请记住之前所说的关于.all_enemies精灵列表的内容:

.enemies_list用来更新敌人的位置并检查碰撞。

这意味着在 中SpaceShooter.on_update(),您将调用enemies_list.update()自动处理敌人的移动,这主要执行以下操作:

for enemy in enemies_list:
    enemy.update()

如果您可以将屏幕外检查直接添加到enemy.update()呼叫中,那就太好了,您可以!记住,arcade是一个面向对象的库。这意味着您可以基于arcade类创建自己的类,并覆盖要修改的方法。在这种情况下,您创建一个新类arcade.Sprite.update()仅覆盖:

17class FlyingSprite(arcade.Sprite):
18    """Base class for all flying sprites
19    Flying sprites include enemies and clouds
20    """
21
22    def update(self):
23        """Update the position of the sprite
24        When it moves off screen to the left, remove it
25        """
26
27        # Move the sprite
28        super().update()
29
30        # Remove if off the screen
31        if self.right < 0:
32            self.remove_from_sprite_lists()

您定义FlyingSprite为将在您的游戏中飞行的任何事物,例如敌人和云。然后您覆盖.update(),首先调用super().update()以正确处理运动。然后,您执行屏幕外检查。

由于您有一个新课程,您还需要对以下内容进行一些小改动.add_enemy()

def add_enemy(self, delta_time: float):
    """Adds a new enemy to the screen

    Arguments:
        delta_time {float} -- How much time as passed since the last call
    """

    # First, create the new enemy sprite
    enemy = FlyingSprite("images/missile.png", SCALING)

Sprite您不是创建新的,而是创建新的FlyingSprite以利用新的.update()

添加云

为了使您的 Python 游戏在视觉上更具吸引力,您可以向天空添加云彩。云在天空中飞舞,就像您的敌人一样,因此您可以以类似的方式创建和移动它们。

.add_cloud()遵循与 相同的模式.add_enemy(),但随机速度较慢:

def add_cloud(self, delta_time: float):
    """Adds a new cloud to the screen

    Arguments:
        delta_time {float} -- How much time has passed since the last call
    """

    # First, create the new cloud sprite
    cloud = FlyingSprite("images/cloud.png", SCALING)

    # Set its position to a random height and off screen right
    cloud.left = random.randint(self.width, self.width + 80)
    cloud.top = random.randint(10, self.height - 10)

    # Set its speed to a random speed heading left
    cloud.velocity = (random.randint(-5, -2), 0)

    # Add it to the enemies list
    self.clouds_list.append(cloud)
    self.all_sprites.append(cloud)

云的移动速度比敌人慢,因此您在第 129 行计算出较低的随机速度。

现在你的 Python 游戏看起来更完整了:

云在游戏窗口飞舞

您的敌人和云现在已创建并自行移动。是时候让玩家使用键盘移动了。

键盘输入

arcade.Window类有处理键盘输入两种功能。您的 Python 游戏将.on_key_press()在按下按键和.on_key_release()松开按键时调用。这两个函数都接受两个整数参数:

  1. symbol 代表按下或释放的实际键。
  2. modifiers表示哪些修饰符被关闭。这些措施包括ShiftCtrl,和Alt键。

幸运的是,您不需要知道哪些整数代表哪些键。该arcade.key模块包含您可能想要使用的所有键盘常量。传统上,使用键盘移动演奏者使用三组不同键中的一组或多组:

  1. 四个方向键UpDownLeft,和Right
  2. IJK, and L,分别映射到 Up、Left、Down 和 Right
  3. 对于左手控制,键WAS, 和D也映射到上、左、下和右

对于此游戏,您将使用箭头和IJKL。每当用户按下移动键时,玩家精灵就会向那个方向移动。当用户释放移动键时,精灵将停止向该方向移动。您还提供了一种使用 退出游戏的方法Q,以及一种使用 暂停游戏的方法P。为此,您需要响应按键和释放:

  • 当一个键被按下时,调用.on_key_press()。在该方法中,您检查按下了哪个键:
    • 如果是Q,那么您只需退出游戏。
    • 如果是P,则您设置一个标志以指示游戏已暂停。
    • 如果它是一个移动键,那么您可以相应地设置播放器的.change_x.change_y
    • 如果它是任何其他键,那么您可以忽略它。
  • 当一个键被释放时,调用.on_key_release()。再次检查释放了哪个键:
    • 如果是移动键,则相应地将玩家的.change_x或设置.change_y为 0。
    • 如果它是任何其他键,那么您可以忽略它。

代码如下所示:

134def on_key_press(self, symbol, modifiers):
135    """Handle user keyboard input
136    Q: Quit the game
137    P: Pause/Unpause the game
138    I/J/K/L: Move Up, Left, Down, Right
139    Arrows: Move Up, Left, Down, Right
140
141    Arguments:
142        symbol {int} -- Which key was pressed
143        modifiers {int} -- Which modifiers were pressed
144    """
145    if symbol == arcade.key.Q:
146        # Quit immediately
147        arcade.close_window()
148
149    if symbol == arcade.key.P:
150        self.paused = not self.paused
151
152    if symbol == arcade.key.I or symbol == arcade.key.UP:
153        self.player.change_y = 5
154
155    if symbol == arcade.key.K or symbol == arcade.key.DOWN:
156        self.player.change_y = -5
157
158    if symbol == arcade.key.J or symbol == arcade.key.LEFT:
159        self.player.change_x = -5
160
161    if symbol == arcade.key.L or symbol == arcade.key.RIGHT:
162        self.player.change_x = 5
163
164def on_key_release(self, symbol: int, modifiers: int):
165    """Undo movement vectors when movement keys are released
166
167    Arguments:
168        symbol {int} -- Which key was pressed
169        modifiers {int} -- Which modifiers were pressed
170    """
171    if (
172        symbol == arcade.key.I
173        or symbol == arcade.key.K
174        or symbol == arcade.key.UP
175        or symbol == arcade.key.DOWN
176    ):
177        self.player.change_y = 0
178
179    if (
180        symbol == arcade.key.J
181        or symbol == arcade.key.L
182        or symbol == arcade.key.LEFT
183        or symbol == arcade.key.RIGHT
184    ):
185        self.player.change_x = 0

在 中.on_key_release(),您只检查会影响您的播放器精灵运动的键。无需检查 Pause 或 Quit 键是否被释放。

现在您可以在屏幕上移动并立即退出游戏:

在屏幕上移动播放器

您可能想知道暂停功能是如何工作的。要查看实际效果,您首先需要学习更新所有 Python 游戏对象。

更新游戏对象

仅仅因为您为所有精灵设置了速度并不意味着它们会移动。为了让它们移动,你必须在游戏循环中一遍又一遍地更新它们。

由于arcade控制 Python 游戏循环,它还通过调用 来控制何时需要更新.on_update()。您可以重写此方法来为您的游戏提供适当的行为,包括游戏移动和其他行为。对于这个游戏,你需要做一些事情来正确更新一切:

  1. 您检查游戏是否已暂停。如果是这样,那么您可以退出,因此不会发生进一步的更新。
  2. 您更新所有精灵以使其移动。
  3. 您检查玩家精灵是否已移出屏幕。如果是这样,那么只需将它们移回屏幕上即可。

暂时就这样了。下面是这段代码的样子:

189def on_update(self, delta_time: float):
190    """Update the positions and statuses of all game objects
191    If paused, do nothing
192
193    Arguments:
194        delta_time {float} -- Time since the last update
195    """
196
197    # If paused, don't update anything
198    if self.paused:
199        return
200
201    # Update everything
202    self.all_sprites.update()
203
204    # Keep the player on screen
205    if self.player.top > self.height:
206        self.player.top = self.height
207    if self.player.right > self.width:
208        self.player.right = self.width
209    if self.player.bottom < 0:
210        self.player.bottom = 0
211    if self.player.left < 0:
212        self.player.left = 0

第 198 行是您检查游戏是否暂停的地方,如果是,则简单地返回。这会跳过所有剩余的代码,因此不会有任何移动。所有精灵的移动都由第 202 行处理。这行代码有以下三个原因:

  1. 每个精灵都是self.all_sprites列表的成员。
  2. 呼叫self.all_sprites.update()在呼叫结果.update()列表中的每一个角色。
  3. 列表中的每个精灵都有.velocity(由.change_x.change_y属性组成)并且在.update()调用它时将处理自己的运动。

最后,您在第 205 到 212 行通过比较精灵的边缘和窗口的边缘来检查玩家精灵是否在屏幕外。例如,在第 205 和 206 行,如果self.player.top超出屏幕顶部,则重置self.player.top到屏幕顶部。现在一切都已更新,您可以绘制所有内容。

在窗口上绘图

由于游戏对象的更新发生在.on_update(),因此绘制游戏对象将在名为 的方法中进行似乎是合适的.on_draw()。由于您已将所有内容组织到精灵列表中,因此此方法的代码非常简短:

231def on_draw(self):
232    """Draw all game objects
233    """
234    arcade.start_render()
235    self.all_sprites.draw()

所有的绘制都从调用arcade.start_render()第 234 行开始。就像更新一样,您可以通过调用self.all_sprites.draw()第 235 行来一次性绘制所有精灵。现在 Python 游戏只有最后一部分需要处理,这是最后一部分初步设计:

当玩家被障碍物击中或用户关闭窗口时,游戏结束。

这是真正的游戏部分!现在,敌人会飞过你的玩家精灵,什么也不做。让我们看看如何添加此功能。

碰撞检测

游戏都是关于一种或另一种形式的碰撞,即使在非电脑游戏中也是如此。没有真实或虚拟的碰撞,就没有曲棍球的击球目标,在西洋双陆棋中没有双六,在国际象棋中也没有办法在骑士叉的末端捕获对手的皇后。

计算机游戏中的碰撞检测要求程序员检测两个游戏对象是否部分占据了屏幕上的相同空间。您使用碰撞检测来射击敌人,限制玩家在墙壁和地板上的移动,并提供障碍物来躲避。根据所涉及的游戏对象和所需的行为,碰撞检测逻辑可能需要复杂的数学运算。

但是,您不必使用arcade. 您可以使用三种不同Sprite方法之一来快速检测碰撞:

  1. Sprite.collides_with_point((x,y))True如果给定点(x,y)在当前精灵的边界内,则返回,False否则返回。
  2. Sprite.collides_with_sprite(Sprite)True如果给定的精灵与当前精灵重叠,则返回,False否则返回。
  3. Sprite.collides_with_list(SpriteList)返回一个包含SpriteList与当前精灵重叠的所有精灵的列表。如果没有重叠的精灵,则列表将为空,这意味着它的长度为零。

由于您对单人精灵是否与任何敌方精灵发生碰撞感兴趣,因此最后一种方法正是您所需要的。您调用self.player.collides_with_list(self.enemies_list)并检查它返回的列表是否包含任何精灵。如果是这样,那么你结束游戏。

那么,你在哪里打这个电话?最好的地方是.on_update(),就在你更新所有东西的位置之前:

189def on_update(self, delta_time: float):
190    """Update the positions and statuses of all game objects
191    If paused, do nothing
192
193    Arguments:
194        delta_time {float} -- Time since the last update
195    """
196
197    # If paused, don't update anything
198    if self.paused:
199        return
200
201    # Did you hit anything? If so, end the game
202    if self.player.collides_with_list(self.enemies_list):
203        arcade.close_window()
204
205    # Update everything
206    self.all_sprites.update()

第 202 和 203 行检查player和 中的任何精灵之间的碰撞.enemies_list。如果返回的列表包含任何精灵,则表示发生碰撞,您可以结束游戏。现在,为什么要更新所有位置之前进行检查?记住 Python 游戏循环中的动作顺序:

  1. 您更新游戏对象的状态。你在.on_update().
  2. 您将所有游戏对象绘制在它们的新位置。你在.on_draw().

如果在更新 中的所有内容后检查碰撞.on_update(),则在检测到碰撞时不会绘制任何新位置。您实际上是根据尚未向用户显示的精灵位置检查碰撞。在玩家看来,游戏似乎在真正发生碰撞之前就结束了!当您首先检查时,您确保玩家可见的内容与您正在检查的游戏状态相同。

现在您有一个看起来不错并提供挑战的 Python 游戏!现在您可以添加一些额外的功能来帮助您的 Python 游戏脱颖而出。

附加功能

您可以将更多功能添加到 Python 游戏中以使其脱颖而出。除了游戏设计中提到的您没有实现的功能之外,您可能还会想到其他功能。本节将介绍两个功能,它们将通过添加声音效果和控制游戏速度为您的 Python 游戏带来一些额外的影响。

声音

声音是任何电脑游戏的重要组成部分。从爆炸到敌人的嘲讽再到背景音乐,您的 Python 游戏在没有声音的情况下有点单调。开箱即用,arcade提供对WAV文件的支持。如果ffmpeg 库已安装且可用,则arcade还支持OggMP3格式的文件。您将添加三种不同的音效和一些背景音乐:

  1. 玩家向上移动时会播放第一个音效
  2. 当玩家向下移动时会播放第二个音效
  3. 发生碰撞时播放第三种音效
  4. 背景音乐是您添加的最后一件事。

您将从音效开始。

声音特效

在您可以播放任何这些声音之前,您必须加载它们。你这样做.setup()

66# Spawn a new enemy every 0.25 seconds
67arcade.schedule(self.add_enemy, 0.25)
68
69# Spawn a new cloud every second
70arcade.schedule(self.add_cloud, 1.0)
71
72# Load your sounds
73# Sound sources: Jon Fincher
74self.collision_sound = arcade.load_sound("sounds/Collision.wav")
75self.move_up_sound = arcade.load_sound("sounds/Rising_putter.wav")
76self.move_down_sound = arcade.load_sound("sounds/Falling_putter.wav")

就像您的精灵图像一样,将所有声音放在一个子文件夹中是一种很好的做法。

加载声音后,您可以在适当的时间播放它们。对于.move_up_soundand .move_down_sound,这发生在.on_key_press()处理程序期间:

134def on_key_press(self, symbol, modifiers):
135    """Handle user keyboard input
136    Q: Quit the game
137    P: Pause the game
138    I/J/K/L: Move Up, Left, Down, Right
139    Arrows: Move Up, Left, Down, Right
140
141    Arguments:
142        symbol {int} -- Which key was pressed
143        modifiers {int} -- Which modifiers were pressed
144    """
145    if symbol == arcade.key.Q:
146        # Quit immediately
147        arcade.close_window()
148
149    if symbol == arcade.key.P:
150        self.paused = not self.paused
151
152    if symbol == arcade.key.I or symbol == arcade.key.UP:
153        self.player.change_y = 5
154        arcade.play_sound(self.move_up_sound)
155
156    if symbol == arcade.key.K or symbol == arcade.key.DOWN:
157        self.player.change_y = -5
158        arcade.play_sound(self.move_down_sound)

现在,每当玩家向上或向下移动时,您的 Python 游戏都会播放声音。

每当.on_update()检测到碰撞时都会播放碰撞声音:

def on_update(self, delta_time: float):
    """Update the positions and statuses of all game objects
    If paused, do nothing

    Arguments:
        delta_time {float} -- Time since the last update
    """

    # If paused, don't update anything
    if self.paused:
        return

    # Did you hit anything? If so, end the game
    if len(self.player.collides_with_list(self.enemies_list)) > 0:
        arcade.play_sound(self.collision_sound)
        arcade.close_window()

    # Update everything
    self.all_sprites.update()

就在窗户关闭之前,会播放碰撞声。

背景音乐

添加背景音乐遵循与添加音效相同的模式。唯一的区别是它何时开始播放。对于背景音乐,您通常在关卡开始时启动它,因此在以下位置加载并启动声音.setup()

66# Spawn a new enemy every 0.25 seconds
67arcade.schedule(self.add_enemy, 0.25)
68
69# Spawn a new cloud every second
70arcade.schedule(self.add_cloud, 1.0)
71
72# Load your background music
73# Sound source: http://ccmixter.org/files/Apoxode/59262
74# License: https://creativecommons.org/licenses/by/3.0/
75self.background_music = arcade.load_sound(
76    "sounds/Apoxode_-_Electric_1.wav"
77)
78
79# Load your sounds
80# Sound sources: Jon Fincher
81self.collision_sound = arcade.load_sound("sounds/Collision.wav")
82self.move_up_sound = arcade.load_sound("sounds/Rising_putter.wav")
83self.move_down_sound = arcade.load_sound("sounds/Falling_putter.wav")
84
85# Start the background music
86arcade.play_sound(self.background_music)

现在,您不仅有音效,还有一些漂亮的背景音乐!

声音限制

arcade目前对声音的作用有一些限制:

  1. 任何声音都没有音量控制
  2. 无法重复声音,例如循环播放背景音乐。
  3. 在您尝试停止之前,无法判断当前是否正在播放声音。
  4. 没有ffmpeg,您只能使用 WAV 声音,它可能很大。

尽管有这些限制,但为您的arcadePython 游戏添加声音是非常值得的。

Python 游戏速度

任何游戏的速度都取决于其帧速率,即屏幕上图形更新的频率。较高的帧率通常会带来更流畅的游戏体验,而较低的帧率则让您有更多时间来执行复杂的计算。

arcadePython 游戏的帧速率由arcade.run(). Python 游戏循环调用.on_update().on_draw()大约每秒 60 次。因此,游戏的帧速率为每秒 60 帧或60 FPS

请注意,上面的描述说帧速率大约为60 FPS。不保证此帧速率是准确的。它可能会根据许多因素上下波动,例如机器上的负载或比正常更新时间更长的时间。作为 Python 游戏程序员,您希望确保您的 Python 游戏的行为相同,无论它以 60 FPS、30 FPS 或任何其他速率运行。那么你怎么做呢?

基于时间的运动

想象一个物体以每分钟 60 公里的速度在太空中移动。您可以通过将该时间乘以对象的速度来计算该对象在任何时间长度内将行进的距离:

根据速度和时间计算距离。

该物体在 2 分钟内移动 120 公里,在半分钟内移动 30 公里。

无论帧速率如何,您都可以使用相同的计算以恒定速度移动精灵。如果您以每秒像素为单位指定精灵的速度,那么如果您知道自上一帧出现以来已经过去了多长时间,您就可以计算出它每帧移动了多少像素。你怎么知道?

回想一下,.on_update()它采用单个参数delta_time. 这是自上次.on_update()调用以来经过的时间量(以秒为单位)。对于以 60 FPS 运行的游戏,delta_time将是 1/60 秒或大约 0.0167 秒。如果将经过的时间乘以精灵移动的量,那么您将确保精灵移动基于经过的时间而不是帧速率。

更新精灵运动

只有一个问题——既不接受Sprite.on_update()也不SpriteList.on_update()接受delta_time参数。这意味着无法将其传递给您的精灵来自动处理。因此,要实现此功能,您需要手动更新您的精灵位置。使用以下代码替换对self.all_sprites.update()in的调用.on_update()

def on_update(self, delta_time: float):
    """Update the positions and statuses of all game objects
    If paused, do nothing

    Arguments:
        delta_time {float} -- Time since the last update
    """

    # If paused, don't update anything
    if self.paused:
        return

    # Did you hit anything? If so, end the game
    if len(self.player.collides_with_list(self.enemies_list)) > 0:
        arcade.play_sound(self.collision_sound)
        arcade.close_window()

    # Update everything
    for sprite in self.all_sprites:
        sprite.center_x = int(
            sprite.center_x + sprite.change_x * delta_time
        )
        sprite.center_y = int(
            sprite.center_y + sprite.change_y * delta_time
        )

在这个新的代码,你手动修改每个精灵的位置,乘以.change_x.change_y通过delta_time。这确保了精灵每秒移动一个恒定的距离,而不是每帧移动一个恒定的距离,这可以使游戏玩法更加流畅。

更新精灵参数

当然,这也意味着您应该重新评估和调整所有精灵的初始位置和速度。回想一下位置,.velocity你的敌人精灵在创建时会给出:

 93def add_enemy(self, delta_time: float):
 94    """Adds a new enemy to the screen
 95
 96    Arguments:
 97        delta_time {float} -- How much time as passed since the last call
 98    """
 99
100    # First, create the new enemy sprite
101    enemy = FlyingSprite("images/missile.png", SCALING)
102
103    # Set its position to a random height and off screen right
104    enemy.left = random.randint(self.width, self.width + 80)
105    enemy.top = random.randint(10, self.height - 10)
106
107    # Set its speed to a random speed heading left
108    enemy.velocity = (random.randint(-20, -5), 0)

使用基于时间的新移动计算,您的敌人现在将以每秒 20 像素的最大速度移动。这意味着在 800 像素宽的窗口上,最快的敌人需要 40 秒才能飞过屏幕。此外,如果敌人从窗口右侧八十个像素开始,那么最快的将需要整整四秒才能出现!

调整位置和速度是使您的 Python 游戏变得有趣和可玩的一部分。首先将每个调整为 10 倍,然后从那里重新调整。对云以及玩家的移动速度也应进行相同的重新评估和调整。

调整和增强

在您的 Python 游戏设计过程中,您没有添加一些功能。要添加到该列表中,您可能在 Python 游戏和测试过程中注意到了一些额外的增强和调整:

  1. 当游戏暂停时,敌人和云彩仍然由预定的函数生成。这意味着,当游戏未暂停时,一大波游戏正在等着您。你如何防止这种情况发生?
  2. 如上所述,由于arcade声音引擎的一些限制,背景音乐不会重复。你如何解决这个问题?
  3. 当玩家与敌人发生碰撞时,游戏会突然结束而不播放碰撞声音。您如何在关闭窗口之前保持游戏打开一两秒钟?

您可能还可以添加其他调整。尝试将其中一些作为练习来实施,并在评论中分享您的结果!

关于来源的说明

您可能已经注意到加载背景音乐时的评论,其中列出了音乐来源和知识共享许可的链接。这样做是因为该声音的创建者需要它。许可证要求规定,为了使用声音,必须提供正确的归属和许可证链接。

以下是一些音乐、声音和艺术来源,您可以搜索有用的内容:

当您制作游戏并使用从其他来源下载的内容(例如艺术、音乐或代码)时,请确保您遵守这些来源的许可条款。

结论

电脑游戏是对编码的一个很好的介绍,arcade图书馆是一个很好的第一步。设计为用于制作游戏的现代 Python 框架,您可以创建具有出色图形和声音的引人入胜的 Python 游戏体验。

在本教程中,您学习了如何:

  • 安装arcade
  • 在屏幕上绘制项目
  • 使用arcadePython 游戏循环
  • 管理屏幕上的图形元素
  • 处理用户输入
  • 播放音效和音乐
  • 描述 Python 游戏编程arcadepygame

我希望你arcade试一试。如果你这样做了,那么请在下面发表评论,祝 Pythoning 快乐!

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。