3.创建动画混合树

3.Playable创建动画混合树


3.1 知识点

思路和步骤

用 Playable API 创建动画混合树,可以理解为:创建一个混合器节点,把多个动画 Clip 接进去,用权重控制混合比例

核心步骤

  • 创建图并设置更新模式
  • 创建动画输出,绑定到 Animator
  • 创建混合器节点(两种创建方式)
  • 将混合器连接到输出
  • 创建动画剪辑节点
  • 连接动画剪辑到混合器(两种连接方式)
  • 播放图
  • 在 Update 中动态设置混合权重
  • 清理资源

代码实现

步骤1:创建图并设置更新模式

// 创建 Playable 图 并设置更新模式为 GameTime
playableGraph = PlayableGraph.Create();
playableGraph.SetTimeUpdateMode(DirectorUpdateMode.GameTime);

先创建 PlayableGraph,并设置时间更新模式为游戏时间。

步骤2:创建动画输出

// 创建动画输出,连接到 Animator 组件
AnimationPlayableOutput animationPlayableOutput =
    AnimationPlayableOutput.Create(playableGraph, "VayneMixIdleAndRun", GetComponent<Animator>());

创建动画输出节点,绑定到场景中的 Animator 组件,混合后的动画结果就能应用到角色上了。

步骤3:创建混合器节点

创建混合器有两种方式,差别主要在“输入端口数什么时候确定”:

方式1:创建时指定输入端口数

// 方式1:创建时指定输入端口数(使用 Connect 方法连接时需要)
animationMixerPlayable = AnimationMixerPlayable.Create(playableGraph, 2);

创建时直接指定输入端口数量(这里是 2 个),适合用 Connect() 这种需要明确端口索引的连接方式。

方式2:创建时不指定输入端口数

// 方式2:创建时不指定输入端口数(使用 AddInput 方法时会自动添加输入端口)
animationMixerPlayable = AnimationMixerPlayable.Create(playableGraph);

创建时不指定输入端口数,用 AddInput() 时会自动追加输入端口(按调用顺序往后排)。如果要用 Connect(),也可以创建后再设置端口数:

animationMixerPlayable.SetInputCount(2);

步骤4:将混合器连接到输出

// 将混合器设置为输出源
animationPlayableOutput.SetSourcePlayable(animationMixerPlayable);

把混合器设置为输出的数据源,混合后的结果就会写回 Animator。

步骤5:创建动画剪辑节点

// 创建两个动画剪辑的可播放项
AnimationClipPlayable clipIdlePlayable = AnimationClipPlayable.Create(playableGraph, clipIdle);
AnimationClipPlayable clipRunPlayable = AnimationClipPlayable.Create(playableGraph, clipRun);

把要混合的动画剪辑包装成 Playable 节点,每个 Clip 对应一个 AnimationClipPlayable

步骤6:连接动画剪辑到混合器

连接动画剪辑到混合器也有两种方式:

方式1:使用 Connect 方法连接

// 方式1:使用 Connect 方法连接(需要先设置输入端口数)
// Connect 参数说明:
// 参数1:源 Playable(要连接的动画剪辑可播放项)
// 参数2:源输出端口(0 - AnimationClipPlayable 只有一个输出端口)
// 参数3:目标 Playable(animationMixerPlayable - 混合器)
// 参数4:目标输入端口(混合器的输入端口索引,0 或 1)
playableGraph.Connect(clipIdlePlayable, 0, animationMixerPlayable, 0);
playableGraph.Connect(clipRunPlayable, 0, animationMixerPlayable, 1);

Connect() 需要先设置混合器的输入端口数,并手动指定目标输入端口索引。连接时不会设置初始权重,需要自己补。

方式2:使用 AddInput 方法连接

// 方式2:使用 AddInput 方法连接(会自动添加输入端口,按顺序添加)
// AddInput 参数说明:
// 参数1:源 Playable(要连接的动画剪辑可播放项)
// 参数2:源输出端口(0 - AnimationClipPlayable 只有一个输出端口)
// 参数3:初始权重值(连接时的初始混合权重)
// 注意:AddInput 会按顺序自动添加输入端口,第一个 AddInput 对应索引 0,第二个对应索引 1
animationMixerPlayable.AddInput(clipIdlePlayable, 0, 1.0f - weight);
animationMixerPlayable.AddInput(clipRunPlayable, 0, weight);

AddInput() 会自动添加输入端口,按顺序分配索引(第一个为 0,第二个为 1)。连接时还能设置初始权重,代码更简洁,绝大多数场景够用。

步骤7:播放图

// 播放该图
playableGraph.Play();

开始播放图,之后每帧 Unity 会自动更新并应用到 Animator。

步骤8:设置混合权重(在 Update 中)

void Update()
{
    // 限制权重值在 0-1 范围内
    weight = Mathf.Clamp01(weight);

    // 设置混合器输入权重,要先传入索引
    // clipIdle 的权重为 (1 - weight),clipRun 的权重为 weight
    // 如果权重加起来不为1,可能导致动画的播放速度异常(总和小于1会变慢,大于1则会变快)
    animationMixerPlayable.SetInputWeight(0, 1.0f - weight);
    animationMixerPlayable.SetInputWeight(1, weight);
}

Update() 中动态调整权重,实现平滑过渡。用 Mathf.Clamp01() 限制权重在 0-1 范围内。

注意:权重总和建议保持为 1。总和不为 1 时混合结果会被整体缩小或放大,姿态贡献不容易直观判断,实战中一般会做归一化。

步骤9:清理资源

void OnDisable()
{
    // 销毁该图创建的所有可播放项和输出
    playableGraph.Destroy();
}

组件禁用时调用 Destroy() 释放所有资源,包括节点和输出,避免内存泄漏。建议放在 OnDisable(),组件被禁用时就能及时释放。

效果展示



3.2 知识点代码

MixAnimationSample.cs

using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Animations;

/// <summary>
/// 动画混合树示例组件
/// 该组件演示了如何使用 animationMixerPlayable 混合两个动画剪辑
/// 通过调整 weight 参数可以控制两个动画的混合比例
/// </summary>
[RequireComponent(typeof(Animator))]
public class MixAnimationSample : MonoBehaviour
{
    /// <summary>
    /// 第一个动画剪辑
    /// </summary>
    public AnimationClip clipIdle;

    /// <summary>
    /// 第二个动画剪辑
    /// </summary>
    public AnimationClip clipRun;

    /// <summary>
    /// 混合权重值,范围 0-1
    /// 0 表示完全播放 clipIdle,1 表示完全播放 clipRun
    /// </summary>
    [Range(0f, 1f)] public float weight;

    /// <summary>
    /// Playable 图,用于管理动画播放流程
    /// </summary>
    PlayableGraph playableGraph;

    /// <summary>
    /// 动画混合器,用于混合两个动画剪辑
    /// </summary>
    AnimationMixerPlayable animationMixerPlayable;

    /// <summary>
    /// 初始化并创建动画混合树
    /// </summary>
    void Start()
    {
        // 创建 Playable 图 并设置更新模式为 GameTime
        playableGraph = PlayableGraph.Create();
        playableGraph.SetTimeUpdateMode(DirectorUpdateMode.GameTime);

        // 创建动画输出,连接到 Animator 组件
        AnimationPlayableOutput animationPlayableOutput =
            AnimationPlayableOutput.Create(playableGraph, "VayneMixIdleAndRun", GetComponent<Animator>());

        // 创建混合器
        // 方式1:创建时指定输入端口数(使用 Connect 方法连接时需要)
        // animationMixerPlayable = AnimationMixerPlayable.Create(playableGraph, 2);
        
        // 方式2:创建时不指定输入端口数(使用 AddInput 方法时会自动添加输入端口)
        animationMixerPlayable = AnimationMixerPlayable.Create(playableGraph);
        // 当然不用AddInput用Connect的话也可以在创建后设置输入端口数
        // animationMixerPlayable.SetInputCount(2);

        // 将混合器设置为输出源
        animationPlayableOutput.SetSourcePlayable(animationMixerPlayable);

        // 创建两个动画剪辑的可播放项
        AnimationClipPlayable clipIdlePlayable = AnimationClipPlayable.Create(playableGraph, clipIdle);
        AnimationClipPlayable clipRunPlayable = AnimationClipPlayable.Create(playableGraph, clipRun);

        // 方式1:使用 Connect 方法连接(需要先设置输入端口数)
        // Connect 参数说明:
        // 参数1:源 Playable(要连接的动画剪辑可播放项)
        // 参数2:源输出端口(0 - AnimationClipPlayable 只有一个输出端口,将动画剪辑的输出连接到混合器的输入端口)
        // 参数3:目标 Playable(animationMixerPlayable - 混合器)
        // 参数4:目标输入端口(混合器的输入端口索引,0 或 1)
        // playableGraph.Connect(clipIdlePlayable, 0, animationMixerPlayable, 0);
        // playableGraph.Connect(clipRunPlayable, 0, animationMixerPlayable, 1);

        // 方式2:使用 AddInput 方法连接(会自动添加输入端口,按顺序添加)
        // AddInput 参数说明:
        // 参数1:源 Playable(要连接的动画剪辑可播放项)
        // 参数2:源输出端口(0 - AnimationClipPlayable 只有一个输出端口)
        // 参数3:初始权重值(连接时的初始混合权重)
        // 注意:AddInput 会按顺序自动添加输入端口,第一个 AddInput 对应索引 0,第二个对应索引 1
        animationMixerPlayable.AddInput(clipIdlePlayable, 0, 1.0f - weight);
        animationMixerPlayable.AddInput(clipRunPlayable, 0, weight);

        
        // 播放该图
        playableGraph.Play();
    }

    /// <summary>
    /// 每帧更新混合权重
    /// 根据 weight 值动态调整两个动画的混合比例
    /// </summary>
    void Update()
    {
        // 限制权重值在 0-1 范围内
        weight = Mathf.Clamp01(weight);

        // 设置混合器输入权重,要先传入索引
        // clipIdle 的权重为 (1 - weight),clipRun 的权重为 weight
        // 权重总和建议保持为 1,不然混合结果的观感容易怪(整体被稀释/放大)
        animationMixerPlayable.SetInputWeight(0, 1.0f - weight);
        animationMixerPlayable.SetInputWeight(1, weight);
    }

    /// <summary>
    /// 组件禁用时清理资源,销毁 Playable 图及其所有可播放项和输出
    /// </summary>
    void OnDisable()
    {
        // 销毁该图创建的所有可播放项和输出
        playableGraph.Destroy();
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏