增加Playable动画的速度控制、刷新逻辑、以及多动画混合播放模式

增加动画速度控制

在原来的PlayAnimation方法参数表中扩展参数speed,并通过clipPlayable1.SetSpeed(speed)来设置动画速度。

增加重复动画切换是否刷新逻辑

有一些动画如果重复播放实际上是不需要刷新的,不如人站着再到站着不用切换,但有一些如子弹射击的动画是需要重复刷新的,为了满足这些自定义的逻辑,在原来的PlayAnimation方法参数表中扩展参数refreshAnimation,只有当其为false的情况才播放同一动画,否则return。

添加Blend动画模式

alt

在实际使用中,Blend模式即多个动画混在一块作为一个Mixer连接到Playable上是很常用的功能,如上图,要在原来的0,1端口结构上扩展一个2端口给Blend用,为此,需要对PlayAnimation方法中的逻辑进行优化(在切换回单个动画时多了一种从Blend的可能),并添加PlayBlendAnimation方法来处理混合动画。

对原来的AnimatorController中PlayAnimation(单个动画)的结构进行优化

首先用来标识第一次播放的IsFirstPlay标记量可以省去,通过判断端口1是否是默认ClipPlayable即可。

        //如果是第一次播放,不存在过度
        if (clipPlayable1.Equals(default(AnimationClipPlayable)))
        {
            //直接连接clipPlayable1
            clipPlayable1 = AnimationClipPlayable.Create(graph, animationClip);
            clipPlayable1.SetSpeed(speed);
            graph.Connect(clipPlayable1, 0, mixer, 0);
            mixer.SetInputWeight(0, 1);
            currentisClipPlayable1 = true;
        }

在非第一次播放的情况,需要进行动画的切换,首先在启动动画切换协程这一块,把避免开启多个协程的逻辑提前,并且把启动协程的逻辑放到每个具体的动画切换分支内,原来我们是放在动画分支最后开启协程时统一传入currentisClipPlayable1标记量(当前正在使用的端口)来完成过渡逻辑,但考虑到现在有Blend模式作为端口2,0和1端口之间的切换已经不能满足功能了所以我们针对具体情况进行端口传参,直接把当前正在使用的端口和要使用的新端口传进去,还可以节省携程内的逻辑判断。

    /// <summary>
    /// 播放动画
    /// </summary>
    public void PlayAnimation(AnimationClip animationClip,float speed = 1, bool refreshAnimation = false, float transtionFixedTime = 0.25f)
    {   
        //如果是第一次播放,不存在过度
        if (clipPlayable1.Equals(default(AnimationClipPlayable)))
        {
            //直接连接clipPlayable1
            clipPlayable1 = AnimationClipPlayable.Create(graph, animationClip);
            clipPlayable1.SetSpeed(speed);
            graph.Connect(clipPlayable1, 0, mixer, 0);
            mixer.SetInputWeight(0, 1);
            currentisClipPlayable1 = true;
        }
        //存在过渡
        else
        {
            //设置过渡
            if (transitionCoroutine != null) StopCoroutine(transitionCoroutine);// 避免开启多个协程
            //blend动画的优先级更高
            if (currentisBlend)
            {
                //从blend切换到普通
                //解除已有的1
                graph.Disconnect(mixer, 0);
                //设置新的动画放到1
                clipPlayable1 = AnimationClipPlayable.Create(graph, animationClip);
                clipPlayable1.SetSpeed(speed);
                graph.Connect(clipPlayable1, 0, mixer, 0);
                transitionCoroutine = StartCoroutine(TransitionAnimation(2, 0, transtionFixedTime));
                currentisClipPlayable1 = true;

            }
            // 1 -> 2
            if (currentisClipPlayable1)
            {   
                //是否刷新,避免重复播放同一个动画(有些动画)
                if (refreshAnimation == true && clipPlayable2.GetAnimationClip() == animationClip) return;

                //解除已有的2
                graph.Disconnect(mixer, 1);
                //设置新的动画放到2
                clipPlayable2 = AnimationClipPlayable.Create(graph, animationClip);
                clipPlayable2.SetSpeed(speed);
                graph.Connect(clipPlayable2, 0, mixer, 1);
                transitionCoroutine = StartCoroutine(TransitionAnimation(0, 1, transtionFixedTime));
            }
            // 2 -> 1
            else
            {   
                //是否刷新
                if (refreshAnimation == true && clipPlayable1.GetAnimationClip() == animationClip) return;

                //解除已有的1
                graph.Disconnect(mixer, 0);
                //设置新的动画放到1
                clipPlayable1 = AnimationClipPlayable.Create(graph, animationClip);
                clipPlayable1.SetSpeed(speed);
                graph.Connect(clipPlayable1, 0, mixer, 0);
                transitionCoroutine = StartCoroutine(TransitionAnimation(1, 0, transtionFixedTime));
            }
            
            currentisClipPlayable1 = !currentisClipPlayable1;
        }

        if (graph.IsPlaying() == false)
        {
            graph.Play();
        }
    }
    /// <summary>
    /// 动画过渡
    /// </summary>
    /// <param name="fixedTime">过渡的时间间隔,越小过渡速度越快</param>
    /// <param name="currentIsClipPlayable1"></param>
    /// <returns></returns>
    private IEnumerator TransitionAnimation(int inputIndex1,int inputIndex2, float fixedTime)
    {
        //硬切判断
        if (fixedTime == 0)
        {
            mixer.SetInputWeight(inputIndex1, 0);
            mixer.SetInputWeight(inputIndex2, 1);

        }
        //当前的权重
        float currentWeight = 1;
        float speed = 1 / fixedTime;

        while (currentWeight > 0)
        {
            //yield return null; 不能放这
            currentWeight = Mathf.Clamp01(currentWeight - Time.deltaTime * speed);
            mixer.SetInputWeight(inputIndex1,currentWeight); // 减少
            mixer.SetInputWeight(inputIndex2, 1 - currentWeight); //增加
            yield return null;
        }
    }

有2个关键点做出了改变,首先是上面说的重复刷新逻辑,以1->2(0->1)为例,这时候当前端口是0,1作为旧端口会被要切换的动画进行覆盖,如果1端口上的动画和要切换的动画一样并且refreshAnimation==true时不刷新动画直接Return。

  if (refreshAnimation == true && clipPlayable2.GetAnimationClip() == animationClip) return;

第二点在动画过渡上,上文已经说过现在动画切换协程的开启分给了每个具体的分支,并且也已经不只是0->1,1->0的切换了,很有可能播放单个动画时当前正在播放Blend动画,就多了一种2->0(2->1也可以随便给一个端口用)的情况,且这种情况优先级更高,出现时,释放0号端口并接上新的要切换的动画,传入当前端口和切换动画使用的端口做权重衰减/增加即可,因为指定了端口参数,协程里避免了判断逻辑更加简洁。

            //blend动画的优先级更高
            if (currentisBlend)
            {
                //从blend切换到普通
                //解除已有的1
                graph.Disconnect(mixer, 0);
                //设置新的动画放到1
                clipPlayable1 = AnimationClipPlayable.Create(graph, animationClip);
                clipPlayable1.SetSpeed(speed);
                graph.Connect(clipPlayable1, 0, mixer, 0);
                transitionCoroutine = StartCoroutine(TransitionAnimation(2, 0, transtionFixedTime));
                currentisClipPlayable1 = true;

            }

最后注意下调用AnimationController的PlayerController方法参数表也要改变(外界->Controller->View->AnimationColler)

对原来的AnimatorController中PlayBlendAnimation(多个动画)的结构进行优化

以上工作都在为播放多个动画做准备,现在已经有0->1,1->0,2->0的逻辑了,实际PlayBlendAnimation的逻辑是0->2和1->2,为此要实现它,大体逻辑和播放单个动画一样,先释放端口再生成新的Mixer并链接上动画设置动画相应的速度等参数,需要动画过渡,传端口号给协程更改权重即可,有多个动画clip封装逻辑复用即可。

首先由于都是切换到Blend端口2,统一对原来的端口2进行断连重置,根据传进来的Clip List数量生成新的Mixer。


    public void ResetBlend(int animationCount)
    {
        //创建Blend混合器
        graph.Disconnect(mixer, 2);
        blendMixer = AnimationMixerPlayable.Create(graph, animationCount);
        graph.Connect(blendMixer, 0, mixer, 2);
    }

再将动画片段们连接到Mixer上。

    for (int i = 0; i < clips.Count; i++)
    {
        CreateAndConnectBlendPlayable(clips[i], i, speed);
    }

    private AnimationClipPlayable CreateAndConnectBlendPlayable(AnimationClip clip,int index,float speed)
    {
        AnimationClipPlayable clipPlayable = AnimationClipPlayable.Create(graph, clip);
        clipPlayable.SetSpeed(speed);
        blendClipPlayableList.Add(clipPlayable);
        graph.Connect(clipPlayable, 0, blendMixer, index);
        return clipPlayable; 
    }

之后再考虑过渡动画的情况,传入协程端口号,更改对应的权重即可。

        //如果是第一次播放,不存在过度
        if (clipPlayable1.Equals(default(AnimationClipPlayable))) return;
        //设置过渡
        if (transitionCoroutine != null) StopCoroutine(transitionCoroutine);// 避免开启多个协程
        if (currentisClipPlayable1)
        {
            transitionCoroutine = StartCoroutine(TransitionAnimation(0, 2, transitionFixedTime));
        }
        else
        {
            transitionCoroutine = StartCoroutine(TransitionAnimation(1, 2, transitionFixedTime));
        }

本节理论性较强,实际就是在对Playable的分支端口进行操作(麻烦的地方就在与端口断连与Mixer生成重新连接的过程),理清0,1,2端口之间的切换逻辑有助于编写自定义Playable动画系统。