前面我们讲的都是直接通过动画片段的引用播放动画,Animancer也提供了直接通过动画名称来播放动画的方法。但这并不是推荐的使用方式,因为通过字符串播放比通过引用播放效率略低,且更难维护。
首先我们需要在角色身上挂载NamedAnimancerComponent组件。NamedAnimancerComponent继承于AnimancerComponent,它的内部多了一个字典,可以用来映射动画名称与动画引用的对应关系。我们可以在面板上指定一个默认动画状态,这里指定为Idle,但取消自动播放选项。

接下来创建一个脚本NamedAnimations,并编写如下代码。
public class NamedAnimations : MonoBehaviour
{
public NamedAnimancerComponent animancer;
public AnimationClip walk;
public AnimationClip run;
public void PlayIdle()
{
animancer.TryPlay("Relax-Idle");
}
public void PlayWalk()
{
var state = animancer.TryPlay("Relax-Walk-Forward");
if (state == null)
Debug.LogWarning("'Relax-Walk-Forward' 没有被注册");
}
public void InitializeWalkState()
{
animancer.States.Create(walk);
Debug.Log("创建状态:" + walk, this);
}
public void PlayRun()
{
animancer.Play(run);
}
}
将脚本挂载到角色身上,并对变量进行赋值。然后在场景中添加四个按钮,分别绑定脚本中的四个方法

接下来运行游戏。先来看PlayIdle的效果

正常播放。这是因为在NamedAnimancerComponent的字典中,我们事先添加了Idle动画,从而可以映射成功。

接下来再试试PlayWalk

显然,Walk动画并不在字典中,所以播放失败了。要在运行时动态注册动画,只需要像脚本中的InitializeWalkState()方法一样,创建一个包含对应动画剪辑的状态即可。

最后PlayRun就是直接通过动画剪辑的引用播放动画,并不需要字典中持有该动画的映射。

接下来我们来实现一个蜘蛛机器人的动画。它只有Move和WakeUp两个动画,我们要利用这两个动画实现两种状态间的无缝切换。这就需要对动画的速度和时间进行控制。

首先创建一个Spider Bot脚本。对于机器人当前的状态,我们可以通过对外界暴露一个IsMoving属性来进行控制。
public class SpiderBot : MonoBehaviour
{
private bool _isMoving;
public bool IsMoving
{
get => _isMoving;
set => _isMoving = value;
}
}
然后在Toggle的值改变事件中绑定这个属性,就可以通过UI来控制机器人的状态了。

接下来,我们要让机器人一开始就处于Sleep状态。这可以通过反向播放WakeUp动画实现。因为WakeUp动画并不是循环的,所以如果动画的Time达到0时,将会一直保持第一帧的姿势。一种简单的实现方式是直接把动画的Speed设置为-1。
public AnimancerComponent animancer;
public ClipTransition wakeUp;
public ClipTransition move;
private void Awake()
{
var state = animancer.Play(wakeUp);
state.Speed = -1;
}
但这种方式存在一些问题:首先,动画的Time会变成负数;其次,虽然实际上没有播放动画,但动画仍然会在每一帧进行计算,这会造成性能上的浪费。除此之外,将Speed设置为0或将IsPlaying设置为false,都无法避免类似的性能消耗。
一种更好的方式是直接暂停整个Playable Graph,这样就可以避免无意义的计算了。
private void Awake()
{
animancer.Play(wakeUp);
// 暂停整个图
animancer.Playable.PauseGraph();
// 计算第一帧
animancer.Evaluate();
}
接下来就是在IsMoving的值改变时,进行动画的切换了。定义两个方法WakeUp()和GoToSleep(),在IsMoving发生变化时调用
public bool IsMoving
{
get => _isMoving;
set
{
if (value)
WakeUp();
else
GoToSleep();
}
}
在WakeUp()方法中,我们需要播放WakeUp动画,将其速度设置为1,并解除暂停Playable Graph
private void WakeUp()
{
if(_isMoving) return;
_isMoving = true;
var state = animancer.Play(wakeUp);
state.Speed = 1;
animancer.Playable.UnpauseGraph();
}
在WakeUp播放结束后,我们需要播放Move动画,可以通过添加结束事件实现(注意需要区分是唤醒还是睡眠)
private void Awake()
{
// ...
wakeUp.Events.OnEnd = OnWakeUpEnd;
}
private void OnWakeUpEnd()
{
// 速度大于0是唤醒
if (wakeUp.State.Speed > 0)
{
animancer.Play(move);
}
// 否则是睡眠
else
{
animancer.Playable.PauseGraph();
}
}
在GoToSleep()方法中,我们需要反向播放WakeUp动画。这里需要注意,WakeUp动画在播放结束时NormalizedTime等于1,但向Move过渡时,NormalizedTime仍会继续增长并超过1。这意味着如果我们反转动画,NormalizedTime也需要花时间回到1,并在达到1时触发OnEnd事件,导致Playable Graph被暂停。此时角色会卡在一个奇怪的姿势。所以我们需要将NormalizedTime手动赋值为1。
private void GoToSleep()
{
if(!_isMoving) return;
_isMoving = false;
var state = animancer.Play(wakeUp);
state.Speed = -1;
if (state.Weight == 0 || state.NormalizedTime > 1)
{
state.NormalizedTime = 1;
}
}
最后来看下效果

为了节省性能,在某些情况下,当角色离相机较远时,我们希望降低动画的更新频率。Animancer也可以很轻松地实现这点,只需要在Evaluate()方法的参数中传入一个时间差x,就可以计算出距离上次更新x秒后动画的动作,并更新到角色身上。
首先创建一个脚本LowUpdateRate,并编写如下代码
public class LowUpdateRate : MonoBehaviour
{
public AnimancerComponent animancer;
// 更新速率
public float updatesPerSecond = 10;
// 上次更新时间
private float _lastUpdateTime;
private void OnEnable()
{
animancer.Playable.PauseGraph();
_lastUpdateTime = Time.time;
}
private void OnDisable()
{
if (animancer != null && animancer.IsPlayableInitialized)
animancer.Playable.UnpauseGraph();
}
private void Update()
{
var time = Time.time;
// 计算距离上次更新的时间差
var timeSinceLastUpdate = time - _lastUpdateTime;
// 如果时间差超过了更新速率则更新动画
if (timeSinceLastUpdate > 1 / updatesPerSecond)
{
animancer.Evaluate(timeSinceLastUpdate);
_lastUpdateTime = time;
}
}
}
可以给角色挂载NamedAnimancerComponent,并指定一个默认播放动画。运行一下看看效果

左侧是正常播放的机器人,右侧是低速率动画的机器人。可以看出左侧机器人的动画要更丝滑一些(受录制帧率的限制可能不明显)。
下面来实现根据摄像机距离动态启用/禁用LowUpdateRate的脚本。创建一个脚本DynamicUpdateRate,并编写如下代码
public class DynamicUpdateRate : MonoBehaviour
{
public LowUpdateRate lowUpdateRate;
public float slowUpdateDistance = 5;
private Transform _camera;
private void Awake()
{
_camera = Camera.main.transform;
}
private void Update()
{
// 计算相机与角色的距离
var offset = _camera.position - transform.position;
var squaredDistance = offset.sqrMagnitude;
// 根据距离启用/禁用LowUpdateRate脚本
lowUpdateRate.enabled = squaredDistance > slowUpdateDistance * slowUpdateDistance;
}
}
将挂载LowUpdateRate的机器人复制一份,并挂载上面这个脚本。运行游戏看下效果
最左侧是正播放的机器人,中间是始终低速率播放动画的机器人,最右侧是应用了动态控制动画速率的机器人。
有些物体只有一个动画,且不需要任何控制播放的逻辑,那就不需要前面那样复杂的操作,我们只需要挂载一个SoloAnimation组件即可实现。

如图所示,我们只需要指定一个动画片段,它就能在游戏运行时自动进行播放。

免责声明:本文系网络转载或改编,未找到原创作者,版权归原作者所有。如涉及版权,请联系删