Playable 基本用法
Playable意思是可播放的,可运行的。Playable整体是树形结构,PlayableGraph相当于一个容器,所有元素都被包含在里面,图中的每个节点都是Playable,叶子节点的Playable包裹原始数据,相当于输入,中间的Mixer根据权重混合多个输入,最后汇总到根部的Output节点,然后由PlayableGraph播放。
Playable的核心类型
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(){//创建PlayableGraphplayableGraph = PlayableGraph.Create();playableGraph.SetTimeUpdateMode(DirectorUpdateMode.GameTime);//创建AnimationClipPlayable包裹AnimationClip,附加到PlayableGraph上var clipPlayable = AnimationClipPlayable.Create(playableGraph, clip);//创建输出节点并把Animator设为目标,Animator会处理PlayableGraphvar playableOutput = AnimationPlayableOutput.Create(playableGraph, "Animation", GetComponent<Animator>());//连接输入源playableOutput.SetSourcePlayable(clipPlayable);// Plays the Graph.playableGraph.Play();}void OnDisable(){playableGraph.Destroy();}
}
这样不使用Animator Controller,通过脚本就可以控制动画播放,而且Animator Controller是不允许运行时添加、删除动画的,使用Playable就可以运行时添加,删除动画。
使用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和1playableGraph.Connect(clipPlayable0, 0, mixerPlayable, 0);playableGraph.Connect(clipPlayable1, 0, mixerPlayable, 1);playableGraph.Play();}void Update(){//保证所有输入源的权重和为1weight = 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();}
}
调整权重在两个动画之间过渡,我们还可以修改某个输入节点的状态
大型的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
每个角色都有的动画如走,跑,跳用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();}
}
两个输出对象分别是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);}}/// <summary>/// 每帧调用/// </summary>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,其他为0for (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 所使用。
随机切换动画
实现从一个默认动画随机切换到另一个动画,这两个动画之间需要做融合,且播放完动画后切会默认动画,大致的流程如下
RandomSelector是一个随机选择器,Mixer是一个混合器,通过调整权重来实现切换
为了方便管理动画,把每个动画片段包裹到AnimUnit,管理动画状态,并输出信息
使用适配器实现多态,AnimAdapter里面有一个AnimBehaviour的引用,适配器本身没有功能,它的具体功能取决于引用AnimBehaviour的哪一个子类
/// <summary>
/// 适配器
/// </summary>
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();}
}
/// <summary>
/// 组件基类
/// </summary>
public abstract class AnimBehaviour
{public bool enable { get; protected set; }public float remainTime { get; protected set; }//记录这个AnimBehaviour属于那个AnimAdapterprotected 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;}
}
/// <summary>
/// 输出的子节点,作为一个空节点,隔开输出和实际的输入
/// Enable就启用所有子节点,Disable就禁用所有子节点
/// </summary>
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类型是否继承AnimAdapterif (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 组件
/// <summary>
/// 包裹AnimationClipPlayable
/// </summary>
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
/// <summary>
/// 动画选择器基类
/// </summary>
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;}/// <summary>/// 根据条件,选择一个动画/// </summary>public virtual int Select(){return currentIndex;}/// <summary>/// 直接指定索引/// </summary>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;}
}
/// <summary>
/// 随机动画选择器
/// </summary>
public class RandomSelector : AnimSelector
{public RandomSelector(PlayableGraph graph) : base(graph){}public override int Select(){currentIndex = Random.Range(0, clipCount);return currentIndex;}
}
1D混合树组件 Mixer
简单的动画混合,根据切换的时间计算速度Speed,当前动画权重递减,目标动画权重递增
速度 * 时间 = 权重
速度 = 权重 / 时间
cur动画切换到tar(绿)动画,被tar(红)打断,此时如果cur的权重 > tar(绿)的权重,tar(绿)的权重要按照2倍的速度递减,tar(红)的权重 = 1 - cur权重 - tar(绿)权重
如果频繁打断动画,就可能有多个动画的权重需要递减到0,此时需要一个数组(del)保存被打断的动画
tar(黄)权重 = 1 - cur权重 - del数组内所有动画权重
切换打断时,如果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;}/// <summary>/// 切换动画/// </summary>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);}}/// <summary>/// 获取切换时间/// </summary>private float GetTargetEnterTime(int index){return ((ScriptPlayable<AnimAdapter>)_mixerPlayable.GetInput(index)).GetBehaviour().GetEnterTime();}/// <summary>/// 调整权重/// </summary>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();}
}
运行时按空格键随机选择一个动画播放,播放完切换到idle
2D混合树组件 BlendTree2D
使用 Compute Shader 把计算移到 GPU 上,在 Resources 目录下新建 Compute Shader “BlendTree2D”
// Each #kernel tells which function to compile; you can have many kernels
// 定义主函数名称
#pragma kernel Computestruct 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();}
}
运行时修改Pointer就会根据距离在动画片段之间做混合
参考
Playable 动画系统