Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
使用 AnimationTree
介绍
Godot 的动画系统因其 AnimationPlayer 是你在所有游戏引擎中能找到的最灵活的之一。它独树一帜,拥有将节点或资源中几乎任何属性动画化的能力,拥有专用的变换、贝塞尔曲线、函数调用、音频和子动画轨道。
然而,通过 AnimationPlayer 混合这些动画的支持相对有限,因为你只能设置固定的交叉渐变过渡时间。
AnimationTree 则是一个用于处理高级过渡的节点。
AnimationTree 与 AnimationPlayer
首先,必须明确 AnimationTree 节点不包含它自己的动画。相反,它使用包含在 AnimationPlayer 节点中的动画。你可以在 AnimationPlayer 节点中创建、编辑或导入动画,然后使用 AnimationTree 节点来控制播放。
AnimationPlayer 和 AnimationTree 可同时用于 2D 和 3D 场景。导入 3D 场景及其动画时,可通过名称后缀简化流程并确保正确属性导入。最终导入的 Godot 场景会在 AnimationPlayer 节点中包含动画。由于通常不直接使用导入的场景(而是实例化或继承),你可以将 AnimationTree 节点放置在包含导入场景的新场景中。之后,将 AnimationTree 节点指向在导入场景中创建的 AnimationPlayer 节点。
这是在第三人称射击游戏演示中使用的方法,以供参考:
为玩家创建了一个以 CharacterBody3D 为根节点的新场景。这个场景中实例化了原来的 .dae(Collada)文件,并创建了 AnimationTree 节点。
创建树
使用 AnimationTree 需先设置根节点。动画根节点是包含并处理子节点、输出动画的类,子节点分为三种类型:
动画节点,从链接的
AnimationPlayer中引用动画。动画根节点,用于混合子节点,可以嵌套。
动画混合节点,是一个在
AnimationNodeBlendTree中使用的二维节点图。混合节点有多个输入端口,有一个输出端口。
有以下几种根节点类型:
AnimationNodeAnimation:从列表中选择一个动画并播放。这是最简单的根节点,一般不用作根节点。AnimationNodeBlendTree:在图中包含多个子节点。有许多混合节点可用,例如 mix、blend2、blend3、one shot 等。这是最常用的根节点之一。AnimationNodeBlendSpace1D:允许在两个动画节点之间进行线性混合。在一维混合空间中控制混合位置,以在动画之间进行混合。AnimationNodeBlendSpace2D:允许在三个动画节点之间进行线性混合。在二维混合空间中控制混合位置,以在动画之间进行混合。AnimationNodeStateMachine:在一个图中包含多个子节点。每个节点作为一个状态使用,并提供多个函数在状态之间进行切换。
混合树
创建 AnimationNodeBlendTree 后,底部面板的 AnimationTree 标签页会显示空白二维图,默认仅含一个 Output(输出)节点。
若要播放动画,需将节点连接至输出。通过添加节点...菜单或右键点击空白处添加节点:
最简单的连接方式是将 Animation(动画)节点直接连接到输出端口,这样即可直接播放动画。
以下是其他可用节点的描述:
Blend2 / Blend3
这些节点将按照用户指定的混合值在两个或三个输入之间进行混合:
混合可以使用过滤器来单独控制哪些轨道进行混合、哪些不进行混合。这对于将动画分层叠加非常有用。
对于更复杂的混合,建议使用混合空间。
OneShot
此节点将执行一次动画,并在完成后返回。你可以自定义淡入淡出的混合时间,以及过滤器。
# Play child animation connected to "shot" port.
animation_tree.set("parameters/OneShot/request", AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE)
# Alternative syntax (same result).
animation_tree["parameters/OneShot/request"] = AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE
# Abort child animation connected to "shot" port.
animation_tree.set("parameters/OneShot/request", AnimationNodeOneShot.ONE_SHOT_REQUEST_ABORT)
# Alternative syntax (same result).
animation_tree["parameters/OneShot/request"] = AnimationNodeOneShot.ONE_SHOT_REQUEST_ABORT
# Get current state (read-only).
animation_tree.get("parameters/OneShot/active"))
# Alternative syntax (same result).
animation_tree["parameters/OneShot/active"]
// Play child animation connected to "shot" port.
animationTree.Set("parameters/OneShot/request", (int)AnimationNodeOneShot.OneShotRequest.Fire);
// Abort child animation connected to "shot" port.
animationTree.Set("parameters/OneShot/request", (int)AnimationNodeOneShot.OneShotRequest.Abort);
// Get current state (read-only).
animationTree.Get("parameters/OneShot/active");
TimeSeek
此节点可跳转至连接到其 in 输入的动画中的某个时间点。使用此节点可以从特定的播放位置开始播放 Animation。请注意,跳转请求值以秒为单位,因此如果你想要从开头播放动画,请将值设为 0.0,或者如果你想从第 3 秒开始播放动画,请将值设为 3.0。
# Play child animation from the start.
animation_tree.set("parameters/TimeSeek/seek_request", 0.0)
# Alternative syntax (same result).
animation_tree["parameters/TimeSeek/seek_request"] = 0.0
# Play child animation from 12 second timestamp.
animation_tree.set("parameters/TimeSeek/seek_request", 12.0)
# Alternative syntax (same result).
animation_tree["parameters/TimeSeek/seek_request"] = 12.0
// Play child animation from the start.
animationTree.Set("parameters/TimeSeek/seek_request", 0.0);
// Play child animation from 12 second timestamp.
animationTree.Set("parameters/TimeSeek/seek_request", 12.0);
TimeScale
此节点允许你缩放连接到其 in 输入的动画的速度。动画的速度将乘以 scale 参数中的数值。将 scale 设为 0 会暂停动画。将 scale 设为负数会使动画倒放。
Transition
该节点是 StateMachine 的简化版:将动画连接至输入端口后,当前状态索引决定播放哪个动画。你可以指定交叉淡入淡出过渡时间,并在检视器中调整输入端口数量、重新排序或删除输入端口。
# Play child animation connected to "state_2" port.
animation_tree.set("parameters/Transition/transition_request", "state_2")
# Alternative syntax (same result).
animation_tree["parameters/Transition/transition_request"] = "state_2"
# Get current state name (read-only).
animation_tree.get("parameters/Transition/current_state")
# Alternative syntax (same result).
animation_tree["parameters/Transition/current_state"]
# Get current state index (read-only).
animation_tree.get("parameters/Transition/current_index"))
# Alternative syntax (same result).
animation_tree["parameters/Transition/current_index"]
// Play child animation connected to "state_2" port.
animationTree.Set("parameters/Transition/transition_request", "state_2");
// Get current state name (read-only).
animationTree.Get("parameters/Transition/current_state");
// Get current state index (read-only).
animationTree.Get("parameters/Transition/current_index");
StateMachine
创建 AnimationNodeStateMachine 时,底部面板的 AnimationTree 标签页会显示空白二维图表,默认包含 Start 和 End 状态。
添加状态可通过右键或点击创建新节点按钮(图标为方框内加号)。可添加动画、混合空间、混合树甚至另一个 StateMachine。编辑复杂子节点时点击状态右侧铅笔图标,返回原 StateMachine 需点击面板左上角的根。
StateMachine 生效前需先连接状态过渡线。点击连接节点按钮(带右箭头的线段图标),在状态间拖拽创建过渡。每对状态可创建双向过渡线。
有三种过渡类型:
Immediate:将立即切换到下一个状态。当前状态将结束,并与新状态的开头相混合。
Sync:立即切换到下一个状态,但会将新状态快进到旧状态的播放位置。
At End :将等待当前状态播放结束,然后切换到下一个状态动画的开头。
过渡也有一些属性。单击任意过渡,它就会显示在检查器面板中:
Xfade Time 是在这个状态和下一个状态之间交叉渐变的时间。
Xfade Curve 让交叉渐变按照曲线而非线性混合进行。
Reset 决定切换至新状态时是否从头播放。
Priority 与代码中的
travel()函数一起使用(后述)。当状态变化时,会优先使用优先级较低的过渡。Switch Mode 为过渡类型(见上文),创建过渡后也可以在此处修改。
Advance Mode 决定前进模式:
Disabled禁用过渡,Enabled仅在travel()期间生效,Auto在满足前进条件或表达式为真时自动触发,或者在没有任何前进条件/表达式时也会触发。
Advance Condition 与 Advance Expression
StateMachine 过渡的最后两个属性是 Advance Condition 和 Advance Expression。当 Advance Mode 设为 Auto 时,这些属性决定是否执行过渡。
Advance Condition 是布尔值检测。在文本框中输入自定义变量名,当 StateMachine 执行到该过渡时,会检测变量是否为 true。如果是,则继续执行过渡。注意:该条件仅能检测 true(真值),无法检测假值。
这使得 Advance Condition 的功能极为有限。若需基于单一属性实现双向过渡,需创建两个值相反的变量并分别检测。为此 Godot 4 新增了 Advance Expression 功能。
Advance Expression 原理类似但功能更强,可评估任意表达式(即能写在 if 语句中的内容)。以下是有效表达式示例:
is_walkingis_walking == true(行为与上面的相同)is_walking && !is_idlevelocity > 0player.is_on_floor()
警告
该表达式区分大小写。如果你引用引擎属性,例如 CharacterBody3D 节点上的 velocity,则应使用 snake_case 命名约定。如果你引用脚本属性,则应与脚本中使用的样式保持一致,在 GDScript 中通常为 snake_case,在 C# 中通常为 PascalCase。
以下是使用 Advance Condition 设置不当的 StateMachine 过渡示例:
此设置无效,因为 Advance Condition 中使用了 ! 运算符(该功能不支持检测假值)。
以下是相同功能的正确实现方案(使用两个相反变量):
以下是改用 Advance Expression 实现的相同功能(无需双变量):
使用 Advance Expressions 需在 AnimationTree 节点的检视器中设置 Advance Expression Base Node。默认指向 AnimationTree 节点本身,但应改为包含动画变量的脚本所在节点。
参见
Advance Expression 使用 Godot 的 Expression 类进行求值。有关编写表达式的更多信息,请参阅表达式求值。
StateMachine 跨状态过渡
Godot 的 StateMachine 实现提供了很多不错的功能,其中之一就是跨状态过渡的能力。你可以向图发出指令,让其从当前状态转到另一个状态,期间会经过所有中间状态。这是通过 A* 算法实现的。如果当前状态和目标状态之间不存在任何可达的过渡路径,图就会立即传送到目标状态。
要使用跨状态过渡,你应该首先从 AnimationTree 节点中获取 AnimationNodeStateMachinePlayback 对象(其被导出为一个属性),然后调用它的多个函数之一:
var state_machine = animation_tree["parameters/playback"]
state_machine.travel("SomeState")
AnimationNodeStateMachinePlayback stateMachine = (AnimationNodeStateMachinePlayback)animationTree.Get("parameters/playback");
stateMachine.Travel("SomeState");
StateMachine 必须正在运行才能使用跨状态过渡。确保调用 start() 或将某个节点连接到 Start。
BlendSpace2D 与 BlendSpace1D
BlendSpace2D 是一个在二维空间进行高级混合的节点。将代表动画的点添加到二维空间中,然后可以控制它们之间的一个点的位置来确定混合:
通过右键或添加点按钮(钢笔加点图标)可在图表任意位置放置点,系统会自动用德劳内算法生成它们之间的三角形。你也可以控制和标注 X 轴与 Y 轴的范围。
最后,你也可以更改混合模式。默认情况下,混合是通过在最近的三角形内插值来实现的。当处理 2D 动画(逐帧)时,你可能希望切换到离散模式。此外,如果你想在离散动画之间切换时保持当前播放位置,请使用捕获模式。此模式可在混合菜单中更改:
BlendSpace1D 与 BlendSpace2D 类似,但存在于一维空间中(一条线),不使用三角形。
同步模式
BlendSpace1D 和 BlendSpace2D 都有一个名为 Sync Mode(同步模式) 的属性,它可以控制动画在混合播放时如何推进。这个新属性取代了以前那个简单的布尔值 sync 属性,让你能够对动画同步进行更精准的控制。
共有四种模式可供选择。
None (默认):未激活的动画会保持静止,不会继续推进。只有当前处于激活状态(也就是权重最高)的动画才会向前播放。
Independent(独立) :处于非激活状态的动画会以
0的权重继续向前推进播放。这种表现和旧版设置里的sync = true是一致的。Cyclic Mutable(可变周期循环) :所有的动画都会进行时间缩放,以保持它们的动作相位始终对齐。系统会根据当前混合的权重,动态计算出一个共享的循环周期。这意味着,如果只有一个动画单独播放(没有混合),它就会保持原本的播放速度。当你所有的动画都是同一种逻辑循环(比如各种走路的动作),但它们的实际时长又略有不同时,这个模式就非常好用。
Cyclic Constant(固定周期循环) :所有的动画都会被强制进行时间缩放,让它们正好在 Cyclic Length(周期长度) 秒内完整播放完一个循环,完全不管它们原本的时长是多少。你需要将
cyclic_length属性设置为你期望的循环时长(该数值必须大于0)。
警告
周期同步模式(Cyclic sync modes)有一个前提条件:所有的混合点都必须使用 AnimationNodeAnimation 节点,而且这些动画的长度必须是有限且固定不变的。如果有任何一个混合点使用了其他类型的节点,系统就会弹出一个警告,并且周期同步功能将不会生效。
备注
当混合的动画时长不一致时,如果你在输出端使用了 AnimationNodeTimeSeek ,会直接破坏同步效果。在这种情况下,你应该改用 AnimationNodeAnimation.use_custom_timeline 属性,在同步开始前先把各个动画的时长统一(标准化)好。
为了更好的混合
为了使混合结果具有确定性(可复现且始终一致),混合属性值必须具有特定的初始值。例如,在要混合两个动画的情况下,如果一个动画具有属性轨道而另一个没有,则计算混合动画时,后一个动画被视作具有该属性初始值的属性轨道。
当使用 Skeleton3D 骨骼的 3D 位置/旋转/缩放轨道时,初始值为骨骼放松姿势。对于其他属性而言,初始值是 0。而如果轨道出现在 RESET 动画中,那么则使用它第一个关键帧的值。
例如,下面的 AnimationPlayer 有两个动画,但其中一个缺少 Position 的属性轨道。
这意味着缺少该 Position 的动画会将这些 Position 视为 Vector2(0, 0)。
可以通过将 Position 的 Property 轨道作为初始值添加到 RESET 动画中来解决这个问题。
备注
请注意,RESET 动画的存在是为了在最初加载对象时定义默认姿势。它被假定只有一帧,并且不应使用时间轴进行播放。
另外请记住,插值模式设为线性或三次的 3D 旋转轨道和用于 2D 旋转的属性轨道,无法使用超过 180 度的初始值旋转作为混合动画。
这种限制对于 Skeleton3D 非常有用,可以防止骨骼在混合动画时穿透身体。因此,Skeleton3D 的骨骼放松姿势应尽可能接近可移动范围的中点。这意味着人形模型最好以 T-pose 导入。
你可以看到,优先考虑从 Bone Rest 出发的最短旋转路径,而不是动画之间的最短旋转路径。
如果需要通过混合动画将 Skeleton3D 本身旋转 180 度以上,则可以使用 Root Motion。
根运动
处理 3D 动画时,一种流行的技术是动画师利用根骨骼为其余骨骼带来运动。这使得角色动画的脚步能够与下方的地板相匹配,还能够实现过场动画中角色与物体的精确交互。
在 Godot 中播放动画时,可以用根运动轨道指定这根骨骼。这会在视觉上抵消这根骨骼的变换(其动画停留在原地)。
这样做以后,可以通过 AnimationTree API 获取实际的运动变换:
# Get the motion delta.
animation_tree.get_root_motion_position()
animation_tree.get_root_motion_rotation()
animation_tree.get_root_motion_scale()
# Get the actual blended value of the animation.
animation_tree.get_root_motion_position_accumulator()
animation_tree.get_root_motion_rotation_accumulator()
animation_tree.get_root_motion_scale_accumulator()
// Get the motion delta.
animationTree.GetRootMotionPosition();
animationTree.GetRootMotionRotation();
animationTree.GetRootMotionScale();
// Get the actual blended value of the animation.
animationTree.GetRootMotionPositionAccumulator();
animationTree.GetRootMotionRotationAccumulator();
animationTree.GetRootMotionScaleAccumulator();
可以将这些值提供给 CharacterBody3D.move_and_slide 等函数,用来控制角色的移动。
还有一个名为 RootMotionView 的工具节点,可以放置在场景中充当角色和动画的自定义地板(这个节点默认在游戏期间禁用)。
使用代码控制
创建树和预览之后,就只剩下一个问题:“这些东西怎么使用代码来控制?”。
要注意动画节点就是资源,因此它们会在所有使用它们的实例之间共享。直接修改节点中的值将会影响到场景中所有使用这个 AnimationTree 的实例。通常是不希望这样的,不过这也有一些不错的用途,比如你可以复制粘贴你的动画树的一部分,或者在不同的动画树中复用具有复杂布局的节点(例如 StateMachine 或混合空间)。
实际的动画数据包含在 AnimationTree 节点中,可以通过属性访问。在 AnimationTree 节点的“Parameters”部分中查看所有可以实时修改的参数:
这很方便,因为它们甚至是 AnimationTree 本身可以都通过 AnimationPlayer 动画化,来实现非常复杂的动画逻辑。
想要通过代码修改这些值,必须获得该属性的路径。这很容易做到,把鼠标悬停在任意参数上即可:
然后,你可以这样设置或读取它们:
animation_tree.set("parameters/eye_blend/blend_amount", 1.0)
# Alternate syntax (same result)
animation_tree["parameters/eye_blend/blend_amount"] = 1.0
animationTree.Set("parameters/eye_blend/blend_amount", 1.0);
备注
StateMachine 的 Advance Expressions 不会显示在参数列表中,因其存储在外部脚本而非 AnimationTree 内。而 Advance Conditions 会出现在参数中。