Animancer学习札记
Table of Contents
请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com
Mecanim和Animancer动画机制之比较
播放动画
不使用代码播放动画
Mecanim不使用代码播放动画的步骤如下:
- 在项目中创建一个AnimatorController asset并打开它。
- 在AnimatorController编辑器窗口中,创建一个state,并为其分配一段animation clip
- 将这个AnimatorController asset分配给模型上的
Animator
组件。Animator进入播放模式,播放指定的动画。
Animancer不使用代码播放动画的步骤如下:
- 添加一个NamedAnimancerComponent组件到模型game object中。
- 将所需的添加animation clip到该组件的Animations数组中。
- 进入播放模式播放动画。
使用代码播放动画
Mecanim使用代码播放动画的步骤如下:
- 编写如下的代码。
- 将这个组件绑定到某模型的game object,并将模型已经挂接好的Animator组件,指派给代码中的_Animator成员变量。
- 在项目的某处创建AnimatorController asset。并将其赋给_Animator成员变量。
- 创建一个idle state,并且将一个animation clip赋给它。它将是默认状态,因为它是首先创建的。
- 创建一个action state,并将一个animation clip赋给它。
- 创建从action到idle的transition,并确保其Exit Time已启用。
using UnityEngine;
public class PlayOnClick : MonoBehaviour
{
[SerializeField]
private Animator _Animator;
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
_Animator.Play("Action");
}
}
}
Animancer使用代码播放动画的步骤如下:
- 编写如下的代码。
- 将这个组件绑定到某模型的game object,并将模型已经挂接好的AnimationComponent组件,指派给代码中的_Animancer成员。
using Animancer;
using UnityEngine;
public class PlayOnClick : MonoBehaviour
{
[SerializeField]
private AnimancerComponent _Animancer;
[SerializeField]
private AnimationClip _Idle;
[SerializeField]
private AnimationClip _Action;
private void OnEnable()
{
_Animancer.Play(_Idle);
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
// 播放完action动画之后,在末尾执行OnEnd操作,
// 回调OnEnable方法,播放idle动画
var state = _Animancer.Play(_Action);
state.Events.OnEnd = OnEnable;
}
}
}
Mecanim的Animator控制器和Animancer之间功能对应
Animator Controllers
Animator Controller的编辑器界面如下图所示:
Animancer可以取代Animator Controllers的这些操作
使用脚本
States
当播放一段AnimationClip
的时候,Animancer会创建一个ClipState类实例对象。这个实例对象可以视为一个state的特列,ClipState对象用来维护管理动画播放进度等属性。当再次播放同一个AnimationClip时,会复用这个ClipState对象。
ClipState类继承自AnimancerState类。AnimancerState还有Controller State和Mixer State两种子state。
获取ClipState对象的最简单的方法是通过表征Animancer的AnimancerComponent
类的Play
方法返回。如下代码所示:
void PlayAnimation(AnimancerComponent animancer, AnimationClip clip)
{
var state = animancer.Play(clip);
state.Time = ...
state.NormalizedTime = ...
state.Speed = ...
state.Events.OnEnd = ...
}
除了上述的方法之外,还可以通过以下的办法返回:
代码 | 描述 |
---|---|
var state = animancer.States.Current; | 和上面代码类似,获取到当前播放的动画对应的ClipState对象,如果使用了多层的概念,则返回base layer的那个动画对应的 |
var state = animancer.Layers[x].CurrentState; | 返回第x层中播放的动画对应的ClipState对象 |
var state = animancer.States[clip]; | 返回由索引值clip指定的动画对应的ClipState对象 |
animancer.States.TryGet(clip, out var state); | 尝试返回由索引值clip指定的动画对应的ClipState对象 |
var state = animancer.States.GetOrCreate(clip); | 返回由索引值clip指定的动画对应的ClipState对象 ,如果对应的动画不存在,则先创建动画再返回对应的ClipState对象 |
var state = animancer.States.Create(key, clip); | 创建由索引值clip指定的动画,然后返回对应的ClipState对象 |
var state = new ClipState(clip); | 创建由索引值clip指定的动画,然后返回对应的ClipState对象 |
animancer.States.Destroy(clip); | 摧毁由索引值clip指定的动画,然后再以此索引值对应创建动画,再返回对应的ClipState对象 |
Keys
当创建一个ClipState之后,Animancer内部会用一个字典将这个state对象存储。可以通过key去获取和重用这个state。
一般地,state使用它正在播放的AnimationClip对象作为这个key,但Animancer允许使用任意一个C# object实例作为它的key
AnimanceComponent类有一个子类NamedAnimancerComponent。此类重载了GetKey
成员方法,可以使用AnimationClip对象本身对应的动画名字字符串作为key,以代替AnimationClip对象本身。所以可以先给AnimationClip注册一个名字到Animancer中,而后在播放阶段直接指定名字而不是AnimationClip本身,即可播放动画,如下代码所示:
void PlayAnimation(AnimancerComponent animancer, AnimationClip clip)
{
animancer.TryPlay("Attack");
// 在播放之前,首先让名字和AnimationClip建立关联,而后就可以播放了
animancer.States.Create("Attack", clip);
animancer.TryPlay("Attack");
// 或者直接创建一个state,然后显式地指定该state的key
var state = new ClipState(clip);
state.Key = "Attack";
}
由于C#的object是引用类型,因此在这些方法中,使用枚举类型的变量,会隐式创建一个object对象来保存该值。这称为装箱。装箱操作对性能有明显的影响,特别是因为创建了object,调用后又立即丢弃并需要进行垃圾收集的时候,这影响更明显。当然这并不意味着不能使用枚举,只是应该意识到效率低下的问题。通常,可以采用显式命名一个object对象的方式作为key,如下代码:
public static class CharacterAction
{
public static readonly object Idle = new object();
public static readonly object Walk = new object();
public static readonly object Run = new object();
// Etc.
}
载入动画后暂停播放
如果显式地使用了AnimancerComponent类的Play方法启动播放一段动画剪辑,但又不想马上播放它,那么可以用以下的方法去控制它:
protected virtual void Awake()
{
// Start paused at the beginning of the animation.
// 假设一个模型,它的初始状态就是“睡着”,并且嘉定睡着的状态是不带任何动画效果的,那么它要启动动画效果时,应该就要
// 先进入“唤醒动作”,如果直接Play唤醒动作_WakeUp。,那就没法控制它何时去被唤醒了,所以下面的代码中,执行了
// Play函数。马上执行Evalute和Playable的PauseGraph函数
_Animancer.Play(_WakeUp);
_Animancer.Evaluate();
_Animancer.Playable.PauseGraph();
// Initialise the OnEnd events here so we don't allocate garbage every time they are used.
_WakeUp.Events.OnEnd = () => _Animancer.Play(MovementAnimation);
_Sleep.Events.OnEnd = _Animancer.Playable.PauseGraph;
}
在上面代码中,如果使用_Animancer.Play(_WakeUp).IsPlaying = false;
这样的代码也能达到暂停的目的。但使用PauseGraph
方法则在性能上更优。这意味这U3D引擎不用为了渲染该物体的动画效果,每一帧都进行动画计算了。要唤醒动画系统,让他继续播放,则使用UnpauseGraph
方法,如下:
// Wake up.
_Animancer.Playable.UnpauseGraph();
_Animancer.Play(_WakeUp);
这些代码的流程演示如下图所示:
混合树 (Blend Tree)
游戏动画中的一个常见任务是在两个或更多相似运动之间混合。最佳的已知示例可能是根据角色速度混合行走和奔跑动画。另一个示例是角色在奔跑过程中转弯时向左或向右倾斜。
重要的是区分过渡 (Transition) 与混合树 (Blend Tree)。虽然两者都用于创建平滑动画,但是它们用于不同类型的情况。
- 转换 (Transition): 用于在给定时间量内从一个动画状态 (Animation State) 平滑转换为另一个状态。转换指定为动画状态机 (Animation State Machine) 的一部分。如果转换迅速,则通常可从一个运动很好地转换为完全不同的运动。
- 混合树 (Blend Tree) :用于允许通过按不同程度组合所有动画的各个部分来平滑混合多个动画。各个运动参与形成最终效果的量使用混合参数进行控制,该参数只是与动画器控制器 (Animator Controller) 关联的数值动画参数之一。要使混合运动有意义,混合的动作必须具有相似性质和时间。混合树 (Blend Tree) 是动画器控制器 (Animator Controller) 中的特殊状态类型。
正向逆向播放动画剪辑
参考网页
https://kybernetik.com.au/animancer/docs/examples/fine-control/spider-bot/ Unity5.x Animator之BlendTree