[Unity Editor] Animaker #2 - Save/Load preset

qweasfjbv·2024년 12월 30일

UnityEditor

목록 보기
2/12

개요


이번에는 animation clip을 반복적으로 만들어야하는 상황에서 조금 더 편의를 제공해주는 preset save/load 기능을 만들어보겠습니다.
해당 정보는 json 파일을 통해 저장할수도 있지만, 에디터 상에서 편하게 조작할 수 있도록 ScriptableObject 와 Editor를 통해 Inspector 창을 커스텀해보도록 하겠습니다.

구현


namespace AutoAnimaker.Core
{
    public static class PresetLoader
    {
        public static List<AnimOptionSO> LoadScriptableObjects()
        {
            List<AnimOptionSO> scriptableObjects = new List<AnimOptionSO>();

            // type : AnimOptionSO
            string[] guids = AssetDatabase.FindAssets("t:AnimOptionSO", new[] { Constants.PATH_PRESET });
            scriptableObjects.Clear();

            foreach (string guid in guids)
            {
                string assetPath = AssetDatabase.GUIDToAssetPath(guid);
                AnimOptionSO asset = AssetDatabase.LoadAssetAtPath<AnimOptionSO>(assetPath);
                if (asset != null)
                {
                    scriptableObjects.Add(asset);
                }
            }

            return scriptableObjects;
        }
    }
}

우선 ScriptableObject에 필요한 정보들을 선언해주고 위와 같이 AssetDatabase.FindAssets 함수를 통해 받아옵니다. ( "t:~" 는 type을 의미합니다.)

Editor 스크립트에서 해당 함수를 통해 Preset 리스트를 받고 선택할 수 있게 만들어줍니다.

private void DrawHeader()
{
			// ...
            
            // ScriptableObject Selection Dropdown
            if (scriptableObjects.Count > 0)
            {
                var names = scriptableObjects.Select(obj => obj.name).ToArray();
                int selectedIndex = scriptableObjects.IndexOf(selectedObject);
                int newSelectedIndex = EditorGUILayout.Popup("Select ScriptableObject", selectedIndex, names);

                if (newSelectedIndex != selectedIndex)
                {
                    selectedObject = scriptableObjects[newSelectedIndex];
                    Debug.Log("Selected ScriptableObject: " + selectedObject.name);
                }
            }
            
            // ...
}

LINQ와 Popup을 사용하여 이름들을 드롭다운으로 출력합니다.

이제 Save/Load 기능을 만들어야 합니다.

Save/Load

Save 와 Load 를 구현할 때 주의해야할 점은, List나 배열같은 경우 대입연산자를 사용하면 Deep Copy가 되지 않는다는 점입니다.
DeepCopy를 사용하지 않고 그냥 대입연산자를 사용하게 되면 아래 사진과 같이 ScriptableObject와 Editor간에 동기화가 생깁니다.

        public SpriteAnimatorStruct GetDeepCopy()
        {
            SpriteAnimatorStruct tmpStruct = new SpriteAnimatorStruct();
            tmpStruct.animationNames = new List<string>(this.animationNames);
            tmpStruct.sprite = this.sprite;
            return tmpStruct;
        }

우선, 해당 자료형에 GetDeepCopy 함수를 만들어줍니다.
(이름과 다르게 class 자료형이기 때문에 필요합니다.)

        public void SavePreset()
        {
        	// other settings...
            
            // NOT REF. Deep Copy
            Array.Clear(selectedObject.animatorStructs, 0, selectedObject.animatorStructs.Count());
            selectedObject.animatorStructs = new SpriteAnimatorStruct[animatorStructs.Length];
            for (int i = 0; i < animatorStructs.Length; i++)
            {
                selectedObject.animatorStructs[i] = animatorStructs[i].GetDeepCopy();
            }
        }

그 후에 위와같이 배열을 선언하고 GetDeepCopy로 받은 데이터를 대입합니다.

Load 기능은 정확히 반대로만하면 되기 때문에 생략하겠습니다.

마무리


여기까지 Preset의 Save/Load를 구현해봤습니다.
아직 Inspector 창을 커스텀하는 일이 남아있지만 CustomEditor와 비슷하게 할 수 있으리라 생각합니다.
지금은 이미 만들어진 Preset에만 저장이 가능하기 때문에, 도중에 Preset 폴더에 SO를 추가한다고 해도 Editor에서 접근하지 못하는 문제점이 있습니다. ( Preset Load를 OnEnable 때 한 번만 합니다.)
따라서, Editor 내부에서 SO를 만드는 기능을 추가해주어야 합니다.

Sprite Preview 기능 또한 Unfocus 되면 바로 닫히도록 되어있는데, 이는 widthPx나 colCount 등을 조정하는데 있어서 불편함이 생길 수 있었습니다. 해당 부분도 바로 고쳐보도록 하겠습니다.

참고자료


https://docs.unity3d.com/6000.0/Documentation/ScriptReference/AssetDatabase.FindAssets.html

0개의 댓글