使用 AnimationTree
前言
通过 AnimationPlayer,Godot 拥有你在所有游戏引擎中能找到的最灵活的动画系统之一。几乎可以在任何节点或资源中对任何属性进行动画处理,以及专门的变换、贝塞尔、函数调用、音频和子动画轨道,这样的能力相当独特。
然而, 通过 AnimationPlayer
混合这些动画的支持相对有限, 只能设置固定的交叉渐变过渡时间.
AnimationTree 是Godot 3.1中引入的一个新节点, 用于处理更高级的变换, 它取代了旧的 AnimationTreePlayer
, 同时增加了大量的功能和灵活性.
创建动画树
首先, 必须明确 AnimationTree
节点不包含它自己的动画. 相反, 它使用包含在 AnimationPlayer
节点中的动画. 通过这种形式, 你可以像往常一样编辑动画(或从3D场景导入动画), 然后使用这个额外节点来控制播放.
在3D场景中经常使用 AnimationTree
. 当从3D交换格式导入场景时, 它们通常自带动画(要么是多个, 要么是在导入时从一个大的动画中拆分出来). 最后, 导入的Godot场景在 AnimationPlayer
节点中包含动画.
很少在Godot中直接使用导入的场景(它们要么实例化, 要么来自继承), 你可以将 AnimationTree
节点放置在包含导入的新场景中. 然后, 将 AnimationTree
节点指向导入场景内创建的 AnimationPlayer
节点.
这是在 第三人称射击游戏演示, 中的设置, 参考下图:
A new scene was created for the player with a CharacterBody3D
as root. Inside this scene, the original .dae
(Collada) file was instantiated and an AnimationTree
node was created.
创建树
可以在 AnimationTree
中使用三种主要节点类型:
Animation nodes, which reference an animation from the linked
AnimationPlayer
.动画根节点, 用于混合子节点.
动画混合节点,在
AnimationNodeBlendTree
中使用,通过多个输入端口进行单图混合。
在 AnimationTree
中设置根节点, 如下几种类型可供选择:
AnimationNodeAnimation
:从列表中选择一个动画并播放它. 这是最简单的根节点, 一般不直接用作根节点.AnimationNodeBlendTree
:包含许多混合类型的节点,如调配, 混合2, 混合3, 一对一等. 这是最常用的根节点之一.AnimationNodeStateMachine
:将多个根节点作为图中的子节点. 每个节点作为一个 状态 使用, 并提供多个函数在状态之间进行切换.AnimationNodeBlendSpace2D
:允许在二维混合空间中放置根节点. 在二维中控制混合位置以混合多个动画.AnimationNodeBlendSpace1D
:以上的简化版本(一维)。
混合树
AnimationNodeBlendTree
可包含用于混合的根节点和常规节点。节点从菜单添加到图中:
所有混合树默认都包含一个 Output
(输出)节点,为了让动画播放,必须有个东西与其相连。
测试此功能最简单的方法是直接连接一个 Animation
(动画)节点:
这会简单地回放动画. 确保 AnimationTree
节点对实际发生的事情是激活的.
以下是可用节点的简短描述:
混合2/混合3
这些节点将通过用户指定输入的两个或三个混合值之间进行混合:
对于更复杂的混合, 建议使用混合空间.
混合也可以使用过滤器, 也就是说, 你可以单独控制通过混合功能的轨道. 这对于动画的层叠非常有用.
OneShot
此节点将执行子动画, 并在完成后返回. 可以用于定制淡入淡出时间, 以及过滤器.
After setting the request and changing the animation playback, the one-shot node automatically clears the request on the next process frame by setting its request
value to AnimationNodeOneShot.ONE_SHOT_REQUEST_NONE
.
GDScriptC#
# Play child animation connected to "shot" port.
animation_tree.set("parameters/OneShot/request", AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE)
# Alternative syntax (same result as above).
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 as above).
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 as above).
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
这个节点可以用来使寻找命令发生在动画图像的任何子代上。使用这个节点类型可以从 AnimationNodeBlendTree
中的开始或某个位置播放 Animation
。
After setting the time and changing the animation playback, the seek node automatically goes into sleep mode on the next process frame by setting its seek_request
value to -1.0
.
GDScriptC#
# Play child animation from the start.
animation_tree.set("parameters/TimeSeek/seek_request", 0.0)
# Alternative syntax (same result as above).
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 as above).
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);
时间缩放
Allows scaling the speed of the animation (or reverse it) connected to the in input via the scale parameter. Setting the scale to 0 will pause the animation.
转换
Very simple state machine (when you don’t want to cope with a StateMachine
node). Animations can be connected to the outputs and transition times can be specified. After setting the request and changing the animation playback, the transition node automatically clears the request on the next process frame by setting its transition_request
value to an empty string (""
).
GDScriptC#
# Play child animation connected to "state_2" port.
animation_tree.set("parameters/Transition/transition_request", "state_2")
# Alternative syntax (same result as above).
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 as above).
animation_tree["parameters/Transition/current_state"]
# Get current state index (read-only).
animation_tree.get("parameters/Transition/current_index"))
# Alternative syntax (same result as above).
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");
二维混合空间
BlendSpace2D
是一个在二维空间进行高级混合的节点. 将点添加到一个二维空间, 然后可以控制位置来确定混合:
The ranges in X and Y can be controlled (and labeled for convenience). By default, points can be placed anywhere (right-click on the coordinate system or use the add point button) and triangles will be generated automatically using Delaunay.
也可以通过禁用 自动三角形 选项来手动绘制三角形, 虽然基本上没必要这么做:
最后, 可能会更改混合模式. 默认情况下, 混合是通过在最近的三角形内插点来实现的. 当处理二维动画(逐帧)时, 你可能希望切换到 离散 模式. 此外, 如果你想在离散动画之间切换时保持当前播放位置, 请使用 进位 模式. 此模式可在 混合 菜单中更改:
一维混合空间
这类似于二维混合空间, 但在一维空间中(所以不需要三角形).
状态机
这个节点是一个状态机,根节点都是状态。根节点可以创建并通过线路连接。状态通过转换连接,它们是具有特殊性质的连接。转换是单向的,但是可以用两个来达到双向连接。
有多种类型的转换:
Immediate(立即):将立即切换到下一个状态。当前状态将结束,并与新状态的开头相混合。
Sync(同步):立即切换到下一个状态,但会将新状态快进并到旧状态的播放位置。
At End(末尾):将等待当前状态播放结束,然后切换到下一个状态动画的开头。
过渡也有一些属性。单击任何过渡,它就会显示在“检查器”面板中:
Switch Mode(切换模式)为过渡类型(见上文),可以在此处创建后修改。
Auto Advance(自动前进)当达到此状态时将自动开启转换。最适合 At End 切换模式。
Advance Condition(前进条件)会在条件成立时打开自动前进。这是一个可以用变量名填充的自定义文本字段。可以从代码中修改变量(稍后将对此进行详细介绍)。
Xfade Time(叠化时间)是在这个状态和下一个状态之间交叉渐变的时间。
Priority(优先级)与代码中的
travel()
函数一起使用(后述)。当从一个状态到另一个状态时,会优先使用优先级较低的过渡。Disabled(禁用)允许禁用此转换(它不会在行程或自动前进期间使用)。
为了更好的混合
在 Godot 4.0+ 中,为了使混合结果具有确定性(结果可复现且始终一致),混合属性值必须具有特定的初始值。例如,在要混合两个动画的情况下,如果一个动画具有属性轨道而另一个动画没有,则计算混合动画时,要好像后一个动画(即本来没有属性轨道的那个)具有初始值的属性轨道一样去处理。
当使用 Skeleton3D 骨骼的 Position/Rotation/Scale 3D 轨道时,初始值为 Bone Rest(骨骼放松姿势)。对于其他属性而言,初始值是 0
,并且如果轨道出现在 RESET
动画中,那么则使用它第一个关键帧的值。
例如,下面的 AnimationPlayer 有两个动画,但其中一个缺少 Position 的属性轨道。
这意味着缺少该 Position 的动画会将这些 Position 视为 Vector2(0, 0)
。
可以通过将 Position 的 Property 轨道作为初始值添加到 RESET
动画中来解决这个问题。
备注
请注意, RESET
动画的存在是为了在最初加载对象时定义默认姿势。它被假定只有一帧,并且不应使用时间轴进行播放。
另请记住,将“插值类型”设置为“线性角”或“三次角”的“Rotation 3D 轨道”和用于 2D 旋转的“属性”轨道,将阻止从初始值旋转超过 180 度的操作作为混合动画。
这种限制对于 Skeleton3D 非常有用,可以防止骨骼在混合动画时穿透身体。因此,Skeleton3D 的 Bone Rest (骨骼放松姿势)值应尽可能接近可移动范围的中点。 这意味着人形模型最好以 T-pose 导入 。
你可以看到,骨骼中最短的旋转路径被优先处理,而不是动画之间的最短旋转路径。
如果需要通过混合动画将 Skeleton3D 本身旋转 180 度以上,则可以使用 Root Motion。
根骨骼运动
处理 3D 动画时,一种流行的技术是动画师利用根骨骼为其余部分骨骼制作运动动画。这使得动画角色的脚步与下面的地板相匹配。并且允许在电影拍摄期间与物体进行精确的交互。
在 Godot 中回放动画时,可以选择这根骨骼作为根运动轨迹。这会在视觉上取消这根骨骼的变换(动画将保持原状)。
然后, 实际运动可以通过 AnimationTree API 作为转换:
GDScriptC#
# 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
的实例。通常是不希望这样的,不过也有一些不错的用法,比如你可以复制粘贴你的动画树的一部分,或者在不同的动画树中复用具有复杂布局的节点(例如状态机和混合树)。
实际的动画数据包含在 AnimationTree
节点中, 并通过属性访问. 检查 AnimationTree
节点的 “参数” 部分, 查看所有可以实时修改的参数:
这很方便, 因为它可以通过 AnimationPlayer
获得动画效果, 甚至是 AnimationTree
本身, 允许实现非常复杂的动画逻辑.
想要通过代码修改这些值, 必须获得该属性的路径. 这是很容易做到的, 把鼠标悬停在任何参数:
允许设置或读取它们:
GDScriptC#
animation_tree.set("parameters/eye_blend/blend_amount", 1.0)
# Simpler alternative form:
animation_tree["parameters/eye_blend/blend_amount"] = 1.0
animationTree.Set("parameters/eye_blend/blend_amount", 1.0);
状态机行程
One of the nice features in Godot’s StateMachine
implementation is the ability to travel. The graph can be instructed to go from the current state to another one, while visiting all the intermediate ones. This is done via the A* algorithm. If there is no path of transitions starting at the current state and finishing at the destination state, the graph teleports to the destination state.
要使用行程能力, 你应该首先从 AnimationTree
节点中检索 AnimationNodeStateMachinePlayback 对象(其被导出为一个属性).
GDScriptC#
var state_machine = animation_tree["parameters/playback"]
AnimationNodeStateMachinePlayback stateMachine = (AnimationNodeStateMachinePlayback)animationTree.Get("parameters/playback");
一旦检索到, 可以调用它提供的许多函数之一:
GDScriptC#
state_machine.travel("SomeState")
stateMachine.Travel("SomeState");
状态机必须正在运行才能使用行程能力。确保调用 start()
或选择一个节点以在加载时自动播放。
© 版权所有 2014-present Juan Linietsky, Ariel Manzur and the Godot community (CC BY 3.0). Revision b1c660f7
.
Built with Sphinx using a theme provided by Read the Docs.