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()
{
//创建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();
}
}
这样不使用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和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();
}
}
调整权重在两个动画之间过渡,我们还可以修改某个输入节点的状态
大型的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,其他为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 所使用。
随机切换动画
实现从一个默认动画随机切换到另一个动画,这两个动画之间需要做融合,且播放完动画后切会默认动画,大致的流程如下
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属于那个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;
}
}
/// <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类型是否继承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 组件
/// <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 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();
}
}
运行时修改Pointer就会根据距离在动画片段之间做混合
参考
Playable 动画系统