유니티에서 topology가 같은 캐릭터간에 애니메이션 공유하기...

김포프 2012-02-04

img 이미지 출처: http://answers.unity3d.com/questions/191282/sharing-animations-between-models.html

본(bone)의 길이가 다른 캐릭터들 사이에서 애니메이션을 공유하려고 한다고 생각해 보죠. 물론 각 캐릭터마다 애니메이션을 따로 만들어주면 좋겠지만 인력이 부족한다던가… 그런데 이렇게 본의 길이가 다른 캐릭터터들 사이에서 애니메이션을 공유할 수 있을까요? 뭐 되니까 이글을 쓰는거죠… -_-; 평행이동과 확대/축소를 사용하지 않으면 가능합니다. 달리 말하면 회전만 이용하면 된다는거죠. 이렇게 생각해보면 이해가 쉬워요. 본인의 오른쪽 팔꿈치를 안쪽으로 90도만큼 회전시켜 보세요. 이제 본인보다 키가 작은 친구에게 똑같이 오른쪽 팔꿈치를 90도만큼 회전시키라고 하는거죠. 그러면 둘의 포즈가 똑같죠? 바로 이 원리 입니다. 이해 되죠?

결국 3DS Max가 뱉어내는 데이터가 어쨌던간에 애니메이션에서 회전만 적용시킬 수 있는 방법만 있으면 되는건데… 유니티에서 이런 걸 공식적으로 지원해주는 찾으려고 포럼을 뒤졌지만 그런것 같진 않더군요. 그러면 파이프라인 어딘가에서 회전 이외의 키프레임 정보를 지워버리는 방법이 없을까 찾아봤지요. 불행히도 딱히 대답을 찾을 순 없었습니다.심지어는 물어보는 사람조차 없더군요. (이 글 쓰고 나서 이 질문을 한 사람을 찾았으나 마땅한 답변이 없더군요. 지금 달린 답변은 제가 이 글 쓰고 난 뒤 단 것 -_-.

뭐든간에 좋은소식은…… 제가 그 꼼수를 찾아냈다는 거지요. 무핫핫….. :D

이렇게 했답니다. (코드는 조 밑에~)

  1. Asset/Editor 폴더 안에 ConvertToRotationOnlyAnim.cs 라는 스크립트 파일을 만듭니다.
  2. 이 스크립트를 호출하는 메뉴 항목을 만듭니다.
  3. Unity에 애니메이션을 import 해옵니다. (Unity가 애니메이션 파일이라고 생각하는 한 어디서 이 애니메이션을 만들었는지는 상관없습니다.)
  4. import해온 애니메이션 애셋에 마우스 오른쪽 버튼을 누르고, 단계 #2에서 추가했던 메뉴 아이템을 선택합니다.
  5. 이제 스트립트 안에서 propertyName 필드 중에 "m_LocalRotation"이란 이름을 포함한 놈만 복사합니다.
  6. 이제 _rot 애니메이션 클립을 게임오브젝트의 애니메이션 컴포넌트에 대입합니다.
  7. 시작 버튼을 누르고….. 즐거워 해봅시다.. 얼씨구나.. 지화자~ -_-

그리고 이게 위에서 말씀드린 스크립트의 전체 소스코드. 주석을 잘 달아놨으니 영어공부할겸….. 대충 보고 이해하세요. ㄱ ㄱ ㅑ ~ -_-

using UnityEditor;
using UnityEngine;

using System.IO;

public class ConvertToRotationOnlyAnim
{
    [MenuItem("Assets/Convert To Rotation Animation")]
    static void ConvertToRotationAnimation()
    {
        // Get Selected Animation Clip
        AnimationClip sourceClip = Selection.activeObject as AnimationClip;
        if (sourceClip == null)
        {
            Debug.Log("Please select an animation clip");
            return;
        }

        // Rotation only anim clip will have "_rot" post fix at the end
        const string destPostfix = "_rot";

        string sourcePath = AssetDatabase.GetAssetPath(sourceClip);
        string destPath = Path.Combine(Path.GetDirectoryName(sourcePath), sourceClip.name) + destPostfix + ".anim";

        // first try to open existing clip to avoid losing reference to this animation from other meshes that are already using it.
        AnimationClip destClip = AssetDatabase.LoadAssetAtPath(destPath, typeof(AnimationClip)) as AnimationClip;
        if (destClip == null)
        {
            // existing clip not found.  Let's create a new one
            Debug.Log("creating a new rotation only animation at " + destPath);
            
            destClip = new AnimationClip();
            destClip.name = sourceClip.name + destPostfix;

            AssetDatabase.CreateAsset(destClip, destPath);
            AssetDatabase.Refresh();

            // and let's load it back, just to make sure it's created?
            destClip = AssetDatabase.LoadAssetAtPath(destPath, typeof(AnimationClip)) as AnimationClip;
        }

        if (destClip == null)
        {
            Debug.Log("cannot create/open the rotation only anim at " + destPath);
            return;
        }

        // clear all the existing curves from destination.
        destClip.ClearCurves();

        // Now copy only rotation curves
        AnimationClipCurveData[] curveDatas = AnimationUtility.GetAllCurves(sourceClip, true);
        foreach (AnimationClipCurveData curveData in curveDatas)
        {
            if (curveData.propertyName.Contains("m_LocalRotation"))
            {
                AnimationUtility.SetEditorCurve(
                    destClip,
                    curveData.path,
                    curveData.type,
                    curveData.propertyName,
                    curveData.curve
                );
            }
        }

        Debug.Log("Hooray! Coverting to rotation-only anim to " + destClip.name + " is done");
    }
}