6.控制PlayableGraph树的播放状态和时序

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

×

喜欢就点赞,疼爱就打赏