通过制作 2D 平台游戏学习 Godot 4 — 第 3 部分:玩家动画
创建完 Player 场景后,我们现在可以继续为 Player 添加动画,然后将其连接到脚本中的输入。我们希望 Player 在按下ui_left和ui_right时有奔跑动画,站立时有静止动画,按下ui_up时有攀爬动画,按下ui_jump时有跳跃动画,按下 ui_attack 时有攻击动画(我们仍需创建)。我们还希望 Player 受到伤害和死亡时有动画。
您将在本部分中学习到的内容:
如何创建多个 AnimatedSprite2D 动画。
如何将节点信号连接到脚本。
首先,让我们添加ui_attack输入操作。我们将为攻击分配 SHIFT 键。我们的玩家在拾取攻击增强拾取物时将需要此攻击动画和输入,这将使其能够在短时间内摧毁箱子和炸弹。
现在,让我们创建动画,从奔跑动画开始。我们将使用此奔跑动画进行左跑和右跑,因此我们不需要创建 run_left 和 run_right 动画。让我们创建一个新动画(单击动画面板中的纸张图标)并将其命名为“run”。导航到 Assets 文件夹中的“res://Assets/Kings and Pigs/Sprites/01-King Human/Run (78x58).png”,然后裁剪出动画帧,如下图所示。
将其 FPS 更改为 10 并保持循环开启。
创建新动画并将其命名为“jump”。导航至 Assets 文件夹中的“res://Assets/Kings and Pigs/Sprites/01-King Human/Jump (78x58).png”,然后裁剪出动画帧,如下图所示。
将其 FPS 更改为 5 并保持其循环开启。
创建新动画并将其命名为“climb”。导航至 Assets 文件夹中的“res://Assets/Kings and Pigs/Sprites/01-King Human/Door In (78x58).png”,然后裁剪出动画帧,如下图所示。
将其 FPS 更改为 10 并保持循环开启。
创建新动画并将其命名为“attack”。导航至 Assets 文件夹中的“res://Assets/Kings and Pigs/Sprites/01-King Human/Attack (78x58).png”,然后裁剪出动画帧,如下图所示。
将其 FPS 更改为 7 并关闭其循环。
创建新动画并将其命名为“damage”。导航至 Assets 文件夹中的“res://Assets/Kings and Pigs/Sprites/01-King Human/Hit (78x58).png”,然后裁剪出动画帧,如下图所示。
将其 FPS 更改为 2 并关闭其循环。
最后,创建一个新的动画并将其命名为“死亡”。导航到 Assets 文件夹中的“res://Assets/Kings and Pigs/Sprites/01-King Human/Dead (78x58).png”,然后裁剪出动画帧,如下图所示。
将其 FPS 更改为 5 并关闭其循环。
创建动画后,我们现在可以继续向我们的 Player 脚本添加一个函数,该函数将根据捕获的输入操作更改动画。让我们从奔跑动画开始。如果我们的玩家按下ui_left或ui_right,我们的奔跑动画就会播放。我们将水平翻转精灵,以便我们可以重复使用“奔跑”动画进行左右移动。我们还需要确保我们没有按下ui_jump输入,否则,我们将在跳跃时奔跑!我们将使用我们的 Input 单例来检查这些输入,因为我们想在我们的physics_process函数中调用这个player_animations()函数,以便我们的动画可以在每一秒重新捕获。
### Player.gd
#旧代码
#动画
func player_animations():
#在左边 (添加 is_action_just_released 以便你在跳跃后继续奔跑)
if Input.is_action_pressed( "ui_left" ) || Input.is_action_just_released( "ui_jump" ):
$AnimatedSprite2D.flip_h = true
$AnimatedSprite2D.play( "run" )
#在右边 (添加 is_action_just_released 以便你在跳跃后继续奔跑)
if Input.is_action_pressed( "ui_right" ) || Input.is_action_just_released( "ui_jump" ):
$AnimatedSprite2D.flip_h = false
$AnimatedSprite2D.play( "run" )
现在,如果你注释掉 move_and_slide 以暂时阻止玩家掉落,并且我们在physics_process() 函数中调用player_animations()函数,我们就可以运行我们的场景,当我们按下键盘上的 A 或 D 时,我们的左右奔跑动画应该会播放。我们将看到动画的急剧翻转非常明显,但这是由于我们的动画帧之间的间隙很大(我在上一部分中解释过)。我为此道歉,因为它看起来很丑——但现在只能这样了。
# 旧代码
# 运动和物理
func _physics_process(delta):
# 垂直运动速度(向下)
velocity.y +=gravity * delta
# 水平运动处理(左、右)
Horizontal_movement()
# 应用运动
#move_and_slide()
# 应用动画
player_animations()
接下来,如果玩家没有按下任何输入操作,让我们播放空闲动画。如果您运行场景,则如果玩家没有运行,则应该处于空闲状态。
### Player.gd
#旧代码
#动画
func player_animations():
#在左边 (添加 is_action_just_released 以便你在跳跃后继续奔跑)
if Input.is_action_pressed( "ui_left" ) || Input.is_action_just_released( "ui_jump" ):
$AnimatedSprite2D.flip_h = true
$AnimatedSprite2D.play( "run" )
#在右边 (添加 is_action_just_released 以便你在跳跃后继续奔跑)
if Input.is_action_pressed( "ui_right" ) || Input.is_action_just_released( "ui_jump" ):
$AnimatedSprite2D.flip_h = false
$AnimatedSprite2D.play( "run" )
#如果没有按下任何按钮,则在空闲状态
if !Input.is_anything_pressed():
$AnimatedSprite2D.play( "idle" )
我们还为攻击动画添加攻击输入。当我们添加攻击增强时,我们将禁用此动画的播放。我们将在 input() 函数中捕获此输入,因为我们不希望它每秒都处理一次。这将导致玩家每次想要攻击时都必须按下它。
我们还将创建一个变量来检查我们是否正在攻击,这样如果我们的玩家正在攻击,我们就可以停止处理其他动画。这将使我们能够奔跑,然后停止攻击并继续奔跑。在下面的代码中,我们看到,如果我们没有按下攻击输入,它将我们的is_attacking变量设置为 true,它只会播放我们的奔跑和空闲动画。
### Player.gd
扩展了 CharacterBody2D
#player 运动变量
@export var speed = 100
@export var grave = 200
@export var jump_height = - 100
var is_attacking = false
#运动和物理
func _physics_process(delta):
#垂直运动速度(向下)
velocity.y += grave * delta
#水平运动处理(左、右)
Horizontal_movement()
#应用运动
#move_and_slide()
#应用动画
if !is_attacking:
player_animations()
#旧代码
#单数输入捕获
func _ input (event):
#攻击
if event.is_action_pressed( "ui_attack" ):
is_attacking = true
$AnimatedSprite2D.play( "attack" )
我们还需要将此变量重置回 false,否则,我们的玩家可能会卡在这个攻击动画中。我们可以通过 AnimatedSprite2D 节点的animation_finished() 信号来做到这一点。信号是 Godot 内置的一种委托机制,允许一个游戏对象对另一个游戏对象的变化做出反应,而无需它们相互引用。每当动画帧播放完毕时,此信号就会触发或发出。当我们的攻击动画播放完毕后,我们将is_attacking值重置回 false,以便我们的其他动画可以播放。
要附加信号,请单击 AnimatedSprite2D 节点,然后在“信号”下的“节点”面板中双击animation_finished选项,并将其连接到要发出此信号的脚本。我们希望它在我们的 Player 脚本中触发,因为我们将更改 Player 脚本中的变量。
您将在脚本中看到它创建了一个函数 on_animated_sprite_2d_animation_finished()。我们将在这里重置is_attacking变量。
### Player.gd
#旧代码
#重置我们的动画变量
func _on_animated_sprite_2d_animation_finished():
is_attacking = false
现在,我们可以继续添加跳跃动画。如果他们在跳跃,我们希望根据一定的高度在 y 轴上向上移动精灵。让我们创建一个变量,我们将其称为jump _ height,它将包含我们的玩家在跳跃时向上移动的最大高度。
### Player.gd
#player 运动变量
@export var speed = 100
@export var Gravity = 200
@export var jump_height = - 100
现在,我们可以继续告诉代码让玩家跳跃。为此,我们将检查玩家是否按下了 ui_jump 输入以及他们是否在地板上。我们不希望他们无休止地跳跃,因此我们将使用内置方法is_on_floor来检查我们的玩家当前是否接地,如果接地,则允许他们跳跃。如果身体在move_and_slide的最后一次调用中与地板发生碰撞,则此方法返回 true 。我们还没有地板,所以我们无法测试这一点。在下一部分中,我们将添加地板(或碰撞),然后我们将看到玩家的跳跃动画。
然后,当我们跳跃时,我们希望根据jump_height变量移动玩家。我们可以通过将玩家的y速度更改为等于jump_height值来实现这一点。这会将玩家向上推 100 像素,模拟跳跃效果。
### Player.gd
#older code
#singular input captures
func _ input (event):
#on attack
if event.is_action_pressed( "ui_attack" ):
is_attacking = true
$AnimatedSprite2D.play( "attack" )
#on jump
if event.is_action_pressed( "ui_jump" ) and is_on_floor():
velocity.y = jump_height
$AnimatedSprite2D.play( "jump" )
稍后我们将为他们的动画添加死亡和伤害功能。现在,我们将添加最后一个动画,即攀爬动画。为此,我们还需要创建一个变量来检查我们的玩家是否正在攀爬——因为如果他们没有爬梯子,我们不想播放这个动画。
### Player.gd
#玩家运动变量
@export var speed = 100
@export var grave = 200
@export var jump_height = - 100
#运动状态
var is_attacking = false
var is_climbing = false
在我们的 input() 函数中,我们将检查玩家是否正在攀爬。当我们与梯子交互时,此攀爬变量将在另一个场景中被触发并返回 true。如果他们正在攀爬,我们需要检查他们是否按下了ui_up输入,以及检查他们是否在地板上。如果所有这些都返回 true,我们将播放攀爬动画。
我们还将重力的强度降低,因为我们想减少重力的影响,使角色在爬梯子时移动得更慢。改变重力值后,我们需要将角色沿 x 轴向上移动,以模拟它们“攀爬”。我们通过调整角色的垂直速度来实现这一点,使其在“爬”梯子时向上移动(-200 像素/秒)。
我们还需要将它们的攀爬值重置回 false,并重置它们的垂直速度,因为当玩家不再站在梯子上时,它们就不应该再攀爬了。
### Player.gd
#旧代码
#单数输入捕获
func _ input (event):
#攻击
时 if event.is_action_pressed( "ui_attack" ):
is_attacking = true
$AnimatedSprite2D.play( "attack" )
#跳跃时
if event.is_action_pressed( "ui_jump" ) and is_on_floor():
$AnimatedSprite2D.play( "jump" )
#爬梯子时
if is_climbing == true:
if Input.is_action_pressed( "ui_up" ):
$AnimatedSprite2D.play( "climb" )
grave = 100
velocity.y = - 200
#重置重力
else :
grave = 200
is_climbing = false
您的代码现在看起来应该是这样的。
如果你现在运行你的场景,你的玩家应该能够左右奔跑(在一个地方,因为你只有在注释掉move_and_slide()方法后才能测试这一点),以及攻击。我们还看不到他们的攀爬或跳跃,但我们将在下一部分中解决这个问题。
恭喜您为玩家的移动动画创建了基础!在下一部分中,我们将创建第一个关卡,我们还将能够在其中测试我们的攀爬和跳跃动画。现在是保存项目并备份项目的好时机,这样如果发生任何破坏游戏的错误,您就可以恢复到此部分。在继续本系列之前,请回过头来复习您学到的知识,一旦您准备好了,我们将在下一部分中与您见面!