SetCurve не работает во время выполнения в сборке Unity с Animancer (аватар не гуманоидный)

Вопрос или проблема

Я пытаюсь генерировать и воспроизводить анимации во время выполнения в Unity с помощью Animancer. Моя текущая реализация использует AnimationClip.SetCurve для создания и воспроизведения анимации, которая работает идеально в редакторе, но не удается в сборке во время выполнения, потому что SetCurve является функцией только для редактора.

Вот мой текущий код:

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;
}

По сути, что делает мой код – считывает ключевые кадры из бинарного анимационного файла (BCK – формат файла для GameCube) и добавляет их в AnimationClip через SetCurve.

Поскольку SetCurve является функцией только для редактора, этот подход работает только в редакторе. Я натолкнулся на подобную проблему в этом вопросе на GitHub, где обсуждались ограничения SetCurve, но я не уверенн, как адаптировать решение, потому что я не использую гуманоидный аватар.
Вот ссылка: https://github.com/KybernetikGames/animancer/issues/362

Кто-нибудь знает альтернативный подход для создания анимационных клипов во время выполнения без использования SetCurve? Любые идеи будут оценены, особенно если вы работали с не гуманоидными аватарами и генерировали анимацию во время выполнения, воспроизводя их с помощью Animancer Pro.

Ответ или решение

Проблема с использованием SetCurve в Unity с Animancer для анимаций ненормированных аватаров

Ваша проблема заключается в том, что метод AnimationClip.SetCurve в Unity является функцией, доступной только в редакторе. Использование этого метода для создания анимационных клипов в реальном времени (runtime) приводит к исключениям после сборки, так как он отсутствует в сборках, предназначенных для выполнения на устройствах. Работа в редакторе — это один контекст, в то время как реальное выполнение игры — это другой. К сожалению, для ваших нужд по созданию анимаций этот метод не подходит, и вам нужно будет рассмотреть другие подходы.

Альтернативные подходы к созданию анимаций в реальном времени

Как вы на самом деле пытаетесь динамически сгенерировать анимации путем чтения данных из файлов, есть несколько альтернатив, которые могут быть более подходящими:

  1. Использование Animancer для создания анимаций:
    Animancer, инструмент, который вы используете, предназначен для упрощения управления анимациями. Вместо того чтобы использовать SetCurve, вы можете использовать Animancer для создания анимаций по кадрам во время выполнения. Создание и работа с анимациями через Animancer позволяет вам игнорировать необходимые базовые подходы, полагаясь на расширенные возможности этого инструмента.

  2. Анимационные контроллеры и состояния:
    Рассмотрите возможность использования Animator Controller. Вы можете создать анимационные состояния и перейти к ним программно, когда это необходимо. Это позволяет управлять анимациями более гибко, даже если требуется a’la‘ импорт данных из ваших файлов. Вам нужно будет адаптировать компоненты вашей анимации к состояниям Animator.

  3. Ключевые кадры без SetCurve:
    Если создание ключевых кадров нужно производить во время исполнения, вам необходимо использовать систему перезаписи анимации. Вместо сохранения и установки ключевых кадров на AnimationClip, вы можете создать собственную структуру, хранящую информацию о времени и значениях, и затем применять эти значения вручную в методе Update ваших скриптов. Например, вы можете использовать корутины для обновления позиции и ориентации ваших объектов на основе хранимых значений.

  4. Запись на лету через компонентов:
    Создайте компонент, который будет читать ваши анимационные данные и применять их напрямую к вашим объектам (например, Transform), избегая необходимости завязываться на AnimationClip. Это обеспечивает более высокую производительность, поскольку вы работаете напрямую с Transform.position и Transform.rotation, минуя дополнительные накладные расходы на управление анимационными клипами.

Пример обработки анимации без SetCurve

Вот пример того, как можно реализовать фреймворк для управления анимациями без использования SetCurve:

private void Update()
{
    if (isAnimating)
    {
        float time = Time.time - startTime;
        UpdateAnimationPositionAndRotation(time);
    }
}

private void UpdateAnimationPositionAndRotation(float time)
{
    foreach (var bone in pose)
    {
        string path = GetFullPath(bone);
        Vector3 position = CalculatePositionAtTime(bone, time);
        Quaternion rotation = CalculateRotationAtTime(bone, time);

        bone.transform.localPosition = position;
        bone.transform.localRotation = rotation;
    }
}

private Vector3 CalculatePositionAtTime(SkeletonJoint joint, float time)
{
    // Используйте анимационные данные для расчета позиции
}

private Quaternion CalculateRotationAtTime(SkeletonJoint joint, float time)
{
    // Используйте анимационные данные для расчета вращения
}

Заключение

Чтобы адаптировать вашу систему анимации для работы на этапе выполнения, необходимо пересмотреть подход к созданию и управлению анимациями. Использование методов, которые напрямую манипулируютTransform’ом вместо зависимостей от AnimationClip.SetCurve, может значительно улучшить производительность и решит проблему совместимости с реальным выполнением игры. Рассматривайте возможность использования возможностей Animancer и облегчающих вашей задаче компонентов, чтобы сделать код более чистым и производительным.

Оцените материал
Добавить комментарий

Капча загружается...