Playable API 是一个强大的工具,用于更灵活地控制动画、音频、脚本等时间轴内容的播放和混合。它提供了比传统 Animator 更底层、更可控的方式管理时间轴行为,尤其适合复杂动画逻辑或动态内容组合的场景。
优点:
1.Playables API 支持动态动画混合,这意味着场景中的对象可以提供自己的动画。例如,武器、宝箱和陷阱的动画可以动态添加到 PlayableGraph 并使用一段时间。
2.Playables API 可播放单个动画,而不会产生创建和管理 AnimatorController 资源所涉及的开销。
3.Playables API 允许用户动态创建混合图并直接逐帧控制混合权重。
4.可在运行时创建 PlayableGraph,根据条件按需添加可播放节点。可量身定制 PlayableGraph 来适应当前情况的要求,而不是提供一个巨大的“一刀切”图形来启用和禁用节点。
核心机制:
核心类型:
1.PlayableGraph
动画控制的核心容器,负责管理所有动画节点(Playable)和输出通道(PlayableOutput)。
PlayableGraph graph = PlayableGraph.Create("MyAnimationGraph");
2.Playable:
所有可播放项的基类型(如AnimationClipPlayable),使用struct实现避免内存分配。
隐式转换子类型为Playable,但反向需显式转换(可能因类型不兼容抛出异常)。
3.PlayableOutput:
输出的基类型(如AnimationPlayableOutput),同样为struct。
必须通过SetSourcePlayable()链接到Playable,否则无效果。
AnimationPlayableOutput output = AnimationPlayableOutput.Create(graph, "AnimationOutput", animator);
注:Playable 和 PlayableOutput 未暴露大量方法。可使用PlayableExtensions和PlayableOutputExtensions静态类提供的扩展方法。
创建与连接:
1.创建可播放项/输出
所有非抽象类型提供静态Create()方法,首个参数为PlayableGraph(拥有该节点)。
var clipPlayable = AnimationClipPlayable.Create(graph, clip);
var output = AnimationPlayableOutput.Create(graph, "Output", animator);
2.连接节点
节点间连接:通过PlayableGraph.Connect(source, sourcePort, target, targetPort)。
输出绑定根节点:output.SetSourcePlayable(rootPlayable)。
PlayableGraph管理
1.生命周期
创建:PlayableGraph.Create("GraphName")。
播放/停止:graph.Play() / graph.Stop()。
手动更新:graph.Evaluate(deltaTime)(适用于非实时更新)。
销毁:必须手动调用graph.Destroy(),否则报错(自动销毁其下所有节点)。
2.注意事项
输入限制:某些Playable类型不支持输入连接(如AnimationClipPlayable)。
权重控制:混合节点需通过SetInputWeight()管理权重。
内存安全:避免频繁创建/销毁,优先重用节点。
使用:
1.播放单个动画
在角色物体挂载以下脚本,如图:
代码:
[RequireComponent(typeof(Animator))]
public class SimplePlayable: MonoBehaviour
{
public Animator animator;
public AnimationClip clip;
private PlayableGraph graph;
void Start()
{
graph = PlayableGraph.Create();
this.CreateSimpleAnimation();
graph.Play();
}
void OnDestroy()
{
if (graph.IsValid())
graph.Destroy();
}
void CreateSimpleAnimation()
{
// 创建AnimationClipPlayable
var clipPlayable = AnimationClipPlayable.Create(graph, clip);
// 创建输出并连接到Animator
var output = AnimationPlayableOutput.Create(graph, "Output", animator);
output.SetSourcePlayable(clipPlayable);
}
}
使用AnimationPlayableUtilities简化动画播放
[RequireComponent(typeof(Animator))]
public class SimplePlayable: MonoBehaviour
{
public Animator animator;
public AnimationClip clip;
private PlayableGraph graph;
void Start()
{
AnimationPlayableUtilities.PlayClip(animator, clip, out graph);
}
void OnDestroy()
{
if (graph.IsValid())
graph.Destroy();
}
}
结果:
2.混合两个动画(Mixer)
在角色物体上挂载以下脚本,如图:
代码:
[RequireComponent(typeof(Animator))]
public class MixerPlayable : MonoBehaviour
{
public AnimationClip clip0;
public AnimationClip clip1;
[Range(0f, 1f)] public float weight;
PlayableGraph playableGraph;
AnimationMixerPlayable mixerPlayable;
void Start()
{
// 创建该图和混合器,然后将它们绑定到 Animator。
playableGraph = PlayableGraph.Create();
var playableOutput = AnimationPlayableOutput.Create(playableGraph, "Animation", GetComponent<Animator>());
mixerPlayable = AnimationMixerPlayable.Create(playableGraph, 2); //2个输入
playableOutput.SetSourcePlayable(mixerPlayable);
// 创建 AnimationClipPlayable 并将它们连接到混合器。
var clipPlayable0 = AnimationClipPlayable.Create(playableGraph, clip0);
var clipPlayable1 = AnimationClipPlayable.Create(playableGraph, clip1);
// 连接动画到Mixer
playableGraph.Connect(clipPlayable0, 0, mixerPlayable, 0);
playableGraph.Connect(clipPlayable1, 0, mixerPlayable, 1);
//播放该图。
playableGraph.Play();
}
void Update()
{
// 设置混合权重(0表示全clip0,1表示全clip1)
weight = Mathf.Clamp01(weight);
mixerPlayable.SetInputWeight(0, 1.0f - weight);
mixerPlayable.SetInputWeight(1, weight);
}
void OnDisable()
{
//销毁该图创建的所有可播放项和输出。
playableGraph.Destroy();
}
}
结果:
3.分层动画(LayerMixer)
创建一个动画遮罩
设置遮罩,如下图,将叠加动画的下半动画不播放
将角色物体上挂载以下脚本,如下图:
代码:
[RequireComponent(typeof(Animator))]
public class LayerMixerPlayable : MonoBehaviour
{
public AnimationClip runClip;
public AnimationClip attackClip;
public AvatarMask attackMask;
PlayableGraph graph;
void Start()
{
// 创建该图和混合器,然后将它们绑定到 Animator。
graph = PlayableGraph.Create();
var playableOutput = AnimationPlayableOutput.Create(graph, "Animation", GetComponent<Animator>());
AnimationLayerMixerPlayable layerMixer = AnimationLayerMixerPlayable.Create(graph, 2);
playableOutput.SetSourcePlayable(layerMixer);
// 基础层(如移动)
var baseLayer = AnimationClipPlayable.Create(graph, runClip);
graph.Connect(baseLayer, 0, layerMixer, 0);
layerMixer.SetInputWeight(0, 1f);
// 叠加层(如攻击)
var attackLayer = AnimationClipPlayable.Create(graph, attackClip);
graph.Connect(attackLayer, 0, layerMixer, 1);
layerMixer.SetInputWeight(1, 1f);
// 设置层级遮罩(可选)
layerMixer.SetLayerMaskFromAvatarMask(1, attackMask); // 仅特定身体部位播放攻击动画
graph.Play();
}
}
结果:将一个跑的动画和一个攻击动画混合,再将攻击动画的下半身添加动画遮罩
4. AnimationClip 和 AnimatorController混合使用
要混合 AnimationClip 和 AnimatorController ,它们必须由可播放项包裹,AnimationClipPlayable (clipPlayable) 包裹 AnimationClip (clip),而 AnimatorControllerPlayable (ctrlPlayable) 包裹 RuntimeAnimatorController (controller)。
新建一个AnimatorController,并增加一个动画片段,如下:
将以下脚本挂载到人物模型上
代码:
[RequireComponent(typeof(Animator))]
public class RuntimeControllerSample : MonoBehaviour
{
public AnimationClip clip;
public RuntimeAnimatorController controller;
public float weight;
PlayableGraph playableGraph;
AnimationMixerPlayable mixerPlayable;
void Start()
{
// 创建该图和混合器,然后将它们绑定到 Animator。
playableGraph = PlayableGraph.Create();
var playableOutput = AnimationPlayableOutput.Create(playableGraph, "Animation", GetComponent<Animator>());
mixerPlayable = AnimationMixerPlayable.Create(playableGraph, 2);
playableOutput.SetSourcePlayable(mixerPlayable);
// 创建 AnimationClipPlayable 并将它们连接到混合器。
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();
}
}
结果:
5.创建具有若干输出的 PlayableGraph
一个 PlayableGraph 可以有许多不同类型的可播放项输出。
将以下脚本挂载到人物模型上
代码:
[RequireComponent(typeof(Animator))]
[RequireComponent(typeof(AudioSource))]
public class MultiOutputSample : MonoBehaviour
{
public AnimationClip animationClip;
public AudioClip audioClip;
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();
}
}
结果:
6.控制播放状态
SetPlayState 方法控制整个树、其分支之一或单个节点的播放状态,设置节点的播放状态时,状态会传播到所有子节点(无论其播放状态如何),例如,如果显式暂停了子节点,则将父节点设置为“播放”也会将其所有子节点设置为“播放”。
在Unity2021.3中SetPlayState已过时,使用.Play(), .Pause()和.SetDelay()来代替。
将以下脚本挂载到人物模型上
代码:
[RequireComponent(typeof(Animator))]
public class PauseSubGraphAnimationSample : MonoBehaviour
{
public AnimationClip clip0;
public AnimationClip clip1;
PlayableGraph playableGraph;
AnimationMixerPlayable mixerPlayable;
void Start()
{
// 创建该图和混合器,然后将它们绑定到 Animator。
playableGraph = PlayableGraph.Create();
var playableOutput = AnimationPlayableOutput.Create(playableGraph, "Animation", GetComponent<Animator>());
mixerPlayable = AnimationMixerPlayable.Create(playableGraph, 2);
playableOutput.SetSourcePlayable(mixerPlayable);
// 创建 AnimationClipPlayable 并将它们连接到混合器。
var clipPlayable0 = AnimationClipPlayable.Create(playableGraph, clip0);
var clipPlayable1 = AnimationClipPlayable.Create(playableGraph, clip1);
playableGraph.Connect(clipPlayable0, 0, mixerPlayable, 0);
playableGraph.Connect(clipPlayable1, 0, mixerPlayable, 1);
mixerPlayable.SetInputWeight(0, 1.0f);
mixerPlayable.SetInputWeight(1, 1.0f);
//clipPlayable1.SetPlayState(PlayState.Paused);
clipPlayable1.Pause();
//播放该图。
playableGraph.Play();
}
void OnDisable()
{
//销毁该图创建的所有可播放项和输出。
playableGraph.Destroy();
}
}
结果:
7.控制时序
将以下脚本挂载到人物模型上
代码:
[RequireComponent(typeof(Animator))]
public class PlayWithTimeControlSample : MonoBehaviour
{
public AnimationClip clip;
public float time;
PlayableGraph playableGraph;
AnimationClipPlayable playableClip;
void Start()
{
playableGraph = PlayableGraph.Create();
var playableOutput = AnimationPlayableOutput.Create(playableGraph, "Animation", GetComponent<Animator>());
// 将剪辑包裹在可播放项中
playableClip = AnimationClipPlayable.Create(playableGraph, clip);
// 将可播放项连接到输出
playableOutput.SetSourcePlayable(playableClip);
// 播放该图。
playableGraph.Play();
//使时间停止自动前进。
//playableClip.SetPlayState(PlayState.Paused);
playableClip.Pause();
}
void Update()
{
//手动控制时间
playableClip.SetTime(time);
}
void OnDisable()
{
// 销毁该图创建的所有可播放项和输出。
playableGraph.Destroy();
}
}
结果:
8.创建 PlayableBehaviour
将以下脚本挂载到人物模型上
代码:示例中受控节点是一系列动画剪辑 (clipsToPlay)。SetInputMethod() 将修改每个动画剪辑的混合权重,确保一次只播放一个剪辑,而 SetTime() 方法将调整本地时间,以便在激活动画剪辑时开始播放。
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);
}
}
override public 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;
}
// 调整输入权重
for (int clipIndex = 0; clipIndex < mixer.GetInputCount(); ++clipIndex)
{
if (clipIndex == m_CurrentClipIndex)
mixer.SetInputWeight(clipIndex, 1.0f);
else
mixer.SetInputWeight(clipIndex, 0.0f);
}
}
}
[RequireComponent(typeof(Animator))]
public class PlayQueueSample : MonoBehaviour
{
public AnimationClip[] clipsToPlay;
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,0);
//以下API在Unity2021.3版本已过时,使用SetSourcePlayable代替
//playableOutput.SetSourceInputPort(0);
playableGraph.Play();
}
void OnDisable()
{
// 销毁该图创建的所有可播放项和输出。
playableGraph.Destroy();
}
}
结果:
9.动态创建与销毁节点
// 动态添加新动画
public void AddDynamicAnimation(AnimationClip clip)
{
var newClipPlayable = AnimationClipPlayable.Create(graph, clip);
mixer.AddInput(newClipPlayable, 0, 1f); // 假设mixer已存在
}
// 销毁节点
public void RemoveAnimation(int index)
{
mixer.GetInput(index).Destroy();
mixer.DisconnectInput(index);
}
创建自定义可播放项:
1. 创建自定义 Playable
继承 PlayableBehaviour:定义自定义逻辑需从基类 PlayableBehaviour 派生,重写其方法(如 PrepareFrame, OnPlayableCreate 等)。
/// <summary>
/// 自定义可播放项行为的实现
/// 根据需要重载 PlayableBehaviour 方法
/// </summary>
public class MyCustomPlayableBehaviour : PlayableBehaviour
{
/// <summary>
/// 对象被复制时触发
/// 使用:深拷贝自定义字段
/// </summary>
/// <returns></returns>
public override object Clone()
{
return base.Clone();
}
/// <summary>
/// 在Playable创建时
/// 使用:初始化资源/缓存引用
/// </summary>
/// <param name="playable"></param>
public override void OnPlayableCreate(Playable playable)
{
base.OnPlayableCreate(playable);
}
/// <summary>
/// 在PlayableGraph启动时
/// 使用:全局初始化操作
/// </summary>
/// <param name="playable"></param>
public override void OnGraphStart(Playable playable)
{
base.OnGraphStart(playable);
}
/// <summary>
/// 准备数据阶段触发
/// 使用:预先计算动画曲线等耗时操作
/// </summary>
/// <param name="playable"></param>
/// <param name="info"></param>
public override void PrepareData(Playable playable, FrameData info)
{
base.PrepareData(playable, info);
}
/// <summary>
/// 该Behaviour开始播放时触发
/// 使用:触发播放事件
/// </summary>
/// <param name="playable"></param>
/// <param name="info"></param>
public override void OnBehaviourPlay(Playable playable, FrameData info)
{
base.OnBehaviourPlay(playable, info);
}
/// <summary>
/// 每帧处理前触发
/// 使用:准备混合参数
/// </summary>
/// <param name="playable"></param>
/// <param name="info"></param>
public override void PrepareFrame(Playable playable, FrameData info)
{
base.PrepareFrame(playable, info);
}
/// <summary>
/// 每帧处理时触发
/// 核心动画逻辑(最重要的方法)
/// </summary>
/// <param name="playable"></param>
/// <param name="info"></param>
/// <param name="playerData"></param>
public override void ProcessFrame(Playable playable, FrameData info, object playerData)
{
base.ProcessFrame(playable, info, playerData);
}
/// <summary>
/// 该Behaviour暂停时触发
/// 使用:暂停音效/保存状态
/// </summary>
/// <param name="playable"></param>
/// <param name="info"></param>
public override void OnBehaviourPause(Playable playable, FrameData info)
{
base.OnBehaviourPause(playable, info);
}
/// <summary>
///
/// </summary>
/// <param name="playable"></param>
public override void OnGraphStop(Playable playable)
{
base.OnGraphStop(playable);
}
/// <summary>
/// Playable销毁时触发
/// 释放非托管资源
/// </summary>
/// <param name="playable"></param>
public override void OnPlayableDestroy(Playable playable)
{
base.OnPlayableDestroy(playable);
}
}
2. 封装到ScriptPlayable
1.通过ScriptPlayable<T>.Create()创建
自定义的 PlayableBehaviour 需通过 ScriptPlayable<T>.Create() 生成可播放项实例。
//创建可播放实例
ScriptPlayable<MyCustomPlayableBehaviour>.Create(playableGraph);
2.通过已有实例创建
//此情况中将克隆该实例,然后将实例分配给 ScriptPlayable<>。
//实际上,此代码与第一种执行的操作完全相同;
//不同之处在于 myPlayable 可能是将要在 Inspector 中配置的公有属性,
//然后可为脚本的每个实例设置行为。
MyCustomPlayableBehaviour myPlayable = new MyCustomPlayableBehaviour();
ScriptPlayable<MyCustomPlayableBehaviour>.Create(playableGraph, myPlayable);
3.获取 PlayableBehaviour 实例
使用 GetBehaviour() 方法:
使用 ScriptPlayable<T> .GetBehaviour() 方法从 ScriptPlayable<T> 中提取 PlayableBehaviour 对象,用于运行时动态控制。
MyCustomPlayableBehaviour behaviour = playable.GetBehaviour();
behaviour.CustomParameter = 10; // 修改参数
4.注意
1.实例克隆机制
若通过 Create(graph, existingBehaviour) 传入已有实例,Unity 会克隆该实例,原始实例修改不会影响已封装的 ScriptPlayable。
2.编辑器配置|
将 PlayableBehaviour 的字段设为 public,可在 Inspector 中配置参数(需配合 PlayableAsset 使用)。(TODO:与PlayableAsset配合使用会在整理TimeLine的时候给出。。。)
3.生命周期管理
PlayableBehaviour 的生命周期由 PlayableGraph 控制,无需手动销毁。
5.优势
灵活控制:通过 PlayableBehaviour 实现动态动画逻辑(如条件混合、事件触发)。
低GC开销:ScriptPlayable<T> 基于值类型,避免内存分配,提升性能。
模块化设计:将复杂逻辑封装为独立 Playable,便于复用和组合。
6.使用
代码:
1.测试调用类
[RequireComponent(typeof(Animator))]
public class MyCustomPlayableBehaviourTest : MonoBehaviour
{
public Animator animator;
public AnimationClip clip;
PlayableGraph graph;
void Start()
{
// 1. 创建 PlayableGraph
graph = PlayableGraph.Create();
this.BuildCustomPlayableBehaviour();
// 5. 播放
graph.Play();
}
void BuildCustomPlayableBehaviour()
{
// 2. 生成自定义 Playable
var playable = ScriptPlayable<MyCustomPlayableBehaviour>.Create(graph);
// 3. 获取行为实例并配置
MyCustomPlayableBehaviour behaviour = playable.GetBehaviour();
behaviour.clip = clip;
behaviour.Initialize(animator, graph);
// 4. 连接到输出
AnimationPlayableOutput output = AnimationPlayableOutput.Create(graph, "Output", animator);
output.SetSourcePlayable(playable);
}
void OnDisable()
{
//销毁该图创建的所有可播放项和输出。
graph.Destroy();
}
}
2.自定义 PlayableBehaviour修改
public class MyCustomPlayableBehaviour : PlayableBehaviour
{
// 公开可配置参数
public AnimationClip clip;
public float speed = 1f;
public bool loop;
// Playable 系统相关
private AnimationClipPlayable clipPlayable;
private PlayableGraph graph;
private Animator animator;
private AnimationPlayableOutput output; // 新增输出节点引用
// 初始化
public void Initialize(Animator targetAnimator, PlayableGraph playableGraph)
{
animator = targetAnimator;
graph = playableGraph;
// 创建动画播放器
clipPlayable = AnimationClipPlayable.Create(graph, clip);
clipPlayable.SetSpeed(speed);
clipPlayable.SetApplyFootIK(false);
// 创建输出节点并连接(关键修复点)
output = AnimationPlayableOutput.Create(graph, "Animation Output", animator);
output.SetSourcePlayable(clipPlayable);
}
public override void ProcessFrame(Playable playable, FrameData info, object playerData)
{
if (!clipPlayable.IsValid()) return;
//添加动画每帧需要的操作
}
/// <summary>
/// Playable销毁时触发
/// 释放非托管资源
/// </summary>
/// <param name="playable"></param>
public override void OnPlayableDestroy(Playable playable)
{
if (clipPlayable.IsValid())
{
clipPlayable.Destroy();
}
}
}
结果:
可视化工具的安装与使用:
1.安装
从以下地址下载PlayableGraph Visualizer 源码到工程
Releases · Unity-Technologies/graph-visualizer (github.com)
将解压缩之后的包完整的复制到Unity工程内即可
然后就可以在Unity中打开此包的显示窗口了
使用以下代码,在创建PlayableGraph时注册PlayableGraph到可视化窗口。
GraphVisualizerClient.Show(playableGraph);
打开示例窗口如下:
性能优化
1.提前创建PlayableGraph:在Awake()或Start()中初始化,避免运行时卡顿。
2.重用Playable节点:对于频繁切换的动画(如攻击、受伤),预先创建节点并通过权重控制显隐,而非反复创建/销毁。
3.限制更新频率:若动画无需每帧更新,可通过graph.Evaluate(deltaTime)手动控制更新。
4.使用Playable TraversalMode,设置遍历模式优化性能:
graph.SetTimeUpdateMode(DirectorUpdateMode.Manual);
graph.SetPlayableTraversalMode(PlayableTraversalMode.Passthrough);
应用场景
1.角色移动混合:根据速度动态混合走、跑、冲刺动画。
2.受伤动画叠加:在基础动画上叠加受伤抖动,不影响其他身体部位。
3.过场动画控制:结合Timeline和Playable API实现复杂的过场动画序列。
注意事项
1.销毁PlayableGraph:在对象销毁时调用graph.Destroy(),防止内存泄漏。
2.动画长度处理:循环动画需手动控制停止,或使用ClipPlayable.SetDuration()。
3.权重归一化:混合时确保权重总和不超过1,避免动画异常。
4.版本兼容性:Playable API在Unity 2017.1+中稳定,但部分功能(如ScriptPlayable<T>)可能需要更新版本。
参考链接: