Playable 动画系统

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 动画系统

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/87704.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity 框架学习--1

由浅入深&#xff0c;慢慢演化实现框架 两个类的实现代码完全一样&#xff0c;就只有类名或类型不一样的时候&#xff0c;而且还需要不断扩展&#xff08;未来会增加各种事件&#xff09;的时候&#xff0c;这时候就用 泛型 继承 来提取&#xff0c;继承解决扩展的问题&#…

Android Framework底层原理之WMS的启动流程

一 概述 今天&#xff0c;我们介绍 WindowManagerService&#xff08;后续简称 WMS&#xff09;的启动流程&#xff0c;WMS 是 Android 系统中&#xff0c;负责窗口显示的的服务。在 Android 中它也起着承上启下的作用。 如下图&#xff0c;就是《深入理解 Android》书籍中的…

Flume原理剖析

一、介绍 Flume是一个高可用、高可靠&#xff0c;分布式的海量日志采集、聚合和传输的系统。Flume支持在日志系统中定制各类数据发送方&#xff0c;用于收集数据&#xff1b;同时&#xff0c;Flume提供对数据进行简单处理&#xff0c;并写到各种数据接受方&#xff08;可定制&…

css 字体渐变样式(设置字体渐变样式+附加实现源码)

问题描述 先看效果图。 解决方案 在对应的css样式里添加如下代码。 我的商品列表在shangpinliebiaobiaotit-view类里面&#xff0c;那么就在shangpinliebiaobiaotit-view设置css渐变样式。 <view class"shangpinliebiaobiaotit-view">商品列表</view&g…

docker desktop搭建 nginx

【docker 桌面版】windows 使用 docker 搭建 nginx 拉取 nginx 镜像 docker pull nginx运行容器 docker run -d -p 80:8081 --name nginx nginx本地磁盘创建 nginx 目录 D:\DockerRep\nginx复制 docker 中的 nginx 配置文件 查看运行的容器 docker ps -a docker cp 9f0f82d66dd…

Spring-Cloud-Loadblancer详细分析_2

LoadBalancerClients 终于分析到了此注解的作用&#xff0c;它是实现不同服务之间的配置隔离的关键 Configuration(proxyBeanMethods false) Retention(RetentionPolicy.RUNTIME) Target({ ElementType.TYPE }) Documented Import(LoadBalancerClientConfigurationRegistrar…

Google FixMatch:SOTA 在半监督学习基准测试中的性能

作为当前计算机视觉应用的首选&#xff0c;深度网络通常通过监督学习&#xff08;一种需要标记数据集的方法&#xff09;来实现其强大的性能。尽管人工智能多年来取得了许多成就和进步&#xff0c;但标记数据的关键任务仍然落在人类专家身上。他们很难满足那些数据饥渴的深度网…

关于达梦网络通信异常问题

一.问题说明 springboot的项目&#xff0c;多数据源&#xff0c;其中一个数据源是达梦数据库。有个根据主键id查询详情的接口&#xff0c;一直报错网络通信异常&#xff0c;或连接尚未建立或者已经关闭。可以确保访问数据库的网络一切正常&#xff0c;单单一张表的接口一直报上…

基于模型的术语定义

文章仅供个人学习使用&#xff0c;请勿传播&#xff01; 原文来源&#xff1a; 袁亦方 大易方圆 OPM对象过程方法 2023-08-13 07:01 https://mp.weixin.qq.com/s/dUtuNLrMwFF_foCrQQyWmA INCOSE系统工程手册第5版使用说明部分&#xff08;内容对应第4版1.5节&#xff09;提出&…

登录验证码实现

Hutool代码改造 Hutool 有参考文档&#xff1b;很多工具类&#xff1b;把一些功能都封装好&#xff1b;都不用你自己去写&#xff1b;直接调用它的工具类 它这里会详细告诉你引入方式Hutool <dependency><groupId>cn.hutool</groupId><artifactId>hu…

数据结构:栈的实现(C实现)

个人主页 &#xff1a; 个人主页 个人专栏 &#xff1a; 《数据结构》 《C语言》 文章目录 前言一、栈的实现思路1. 结构的定义2. 初始化栈(StackInit)3. 入栈(StackPush)4. 出栈(StackPop)5. 获取栈顶元素(StackTop)6. 检查栈是否为空(StackEmpty)7. 销毁栈(StackDestroy) 二、…

Von Maur, Inc EDI 需求分析

Von Maur, Inc 是一家历史悠久的卖场&#xff0c;成立于19世纪&#xff0c;总部位于美国。作为一家知名的零售商&#xff0c;Von Maur 主要经营高端时装、家居用品和美妆产品。其使命是为顾客提供优质的产品和无与伦比的购物体验。多年来&#xff0c;Von Maur 凭借其卓越的服务…

ubuntu网络管理

主机-ip&#xff0c;service—port 分别查看/etc/hosts&#xff0c;/etc/host.conf&#xff1b;/etc/services&#xff0c;/etc/resolv.conf&#xff1b; 内核更新——linux-image-generic 6.2.0-24.24 非常抱歉&#xff0c;我误解了你的问题。如果你想更新已安装的内核版本…

Android高通8.1 Selinux问题

1、最近客户提了一个需求&#xff0c;说要在user版本上面切分辨率&#xff0c;默认屏幕分辨率是2.5 k 执行adb shell指令之后变成 4k 然后adb shell wm size可以查看 2、一开始我能想到就是在文件节点添加权限&#xff0c;这里不管是mtk还是qcom&#xff08;高通平台&#xff…

深度剖析堆栈指针

为什么打印root的值与&root->value的值是一样的呢 测试结果&#xff1a; *号一个变量到底取出来的是什么&#xff1f; 以前我写过一句话&#xff0c;就是说&#xff0c;如果看到一个*变量&#xff0c;那就是直逼这个变量所保存的内存地址&#xff0c;然后取出里面保存的…

【C语言】每日一题---1

大家好&#xff0c;我是苏貝&#xff0c;本篇博客是系列博客每日一题的第一篇&#xff0c;本系列的题都不会太难&#xff0c;如果大家对这种系列的博客感兴趣的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 下面代码的结果是&#xff1a; #include <…

[保研/考研机试] KY7 质因数的个数 清华大学复试上机题 C++实现

描述 求正整数N(N>1)的质因数的个数。 相同的质因数需要重复计算。如1202*2*2*3*5&#xff0c;共有5个质因数。 输入描述&#xff1a; 可能有多组测试数据&#xff0c;每组测试数据的输入是一个正整数N&#xff0c;(1<N<10^9)。 输出描述&#xff1a; 对于每组数…

微服务01-SpringCloud

1、简介 SpringCloud集成了各种微服务功能组件&#xff0c;并基于SpringBoot实现了这些组件的自动装配&#xff0c;从而提供了良好的开箱即用体验。 其中常见的组件包括&#xff1a; 2、服务拆分和远程调用 2.1 服务拆分 这里总结了微服务拆分时的几个原则&#xff1a; …

卷积神经网络CNN

卷积神经网络CNN 1 应用领域1 检测任务2 分类和检索3 超分辨率重构4 医学任务5 无人驾驶6 人脸识别 2 卷积的作用3 卷积特征值计算方法4 得到特征图表示5 步长和卷积核大小对结果的影响1 步长2 卷积核 6 边缘填充方法7 特征图尺寸计算与参数共享8 池化层的作用9 整体网络架构10…