2025
05.13

 

Animator 샘플링의 Serialize 문제

현재 작업해놓은 애니메이션 컨트롤러는

1. 커스텀 인스펙터에서 Slider로 애니메이션을 조정함 

2. 조정된 슬라이더의 타임을 따라 애니메이션대로 실제 애니메이션이 재생됨

의 기능을 가지고 있다.

 

Spine 기반으로 작업했던 것을 포팅하는 과정에서 시리얼라이즈 관련 문제가 발생했다.

Spine의 경우 별도 처리 없어도 Transform값이 시리얼라이즈되지 않았던 것으로 기억하는데,

Transform 값이 전부 시리얼라이즈 되서 gif diff 에 길게 나타나고 있었다.

 

3D 모델링의 경우 다음의 코드로 애니메이션을 강제 샘플링할 수 있다.

float normalizedTime = (time / clip.length);
_animator.Play(stateName, 0, normalizedTime);
_animator.Update(0f); // 즉시 반영

 

스테이트 명을 기반으로 overrideAnimator 에서 clip을 가져오는 에디터 코드를 만드는 부분이 껄끄러웠던 것은 뒤로하고,

이 방법으로 재생한 경우 Transform이 마지막 포즈대로 저장되버리는 문제가 있다.

 

사실 기능상 문제는 아니지만, 모든 본에 Transform override가 발생하는 것이니 프리팹의 텍스트파일로서의 용량이 커지게된다.

 

해결법은?

일단 PrefabStage 에서 처리를 해줘야 된다는 것은 확실.

간단히 생각해보면 방법은 3개정도 있다.

 

1. 애초에 저장되지 않는 내장 프리뷰 기능(Animation Window)를 사용한다.

  - 리플렉션을 통해 접근할 수 있다고 한다.

2. 애니메이션 재생할 때 메쉬를 바꿔치기해서 hideflag를 저장하지않게 설정 후, 닫을 때 원본을 다시 활성화 하는건?

  - 실제 오브젝트가 아니니 별도로 메쉬나 파티클을 본에 붙일 수 없게된다.

3. 시리얼라이즈된 트랜스폼 오버라이드를 전부 되돌리기

  - 간단해보인다. 다만 본 아래에 별도로 메쉬나 파티클을 부착하는 경우 그것도 Transform의 override니 문제가 될 수 있겠다.

 

2번은 기능이 제한되고, 1번은 리플렉션이 까다로우니 가장 간단해 보이는 3번으로 진행.

 

프리팹 스테이지에서 처리를 하면 되는데,

처음에는 OnPrefabStageClosing를 시도해봤는데 콜백이 호출되는 시점에 값을 변경해도 저장되지 않는다.

저장과 관련있는 PrefabStage.prefabSaving 의 콜백에서 시도해보니 정상 적용된다.

[InitializeOnLoad]
public static class Editor_PrefabStage_AnimationController
{
    static Editor_PrefabStage_AnimationController()
    {
        PrefabStage.prefabSaving += OnPrefabSaving;
    }

    static void OnPrefabSaving(GameObject stage)
    {
        // 내용을 채우면 된다.
    }
}

 

여기에 Mesh나 Particle을 붙이는 경우가 있으니 예외처리만 더해서 끝내면 된다.

전체코드는 실제 프로젝트에 사용한 코드라서 제네릭 하지 않으니 주의.

 

전체코드

더보기
using KindHero.Battle;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;

/// <summary>
/// 프리팹을 닫을 때, 애니메이션 프리뷰 때문에 발생한 Transform 오버라이드 변경을 되돌립니다.
/// </summary>
[InitializeOnLoad]
public static class Editor_PrefabStage_AnimationController
{
    static Editor_PrefabStage_AnimationController()
    {
        //PrefabStage.prefabStageClosing += OnPrefabStageClosing; // Closing 콜백 시점은 저장이 되지않는다.
        PrefabStage.prefabSaving += OnPrefabSaving;
    }

    static void OnPrefabSaving(GameObject stage)
    {
        if (stage == null) return;
        if (!stage.TryGetComponent(out UnitController unitController)) return;
        if (unitController == null) return;
        if (unitController.UnitAnimControl == null) return;
        if (unitController.UnitAnimControl.Animator == null) return;
        if (unitController.UnitAnimControl.Animator.gameObject == null) return;

        RemoveOverridesRecursive(unitSpineAnimation.UnitAnimControl.Animator.gameObject);
    }

    static void RemoveOverridesRecursive(GameObject obj)
    {
        foreach (var t in obj.GetComponentsInChildren<Transform>(true))
        {
            if (PrefabUtility.IsPartOfPrefabInstance(t.gameObject))
            {
                var propertyModifications = PrefabUtility.GetPropertyModifications(t);
                if (propertyModifications != null)
                {
                    if (t.TryGetComponent(out ParticleSystem _)) continue;
                    if (t.TryGetComponent(out Renderer _)) continue;

                    var filtered = new System.Collections.Generic.List<PropertyModification>();
                    foreach (var mod in propertyModifications)
                    {
                        if (mod.propertyPath.StartsWith("m_LocalPosition") ||
                            mod.propertyPath.StartsWith("m_LocalRotation") ||
                            mod.propertyPath.StartsWith("m_LocalScale"))
                        {
                            continue; // 제거 대상이므로 포함 안 함
                        }

                        filtered.Add(mod); // 유지할 것만 남김
                    }

                    if (filtered.Count != propertyModifications.Length)
                    {
                        // 프리팹 스테이지에서는 일반적인 Prefab Instance가 아니라 prefab 원본 편집 모드이기 때문에
                        // SetPropertyModifications는 잘 작동하지 않을 수 있습니다.
                        // PrefabUtility.SetPropertyModifications(t, filtered.ToArray());

                        PrefabUtility.RevertObjectOverride(t.transform, InteractionMode.AutomatedAction);
                        PrefabUtility.RevertObjectOverride(t.transform, InteractionMode.UserAction);
                        //Debug.Log("RevertObjectOverride: " + t.gameObject.name);
                    }
                }
            }
        }
    }
}