通过制作2D平台游戏学习Godot 4 —— 第二部分:玩家设置与移动输入
您将在本部分中学习到的内容:
- 如何创建、运行和实例化新场景。
- 如何向节点添加输入动作、碰撞和物理。
- 如何通过脚本操纵场景。
- 如何添加计算速度和运动。
现在我们已经设置好了项目,现在可以开始创建块,最终将我们的骨架项目变成一个完整的游戏。在这一部分中,我们将创建一个新的场景,其中包含组成玩家的节点。我们的玩家是我们在玩游戏时将控制的实际角色——它的场景将包含所有节点和脚本,这些节点和脚本将使我们能够看到这个角色并控制它们,以便我们可以四处奔跑并与世界互动。
在主场景中,我们可以继续删除上一节中添加的 Sprite2D 节点 — 因为我们只添加了该节点来演示纹理过滤更改。我们将为 Player 创建一个全新的场景,然后在主场景中实例化(或实例化)它。实例化允许我们从模板复制对象,以便我们可以单独修改它们。换句话说,我们将 Player 与主场景分开,因此我们可以将其视为单独的对象。这样,我们的 Player 就是它自己的实体 — 一个不依赖于我们的关卡(或主)场景的实体。
继续从主场景中删除 Sprite2D 节点,以便我们剩下这些:
然后,让我们创建一个新场景。让我们创建一个新场景。您可以通过单击 Main(*) 旁边的加号图标来执行此操作,这将创建一个新的空场景。
在场景码头中,添加一个新的根节点。我们需要将此根节点设为 CharacterBody2D 节点,这是一个专门用于角色的 2D 物理身体节点,可通过脚本移动。我们需要此节点为玩家角色提供物理特性,使其可以四处移动。
双击新添加的 CharacterBody2D 节点将其重命名为“Player”,然后将场景保存在 Scenes 文件夹下。
您将看到 Player 根节点旁边弹出一个黄色三角形。如果将鼠标悬停在它上面,它会告诉您该节点没有形状。它所指的形状是碰撞形状,该形状将围绕我们的角色,以便它可以与关卡中的其他项目发生碰撞和互动。
让我们通过向我们的玩家场景添加 CollisionShape2D 节点来清除此警告。
这将返回另一个警告,因为即使我们添加了碰撞节点,我们仍然没有为其分配碰撞形状。我们需要设置shape属性来配置形状。我们可以通过单击我们的CollisionShape2D节点并在Inspector面板中的Shape属性中为其分配一个形状来做到这一点。让我们添加一个新的RectangleShape2D形状。
我选择 RectangleShape2D 形状是因为这是我的角色的形状 — 我们以后也可以随时更改此形状以满足我们的需求。您为对象选择的形状类型取决于您希望它们完成的任务(它们需要从哪里碰撞)以及它们的身体形状。因此,圆形角色可能具有 CircleShape2D,依此类推。按 F 键将焦点放在您的节点上。您将看到您的形状已添加。
接下来,我们需要在 Player 场景中添加一个精灵,以便我们能够直观地看到我们的节点。如果您想要一个不移动或没有动画的项目,您可以添加一个 Sprite2D 节点来充当这个精灵。在我们的例子中,我们希望 Player 四处移动,并使用动画来实现这一点。因此,我们将添加一个 AnimatedSprite2D 节点。AnimatedSprite2D 与 Sprite2D 节点类似,只是它带有多个纹理作为动画帧。
确保在添加此节点时选择了根节点(Player),而不是选择了 CollisionShape2D 节点。我们希望 AnimatedSprite2D 节点成为根节点的子节点,而不是碰撞形状!
此节点还会返回警告,因为它需要 SpriteFrames 资源。此资源允许您导入图像文件(或包含所述文件的文件夹)以提供精灵的动画帧。我们可以通过 Sprite Frames 属性在 Inspector 面板中添加新的 SpriteFrames 资源。单击 Sprite Frames 属性旁边的
如果您单击此新添加的资源,它将在下面的编辑器中打开动画面板。在这里我们可以配置我们的 SpriteFrames 资源。配置包括添加新动画、删除动画、播放动画或编辑动画的功能。默认动画可以删除或重命名。我们将在下一部分中添加所有玩家的动画,以便我们可以更详细地探索此节点。现在,让我们通过单击“从 Sprite Sheet 添加帧”选项为默认动画分配一个精灵。
会弹出一个窗口,让我们选择 Sprite Sheet。此 Spritesheet 将包含组成完整动画的动画帧。我们想添加一个基本的空闲动画,因此请进入“res://Assets/Kings and Pigs/Sprites/01-King Human/”并选择“Idle (78x58).png”表。
从这里开始,我们需要裁剪动画。我们需要将水平和垂直值更改为我们可以计算的动画帧数。水平方向(列方向)我们计算出 11 个国王,垂直方向(行方向)我们计算出 1 个国王。
让我们为空闲动画选择每个帧(按顺序选择它们)。
稍后,您可能会注意到,当我们的玩家改变方向(左转或右转)时,会出现一个奇怪的间隙,因为我们的 Sprite 旁边有一个很大的间隙 — 即 Sprite Frame 不在中心。由于这不是我们要发布的游戏,而且我们获得的资产是免费的,所以我们不会太在意这一点,但如果您正在制作想要发布的游戏,请确保您制作或获得的 spritesheet 中的精灵在裁剪时处于中心位置。如果这对您来说是一个大问题,那么我建议您去 Itch.io 上寻找其他资产!
图 5:因精灵空间而导致的尴尬翻转示例
好的,让我们将“默认”动画重命名为空闲。我们还想关闭循环,因为我们不希望空闲动画不断循环。5 FPS 对我来说也太慢了,所以我要将 FPS 提高到 10。您可以尝试使用 FPS 值来查看您喜欢的值 — 只需考虑您希望动画播放的速度即可。
现在我们已经设置好了播放器,让我们在场景中添加一个新脚本,这样我们就可以添加代码来移动播放器。选择您的根节点,然后单击旁边的滚动图标以添加新脚本。将此脚本重命名为“播放器”,并将其保存在您的脚本文件夹下。
您可以将脚本添加到任何节点,并且您将能够通过脚本顶部的“extends Node”代码识别脚本所附加的节点。我们有一行写着“extends CharacterBody2D:”。此行表示此脚本继承自名为 CharacterBody2D 的类。
我们希望玩家可以向四个方向移动:左、右、上、下。当我们按下左和右输入时,玩家应该向左或向右移动。当我们按下上输入爬梯子时,玩家应该向上移动。我们只会在玩家坠落时向下移动,因此我们将为玩家添加速度,而不是为此添加输入。我们还需要为玩家添加一个输入,以便在玩家按下跳跃键时跳跃。
让我们从左、右和上移动的输入开始。Godot 已经内置了输入操作,但我们将为这些操作添加更多输入。输入操作映射到物理控件,例如键盘按键、操纵杆、按钮等。然后,这些操作被分配给将在游戏中移动我们的角色的功能。
要添加输入操作,我们转到“项目设置”>“输入映射”。通过启用“显示内置操作”切换按钮来显示内置操作。
我们想为 ui_left、ui_right 和 ui_up 输入添加额外的输入。ui_ 代表“用户输入”,您在未来的输入操作中不必遵循此命名约定,但对于我们的项目,我们会这样做。您可以通过按下旁边的铅笔图标来编辑这些输入。您还可以通过单击旁边的加号图标将其他键分配给此输入。
让我们将键盘上的“W”键分配给 ui_up 输入。现在我们可以同时使用“W”和“⇧”键向上移动。
让我们将键盘上的“A”键分配给我们的 ui_left 输入。现在我们可以同时使用“A”和“⇦”键向左移动。
让我们将键盘上的“D”键分配给我们的 ui_right 输入。现在我们可以同时使用“D”和“⇨”键向右移动。
最后,让我们添加一个名为 ui_jump 的新输入,并将“Space”键分配给它。现在,当我们按下 Space 键时,我们就可以跳跃。
在我们的代码中,我们现在可以继续添加移动函数。目前,我们只会左右移动玩家。在下一部分添加移动动画时,我们将添加跳跃和攀爬功能。要移动玩家,我们需要使用 _physics_process 函数。此函数在每一帧处理 CharacterBody2D 节点的物理和移动,因此我们的玩家可以在每一帧移动。
我们首先必须定义玩家的速度和重力变量,因为我们希望玩家以“x”速度水平移动,以“x”重力垂直下落。我们将导出这些变量,以便可以从 Godot 编辑器中编辑它们。
### Player.gd
扩展了 CharacterBody2D
#player 运动变量
@export var speed = 100
@export var Gravity = 200
您现在可以在 Inspector 面板中更改播放器的这些变量值。
在 Godot 中,我们可以通过 Input 单例(可以在任何函数和任何类中调用)或 input() 函数(每当发生输入事件时都会调用)捕获输入。如果您想捕获一次输入(例如跳跃时),您将在 input() 函数中捕获输入,但如果您想在输入发生时捕获输入(例如冲刺时),您将使用 Input 单例。我制作了一个 Mural 板,其中给出了可由 Input 单例和函数调用的方法的示例,您可以在此处查看。
图 5:输入单例与 input() 函数
现在,我们需要创建一个函数来处理玩家的水平移动(向左或向右)。在这个函数中,我们将计算“ui_left”和“ui_right”输入的水平向量 (1, -1) 值。如果通过“Input.get_action_strength(“ui_right”)”方法按下右键,我们的函数将返回 1。如果通过“Input.get_action_strength(“ui_left”)”按下左键,该函数将返回 1。通过从右值中减去左值,如果向左移动,则得到 -1,如果向右移动,则得到 1,如果未按下或同时按下,则得到 0。
图 6:玩家向左或向右移动时的矢量位置
然后我们将这些返回值(0、1 或 -1)乘以角色的速度来设置水平速度。这会让角色根据玩家的输入向左或向右移动。
### Player.gd
extends CharacterBody2D
#player movement variables
@export var speed = 100
@export var gravity = 200
func horizontal_movement():
# if keys are pressed it will return 1 for ui_right, -1 for ui_left, and 0 for neither
var horizontal_input = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
# horizontal velocity which moves player left or right based on input
velocity.x = horizontal_input * speed
现在,我们可以在 _physics_process 函数中调用此函数,该函数将每秒计算这些移动值(delta)。delta 参数表示自上一个物理帧以来经过的时间(以秒为单位),用于与帧速率无关的移动。在计算玩家的水平移动之前,我们需要计算玩家的垂直移动。我们希望 y 速度在重力的作用下将玩家拉下。然后,我们将调用我们的 Horizontal_movement() 函数来计算我们的水平移动。
我们将使用 move_and_slide 方法应用这些移动。此方法根据 x 和 y 速度移动物体。如果物体与另一个物体发生碰撞,它将沿着另一个物体滑动(默认情况下仅在地板上),而不是立即停止。如果我们使用 move_and_collide 方法,我们的玩家将不会启用重力,因为该方法不需要速度。
### Player.gd
extends CharacterBody2D
#player movement variables
@export var speed = 100
@export var gravity = 200
#movement and physics
func _physics_process(delta):
# vertical movement velocity (down)
velocity.y += gravity * delta
# horizontal movement processing (left, right)
horizontal_movement()
#applies movement
move_and_slide()
func horizontal_movement():
# if keys are pressed it will return 1 for ui_right, -1 for ui_left, and 0 for neither
var horizontal_input = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
# horizontal velocity which moves player left or right based on input
velocity.x = horizontal_input * speed
您的代码现在应该看起来像这样。
让我们在主场景中实例化玩家场景,以便测试我们的移动。在主场景中,单击链条图标以实例化一个新场景,即我们的玩家场景。这会将我们的玩家场景作为子场景添加到我们的主场景中 — 同时仍将我们的玩家场景视为独立对象。
缩小并将 Player 节点移动到主场景的蓝色框架内。此蓝色边框代表您的游戏屏幕。
您可以使用所选的移动工具来移动您的播放器。
如果您现在使用 F5 键或按下检查器面板上方的播放按钮来运行场景,您将看到您可以移动您的角色,但它会一直下落,直到它从我们的屏幕上消失。它下落是因为它还没有地板来阻止它——我们将在接下来的几个部分中添加这一点。
恭喜您设置了播放器并为其添加了一些动作!在下一部分中,我们将为我们的玩家角色添加动画,以便它开始变得生动。现在是保存项目并备份项目的好时机,这样如果发生任何破坏游戏的错误,您就可以恢复到此部分。在继续本系列之前,请回过头来复习您学到的知识,一旦您准备好了,我们将在下一部分与您见面!