第4章:让我们把它变成一款“游戏”
在上一章中,我们通过让玩家发射子弹并击败敌人,完成了游戏玩法的基础部分。
在本章中,我们将进一步推进,使这个项目真正能够作为一款游戏进行游玩。
要成为一款游戏还缺少什么?
在当前版本中,玩家可以移动,敌人也存在,但仍缺少几个要素。
- 舞台太小,需要扩大。
- 玩家虽然有受伤反应,但实际上无法被击败。我们也看不到玩家剩余的HP,因此需要将其可视化。
- 最后,游戏没有目标。理想情况下,我们可以加入Boss战,但为了简化,我们设定为:击败5个敌人即可通关。
在上一章中,我们通过让玩家发射子弹并击败敌人,完成了游戏玩法的基础部分。
在本章中,我们将进一步推进,使这个项目真正能够作为一款游戏进行游玩。
在当前版本中,玩家可以移动,敌人也存在,但仍缺少几个要素。
首先,让我们通过绘制更多图块来扩展阶段。由于阶段将变得更大,玩家最终会移动到当前摄像机范围之外,因此我们还需要确保摄像机跟随玩家。
图块是使用 基础(TileMapLayer) 节点创建的,因此让我们使用此节点来扩展阶段。
切换到 stage1 场景选项卡。
在场景窗口中,选择 基础(TileMapLayer) 节点。
在底部窗口中,打开 TileMap 选项卡并选择一个图块。
在右侧空间自由放置图块。为了保持阶段平衡:
空中的平台和墙壁高度不应超过 4 个图块。
间隙或坑洞的宽度应约为 4 个图块 或更窄,以便可以轻松跳过。
一旦布置了足够的图块,请继续下一步。
在 ACTION GAME MAKER 中,摄像机跟随功能通过名为 目标 ID(Target ID) 的系统进行控制。还记得我们创建玩家并添加 CameraTargetSettings 节点时吗?该节点有一个名为 目标 ID 的属性。
摄像机节点 ZoomCamera2D 也拥有一个 目标 ID 属性。当摄像机和游戏对象共享相同的目标 ID 时,摄像机将自动跟随该对象。
现在让我们为 ZoomCamera2D 和玩家的 CameraTargetSettings 配置目标 ID。
在 Stage1 场景选项卡中,选择 InitialCamera (ZoomCamera2D) 节点。
在检查器(Inspector)中,展开 目标 ID (数组…) 属性。
点击 + 添加元素 按钮。
将出现一个文本字段。删除占位符文本 <null> 并输入 player。
切换到 player 场景选项卡。
选择 CameraTargetSettings 节点。
在检查器中,在 目标 ID 字段中输入 player。
现在让我们测试运行扩展后的场景,以检查一切是否正常工作。尝试将玩家移动到场景的最右侧边缘。
如果设置正确,摄像机应能平滑地跟随玩家。
摄像机未跟随: 请仔细检查 Stage1 中的 InitialCamera 和玩家的 CameraTargetSettings 中的目标 ID 设置。
无法到达场景最右侧: 在 Base (TileMapLayer) 中调整坑洞的大小或墙壁的高度。
就像敌人一样,当角色的生命值(HP)降至 0 时,角色也应被击败。由于没有适合死亡动画的帧,让我们使用粒子来创建视觉效果。
粒子本质上是大量散布的微小图像,用于创建某种效果。可以想象成彩纸屑——这样更容易理解。使用粒子,您可以自由调整每个“碎片”的外观及其运动方式。
在Godot 引擎中,这通常通过GPUParticle2D或CPUParticle2D节点来实现。
在ACTION GAME MAKER中,粒子通过ParticleObject节点进行处理。
ParticleObject自带超过20 种粒子模板,您可以直接使用。
创建粒子对象与创建游戏对象完全相同。
创建一个新场景标签页。
对于根节点,选择GameObject。
将对象名称设置为 DeathParticle,选择模板为particles,然后点击创建。
在场景窗口中,选择新创建的DeathParticle节点。
在检查器中,将粒子模板从None更改为Fireworks。
将自动添加一个GPUParticles2D节点。
在 GPUParticles2D 检查器中,勾选Emitting属性。这将启用粒子发射。
您现在应该能在场景视图中看到粒子正在发射,形成烟花效果。
最后,右键点击 [未保存](*) 标签页,将场景保存为 deathparticle.tscn。
当玩家的生命值(HP)降至 0 时,他们应停止移动并触发“死亡粒子”效果。
从**任意状态(AnyState)**连接到此状态是合理的,但存在一个问题:
目前,如下所示,无论玩家处于何种状态,只要与敌人的攻击发生碰撞,玩家就会进入**受到伤害(Take Damage)**状态。
由于 HP = 0 意味着玩家已经受到过攻击,他们甚至可能在“死亡”状态下仍然进入**受到伤害(Take Damage)**状态。
为了解决这个问题,我们将为伤害转换添加**“HP 不为 0”**的条件,以防止玩家在已经死亡时触发该转换。
由于没有完全匹配的动画,我们将复用**受伤(Damage)动画。但为了在视觉上有所区分,我们还将使用滤镜(Filter)**效果,以明确显示玩家已被击败。
打开**玩家(Player)**场景。
将编辑器从2D切换为脚本(Script)。

在受到伤害(Take Damage)状态附近的上方区域,右键单击并选择添加状态(Add State)。
将新状态重命名为死亡(Death)。
将动画(Animation)设置为DamageTaken。
展开**动作设置(Action Settings)**部分。
启用忽略移动输入(Ignore Movement Input)。
单击+ 添加可执行动作(Add Executable Action)。
选择显示粒子(DisplayParticle)。
在粒子对象路径字段中,单击
图标,并选择之前创建的DeathParticle.tscn。
单击添加(Add)。
再次单击+ 添加可执行动作(Add Executable Action)。
选择应用对象滤镜(ApplyObjectFilter)。
将滤镜类型(Filter Type)设置为透明(Transparent),将结束时间(Finish Time)设置为3.0 秒。这将使玩家逐渐在 3 秒内淡出。
单击添加(Add)。
如果动作列表看起来正确,则死亡状态已准备就绪。
右键单击任意状态(AnyState) → 添加链接(Add Link) → 将其连接到死亡(Death)。
单击+ 添加其他条件(Add Other Condition)。
选择HP 为零(HPIsZero)并单击添加(Add)。
最后,我们需要确保当 HP 已经为 0 时,**受到伤害(Take Damage)转换不会触发。我们可以通过启用反向(Is Reversed)**选项来实现。
选择从**任意状态(AnyState)到受到伤害(Take Damage)**的链接。
单击+ 添加条件(Add Condition)。
选择HP 为零(HPIsZero)。
启用反向(Is Reversed)。这将把条件更改为**“HP 不为零”**。
如果“其他条件”面板显示反转后的条件(≠ 图标高亮),则设置正确。
现在让我们进行测试。由于玩家的初始 HP 设置为 1,他们在受到一次敌人攻击后就会进入死亡状态。
如果一切设置正确:
受到攻击时,应出现**死亡粒子(Death Particle)**效果。
玩家应逐渐淡出。
击败后,按F5重置游戏。
粒子不显示: 检查DeathParticle.tscn和**显示粒子(DisplayParticle)**动作。
玩家未淡出: 检查**应用对象滤镜(ApplyObjectFilter)**动作的设置。
死亡后仍触发伤害反应: 仔细检查链接条件以及**受到伤害(TakeDamage)**转换上的反转条件。
目前,玩家被一击击败过于严苛,因此我们需要增加玩家的 HP。但如果玩家拥有更多 HP,我们还需要一种方式来显示剩余量。为此,我们将创建一个 HP 条。
在 ACTION GAME MAKER 中,您可以使用 SimpleGauge 或 ImageGauge 节点来显示计量条。本教程将使用 SimpleGauge。
由于相机现在会跟随玩家移动,如果将 HP 条放置在与玩家相同的图层中,它将会移出视野。对于 HP 条等需要始终可见的元素,您应该使用 UI 图层。
您可能还记得,之前的 ACTION GAME MAKER 图层 结构如下。UI 图层 和 屏幕效果图层 是 不受相机影响的特殊图层。
这意味着放置在 UI 图层中的任何内容将始终保持可见,非常适合用于 HP 条。另一方面,屏幕效果图层专用于由动作触发的特殊效果,因此我们将在此处使用 UI 图层。
选择玩家的 BaseSettings 节点。
在检查器中,将 HP 和 Max HP 的值从 1 更改为 10。
切换到 stage1 场景标签页,并将编辑器视图从 Script 改回 2D。

选择 UI 节点。
点击场景窗口左上角的 + (Add Child Node) 按钮。
选择 SimpleGauge 并点击 Create。
调整大小。最初,它可能会显得被压扁。
拖动橙色手柄将其扩展为合适的大小(参见教程中的参考图片)。
UI 图层的可见范围由细蓝线标记。将计量条移动到此边界内的 左上角。
接下来,将计量条链接到玩家的 HP。在检查器中,将 Variable Type 设置为 Object。
会出现一个新属性:Specify Target Object Path。点击
图标。
选择 player.tscn 并点击 Open。
Variable Name 字段应自动显示 hp。这指定了用作 当前值 的变量,因此保持原样即可。
勾选 Use Variable as Max Value 复选框。
重复步骤 9–10,再次指定 player.tscn。
在最大变量字段中,它可能默认为 object_id。将其更改为 max_hp。这告诉计量条使用玩家的最大 HP 作为其最大值。
点击 Test Play 按钮运行游戏。如果设置正确:
HP 条将显示在屏幕左上角。
当玩家受到伤害时,HP 条将相应减少。
计量条不可见: 确保 SimpleGauge 是 UI 图层 的子节点,并且位于蓝色边界内。
HP 初始值过低: 请再次检查 BaseSettings 中玩家的 HP 是否已设置为 10。
即使最大 HP 存在,HP 也瞬间降至 0: 确认 BaseSettings 中的 Max HP 已设置为 10,并且 SimpleGauge 的 Max Value Variable 已正确设置为 max_hp。
在测试游玩时,您可能已经注意到,当玩家掉入坑中时,他们会无限坠落。让我们通过将玩家设置为在掉入坑中时“失败”来解决这个问题。
解决方案是限制摄像机的移动范围,使其不会无限向下追逐,然后当玩家离开摄像机范围时,将其设置为进入“死亡”状态。
选中 InitialCamera 节点。
在检查器中,展开 Limits 部分。
将 Bottom 限制值从 10000000 更改为 500。
运行测试游玩。
1000 或 2000,直到其正确对齐。由于 “死亡” 状态已存在,我们只需在现有链接中添加一个新条件:“OffScreen”。
切换到 Player 场景,并将编辑器视图更改为 Script。
选中从 AnyState → Death 的链接。
点击 + Add Condition。
选择条件 OffScreen。
将 Data Type 从 Unset 更改为 This Node。
将 Connection With Previous Condition 设置为 OR,然后添加。
确认条件列表现在正确显示:
HPIsZero
OR
OffScreen
运行测试游玩并掉入坑中。
如果设置正确,当玩家掉出摄像机范围时,应播放烟花粒子效果,表示失败。
我们需要一种方法来从数字 5 开始倒计时。每次击败一名敌人,这个数字应减少,当它达到 0 时,游戏应触发通关事件。为了实现这一点,我们将使用一种称为变量的东西。
变量就像一个存储值的容器。例如,我们之前处理过的玩家 HP(生命值)实际上就是一个变量。当玩家受到敌人攻击时,HP 容器内的值会减少 1。
在 ACTION GAME MAKER 中,定义变量主要有两种方式:
我们之前使用的 HP 就是第一种类型的例子。
对象变量 (VariableSettings): 与特定对象绑定的变量。如果对象被移除,变量也会随之消失。
项目变量: 可以在项目中任何地方访问的全局变量。只要项目正在运行,它们就保持可用,适用于保存数据等情况。
在实际应用中:
HP、攻击力、跳跃力 → 最好作为 对象变量 处理。
最高分、金币数量、生命数 → 最好作为 项目变量 处理,因为它们会在多个关卡中持久存在,并且应在整个游戏中共享。
从技术上讲,两种类型都可以。然而,由于多个对象(敌人)将与这个数字交互,我们将使用 项目变量。
因此流程将是:
当敌人进入 “消失” 状态 → 将“剩余敌人”计数减 1。
当“剩余敌人”达到 0 → 触发游戏通关序列。
我们将创建一个专门的 游戏对象 来处理击杀计数。该对象将放置在 UI 层,以便始终可见。与 HP 条一样,它将显示已击败敌人的数量,让玩家知道还剩多少敌人需要清除。
步骤如下:
项目变量位于数据库中。现在让我们添加一个。
点击左上角菜单中的数据库按钮。

将打开一个名为数据管理的新窗口。
从用户数据库选项卡切换到项目变量选项卡。
点击窗口左上角的+按钮。
底部将出现一个名为variables1的新行。将其重命名为剩余敌人。
将其值设置为5.0,因为我们希望玩家击败 5 个敌人。
点击确定关闭窗口。设置完成。
在 ACTION GAME MAKER 中,您可以使用 更改属性 动作来修改变量。
由于支持基本的算术运算(加、减、乘、除),我们可以将逻辑实现为:剩余敌人 -= 1。
打开 enemy 场景标签页,并将编辑器视图切换为 脚本。
选择 Vanish 状态,然后点击 + 添加可执行动作。
选择 ChangeObjectProperty。
按如下方式配置字段:
目标对象类型:项目数据库
数据库类型:项目变量
记录名称:Remaining Enemies
表达式:-=
常量值:1
共有 五个 字段需要设置,请仔细核对每一项。
通过此动作,项目变量 Remaining Enemies 将减少 1。
最后,重新排列执行动作的顺序。动作是 从上到下 执行的,因此如果 Vanish Self 在 更改属性 之前执行,变量将不会被更新。
拖动 更改属性 旁边的 汉堡菜单(三条横线)图标,将其移动到 顶部,使 更改属性 首先 执行。
-= 而不是单独的 -?在编程中,-= 是一个简写运算符,表示:
新值 = 旧值 - 1
它在一个步骤中同时完成 减法 并将结果 赋值回变量。
如果只使用 -(减号)而不加 =,则无法指定将结果存储到何处,因此变量实际上不会发生变化。
我们将创建一个 UI 对象来管理“剩余敌人”变量。
该对象将显示“剩余敌人”的当前值,并在该值达到 0 时触发清除序列。为了清晰起见,我们将通过 Sprite2D 显示敌人的图标,并使用动作在其旁边显示 剩余敌人 变量。对于敌人图标,我们可以直接复用现有的敌人精灵图像。
将编辑器视图切换为 2D。
打开一个新场景标签页,并将根节点选择为 GameObject。
将对象名称设置为 RemainingEnemiesManager,选择模板:UI,类型:Empty,然后点击创建。
保存新创建的场景。
从文件系统中,将 enemy.png(敌人使用的精灵)拖入编辑器视图中原点(红绿轴交汇处)左侧的空白区域。
将自动添加一个名为 Enemy 的 Sprite2D。在 Godot 中,当你直接将图像文件拖放到编辑器视口中时,它会自动创建一个 Sprite2D 节点并为你分配纹理。
首先,我们只关注显示变量。我们只需要一个名为**“计数”的状态,用于显示剩余敌人**变量的值。
要显示变量,请使用DisplayText动作。
在场景窗口中,选择 RemainingEnemiesManager(游戏对象)节点,然后点击
+(附加脚本)。
创建 RemainingEnemiesManager.vs。
将默认的 State001 重命名为 Count。
添加 DisplayText 执行动作,并按如下方式配置基本设置:
文本类型:变量
变量来源:数据管理
数据库类型:项目变量
记录名称:剩余敌人
按如下方式配置布局与动作部分:
无限持续时间:开
字体:New SystemFont
字体大小:64
显示大小:x = 80, y = 80
边距(上/左/右/下):均为 0
水平对齐:居中
垂直对齐:居中
注意:关于“DisplayText”动作中的放置
此动作会创建一个指定大小的文本框,以参考点为中心,然后在您选择的持续时间内在其中显示指定的文本(变量)。
当参考点为**“此对象的中心”时,意味着原点**(红色和绿色轴相交的点)。
在此设置中,您正在创建一个以原点为中心的 80×80 像素 文本框,将文本对齐到中心,并将显示持续时间设置为无限。
让我们将其放入场景并进行测试。因为我们希望它始终可见,所以我们将把它放在 UI 层 中。
切换到 stage1 场景标签,并将编辑器更改为 2D。

选择 UI (CanvasLayer) 下的 SimpleGauge 节点。
在文件系统中,选择 RemainingEnemiesManager.tscn,并将其拖放至 UI 层蓝色框架内 的 右上角 区域。
运行测试播放并确认:
开始时正确显示 剩余敌人 = 5。
击败一个敌人后,数字减少为 4。
图标和数字均未显示:
确保该对象是 UI 层的子对象,并且放置在 蓝色框架内(UI 显示区域)。
图标显示但数字未显示:
在剩余敌人管理器对象中,确认图标位于原点附近,并仔细检查 Count 状态中的 DisplayText 动作设置。
击败敌人时数字未变化:
在敌人对象的 Vanish 状态中,验证执行顺序是否为先“更改属性”,然后**“移除自身”**。
接下来,让我们构建清除序列。我们将同样使用 DisplayText 动作来实现。
我们将在屏幕中央醒目地显示 “STAGE CLEAR”,以营造正式通关的感觉。
过渡条件将是 Remaining Enemies(剩余敌人)变量达到 0 时触发。
切换到 RemainingEnemiesManager 场景标签页,并将编辑器视图更改为 Script(脚本)。
在 Count 状态附近,点击 Add State(添加状态)。
将新状态重命名为 Stage Clear。
点击 + Add Executable Action(添加可执行动作)。
选择 DisplayText。
按如下方式配置(字段较多,请仔细核对每一项):
Text Body(文本内容): STAGE CLEAR
Unlimited Duration(无限持续时间): 开启
Font(字体): New SystemFont
Font Size(字体大小): 96
Display Area(显示区域): x = 1200, y = 120
Horizontal Alignment(水平对齐): Center(居中)
Vertical Alignment(垂直对齐): Center(居中)
Reference Point(参考点): Use Scene as Base(使用场景作为基准)
Anchor(锚点): Center(居中)
“Use Scene as Base”是什么意思?
这意味着显示不是基于该对象的位置,而是锚定在整个游戏场景上——即摄像机显示的区域。在此设置中,文本框(1200×120)位于场景中央,而 STAGE CLEAR 文本则位于该文本框的中央。
右键点击 Count 状态 → Add Link(添加链接)→ 将其连接到 Stage Clear。
点击 + Add Condition(添加条件)。
选择 SwitchVariableChanged。
配置条件:
Variable Type(变量类型): Variable
Target Type(目标类型): Project Variable(项目变量)
Database Record Name(数据库记录名称): Remaining Enemies
Variable Condition(变量条件): =
(这将使过渡在 Remaining Enemies == 0 时发生。)
首先,我们需要足够的敌人——总共放置 五个——然后测试清除序列是否正确触发。
切换到 stage1 场景标签页,并将编辑器视图设置为 2D。
在 BaseLayer 下,选择一个子节点,例如 enemy 或 player,以准备放置。
从 FileSystem(文件系统)中,将 enemy.tscn 拖入场景以放置另一个敌人。
重复步骤 1–3,直到放置了 enemy5。
开始测试运行。
如果一切设置正确,击败所有敌人后应显示 STAGE CLEAR。
“我放置的五个敌人不见了”:
它们可能掉出了屏幕范围。请确保每个敌人都放置在坚实的地面上,并且在 Template Move 中启用了 Don’t fall off ledges(不从边缘掉落)选项。
“计数达到 0 时没有任何反应”:
请验证 Stage Clear 状态的 DisplayText 设置,并确认链接的过渡条件(项目变量 Remaining Enemies 等于 0)是否正确。