Playable 动画系统

Playable 基本用法

Playable 动画系统_第1张图片
Playable意思是可播放的,可运行的。Playable整体是树形结构,PlayableGraph相当于一个容器,所有元素都被包含在里面,图中的每个节点都是Playable,叶子节点的Playable包裹原始数据,相当于输入,中间的Mixer根据权重混合多个输入,最后汇总到根部的Output节点,然后由PlayableGraph播放。

Playable 动画系统_第2张图片
Playable的核心类型
Playable 动画系统_第3张图片
Playable的输出类型

这些不同类型的Playable都是结构体,所以它们之间不是继承关系,但是可以隐式转换,如

AnimationClipPlayable clipPlayable = AnimationClipPlayable.Create(playableGraph, clip);
//隐式转换
Playable playable = clipPlayable;

播放单个动画片段

官方示例

[RequireComponent(typeof(Animator))]
public class PlayAnimationSample : MonoBehaviour
{
    public AnimationClip clip;
    private PlayableGraph playableGraph;

    void Start()
    {
        //创建PlayableGraph
        playableGraph = PlayableGraph.Create();
        playableGraph.SetTimeUpdateMode(DirectorUpdateMode.GameTime);
        
        //创建AnimationClipPlayable包裹AnimationClip,附加到PlayableGraph上
        var clipPlayable = AnimationClipPlayable.Create(playableGraph, clip);
        
        //创建输出节点并把Animator设为目标,Animator会处理PlayableGraph
        var playableOutput = AnimationPlayableOutput.Create(playableGraph, "Animation", GetComponent<Animator>());

        //连接输入源
        playableOutput.SetSourcePlayable(clipPlayable);

        // Plays the Graph.
        playableGraph.Play();
    }

    void OnDisable()
    {
        playableGraph.Destroy();
    }
}

Playable 动画系统_第4张图片
这样不使用Animator Controller,通过脚本就可以控制动画播放,而且Animator Controller是不允许运行时添加、删除动画的,使用Playable就可以运行时添加,删除动画。
Playable 动画系统_第5张图片
使用PlayableGraph Visualizer查看Playable结构

创建动画混合树

[RequireComponent(typeof(Animator))]
public class MixAnimationSample : MonoBehaviour
{
    public AnimationClip clip0;
    public AnimationClip clip1;
    public float weight;
    
    private PlayableGraph playableGraph;
    private AnimationMixerPlayable mixerPlayable;
    private AnimationClipPlayable clipPlayable0;

    void Start()
    {
        playableGraph = PlayableGraph.Create();
        playableGraph.SetTimeUpdateMode(DirectorUpdateMode.GameTime);

        var playableOutput = AnimationPlayableOutput.Create(playableGraph, "Animation", GetComponent<Animator>());

        //创建AnimationMixerPlayable,2表示输入的数量
        mixerPlayable = AnimationMixerPlayable.Create(playableGraph, 2);
        playableOutput.SetSourcePlayable(mixerPlayable);

        clipPlayable0 = AnimationClipPlayable.Create(playableGraph, clip0);
        var clipPlayable1 = AnimationClipPlayable.Create(playableGraph, clip1);

        //连接两个Playable,clipPlayable是源头,mixerPlayable是目标
        //clipPlayable0和clipPlayable1的默认输出端口号是0,分别连接到mixerPlayable输入端口0和1
        playableGraph.Connect(clipPlayable0, 0, mixerPlayable, 0);
        playableGraph.Connect(clipPlayable1, 0, mixerPlayable, 1);
        
        playableGraph.Play();
    }

    void Update()
    {
        //保证所有输入源的权重和为1
        weight = Mathf.Clamp01(weight);
        mixerPlayable.SetInputWeight(0, 1.0f-weight);
        mixerPlayable.SetInputWeight(1, weight);
        
        //切换输入的状态
        if (Input.GetKeyDown(KeyCode.Space))
        {
            if (clipPlayable0.GetPlayState() == PlayState.Playing)
            {
                clipPlayable0.Pause();
            }
            else
            {
                clipPlayable0.Play();
                clipPlayable0.SetTime(0f);
            }
        }
    }

    void OnDisable()
    {
        playableGraph.Destroy();
    }
}


调整权重在两个动画之间过渡,我们还可以修改某个输入节点的状态
Playable 动画系统_第6张图片
大型的RPG或FPS游戏,没必要把大量的动画都添加到Graph中,我们可以预先创建好需要的子树,然后根据需要在添加到Graph中

混合AnimationClip和AnimatorController

[RequireComponent(typeof(Animator))]
public class RuntimeControllerSample : MonoBehaviour
{
    public AnimationClip clip;
    public RuntimeAnimatorController controller;
    public float weight;
    private PlayableGraph playableGraph;
    private AnimationMixerPlayable mixerPlayable;

    void Start()
    {
        playableGraph = PlayableGraph.Create();
        var playableOutput = AnimationPlayableOutput.Create(playableGraph, "Animation", GetComponent<Animator>());
        mixerPlayable = AnimationMixerPlayable.Create(playableGraph, 2);
        playableOutput.SetSourcePlayable(mixerPlayable);
        
        var clipPlayable = AnimationClipPlayable.Create(playableGraph, clip);
        var ctrlPlayable = AnimatorControllerPlayable.Create(playableGraph, controller);

        playableGraph.Connect(clipPlayable, 0, mixerPlayable, 0);
        playableGraph.Connect(ctrlPlayable, 0, mixerPlayable, 1);
        
        playableGraph.Play();
    }

    void Update()
    {
        weight = Mathf.Clamp01(weight);
        mixerPlayable.SetInputWeight(0, 1.0f-weight);
        mixerPlayable.SetInputWeight(1, weight);
    }

    void OnDisable()
    {
        playableGraph.Destroy();
    }
}

AnimationClipPlayable包裹AnimationClip,而AnimationrControllerPlayable则包裹RuntimeAnimationrController

Playable 动画系统_第7张图片
每个角色都有的动画如走,跑,跳用Animator管理,角色的特殊动画用Playable和Animator融合

多个输出

[RequireComponent(typeof(Animator))]
[RequireComponent(typeof(AudioSource))]
public class MultiOutputSample : MonoBehaviour
{
    public AnimationClip animationClip;
    public AudioClip audioClip;
    private PlayableGraph playableGraph;

    void Start()
    {
        playableGraph = PlayableGraph.Create();

        var animationOutput = AnimationPlayableOutput.Create(playableGraph, "Animation", GetComponent<Animator>());
        var audioOutput = AudioPlayableOutput.Create(playableGraph, "Audio", GetComponent<AudioSource>());
        
        var animationClipPlayable = AnimationClipPlayable.Create(playableGraph, animationClip);
        var audioClipPlayable = AudioClipPlayable.Create(playableGraph, audioClip, true);
        
        animationOutput.SetSourcePlayable(animationClipPlayable);
        audioOutput.SetSourcePlayable(audioClipPlayable);

        playableGraph.Play();
    }

    void OnDisable()
    {
        playableGraph.Destroy();
    }
}

Playable 动画系统_第8张图片
两个输出对象分别是Animator和AudioSource

自定义PlayableBehaviour实现动画队列

PlayableBehaviour 是一个用于实现自定义 Playable 的基类,它可以让开发者通过继承该类来自定义 Playable 行为,可以用于在播放过程中控制动画的逻辑

public class PlayQueuePlayable : PlayableBehaviour
{
    private int m_CurrentClipIndex = -1;
    private float m_TimeToNextClip;
    private Playable mixer;

    public void Initialize(AnimationClip[] clipsToPlay, Playable owner, PlayableGraph graph)
    {
        owner.SetInputCount(1);
        mixer = AnimationMixerPlayable.Create(graph, clipsToPlay.Length);
        graph.Connect(mixer, 0, owner, 0);
        owner.SetInputWeight(0, 1);
        for (int clipIndex = 0; clipIndex < mixer.GetInputCount(); ++clipIndex)
        {
            graph.Connect(AnimationClipPlayable.Create(graph, clipsToPlay[clipIndex]), 0, mixer, clipIndex);
            mixer.SetInputWeight(clipIndex, 1.0f);
        }
    }

    /// 
    /// 每帧调用
    /// 
    public override void PrepareFrame(Playable owner, FrameData info)
    {
        if (mixer.GetInputCount() == 0)
            return;
        
        m_TimeToNextClip -= (float)info.deltaTime;
        if (m_TimeToNextClip <= 0.0f)
        {
            m_CurrentClipIndex++;
            if (m_CurrentClipIndex >= mixer.GetInputCount())
                m_CurrentClipIndex = 0;

            //切换到下一个动画片段
            var currentClip = (AnimationClipPlayable)mixer.GetInput(m_CurrentClipIndex);
            currentClip.SetTime(0);
            m_TimeToNextClip = currentClip.GetAnimationClip().length;
        }

        //当前片段权重设为1,其他为0
        for (int clipIndex = 0; clipIndex < mixer.GetInputCount(); ++clipIndex)
        {
            mixer.SetInputWeight(clipIndex, clipIndex == m_CurrentClipIndex ? 1.0f : 0.0f);
        }
    }
}

[RequireComponent(typeof (Animator))]
public class PlayQueueSample : MonoBehaviour
{
    public AnimationClip[] clipsToPlay;
    private PlayableGraph playableGraph;

    void Start()
    {
        playableGraph = PlayableGraph.Create();
        var playQueuePlayable = ScriptPlayable<PlayQueuePlayable>.Create(playableGraph);
        var playQueue = playQueuePlayable.GetBehaviour();
        playQueue.Initialize(clipsToPlay, playQueuePlayable, playableGraph);
        
        var playableOutput = AnimationPlayableOutput.Create(playableGraph, "Animation", GetComponent<Animator>());
        playableOutput.SetSourcePlayable(playQueuePlayable);
        playableOutput.SetSourceInputPort(0);

        playableGraph.Play();
    }

    void OnDisable()
    {
        playableGraph.Destroy();
    }
}

ScriptPlayable< T>.Create 是一个静态方法,用于创建一个ScriptPlayable< T>实例,并添加到PlayableGraph中。ScriptPlayable< T> 是一个结构体,用于创建自定义的 Playable 行为,其中T需要继承 PlayableBehaviour 。ScriptPlayable 结构体还提供了一些静态方法,用于创建和管理可播放对象。

ScriptPlayable< T>实例实际上是将泛型 T 包装在一个结构体中。这个结构体提供了一些方法,使得 T 类型能够被 PlayableGraph 所使用。

随机切换动画

实现从一个默认动画随机切换到另一个动画,这两个动画之间需要做融合,且播放完动画后切会默认动画,大致的流程如下
Playable 动画系统_第9张图片
RandomSelector是一个随机选择器,Mixer是一个混合器,通过调整权重来实现切换
Playable 动画系统_第10张图片
为了方便管理动画,把每个动画片段包裹到AnimUnit,管理动画状态,并输出信息

Playable 动画系统_第11张图片
使用适配器实现多态,AnimAdapter里面有一个AnimBehaviour的引用,适配器本身没有功能,它的具体功能取决于引用AnimBehaviour的哪一个子类

/// 
/// 适配器
/// 
public class AnimAdapter : PlayableBehaviour
{
    private AnimBehaviour _behaviour;
    
    public void Init(AnimBehaviour behaviour)
    {
        _behaviour = behaviour;
    }

    public void Enable()
    {
        _behaviour?.Enable();
    }
    
    public void Disable()
    {
        _behaviour?.Disable();
    }

    public override void PrepareFrame(Playable playable, FrameData info)
    {
        _behaviour?.Execute(playable, info);
    }

    public float GetEnterTime()
    {
        return _behaviour.GetEnterTime();
    }

    public override void OnGraphStop(Playable playable)
    {
        base.OnGraphStop(playable);
        _behaviour?.Stop();
    }
}
/// 
/// 组件基类
/// 
public abstract class AnimBehaviour
{
    public bool enable { get; protected set; }
    public float remainTime { get; protected set; }
    
    //记录这个AnimBehaviour属于那个AnimAdapter
    protected Playable _adapterPlayable;
    protected float _enterTime;
    protected float _clipLength;
    
    public AnimBehaviour(){}

    public AnimBehaviour(PlayableGraph graph, float enterTime = 0)
    {
        _adapterPlayable = ScriptPlayable<AnimAdapter>.Create(graph);
        ((ScriptPlayable<AnimAdapter>)_adapterPlayable).GetBehaviour().Init(this);

        _enterTime = enterTime;
        _clipLength = float.NaN;
    }

    public virtual void Enable()
    {
        enable = true;
        remainTime = GetClipLength();
    }
    
    public virtual void Disable()
    {
        enable = false;
    }

    public virtual void Execute(Playable playable, FrameData info)
    {
        if (!enable)
            return;
        remainTime = remainTime > 0 ? remainTime - info.deltaTime : 0;
    }

    public virtual void Stop()
    {
        
    }

    public Playable GetAnimAdapterPlayable()
    {
        return _adapterPlayable;
    }

    public virtual void AddInput(Playable playable)
    {
        
    }

    public void AddInput(AnimBehaviour behaviour)
    {
        AddInput(behaviour.GetAnimAdapterPlayable());
    }

    public virtual float GetEnterTime()
    {
        return _enterTime;
    }
    
    public virtual float GetClipLength()
    {
        return _clipLength;
    }
}
/// 
/// 输出的子节点,作为一个空节点,隔开输出和实际的输入
/// Enable就启用所有子节点,Disable就禁用所有子节点
/// 
public class Root : AnimBehaviour
{
    public Root(PlayableGraph graph) : base(graph)
    {
        
    }

    public override void AddInput(Playable playable)
    {
        _adapterPlayable.AddInput(playable, 0, 1);
    }

    public override void Enable()
    {
        base.Enable();
        for (int i = 0; i < _adapterPlayable.GetInputCount(); ++i)
        {
            AnimHelper.Enable(_adapterPlayable.GetInput(i));
        }
        _adapterPlayable.SetTime(0f);
        _adapterPlayable.Play();
    }
    
    public override void Disable()
    {
        base.Disable();
        for (int i = 0; i < _adapterPlayable.GetInputCount(); ++i)
        {
            AnimHelper.Disable(_adapterPlayable.GetInput(i));
        }
        _adapterPlayable.Pause();
    }
}
public class AnimHelper
{
    public static void Enable(Playable playable)
    {
        var adapter = GetAdapter(playable);
        if (adapter != null)
        {
            adapter.Enable();
        }
    }

    public static void Enable(AnimationMixerPlayable mixer, int index)
    {
        Enable(mixer.GetInput(index));
    }
    
    public static void Disable(Playable playable)
    {
        var adapter = GetAdapter(playable);
        if (adapter != null)
        {
            adapter.Disable();
        }
    }
    
    public static void Disable(AnimationMixerPlayable mixer, int index)
    {
        Disable(mixer.GetInput(index));
    }

    public static AnimAdapter GetAdapter(Playable playable)
    {
        //检查playbble类型是否继承AnimAdapter
        if (typeof(AnimAdapter).IsAssignableFrom(playable.GetPlayableType()))
        {
            return ((ScriptPlayable<AnimAdapter>)playable).GetBehaviour();
        }
        return null;
    }

    public static void SetOutput(PlayableGraph graph, Animator animator, AnimBehaviour behaviour)
    {
        Root root = new Root(graph);
        root.AddInput(behaviour);
        var output = AnimationPlayableOutput.Create(graph, "Anim", animator);
        output.SetSourcePlayable(root.GetAnimAdapterPlayable());
    }

    public static void Start(PlayableGraph graph, AnimBehaviour behaviour)
    {
        graph.Play();
        behaviour.Enable();
    }
    
    public static void Start(PlayableGraph graph)
    {
        graph.Play();
        //获取output的子节点,即root节点
        GetAdapter(graph.GetOutputByType<AnimationPlayableOutput>(0).GetSourcePlayable()).Enable();
    }

    public static ComputeShader LoadCompute(string name)
    {
        ComputeShader computeShader = Resources.Load<ComputeShader>("Compute/" + name);
        //拷贝一份实例,不然多个对象公用一个shader数据会冲突
        return Object.Instantiate(computeShader);
    }
}

AnimUnit 组件

/// 
/// 包裹AnimationClipPlayable
/// 
public class AnimUnit : AnimBehaviour
{
    private AnimationClipPlayable _clipPlayable;
    
    public AnimUnit(PlayableGraph graph, AnimationClip clip, float enterTime = 0) : base(graph, enterTime)
    {
        _clipPlayable = AnimationClipPlayable.Create(graph, clip);
        _adapterPlayable.AddInput(_clipPlayable, 0, 1f);
        _clipLength = clip.length;
        Disable();
    }
    
    public override void Enable()
    {
        base.Enable();
        _adapterPlayable.SetTime(0);
        _clipPlayable.SetTime(0);
        _adapterPlayable.Play();
        _clipPlayable.Play();
    }
    
    public override void Disable()
    {
        base.Disable();
        _adapterPlayable.Pause();
        _clipPlayable.Pause();
    }
}

随机动画选择器组件 RandomSelector

/// 
/// 动画选择器基类
/// 
public class AnimSelector : AnimBehaviour
{
    public int currentIndex { get; protected set; }
    public int clipCount { get; protected set; }
    
    private AnimationMixerPlayable _mixer;
    private List<float> _enterTimes;
    private List<float> _clipLengths;
    
    public AnimSelector(PlayableGraph graph) : base(graph)
    {
        _mixer = AnimationMixerPlayable.Create(graph);
        _adapterPlayable.AddInput(_mixer, 0, 1f);
        currentIndex = -1;
        _enterTimes = new List<float>();
        _clipLengths = new List<float>();
    }

    public override void AddInput(Playable playable)
    {
        _mixer.AddInput(playable, 0);
        clipCount++;
    }

    public void AddInput(AnimationClip clip, float enterTime)
    {
        AddInput(new AnimUnit(_adapterPlayable.GetGraph(), clip, enterTime));
        _enterTimes.Add(enterTime);
        _clipLengths.Add(clip.length);
    }

    public override void Enable()
    {
        base.Enable();

        if (currentIndex < 0 || currentIndex >= clipCount)
            return;
        
        _mixer.SetInputWeight(currentIndex, 1f);
        AnimHelper.Enable(_mixer, currentIndex);
        _adapterPlayable.SetTime(0);
        _adapterPlayable.Play();
        _mixer.SetTime(0);
        _mixer.Play();
    }
    
    public override void Disable()
    {
        base.Disable();

        if (currentIndex < 0 || currentIndex >= clipCount)
            return;
        
        _mixer.SetInputWeight(currentIndex, 0f);
        AnimHelper.Disable(_mixer, currentIndex);
        _adapterPlayable.Pause();
        _mixer.Pause();
        currentIndex = -1;
    }

    /// 
    /// 根据条件,选择一个动画
    /// 
    public virtual int Select()
    {
        return currentIndex;
    }

    /// 
    /// 直接指定索引
    /// 
    public void Select(int index)
    {
        currentIndex = index;
    }

    public override float GetEnterTime()
    {
        if(currentIndex >= 0 && currentIndex < _enterTimes.Count)
            return _enterTimes[currentIndex];
        return 0;
    }
    
    public override float GetClipLength()
    {
        if(currentIndex >= 0 && currentIndex < _clipLengths.Count)
            return _clipLengths[currentIndex];
        return 0;
    }
}
/// 
/// 随机动画选择器
/// 
public class RandomSelector : AnimSelector
{
    public RandomSelector(PlayableGraph graph) : base(graph)
    {
    }
    
    public override int Select()
    {
        currentIndex = Random.Range(0, clipCount);
        return currentIndex;
    }
}

1D混合树组件 Mixer

Playable 动画系统_第12张图片
简单的动画混合,根据切换的时间计算速度Speed,当前动画权重递减,目标动画权重递增
速度 * 时间 = 权重
速度 = 权重 / 时间
Playable 动画系统_第13张图片
cur动画切换到tar(绿)动画,被tar(红)打断,此时如果cur的权重 > tar(绿)的权重,tar(绿)的权重要按照2倍的速度递减,tar(红)的权重 = 1 - cur权重 - tar(绿)权重

Playable 动画系统_第14张图片
如果频繁打断动画,就可能有多个动画的权重需要递减到0,此时需要一个数组(del)保存被打断的动画
tar(黄)权重 = 1 - cur权重 - del数组内所有动画权重

Playable 动画系统_第15张图片
切换打断时,如果cur(蓝)权重 < tar(绿)权重,就交换cur和tar

public class Mixer : AnimBehaviour
{
    public int inputCount { get; private set; }
    public int currentIndex => _currentIndex;
    public bool IsTransition => _isTransition;

    private AnimationMixerPlayable _mixerPlayable;
    //当前动画索引
    private int _currentIndex;
    //目标动画索引
    private int _targetIndex;
    //递减列表
    private List<int> _declineList;
    private float _timeToNext;
    //当前权重的递减速度
    private float _currentSpeed;
    //递减列表中权重的递减速度
    private float _declineSpeed;
    //是否在切换中
    private bool _isTransition;
    

    public Mixer(PlayableGraph graph) : base(graph)
    {
        _mixerPlayable = AnimationMixerPlayable.Create(graph, 0, true);
        //连接到adapter上
        _adapterPlayable.AddInput(_mixerPlayable, 0, 1);
        
        _targetIndex = -1;
        _declineList = new List<int>();
    }

    public override void AddInput(Playable playable)
    {
        base.AddInput(playable);
        
        _mixerPlayable.AddInput(playable, 0, 0f);
        inputCount++;
        if(inputCount == 1)
        {
            _mixerPlayable.SetInputWeight(0, 1f);
            _currentIndex = 0;
        }
    }

    public override void Enable()
    {
        base.Enable();

        if (inputCount > 0)
        {
            AnimHelper.Enable(_mixerPlayable, 0);
        }
        
        _adapterPlayable.SetTime(0);
        _mixerPlayable.SetTime(0);
        _adapterPlayable.Play();
        _mixerPlayable.Play();

        _mixerPlayable.SetInputWeight(0, 1f);
        _currentIndex = 0;
        _targetIndex = -1;
    }
    
    public override void Disable()
    {
        base.Disable();
        
        _adapterPlayable.Pause();
        _mixerPlayable.Pause();

        for (int i = 0; i < inputCount; ++i)
        {
            _mixerPlayable.SetInputWeight(i, 0);
            AnimHelper.Disable(_mixerPlayable, i);
        }
    }

    public override void Execute(Playable playable, FrameData info)
    {
        base.Execute(playable, info);

        if (!enable || !_isTransition || _targetIndex < 0)
            return;

        if (_timeToNext > 0f)
        {
            _timeToNext -= info.deltaTime;

            //所有递减动画的权重之和
            float declineWeight = 0;
            for (int i = 0; i < _declineList.Count; ++i)
            {
                float w = ModifyWeight(_declineList[i], -info.deltaTime * _declineSpeed);
                if (w <= 0f)
                {
                    AnimHelper.Disable(_mixerPlayable, _declineList[i]);
                    _declineList.Remove(_declineList[i]);
                }
                else
                {
                    declineWeight += w;
                }
            }
            
            float curWeight = ModifyWeight(_currentIndex, -info.deltaTime * _currentSpeed);
            SetWeight(_targetIndex, 1 - declineWeight - curWeight);
            return;
        }

        //切换完成后
        _isTransition = false;
        AnimHelper.Disable(_mixerPlayable, _currentIndex);
        _currentIndex = _targetIndex;
        _targetIndex = -1;
    }

    /// 
    /// 切换动画
    /// 
    public void TransitionTo(int index)
    {
        if (_isTransition && _targetIndex >= 0)
        {
            //切换中
            if (index == _targetIndex)
                return;

            if (index == _currentIndex)
            {
                _currentIndex = _targetIndex;
            }
            else if (GetWeight(_currentIndex) > GetWeight(_targetIndex))
            {
                //被打断时,当前权重大于目标权重
                _declineList.Add(_targetIndex);
            }
            else
            {
                //被打断时,当前权重小于目标权重,交换
                _declineList.Add(_currentIndex);
                _currentIndex = _targetIndex;
            }
        }
        else
        {
            if (index == _currentIndex) 
                return;
        }

        _targetIndex = index;

        //传入的targetIndex有可能已在列表里面,需要移除
        _declineList.Remove(_targetIndex);
        AnimHelper.Enable(_mixerPlayable, _targetIndex);

        // _timeToNext = GetTargetEnterTime(_targetIndex);
        _timeToNext = GetTargetEnterTime(_targetIndex) * (1f - GetWeight(_targetIndex));
        _currentSpeed = GetWeight(_currentIndex) / _timeToNext;
        _declineSpeed = 2f / _timeToNext;

        _isTransition = true;
    }

    public float GetWeight(int index)
    {
        return index >= 0 && index < inputCount ? _mixerPlayable.GetInputWeight(index) : 0;
    }
    
    public void SetWeight(int index, float weight)
    {
        if (index >= 0 && index < inputCount)
        {
            _mixerPlayable.SetInputWeight(index, weight);
        }
    }
    
    /// 
    /// 获取切换时间
    /// 
    private float GetTargetEnterTime(int index)
    {
        return ((ScriptPlayable<AnimAdapter>)_mixerPlayable.GetInput(index)).GetBehaviour().GetEnterTime();
    }

    /// 
    /// 调整权重
    /// 
    private float ModifyWeight(int index, float delta)
    {
        if (index < 0 || index >= inputCount)
            return 0;
        float weight = Mathf.Clamp01(GetWeight(index) + delta);
        _mixerPlayable.SetInputWeight(index, weight);
        return weight;
    }
}

测试脚本

public class RandomSelectorExample : MonoBehaviour
{
    public bool isTransition;
    public float remainTime;
    public AnimationClip[] clips;
     
    private PlayableGraph _graph;
    private Mixer _mixer;
    private RandomSelector _randomSelector;
    
    void Start()
    {
        _graph = PlayableGraph.Create();

        var idle = new AnimUnit(_graph, clips[0], 0.5f);
        _randomSelector = new RandomSelector(_graph);
        for(int i = 1; i < clips.Length; i++)
        {
            _randomSelector.AddInput(clips[i], 0.5f);
        }

        _mixer = new Mixer(_graph);
        _mixer.AddInput(idle);
        _mixer.AddInput(_randomSelector);
        
        _randomSelector.Select();
        AnimHelper.SetOutput(_graph, GetComponent<Animator>(), _mixer);
        AnimHelper.Start(_graph);
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            _randomSelector.Select();
            _mixer.TransitionTo(1);
        }

        isTransition = _mixer.IsTransition;
        remainTime = _randomSelector.remainTime;

        if (!_mixer.IsTransition && _randomSelector.remainTime < 0.5f && _mixer.currentIndex != 0)
        {
            _mixer.TransitionTo(0);
        }
    }

    void OnDestroy()
    {
        _graph.Destroy();
    }
}

Playable 动画系统_第16张图片

Playable 动画系统_第17张图片
运行时按空格键随机选择一个动画播放,播放完切换到idle

2D混合树组件 BlendTree2D

Playable 动画系统_第18张图片

使用 Compute Shader 把计算移到 GPU 上,在 Resources 目录下新建 Compute Shader “BlendTree2D”
Playable 动画系统_第19张图片
在这里插入图片描述

// Each #kernel tells which function to compile; you can have many kernels
// 定义主函数名称
#pragma kernel Compute

struct DataPair
{
    float x;
    float y;
    float weight;
};

float pointerX;
float pointerY;
//很小的数,防止除以0
float eps;
//定义结构化缓存
RWStructuredBuffer<DataPair> dataBuffer;

float mdistance(DataPair data)
{
    return abs(pointerX - data.x) + abs(pointerY - data.y) + eps;
}

//声明XYZ三个维度线程组中的线程数量
[numthreads(16,1,1)]
void Compute (uint3 id : SV_DispatchThreadID)
{
    dataBuffer[id.x].weight = 1 / mdistance(dataBuffer[id.x]);
}
[Serializable]
public struct BlendClip2D
{
    public AnimationClip clip;
    public Vector2 pos;
}

public class BlendTree2D : AnimBehaviour
{
    private struct DataPair
    {
        public float x;
        public float y;
        public float weight;
    }
    
    private AnimationMixerPlayable _mixer;
    private DataPair[] _dataPairs;
    //把权重的计算移到GPU上
    private ComputeShader _computeShader;
    //传递数据
    private ComputeBuffer _computeBuffer;
    //shader中定义的计算主函数
    private int _kernel;
    private int _clipCount;
    private Vector2 _lastPointer;
    private int _pointerX;
    private int _pointerY;
    private float _total;

    public BlendTree2D(PlayableGraph graph, BlendClip2D[] clips, float enterTime = 0f, float eps = 1e-5f) : base(graph, enterTime)
    {
        _mixer = AnimationMixerPlayable.Create(graph);
        _dataPairs = new DataPair[clips.Length];
        _adapterPlayable.AddInput(_mixer, 0, 1f);
        for (int i = 0; i < clips.Length; i++)
        {
            var clip = clips[i].clip;
            var clipPlayable = AnimationClipPlayable.Create(graph, clip);
            _mixer.AddInput(clipPlayable, 0);
            _dataPairs[i].x = clips[i].pos.x;
            _dataPairs[i].y = clips[i].pos.y;
        }

        _computeShader = AnimHelper.LoadCompute("BlendTree2D");
        //stride需要设置为4的倍数
        _computeBuffer = new ComputeBuffer(_dataPairs.Length, 12);
        _kernel = _computeShader.FindKernel("Compute");
        _computeShader.SetBuffer(_kernel, "dataBuffer", _computeBuffer);
        _computeShader.SetFloat("eps", eps);
        _pointerX = Shader.PropertyToID("pointerX");
        _pointerY = Shader.PropertyToID("pointerY");

        _clipCount = clips.Length;
        
        _lastPointer.Set(1, 1);
        SetPointer(0,0);
    }

    public override void Enable()
    {
        base.Enable();
        _adapterPlayable.SetTime(0);
        _adapterPlayable.Play();
        _mixer.SetTime(0);
        _mixer.Play();

        for (int i = 0; i < _clipCount; i++)
        {
            _mixer.GetInput(i).SetTime(0);
            _mixer.GetInput(i).Play();
        }
        //初始化权重
        SetPointer(0, 0);
    }

    public override void Disable()
    {
        base.Disable();
        _adapterPlayable.Pause();
        _mixer.Pause();
        for (int i = 0; i < _clipCount; i++)
        {
            _mixer.GetInput(i).Pause();
        }
    }

    public void SetPointer(Vector2 input)
    {
        SetPointer(input.x, input.y);
    }

    public void SetPointer(float x, float y)
    {
        if (_lastPointer.x == x && _lastPointer.y == y)
            return;
        
        _lastPointer.Set(x, y);
        _computeShader.SetFloat(_pointerX, x);
        _computeShader.SetFloat(_pointerY, y);
        _computeBuffer.SetData(_dataPairs);
        //运行计算着色器,以 X、Y 和 Z 维度中的指定计算着色器线程组启动
        _computeShader.Dispatch(_kernel, _clipCount, 1, 1);
        _computeBuffer.GetData(_dataPairs);

        _total = 0;
        int i;
        for (i = 0; i < _clipCount; ++i)
        {
            _total += _dataPairs[i].weight;
        }
        for (i = 0; i < _clipCount; ++i)
        {
            _mixer.SetInputWeight(i, _dataPairs[i].weight / _total);
        }
    }

    public override void Stop()
    {
        base.Stop();
        _computeBuffer.Dispose();
    }
}

测试脚本

public class BlendTree2DExample : MonoBehaviour
{
    public Vector2 pointer;
    public BlendClip2D[] clips;
     
    private PlayableGraph _graph;
    private BlendTree2D _blendTree2D;
    
    void Start()
    {
        _graph = PlayableGraph.Create();
        _blendTree2D = new BlendTree2D(_graph, clips);
        
        AnimHelper.SetOutput(_graph, GetComponent<Animator>(), _blendTree2D);
        AnimHelper.Start(_graph);
    }
    
    void Update()
    {
        _blendTree2D.SetPointer(pointer);
    }
    
    void OnDestroy()
    {
        _graph.Destroy();
    }
}

Playable 动画系统_第20张图片
运行时修改Pointer就会根据距离在动画片段之间做混合

参考

Playable 动画系统

你可能感兴趣的:(动画,unity)