ArcadePython 游戏框架入门

网友投稿 1002 2022-05-29

目录

背景和设置

基本arcade程序

arcade概念

初始化

窗口和坐标

画画

面向对象设计

游戏循环

Python 游戏设计基础

导入和常量

窗口类

精灵和精灵列表

调度功能

添加敌人

移动精灵

移除精灵

添加云

键盘输入

更新游戏对象

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

基于您的平台的完整安装说明可用于Windows、Mac、Linux甚至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 概念

就像pygame,arcade代码运行在几乎所有支持 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_都以一致的命名和参数模式开始并遵循。绘制填充和轮廓形状有不同的功能:

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

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知道何时绘制任何东西呢?让我们来看看这意味着什么。

游戏循环

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

该程序确定游戏是否结束。如果是,则循环结束。

处理用户输入。

游戏对象的状态根据用户输入或时间等因素进行更新。

游戏根据新状态显示视觉效果并播放声音效果。

在 中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的精灵列表的工具。精灵列表的作用不仅仅是保存所有的精 灵。它们实现了三个重要的行为:

您可以通过一次调用来更新列表中的所有精灵SpriteList.update()。

您可以通过一次调用来绘制列表中的所有精灵SpriteList.draw()。

您可以检查单个精灵是否与列表中的任何精灵发生碰撞。

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

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

您用于.clouds_list更新云位置。

最后,你.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.left,Sprite.right,Sprite.top,和Sprite.bottom边缘,以及。

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

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

调度功能

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

要调用的函数的名称

每次调用之间等待的时间间隔,以秒为单位

由于您希望在整个游戏中同时出现敌人和云,因此您设置了一个计划函数来创建新的敌人,第二个函数来创建新的云。该代码进入.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 游戏设计来看,敌人具有三个关键属性:

它们出现在窗口右侧的随机位置。

它们沿直线向左移动。

当它们离开屏幕时它们就会消失。

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

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_x和Sprite.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()松开按键时调用。这两个函数都接受两个整数参数:

symbol 代表按下或释放的实际键。

modifiers表示哪些修饰符被关闭。这些措施包括Shift,Ctrl,和Alt键。

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

四个方向键Up,Down,Left,和Right

键I, J, K, and L,分别映射到 Up、Left、Down 和 Right

对于左手控制,键W, A, S, 和D也映射到上、左、下和右

对于此游戏,您将使用箭头和I/ J/ K/ L。每当用户按下移动键时,玩家精灵就会向那个方向移动。当用户释放移动键时,精灵将停止向该方向移动。您还提供了一种使用 退出游戏的方法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()。您可以重写此方法来为您的游戏提供适当的行为,包括游戏移动和其他行为。对于这个游戏,你需要做一些事情来正确更新一切:

您检查游戏是否已暂停。如果是这样,那么您可以退出,因此不会发生进一步的更新。

您更新所有精灵以使其移动。

您检查玩家精灵是否已移出屏幕。如果是这样,那么只需将它们移回屏幕上即可。

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

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 行处理。这行代码有以下三个原因:

每个精灵都是self.all_sprites列表的成员。

呼叫到self.all_sprites.update()在呼叫结果.update()列表中的每一个角色。

列表中的每个精灵都有.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方法之一来快速检测碰撞:

Sprite.collides_with_point((x,y))True如果给定点(x,y)在当前精灵的边界内,则返回,False否则返回。

Sprite.collides_with_sprite(Sprite)True如果给定的精灵与当前精灵重叠,则返回,False否则返回。

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 游戏循环中的动作顺序:

您更新游戏对象的状态。你在.on_update().

您将所有游戏对象绘制在它们的新位置。你在.on_draw().

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

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

附加功能

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

声音

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

当玩家向上移动时会播放第一个音效。

当玩家向下移动时会播放第二个音效。

发生碰撞时播放第三种音效。

背景音乐是您添加的最后一件事。

您将从音效开始。

在您可以播放任何这些声音之前,您必须加载它们。你这样做.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目前对声音的作用有一些限制:

任何声音都没有音量控制。

无法重复声音,例如循环播放背景音乐。

在您尝试停止之前,无法判断当前是否正在播放声音。

没有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 游戏和测试过程中注意到了一些额外的增强和调整:

当游戏暂停时,敌人和云彩仍然由预定的函数生成。这意味着,当游戏未暂停时,一大波游戏正在等着您。你如何防止这种情况发生?

如上所述,由于arcade声音引擎的一些限制,背景音乐不会重复。你如何解决这个问题?

当玩家与敌人发生碰撞时,游戏会突然结束而不播放碰撞声音。您如何在关闭窗口之前保持游戏打开一两秒钟?

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

关于来源的说明

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

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

OpenGameArt.org:声音、音效、精灵和其他艺术作品

Kenney.nl:声音、音效、精灵和其他艺术作品

玩家艺术 2D:精灵和其他艺术作品

CC Mixer:声音和音效

Freesound:声音和音效

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

结论

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

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

安装arcade库

在屏幕上绘制项目

使用arcadePython 游戏循环

管理屏幕上的图形元素

处理用户输入

播放音效和音乐

描述 Python 游戏编程arcade与pygame

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

5G游戏 Python

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:《MindSpore基础实践》——MindSpore基础
下一篇:技术大咖汇聚,华为云生态CTO圆桌会畅谈SaaS化构建之道
相关文章