创建BlendAnimationNode虚拟节点,封装混合动画Mixer和PlayableList。

using GraphVisualizer;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;

public class BlenderAnimationNode : AnimationNodeBase
{
    public AnimationMixerPlayable blendMixer;
    private List<AnimationClipPlayable> blendClipPlayableList = new List<AnimationClipPlayable>(10);

    public void Init(PlayableGraph graph,AnimationMixerPlayable outputMixer,List<AnimationClip> clips,float speed, int inputPort)
    {
        blendMixer = AnimationMixerPlayable.Create(graph, clips.Count);
        graph.Connect(blendMixer, 0, outputMixer, InputPort);
        this.InputPort = inputPort;
        for (int i = 0; i < clips.Count; i++)
        {
            CreateAndConnectBlendPlayable(graph, clips[i], i, speed);
        }
    }

    public void Init(PlayableGraph graph, AnimationMixerPlayable outputMixer, AnimationClip clip1,AnimationClip clip2, float speed, int inputPort)
    {
        blendMixer = AnimationMixerPlayable.Create(graph, 2);
        graph.Connect(blendMixer, 0, outputMixer, InputPort);
        this.InputPort = inputPort;
        CreateAndConnectBlendPlayable(graph, clip1, 0, speed);
        CreateAndConnectBlendPlayable(graph, clip2, 1, speed);
    }

    private AnimationClipPlayable CreateAndConnectBlendPlayable(PlayableGraph graph,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;
    }


    public void SetBlendWeight(float clip1Weight)
    {
        blendMixer.SetInputWeight(0, clip1Weight);
        blendMixer.SetInputWeight(1, 1 - clip1Weight);
    }

    public void SetBlendWeight(List<float> weightList)
    {
        for (int i = 0; i < blendClipPlayableList.Count; i++)
        {
            blendMixer.SetInputWeight(i, weightList[i]);
        }
    }

    public override void SetSpeed(float speed)
    {
        for (int i = 0; i < blendClipPlayableList.Count; i++)
        {
            blendClipPlayableList[i].SetSpeed(speed);
        }
    }
    public override void PushPool()
    {
        blendClipPlayableList.Clear();
        base.PushPool();
    }
}

相比SingleMode,Blend节点有多个动画的List,在回收时要清空数据,所以首先重写PushPool。

    public override void PushPool()
    {
        blendClipPlayableList.Clear();
        base.PushPool();
    }

类似的,把原来在AnimationController里面的有关BlendMixer创建,连接,设置速度的逻辑转移到抽象节点下,逻辑相同。

    /// <summary>
    /// 播放混合动画
    /// </summary>
    /// <param name="clips"></param>
    /// <param name="speed"></param>
    /// <param name="transitionFixedTime"></param>
    public void PlayBlendAnimaiton(List<AnimationClip> clips,float speed = 1,float transitionFixedTime = 0.25f)
    {
        BlenderAnimationNode blenderAnimationNode = PoolManager.Instance.GetObject<BlenderAnimationNode>();
        // 如果是第一次播放,不存在过渡
        if (currentNode == null)
        {
            blenderAnimationNode.Init(graph, mixer, clips, speed, inputPort0);
            mixer.SetInputWeight(inputPort0, 1);
        }
        else
        {
            DestoryNode(previousNode);
            blenderAnimationNode.Init(graph, mixer, clips, speed, inputPort1);
            previousNode = currentNode;
            StartTransitionAnimation(transitionFixedTime);
        }
        this.speed = speed; //只需要把记录值更新一下即可,在Init时实际每个动画都已经设置好了速度,不用使用属性再赋值
        currentNode = blenderAnimationNode;
        if (graph.IsPlaying() == false) graph.Play();
    }


    /// <summary>
    /// 播放混合动画(2个)
    /// </summary>
    /// <param name="clips"></param>
    /// <param name="speed"></param>
    /// <param name="transitionFixedTime"></param>
    public void PlayBlendAnimaiton(AnimationClip clip1,AnimationClip clip2, float speed = 1, float transitionFixedTime = 0.25f)
    {
        BlenderAnimationNode blenderAnimationNode = PoolManager.Instance.GetObject<BlenderAnimationNode>();
        // 如果是第一次播放,不存在过渡
        if (currentNode == null)
        {
            blenderAnimationNode.Init(graph, mixer, clip1,clip2, speed, inputPort0);
            mixer.SetInputWeight(inputPort0, 1);
        }
        else
        {
            DestoryNode(previousNode);
            blenderAnimationNode.Init(graph, mixer, clip1,clip2, speed, inputPort1);
            previousNode = currentNode;
            StartTransitionAnimation(transitionFixedTime);
        }
        this.speed = speed; //只需要把记录值更新一下即可,在Init时实际每个动画都已经设置好了速度,不用使用属性再赋值
        currentNode = blenderAnimationNode;
        if (graph.IsPlaying() == false) graph.Play();
    }


    public void SetBlendWeight(float clip1Weight)
    {
        (currentNode as BlenderAnimationNode).SetBlendWeight(clip1Weight);
    }

    public void SetBlendWeight(List<float> weightList)
    {
        (currentNode as BlenderAnimationNode).SetBlendWeight(weightList);
    }

AnimationController里此时只需要考虑动画过渡,断连,连接的逻辑,由于我们约定了新旧两个端口,并在动画过渡里完善了交换逻辑,不需要多余的判断条件,只有两种情况,要么是第一次播放,使用0端口,要么就是非第一次播放,使用最新的1端口。设置权重的逻辑相同,调用抽象节点内封装的方法即可。

到此,我么已经完成了对AnimationController的重构,通过指定端口大大简化了判断逻辑,并通过抽象节点的封装使得代码逻辑更加清晰,同时,这种重构增加了Blend动画到Blend动画的过渡,因为我们原来实际只考虑了播放单个动画和单个到多个的过渡,如果想实现Blend到Blend的过渡必须新加判断逻辑并考虑判断的优先顺序,而现在通过抽象节点,在AnimationController里并不考虑具体是哪个节点到哪个节点切换,上层只负责根据传进来的参数调用抽象节点的方法完成及诶点断连,连接。至于节点具体如何释放,建立由抽象节点内部逻辑负责。

alt