You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity构建中Animancer非人形Avatar运行时SetCurve失效求助

问题描述

我正尝试在Unity中使用Animancer在运行时生成并播放动画。当前实现通过AnimationClip.SetCurve创建动画,编辑器中运行正常,但构建后失效,因为SetCurve是编辑器专属函数。

我的代码功能是读取GameCube的BCK二进制动画文件中的关键帧,通过SetCurve添加到AnimationClip中,核心代码如下:

public AnimationClip CreateAnimationClipFromBCK(BMD bmd, BCK bck, string startBone = "##STOP##")
{
    AnimationClip clip = new AnimationClip();
    clip.name = bck.Name.Replace(".bck", "");

    if (bck.LoopMode == LoopType.Loop) clip.wrapMode = UnityEngine.WrapMode.Loop; 
    if (bck.LoopMode == LoopType.Once) clip.wrapMode = UnityEngine.WrapMode.Once;

    List<SkeletonJoint> pose = bmd.JNT1Tag.AnimatedJoints;
    if (bck.m_animationData == null) return null;

    int numJoints = Math.Min(pose.Count, bck.m_animationData.Count);
    AnimationCurveHolder animationData = new AnimationCurveHolder();

    for (int i = 0; i < numJoints; i++)
    {
        string boneName = pose[i].Name;
        if(startBone != "##STOP##") if (!IsDescendantOf(pose[i], startBone)) continue;

        var jointAnim = bck.m_animationData[i];
        string path = GetFullPath(pose[i]);

        animationData.PositionCurvesX[path] = new AnimationCurve();
        animationData.PositionCurvesY[path] = new AnimationCurve();
        animationData.PositionCurvesZ[path] = new AnimationCurve();
        animationData.RotationCurvesX[path] = new AnimationCurve();
        animationData.RotationCurvesY[path] = new AnimationCurve();
        animationData.RotationCurvesZ[path] = new AnimationCurve();
        animationData.RotationCurvesW[path] = new AnimationCurve();
    }

    for (int j = 0; j < bck.AnimLengthInFrames; j++)
    {
        float ftime = j;
        for (int i = 0; i < numJoints; i++)
        {
            if(startBone != "##STOP##") if (!IsDescendantOf(pose[i], startBone)) continue;

            var jointAnim = bck.m_animationData[i];
            string path = GetFullPath(pose[i]);

            OpenTK.Vector3 translation = new OpenTK.Vector3(
                bck.GetAnimValue(jointAnim.TranslationsX, ftime),
                bck.GetAnimValue(jointAnim.TranslationsY, ftime),
                bck.GetAnimValue(jointAnim.TranslationsZ, ftime)
            );

            OpenTK.Vector3 rot = new OpenTK.Vector3(
                bck.GetAnimValue(jointAnim.RotationsX, ftime),
                bck.GetAnimValue(jointAnim.RotationsY, ftime),
                bck.GetAnimValue(jointAnim.RotationsZ, ftime)
            );

            pose[i].Rotation =
                OpenTK.Quaternion.FromAxisAngle(new OpenTK.Vector3(0, 0, 1), WMath.DegreesToRadians(rot.Z)) *
                OpenTK.Quaternion.FromAxisAngle(new OpenTK.Vector3(0, 1, 0), WMath.DegreesToRadians(rot.Y)) *
                OpenTK.Quaternion.FromAxisAngle(new OpenTK.Vector3(1, 0, 0), WMath.DegreesToRadians(rot.X));

            animationData.PositionCurvesX[path].AddKey(ftime, translation.X);
            animationData.PositionCurvesY[path].AddKey(ftime, translation.Y);
            animationData.PositionCurvesZ[path].AddKey(ftime, translation.Z);
            animationData.RotationCurvesX[path].AddKey(ftime, pose[i].Rotation.X);
            animationData.RotationCurvesY[path].AddKey(ftime, pose[i].Rotation.Y);
            animationData.RotationCurvesZ[path].AddKey(ftime, pose[i].Rotation.Z);
            animationData.RotationCurvesW[path].AddKey(ftime, pose[i].Rotation.W);
        }
    }

    foreach (var kvp in animationData.PositionCurvesX) clip.SetCurve(kvp.Key, typeof(Transform), "localPosition.x", kvp.Value);
    foreach (var kvp in animationData.PositionCurvesY) clip.SetCurve(kvp.Key, typeof(Transform), "localPosition.y", kvp.Value);
    foreach (var kvp in animationData.PositionCurvesZ) clip.SetCurve(kvp.Key, typeof(Transform), "localPosition.z", kvp.Value);
    foreach (var kvp in animationData.RotationCurvesX) clip.SetCurve(kvp.Key, typeof(Transform), "localRotation.x", kvp.Value);
    foreach (var kvp in animationData.RotationCurvesY) clip.SetCurve(kvp.Key, typeof(Transform), "localRotation.y", kvp.Value);
    foreach (var kvp in animationData.RotationCurvesZ) clip.SetCurve(kvp.Key, typeof(Transform), "localRotation.z", kvp.Value);
    foreach (var kvp in animationData.RotationCurvesW) clip.SetCurve(kvp.Key, typeof(Transform), "localRotation.w", kvp.Value);

    _animationCurveHolders.Add(animationData);
    return clip;
}

由于未使用人形Avatar,不确定如何适配官方讨论中的方案,求无需SetCurve的运行时动画生成播放方案,尤其是适配非人形骨架和Animancer Pro的实现建议。


解决方案

1. 自定义Animancer状态直接驱动Transform

跳过AnimationClip,直接基于Animancer的Playable架构,手动采样动画曲线并设置Transform属性:

  • 核心思路:保留原代码生成的AnimationCurveHolder数据,创建继承自AnimancerState的自定义状态,在每一帧根据播放时间采样曲线,直接更新关节的localPosition和localRotation。
  • 示例代码框架:
public class RuntimeBoneAnimationState : AnimancerState
{
    private Dictionary<string, Transform> _boneTransforms;
    private AnimationCurveHolder _animationData;
    private float _duration;
    private WrapMode _wrapMode;

    public RuntimeBoneAnimationState(Dictionary<string, Transform> boneTransforms, AnimationCurveHolder animationData, float duration, WrapMode wrapMode)
    {
        _boneTransforms = boneTransforms;
        _animationData = animationData;
        _duration = duration;
        _wrapMode = wrapMode;
    }

    protected override void OnUpdate()
    {
        base.OnUpdate();
        float time = GetWrappedTime(Time, _duration, _wrapMode);
        
        foreach (var kvp in _boneTransforms)
        {
            string path = kvp.Key;
            Transform bone = kvp.Value;
            
            // 采样位置曲线
            Vector3 pos = new Vector3(
                _animationData.PositionCurvesX[path].Evaluate(time),
                _animationData.PositionCurvesY[path].Evaluate(time),
                _animationData.PositionCurvesZ[path].Evaluate(time)
            );
            bone.localPosition = pos;
            
            // 采样旋转曲线
            Quaternion rot = new Quaternion(
                _animationData.RotationCurvesX[path].Evaluate(time),
                _animationData.RotationCurvesY[path].Evaluate(time),
                _animationData.RotationCurvesZ[path].Evaluate(time),
                _animationData.RotationCurvesW[path].Evaluate(time)
            );
            bone.localRotation = rot;
        }
    }

    private float GetWrappedTime(float time, float duration, WrapMode wrapMode)
    {
        if (wrapMode == WrapMode.Loop)
            return time % duration;
        else if (wrapMode == WrapMode.Once)
            return Mathf.Clamp(time, 0, duration);
        return time;
    }
}
  • 使用方式:加载BCK数据后,将关节路径与对应Transform映射,创建该状态并添加到AnimancerComponent中播放。

2. 用PlayableGraph构建运行时动画轨道

利用Unity的Playable API,将每个关节的曲线转换成独立的AnimationCurvePlayable,构建动画图后连接到Animancer:

  • 步骤:
    1. 为每个关节的位置/旋转曲线创建AnimationCurvePlayable。
    2. 将这些Playable绑定到对应Transform的属性。
    3. 将整个PlayableGraph包装成AnimancerState,纳入Animancer的播放管理。
  • 优势:利用Unity原生动画系统的插值优化,性能比纯手动驱动更稳定,适合复杂动画场景。

3. Animancer Pro专属优化

Animancer Pro提供了更灵活的Playable扩展:

  • 使用PlayableWrapperState快速封装自定义Playable,无需手动继承AnimancerState。
  • 利用AnimancerLayer的Playable属性直接插入自定义动画轨道,与现有动画混合。
  • 对于批量关节动画,可使用GenericAnimationState的底层API直接注入曲线数据。

内容的提问来源于stack exchange,提问作者Finn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 16:40:56