美文网首页
[FA] 自定义Animator更新进度的实现与测试

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

作者: _Walker__ | 来源:发表于2023-04-25 14:04 被阅读0次

    记录环境

    • Unity 2021.3.21f1
    • Timeline 1.6.4

    问题背景

      为了解决[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)做的事情耗时并没有很高。我们推测性能差距主要不在于多线程,而是执行次数。

    • 固定30fps执行频率,会在一个Unity帧里执行多次动画模拟,耗时自然要多几倍
    • 自己的脚本是循环每个Animator逐个执行它们的Update,Unity内部可能有类似批处理的方式

      带着这个推测,我们确实也找到了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

    相关文章

      网友评论

          本文标题:[FA] 自定义Animator更新进度的实现与测试

          本文链接:https://www.haomeiwen.com/subject/wdkyjdtx.html