[FA] 自定义Animator更新进度的实现与测试

2023-04-25  本文已影响0人  _Walker__

记录环境

问题背景

  为了解决[RS] Timeline踩坑(3):多轨道播放不同步中【案例1:执行时长差异】的问题,我想了一个处理方案:

我们自己在脚本里更新Animator,禁用Unity默认的行为。
保证Animator、TML都是以30fps的固定时间步执行。

// 停止Animator自己身的更新
animator.playableGraph.SetTimeUpdateMode(DirectorUpdateMode.Manual);

private const float Step = 1 / 30f;
private float _total = 0;

void Update()
{
    _total += Time.deltaTime;
    while (_total >= Step)
    {
        _total -= Step;
        foreach (Animator animator in _animators)
        {
            animator.Update(Step);
        }
    }
}

  经测试,用上面的代码可以达到目的,但是有严重的性能问题。我在红米6上进行测试,同时更新30个Animator,每帧主线程耗时能高达30ms,这是完全无法接受的。(耗时参考下图)
  从Profiler的性能对比来看,自定义的更新比Unity内部更新,耗时多出了2倍,性能差距非常大。但是在观察Profiler的Timeline模式时,子线程(Job)做的事情耗时并没有很高。我们推测性能差距主要不在于多线程,而是执行次数。

  带着这个推测,我们确实也找到了Unity给出的解决方案[1],下面是核心实现。

用AnimatorControllerPlayable将多个Animator连接到一个PlayableGraph上,然你通过这一个PlayableGraph批量更新所有的Animator

private const float Step = 1 / 30f;
private float _total = 0;

private Animator[] _animators;
private PlayableGraph _globalAniGraph;

void Awake()
{
    _globalAniGraph = PlayableGraph.Create("GlobalAnimatorGraph");
    _globalAniGraph.SetTimeUpdateMode(DirectorUpdateMode.Manual);

    _animators = GetComponentsInChildren<Animator>();
    for (int i = 0; i < _graphs.Length; ++i)
    {
        Animator animator = _animators[i];
        var playable = AnimatorControllerPlayable.Create(_globalAniGraph, animator.runtimeAnimatorController);
        var output = AnimationPlayableOutput.Create(_globalAniGraph, animator.name + "Output", animator);
        output.SetSourcePlayable(playable);
    }
}

void Update()
{
    _total += Time.deltaTime;
    while (_total >= Step)
    {
        _total -= Step;
        _globalAniGraph.Evaluate(Step);
    }
}
性能对比图

  上图是我做的几种性能测试,都是在红米6上,同时执行30个Animator。从左到右依次是:

  1. Unity默认的Animator自动更新
  2. 以30fps的固定帧率在脚本里,逐个调用Animator.Update(最上面代码的做法)
  3. 以30fps的固定帧率,用一个PlayableGraph更新所有Animator(第2段代码的做法)
  4. 用一个PlayableGraph更新所有Animator,每个Unity帧只执行一次

  从结果看,做法4的性能跟原生(做法1)的几乎一致。做法3与2相比也有比较大的提升。为了解决不同步问题,需要的做法是3,但由于一帧内执行的Evaluate次数多,它仍有很高的性能消耗。所以,暂时不准备落实到项目里。

参考文章:

[1] Prevent Animator from auto updating?
[2] PlayableGraph Evaluate performance/best practices

上一篇下一篇

猜你喜欢

热点阅读