传递给我一个AnimationClip,我就播放它,如果有上一个动画,那么就自动过渡。

自定义动画控制器

using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;

public class Animation_Controller : MonoBehaviour
{
    [SerializeField] Animator animator;
    private PlayableGraph graph;
    public AnimationMixerPlayable mixer;
    private AnimationClipPlayable clipPlayable1;
    private AnimationClipPlayable clipPlayable2;
    private bool isFirstPlay = true;
    private bool currentisClipPlayable1;

    private void Start()
    {
        // 创建图
        graph = PlayableGraph.Create("Animation");

        // 设置图的时间模式
        graph.SetTimeUpdateMode(DirectorUpdateMode.GameTime);

        // 创建混合器
        mixer = AnimationMixerPlayable.Create(graph,2);

        // 创建Output
        AnimationPlayableOutput playableOutput = AnimationPlayableOutput.Create(graph, "Animation", animator);

        // 让混合器链接上OutPut
        playableOutput.SetSourcePlayable(mixer);

    }
    /// <summary>
    /// 播放动画
    /// </summary>
    public void PlayAnimation(AnimationClip animationClip)
    {   
        //如果是第一次播放,不存在过度
        if (isFirstPlay)
        {
            //直接连接clipPlayable1
            clipPlayable1 = AnimationClipPlayable.Create(graph, animationClip);
            graph.Connect(clipPlayable1, 0, mixer, 0);
            isFirstPlay = false;
            currentisClipPlayable1 = true;
        }
        //存在过渡
        else
        {   
            // 1 -> 2
            if (currentisClipPlayable1)
            {
                //解除已有的2
                graph.Disconnect(mixer, 1);
                //设置新的动画放到2
                clipPlayable2 = AnimationClipPlayable.Create(graph, animationClip);
                graph.Connect(clipPlayable2, 0, mixer, 1);
                mixer.SetInputWeight(1, 1);
            }
            // 2 -> 1
            else
            {
                //解除已有的1
                graph.Disconnect(mixer, 0);
                //设置新的动画放到1
                clipPlayable1 = AnimationClipPlayable.Create(graph, animationClip);
                graph.Connect(clipPlayable1, 0, mixer, 0);
                mixer.SetInputWeight(0, 1);
            }
            currentisClipPlayable1 = !currentisClipPlayable1;
            //TODO:设置过渡
        }
        if (graph.IsPlaying() == false)
        {
            graph.Play();
        }
    }
    private void OnDestroy()
    {
        graph.Destroy();
    }
    private void OnDisable()
    {
        graph.Stop();
    }
}

通过外界传进来一个animationClip包进clipPlayable进行播放,主要逻辑就是动态添加删除mixer的两个输入端口来维护新旧动画,总是更新比较老的那个端口的ClipPlayable,举例来说,第一次播放动画,默认使用端口1,就是最新的,第二次播放,使用端口2,1和2上的动画混合,第三次播放,端口1是相对较老的,所以解除1上的连接,将新动画连接到1上(ClipPlayable是结构体,不存在GC),第四次播放,端口2是相对较老的,解除2的连接,将新动画连接到2上,通过currentisClipPlayable1标记老端口是谁(就两个端口,一个新,一个老,当前是1,1就是新的,当前不是1,1就是老的)。

public class TestPlayable : MonoBehaviour
{
    [SerializeField] AnimationClip animationClip1;
    [SerializeField] AnimationClip animationClip2;
    [SerializeField] Animation_Controller animation_controller;

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Alpha1))
        {
            animation_controller.PlayAnimation(animationClip1);
        }

        if (Input.GetKeyDown(KeyCode.Alpha2))
        {
            animation_controller.PlayAnimation(animationClip2);
        }
    }
}

测试类,目前实现了新旧动画的切换(无过渡);

动画过渡

因为动画过渡涉及到时间变化量,我们用协程做。

    /// <summary>
    /// 动画过渡
    /// </summary>
    /// <param name="fixedTime">过渡的时间间隔,越小过渡速度越快</param>
    /// <param name="currentIsClipPlayable1"></param>
    /// <returns></returns>
    private IEnumerator TransitionAnimation(float fixedTime,bool currentIsClipPlayable1)
    {
        //当前的权重
        float currentWeight = 1;
        float speed = 1 / fixedTime;

        while (currentWeight > 0)
        {
            //yield return null; 不能放这
            currentWeight = Mathf.Clamp01(currentWeight - Time.deltaTime * speed);
            if (currentIsClipPlayable1) //当前播放端口1动画,从1过渡到2
            {
                mixer.SetInputWeight(0, currentWeight);
                mixer.SetInputWeight(1, 1 - currentWeight);
            }
            else
            {
                mixer.SetInputWeight(0, 1 - currentWeight);
                mixer.SetInputWeight(1, currentWeight);
            }
            yield return null;
        }
    }

yield return null的位置注意不能放前面,否则切换是停了一帧会有视觉暂留的BUG。

    /// <summary>
    /// 播放动画
    /// </summary>
    public void PlayAnimation(AnimationClip animationClip, float fixedTime = 0.25f)
    {   
  		......

            //设置过渡
            if (transitionCoroutine != null) StopCoroutine(transitionCoroutine);// 避免开启多个协程
            transitionCoroutine = StartCoroutine(TransitionAnimation(fixedTime, currentisClipPlayable1));
            currentisClipPlayable1 = !currentisClipPlayable1;
		......
    }

对应完善上层调用逻辑。

效果如下,能看到动画权重线颜色渐变,同时人物动画过渡。

alt