【Unity】Animancer学习笔记

使用注意点

  1. 在初始化阶段暂停了动画,可以使用AnimancerComponent.Evaluate(),将动画立即应用到物体上,否则可能会出现人物还是T-Post状态
  2. 非循环动画在播放完动作后,且后续没有其他动画时,AnimancerState.Time依然在计算,为了避免不必要的性能开销,应该使用AnimancerPlayable.UnpauseGraph暂停动画
  3. 当物体被OnDisable的时候,应该将使用AnimancerPlayable.UnpauseGraph取消动画的暂停。如果不取消,再次启用时系统将会再次初始化一遍。if (_Animancer != null && _Animancer.IsPlayableInitialized) _Ainmancer.Playable.UnpauseGraph();
  4. 在初始化的时候可以使用AnimancerComponent.GetOrCreate(ITransition)来检查AnimancerState是否存在
    • 万向走(且同一方向上即有走又有跑)使用2D Freeform Directional
    • 四向走(多方向)使用2D Freeform Cartesian

基础内容

Clip动画片段

  • AnimationClip:Unity源生的一个类,引用指定的动画Clip。

  • ClipTransition:与AnimationClip类似,不仅能引用指定的Clip,还能使用内置方法管理如何播放这个AnimationClip

  • ITransition:是一个接口,通常unity不允许将接口序列化的展示到Inspector窗口上,但是ITransition已经使用PropertyDrawer做到能耐序列化接口了,只需在字段前面添加[SerializeReference]属性即可

AnimancerState

AnimancerComponent.Play()返回的值——Clip控制器,可以用这个来控制Clip的属性:

  • .NormalizedTime:归一化时间,该Clip的播放的进度
  • .Speed:播放速度,负数可倒放

组件

这里的组件都是Animancer帮我们做的好组件,是代替Unity.AnimatorController的。所以在引用这些组件之后,就不需要配置Controller

AnimancerComponent.Play()

动画控制器,三个参数,播放的动画引用AnimationClipClipTransition(后面将这两个统称为Clip)、过渡时间、播放方式

NamedAnimancerComponent.TryPlay()

  • 可以使用使用名称字符串的形式播放动画,但是在这之前得先使用NamedAnimancerComponent.State.Create(Clip)创建一个状态

  • 也可以直接使用引用NamedAnimancerComponent.Play(Clip)播放动画

  • 将Clip直接放到NamedAnimancerComponent.Animations中时,并不会自动播放,除非勾选自动播放

总结一下:

可以使用.TryPlay()直接使用字符串名称播放指定动画,但是这个动画需要先存入States

SoloAnimation

性能不一定最好,同样的事,传统的Animation同样能做到,并且性能稍微好一点点。

他的用途仅限于需要使用人形或Generic Rig,并且只想播放一个动画的情况


动画速度与时间精度控制

倒放

直接向后播放:Speed设置为-1

从结尾向后播放:NormalizedTime设置为1

暂停

  • 调用AnimancerComponent.Playable.PauseGraph()停止动画,并不是时停,而是会将动画播放到结尾的位置

  • 需要使用.UnpauseGraph()手动退出暂停状态

  • 非循环动画在播放完动作后,AnimancerState.Time依然在计算,照成不必要的性能消耗,应该使用AnimancerComponent.Playable.PauseGraph()停止动画,如果有必要的话还可以使用AnimancerComponent.Evaluate()将动画立即应用到对象上,例如在初始化的时候暂停动画。

  • 从上面这点可以看出,如果暂停了动画并且后续没有动画变化,可以使用暂停,避免性能消耗

时间

如果在动画最开始的时候调用了AnimancerComponent.Playable.PauseGraph(),动画处于暂停状态,并没有应用到人物上,这时就需要使用AnimancerComponent.Evaluate(),将当前的所有动画的状态应用到对象上了

抽帧

AnimancerComponent.Evaluate(value):将时间提前value秒,并立即应用所有的当前状态

1
2
3
4
5
6
7
8
9
10
private void Update()
{
var time = Time.time;
var timeSinceLastUpdate = time - _LastUpdateTime;
if (timeSinceLastUpdate > 1 / _UpdatesPerSecond)
{
_Animancer.Evaluate(timeSinceLastUpdate);
_LastUpdateTime = time;
}
}

拓展:

  1. 使用另外一个脚本,通过距离判断是否执行这个Update方法或这个Component,就可以降低原理玩家的怪物的动画。值得注意的是,Unity调用MonoBehaviour事件方法(如OnEnable或Update)都比在C#中正常调用方法开销更大。所以如果能使用一个单例脚本来管理怪物的动画抽帧,更能提高性能,但是这个方法也更复杂。可以不用每帧都判断,可以每隔10帧判断一下。Renderer.isVisible也是不错的方法
  2. 使用这种方法可能会影响Animation EvnetsAnimancer Events

Root Motion

添加Apply Root Motion属性

开启Root Motion,并不一定是很好的选择,很多游戏并没有开启Root Motion功能

Animancer并没有给我们添加默认的是否运用Root Motion选项,需要我们自己绑定一个变量:

创建一个实现ITransition或继承Transition Type的类,就可以将额外的数据绑定到动画上

1
2
3
4
5
6
7
8
9
10
11
[Serializable]
public class MotionTransition : ClipTransition
{
[SerializeField, Tooltip("Should Root Motion be enabled when this animation plays?")]
private bool _ApplyRootMotion;
public override void Apply(AnimancerState state)
{
base.Apply(state);
state.Root.Component.Animator.applyRootMotion = _ApplyRootMotion;
}
}

  • 使用OnAnimatorMove方法,将Root Motion动画的移动效果让人为可控
  • 使用Root Motion后人物的移动与物理相关,如果人物使用的是Rigidbody控制移动的话,应该将AnimatorUpdateMode设置为AnimatePhysics。这样他就只会被物理更新移动,也就是在FixedUpdate更新而不是Update中

Root Motion重定向

通常我们会将角色的逻辑Component与模型分开,如下

这时就需要使用重定向,将Animator的动作映射到父节点上。不然父节点会留在原地,只有模型会前进

Animancer已经为我们封装好了,使用RedirectRootMotionToCharacterControllerRedirectRootMotionToRigidbodyRedirectRootMotionToTransform脚本,将其放置在模型上并设置相应的AnimatorTarget属性就好了。源码很简单


线性混合

通常需要根据人物的移动速度播放idlewalkrun,Unity源生的解决办法是Blend TreeAnimancer也可以使用并控制动画),Animancer提供了叫做Mixer State(类似于Blen Tree)的方法

使用Blend Tree

引用Bled Tree

ControllerTransition类似ClipTransition可以引用Unity.RuntimeAnimatorController,这里面可以配置Blend Tree

参数控制

引用到Unity.Blend Tree之后,就可以参数来控制动画的播放状态了。Animacer也为我们准备了一个控制器来做这件事。

Float1ControllerTransition一个ScriptableObject类。可以引用指定的Unity.RuntimeAnimatorController,还能控制里面Blend Tree的单浮点混合树的参数

v8.0版本不一样了,详细参考下文的Mixer参数部分。需要注意的是,创建的ScriptableObject的名称需要与Unity.Animator中的参数名称一样

使用方法

创建一个脚本引用AnimancerComponent组件和Float1ControllerTransition使用_animancer.Play(Comtroller)就可以播放里面的Blend Tree了。然后改变Blend Tree的参数,就可以改变动画状态了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public sealed class LinearBlendTreeLocomotion : MonoBehaviour
{
[SerializeField] private AnimancerComponent _Animancer;
[SerializeField] private Float1ControllerTransitionAsset.UnShared _Controller;

private void OnEnable()
{
_Animancer.Play(_Controller);
}
/// <summary>Controlled by a <see cref="UnityEngine.UI.Slider"/>.</summary>
public float Speed
{
get => _Controller.State.Parameter;
set => _Controller.State.Parameter = value;
}
}

使用Mixer

官方介绍原文:

Mixer States are essentially just Blend Trees which are constructed using code at runtime instead of being configured in the Unity Editor as part of an Animator Controller. Specifically, they can be constructed using code and you can access their internal details even though in this example we are just using a Transition.

翻译:

混合器状态本质上只是混合树,它们是在运行时使用代码构建的,而不是在 Unity 编辑器中配置为动画控制器的一部分。具体来说,它们可以使用代码构建,并且您可以访问它们的内部详细信息,即使在本示例中我们仅使用Transition

旧版本:

与使用Bled Tree的方法类似,也需要用到参数控制器。只需将上面代码的Float1ControllerTransitionAsset.UnShared替换成LinearMixerTransitionAsset.UnShared就可以了

使用方法

v8.0版本:学着学着官方来了波大的,直接更新了一个大版本

使用Assets/Create/Animancer/Transition Asset创建一个Transition Assets将其改成LinearMixerTransition类型,然后添加动画、设置动画参数就可以使用了v8.0引入了一个新的功能,String Asset

Assets/Create/Animancer/String Asset创建一个String Asset

MixerTransition指定这个参数

再使用另一个脚本控制这个参数的数值,就能控制角色的混合状态了

1
2
3
4
5
6
7
8
9
[SerializeField] private Slider _Slider;
[SerializeField] private AnimancerComponent _Animancer;
[SerializeField] private StringAsset _ParameterName;

protected virtual void Awake()
{
_Parameter = _Animancer.Parameters.GetOrCreate<float>(_ParameterName);
_Slider.onValueChanged.AddListener(_Parameter.SetValue);// 这里是监听slider来控制数值,实际使用时可以使用输入系统
}
Time Synchronization时间同步

从下图可看出walk和run动画帧数差的很多

这样会导致,他们相应的姿势没有对齐

动画不正确的话,需要确认的有两件事

  1. 需要两个融合的动作是否相似,确保每个动画以相同的姿势开始这里的示例动画全部设置为在角色左脚接触地面时开始
  2. 选择Sync开关启用时间同步,开启后当Mixer混合这些动画的时候就能确保两个动画以相同的速度播放即使帧率不一致

将run动画的Cycle Offset设置为0.5,这样run动画和walk一样在第一帧是左脚接触地面的了

勾选walk和run的Sync。因为idle对角的影响不大,所以不用勾选;反而如果勾选了,由于idle足足有176帧,会让动画移动的很慢

最终效果:

速度推测

动画速度参数只设置了从0到1,如果超过1,参数依然按1计算

勾选ExtrapolateSpeed之后,Mixer能根据速度参数,来加速动画


Editor

想要使用某个值,将他应用到动画设置里,但是又嫌进入PlayMode麻烦,可以使用写一个void OnValidate()方法:

1
2
3
4
5
6
7
8
9
10
[SerializeField] private AnimationClip _Open;
[SerializeField, Range(0, 1)] private float _Openness;

#if UNITY_EDITOR
private void OnValidate()
{
if (_Open != null)
AnimancerUtilities.EditModeSampleAnimation(_Open, this, _Openness * _Open.length);
}
#endif

当Inspector窗口的_Open被实例化并且_Openness的值被改变了,就会通过MonoBehaviour Message传递信息,实时反馈到场景中


属性

数据单位

  • Meters:在Inspector窗口中显示一个m,表达这个数据的单位是米
  • DegreesPerSecond°/s,旋转角度,度每秒
  • MetersPerSecondm/s,移动速度,米每秒
  • Multiplierx,倍率

Animancer官方网站