Вопрос или проблема
Я пытаюсь генерировать и воспроизводить анимации во время выполнения в 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) приводит к исключениям после сборки, так как он отсутствует в сборках, предназначенных для выполнения на устройствах. Работа в редакторе — это один контекст, в то время как реальное выполнение игры — это другой. К сожалению, для ваших нужд по созданию анимаций этот метод не подходит, и вам нужно будет рассмотреть другие подходы.
Альтернативные подходы к созданию анимаций в реальном времени
Как вы на самом деле пытаетесь динамически сгенерировать анимации путем чтения данных из файлов, есть несколько альтернатив, которые могут быть более подходящими:
-
Использование Animancer для создания анимаций:
Animancer, инструмент, который вы используете, предназначен для упрощения управления анимациями. Вместо того чтобы использоватьSetCurve
, вы можете использоватьAnimancer
для создания анимаций по кадрам во время выполнения. Создание и работа с анимациями черезAnimancer
позволяет вам игнорировать необходимые базовые подходы, полагаясь на расширенные возможности этого инструмента. -
Анимационные контроллеры и состояния:
Рассмотрите возможность использованияAnimator
Controller. Вы можете создать анимационные состояния и перейти к ним программно, когда это необходимо. Это позволяет управлять анимациями более гибко, даже если требуется a’la‘ импорт данных из ваших файлов. Вам нужно будет адаптировать компоненты вашей анимации к состояниям Animator. -
Ключевые кадры без SetCurve:
Если создание ключевых кадров нужно производить во время исполнения, вам необходимо использовать систему перезаписи анимации. Вместо сохранения и установки ключевых кадров наAnimationClip
, вы можете создать собственную структуру, хранящую информацию о времени и значениях, и затем применять эти значения вручную в методеUpdate
ваших скриптов. Например, вы можете использовать корутины для обновления позиции и ориентации ваших объектов на основе хранимых значений. -
Запись на лету через компонентов:
Создайте компонент, который будет читать ваши анимационные данные и применять их напрямую к вашим объектам (например,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
и облегчающих вашей задаче компонентов, чтобы сделать код более чистым и производительным.