6.Playable控制PlayableGraph树的播放状态和时序
6.1 知识点
思路和步骤
用 Playable API 控制 PlayableGraph 的播放状态和时序,重点集中在三件事:用 GetPlayState() 读状态,用 Play() / Pause() 切换播放,用 SetTime() 重置时间(例如从头再来)。
核心步骤:
- 创建图并设置更新模式
- 创建动画输出和混合器
- 创建动画剪辑节点并连接到混合器
- 播放图
- 在 Update 中控制播放状态(获取状态、暂停/播放、重置时间)
- 根据播放状态更新权重
- 清理资源
代码实现
步骤1:创建图并设置更新模式
// 创建 Playable 图并设置更新模式为 GameTime
playableGraph = PlayableGraph.Create();
playableGraph.SetTimeUpdateMode(DirectorUpdateMode.GameTime);
先创建 PlayableGraph,并设置时间更新模式为游戏时间。
步骤2:创建动画输出和混合器
// 创建动画输出,连接到 Animator 组件
AnimationPlayableOutput animationPlayableOutput =
AnimationPlayableOutput.Create(playableGraph, "Animation", GetComponent<Animator>());
// 创建混合器,支持 2 个输入
animationMixerPlayable = AnimationMixerPlayable.Create(playableGraph, 2);
// 将混合器设置为输出源
animationPlayableOutput.SetSourcePlayable(animationMixerPlayable);
创建动画输出节点并绑定 Animator。然后创建混合器,设置 2 个输入端口,并把混合器设为输出源。
步骤3:创建动画剪辑节点并连接到混合器
// 创建两个动画剪辑的可播放项
clipPlayable1 = AnimationClipPlayable.Create(playableGraph, clip1);
clipPlayable2 = AnimationClipPlayable.Create(playableGraph, clip2);
// 将两个动画剪辑连接到混合器的输入端口
playableGraph.Connect(clipPlayable1, 0, animationMixerPlayable, 0);
playableGraph.Connect(clipPlayable2, 0, animationMixerPlayable, 1);
// 设置混合器输入权重(两个动画各占 50%)
animationMixerPlayable.SetInputWeight(0, 0.5f);
animationMixerPlayable.SetInputWeight(1, 0.5f);
把两个动画剪辑包装成 Playable 节点,连接到混合器输入端口。初始权重各占 50%。
步骤4:播放图
// 播放该图
playableGraph.Play();
开始播放图,之后每帧 Unity 会自动更新并应用到 Animator。
步骤5:在 Update 中控制播放状态
void Update()
{
// 检测数字键 1 按下,控制 clip1 的播放状态
if (Input.GetKeyDown(KeyCode.Alpha1))
{
// 如果 clip1 正在播放,则暂停它
if (clipPlayable1.GetPlayState() == PlayState.Playing)
{
clipPlayable1.Pause();
}
// 如果 clip1 已暂停,则恢复播放并重置时间
else
{
clipPlayable1.Play();
clipPlayable1.SetTime(0f);
}
// 更新权重,确保总和为 1
UpdateWeights();
}
// 检测数字键 2 按下,控制 clip2 的播放状态
if (Input.GetKeyDown(KeyCode.Alpha2))
{
// 如果 clip2 正在播放,则暂停它
if (clipPlayable2.GetPlayState() == PlayState.Playing)
{
clipPlayable2.Pause();
}
// 如果 clip2 已暂停,则恢复播放并重置时间
else
{
clipPlayable2.Play();
clipPlayable2.SetTime(0f);
}
// 更新权重,确保总和为 1
UpdateWeights();
}
}
在 Update() 里检测按键并切换播放状态:先用 GetPlayState() 判断当前是否在播;在播就 Pause(),不在播就 Play(),同时 SetTime(0f) 让它从头开始。
步骤6:根据播放状态更新权重
void UpdateWeights()
{
bool clip1Playing = clipPlayable1.GetPlayState() == PlayState.Playing;
bool clip2Playing = clipPlayable2.GetPlayState() == PlayState.Playing;
// 如果两个动画都在播放,各占 50%
if (clip1Playing && clip2Playing)
{
animationMixerPlayable.SetInputWeight(0, 0.5f);
animationMixerPlayable.SetInputWeight(1, 0.5f);
}
// 如果只有 clip1 在播放,clip1 权重为 1.0,clip2 权重为 0
else if (clip1Playing && !clip2Playing)
{
animationMixerPlayable.SetInputWeight(0, 1.0f);
animationMixerPlayable.SetInputWeight(1, 0f);
}
// 如果只有 clip2 在播放,clip2 权重为 1.0,clip1 权重为 0
else if (!clip1Playing && clip2Playing)
{
animationMixerPlayable.SetInputWeight(0, 0f);
animationMixerPlayable.SetInputWeight(1, 1.0f);
}
// 如果两个都暂停,这里简单回到均分(看起来更“中性”)
else
{
animationMixerPlayable.SetInputWeight(0, 0.5f);
animationMixerPlayable.SetInputWeight(1, 0.5f);
}
}
根据两个动画的播放状态动态调整权重,保持总和为 1。两个都播放时各占 50%;只播一个时该动画权重为 1,另一个为 0。
步骤7:清理资源
void OnDisable()
{
// 销毁该图创建的所有可播放项和输出
playableGraph.Destroy();
}
组件禁用时调用 Destroy() 释放资源(节点与输出),避免内存泄漏。建议放在 OnDisable(),组件禁用时即可释放。
效果展示


按下 1 / 2 控制两个 clip 的播放状态;都暂停时因为两路都不在播,最终输出也会停住。
6.2 知识点代码
PauseSubGraphAnimationSample.cs
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Animations;
/// <summary>
/// 控制 PlayableGraph 树播放状态和时序的示例组件
/// 该组件演示了如何通过 Play() / Pause() 这类接口控制 PlayableGraph 树上节点的播放状态和时序
/// 设置节点的播放状态时,状态会传播到所有子节点
/// 例如,如果显式暂停了子节点,则将父节点设置为"播放"也会将其所有子节点设置为"播放"
/// </summary>
[RequireComponent(typeof(Animator))]
public class PauseSubGraphAnimationSample : MonoBehaviour
{
/// <summary>
/// 第一个动画剪辑
/// </summary>
public AnimationClip clip1;
/// <summary>
/// 第二个动画剪辑
/// </summary>
public AnimationClip clip2;
/// <summary>
/// Playable 图,用于管理动画播放流程
/// </summary>
PlayableGraph playableGraph;
/// <summary>
/// 动画混合器,用于混合两个动画剪辑
/// </summary>
AnimationMixerPlayable animationMixerPlayable;
/// <summary>
/// 第一个动画剪辑的可播放项
/// </summary>
AnimationClipPlayable clipPlayable1;
/// <summary>
/// 第二个动画剪辑的可播放项
/// </summary>
AnimationClipPlayable clipPlayable2;
/// <summary>
/// 初始化并创建动画混合树
/// </summary>
void Start()
{
// 创建 Playable 图并设置更新模式为 GameTime
playableGraph = PlayableGraph.Create();
playableGraph.SetTimeUpdateMode(DirectorUpdateMode.GameTime);
// 创建动画输出,连接到 Animator 组件
AnimationPlayableOutput animationPlayableOutput =
AnimationPlayableOutput.Create(playableGraph, "Animation", GetComponent<Animator>());
// 创建混合器,支持 2 个输入
animationMixerPlayable = AnimationMixerPlayable.Create(playableGraph, 2);
// 将混合器设置为输出源
animationPlayableOutput.SetSourcePlayable(animationMixerPlayable);
// 创建两个动画剪辑的可播放项
clipPlayable1 = AnimationClipPlayable.Create(playableGraph, clip1);
clipPlayable2 = AnimationClipPlayable.Create(playableGraph, clip2);
// 将两个动画剪辑连接到混合器的输入端口
// Connect 参数说明:
// 参数1:源 Playable(要连接的动画剪辑可播放项)
// 参数2:源输出端口(0 - AnimationClipPlayable 只有一个输出端口)
// 参数3:目标 Playable(animationMixerPlayable - 混合器)
// 参数4:目标输入端口(混合器的输入端口索引,0 或 1)
playableGraph.Connect(clipPlayable1, 0, animationMixerPlayable, 0);
playableGraph.Connect(clipPlayable2, 0, animationMixerPlayable, 1);
// 设置混合器输入权重(两个动画各占 50%)
animationMixerPlayable.SetInputWeight(0, 0.5f);
animationMixerPlayable.SetInputWeight(1, 0.5f);
// 播放该图
playableGraph.Play();
}
/// <summary>
/// 每帧检测输入,控制动画剪辑的播放状态
/// 按数字键 1 可以切换 clip1 的播放/暂停状态
/// 按数字键 2 可以切换 clip2 的播放/暂停状态
/// 权重会根据播放状态动态调整,确保总和始终为 1
/// </summary>
void Update()
{
// 检测数字键 1 按下,控制 clip1 的播放状态
if (Input.GetKeyDown(KeyCode.Alpha1))
{
// 如果 clip1 正在播放,则暂停它
if (clipPlayable1.GetPlayState() == PlayState.Playing)
{
clipPlayable1.Pause();
}
// 如果 clip1 已暂停,则恢复播放并重置时间
else
{
clipPlayable1.Play();
clipPlayable1.SetTime(0f);
}
// 更新权重,确保总和为 1
UpdateWeights();
}
// 检测数字键 2 按下,控制 clip2 的播放状态
if (Input.GetKeyDown(KeyCode.Alpha2))
{
// 如果 clip2 正在播放,则暂停它
if (clipPlayable2.GetPlayState() == PlayState.Playing)
{
clipPlayable2.Pause();
}
// 如果 clip2 已暂停,则恢复播放并重置时间
else
{
clipPlayable2.Play();
clipPlayable2.SetTime(0f);
}
// 更新权重,确保总和为 1
UpdateWeights();
}
}
/// <summary>
/// 根据两个动画剪辑的播放状态更新权重
/// 确保权重总和始终为 1
/// 如果两个都播放,各占 0.5;如果只有一个播放,该动画权重为 1.0,另一个为 0
/// </summary>
void UpdateWeights()
{
bool clip1Playing = clipPlayable1.GetPlayState() == PlayState.Playing;
bool clip2Playing = clipPlayable2.GetPlayState() == PlayState.Playing;
// 如果两个动画都在播放,各占 50%
if (clip1Playing && clip2Playing)
{
animationMixerPlayable.SetInputWeight(0, 0.5f);
animationMixerPlayable.SetInputWeight(1, 0.5f);
}
// 如果只有 clip1 在播放,clip1 权重为 1.0,clip2 权重为 0
else if (clip1Playing && !clip2Playing)
{
animationMixerPlayable.SetInputWeight(0, 1.0f);
animationMixerPlayable.SetInputWeight(1, 0f);
}
// 如果只有 clip2 在播放,clip2 权重为 1.0,clip1 权重为 0
else if (!clip1Playing && clip2Playing)
{
animationMixerPlayable.SetInputWeight(0, 0f);
animationMixerPlayable.SetInputWeight(1, 1.0f);
}
// 如果两个都暂停,这里简单回到均分(看起来更“中性”)
else
{
animationMixerPlayable.SetInputWeight(0, 0.5f);
animationMixerPlayable.SetInputWeight(1, 0.5f);
}
}
/// <summary>
/// 组件禁用时清理资源,销毁 Playable 图及其所有可播放项和输出
/// </summary>
void OnDisable()
{
// 销毁该图创建的所有可播放项和输出
playableGraph.Destroy();
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com