부트캠프

[멋쟁이사자처럼 유니티 TIL] 딕셔너리 인스펙터에 노출시키기, 커스텀 에디터로 데이터 이사하기 (+ TryGetValue, ISerializationCallbackReceiver, SerializedDictionary)

맛난과자 2024. 6. 20. 22:59

 

목차

▶ 딕셔너리 직렬화하는 법

    → 시간복잡도(Big-O)

    → TryGetValue

    ISerializationCallbackReceiver

    → SerializedDictionary

 

▶ 커스텀 에디터로 데이터 이사하는 법


 

 

오늘은 코드를 예쁘게 짜는 리팩토링 작업을 하였다.

주로 NewChanController 스크립트의 기능들을 DamageField 스크립트로 옮기는 작업을 하였다.

 

이 과정에서 딕셔너리를 쓰게 되었다.

 

 

 

딕셔너리를 쓴 이유

 

바로 시간복잡도 때문이다.

 

자료 구조 추가 검색 삭제 인덱스 접근
List<T> O(1) O(n) O(n) O(1)
Dictionary<T> O(1) O(1) O(1) -

 

여기서 O(n)는 Big-O 라는 아이인데,

알고리즘이 얼마나 효율적인지를 나타내는 표기법이다.

 

n : 연산의 합의 최고차항

 

예를 들어 연산값이 n(n-1) 이면 최고차항이 n^2이므로 O(n^2)이다.

입력값이 바로 반환되면 n(1)이 될 것이다.

 

여튼 표를 통해 딕셔너리가 검색과 삭제 부분에서 더 효율적이라는 것을 알 수 있다.

 

 

 

 

딕셔너리 Serialize하기

 

문제는 유니티가 딕셔너리를 인스펙터에서 안 보여준다는 사실^^

그래서 보통 Key List, Value List로 각각 값을 받은 후 딕셔너리로 변환한다.

 

 

 

이를 활용하는 과정에서 TryGetValue() 함수를 알게되었다.

 

TryGetValue()

 

: Dictionary 에 지정한 키가 포함되어 있는지 확인하고, 연결된 값을 검색하는 함수.

bool TryGetValue(TKey key, out TValue vlue)

 

첫 번째 매개변수는 해당 키의 존재를 확인하고,

두 번째 매개변수는 키가 존재하면 해당 키에 연결된 값을 변수에 할당한다.

 

함수는 키가 존재할 경우 true, 없으면 false를 반환한다.

// TryGetValue 활용 예시
if (tests.TryGetValue("Math", out int score))
{
    Debug.Log($"수학 시험의 점수는 {score}입니다.");   // score에 담긴 Math의 값 출력됨
}
else
{
    Debug.Log("수학 시험 점수를 찾을 수 없습니다.");
}

 

 

 

 

 

딕셔너리 시리얼라이즈화를 좀 더 편리하게 방법 중에는

ISerializationCallbackReceiver와 SerializedDictionary가 있다.

 

 

 

 

 

ISerializationCallbackReceiver

 

: 딕셔너리를 직렬화할 수 있도록 도와주는 인터페이스

 

이를 사용하면 함수 두 개를 만들게끔 되어있다.

 

 

OnBeforeSerialize()

: 직렬화 할 때 사용된다. Dictionary를 Key List, Value List로 변환하는 코드를 안에 작성해야한다.

 

OnAfterSerialize()

: 직렬화된 데이터를 다시 객체로 변환(Deserialize)할 때 사용한다. Key List, Value List를 Dictionary로 변환하는 코드를 작성해야한다.

 

 

다음은 활용 예시이다.

[Serializable]
public class TestScores : ISerializationCallbackReceiver   // 인터페이스 상속
{
    // 직렬화할 수 없는 Dictionary
    [NonSerialized]
    public Dictionary<string, int> tests = new Dictionary<string, int>();

    // 직렬화를 위한 리스트
    [SerializeField]
    private List<string> keys = new List<string>();

    [SerializeField]
    private List<int> values = new List<int>();

    // 직렬화되기 전에 호출됨
    public void OnBeforeSerialize()
    {
        keys.Clear();
        values.Clear();

        foreach (var kvp in tests)
        {
            keys.Add(kvp.Key);
            values.Add(kvp.Value);
        }
    }

    // 디시리얼라이즈된 후에 호출됨
    public void OnAfterDeserialize()
    {
        tests = new Dictionary<string, int>();

        for (int i = 0; i < keys.Count; i++)
        {
            tests[keys[i]] = values[i];
        }
    }
}

 

 

※ 참고로 이 인터페이스를 입력한 직후에는 인터페이스에 빨간 밑줄이 쳐져있을 것이다.

당황하지 않고 Alt + Enter로 또는 직접 OnBeforeSerialize(), OnAfterSerialize() 함수를 입력하면 오류는 사라질 것이다.

 

 

 

 

 

SerializedDictionary

 

앞의 두 방식 모두 인스펙터에 진짜 딕셔너리가 직렬화되는 것은 아니다.

Key와 Value가 예쁘게 구분되어 있는 진짜 딕셔너리가 보고 싶은가?

그렇다면 Serialized Dictionary 에셋을 사용하자.

https://assetstore.unity.com/packages/tools/utilities/serialized-dictionary-243052

 

Serialized Dictionary | 유틸리티 도구 | Unity Asset Store

Use the Serialized Dictionary from ayellowpaper on your next project. Find this utility tool & more on the Unity Asset Store.

assetstore.unity.com

 

들어가면 보이는 화면처럼 정말 보기 좋게 구분되어 있다.

 

사용 방법도 간단하다.

리스트나 딕셔너리 방식과 같이 SerializedDictionary<>로 선언하면 끝!

public SerializedDictionary<TKey, TValue> newDictionary = new SerializedDictionary<TKey, TValue>();

 

활용은 일반 딕셔너리와 똑같다.

 

 

부트캠프 내에서 활용한 결과,

 

너무 깔끔하지 아니한가.

게다가 무료? 

당장 깔아.

보자.

 

 

 


 

 

 

 

커스텀 에디터로 데이터 이사하기

 

리스트의 값들을 모두 바인딩했던 본인!

Serialized Dictionary에 다시 모든 값들을 바인딩하려니... 너무 귀찮다.

 

이를 편리하게 하기 위해 버튼 한 번이면 데이터를 이사할 수 있는 에디터를 만들어보자.

 

 

 

먼저 EditorWindow 인터페이스를 상속하는 스크립트를 하나 만들고 원하는대로 에디터를 만든다.

커스텀 에디터를 만드는 방법은 이전 글을 통해 자세히 알 수 있다.

https://snack-game.tistory.com/15

 

[멋쟁이사자처럼 유니티 TIL] 접근 제한자 개념, 에디터 스크립팅

접근제한자 : 변수 선언 시 해당 변수에 대한 접근의 범위를 정하는 키워드이다.  ● 종류 및 특징 private - 클래스 내에서만 접근 가능하다.- 외부에서는 호출, 수정 모두 불가능하다.- 접근제

snack-game.tistory.com

 

 

 

이후 다음과 같은 함수를 추가한다.

// 예시 코드
private void OnGUI()
{
    _controller = (DataController)EditorGUILayout.ObjectField(_controller, typeof(DataController), true);


    if (GUILayout.Button("Migrate"))
    {
        foreach (var data in _controller.DataInspectorList)    // 기존 리스트에 바인딩된 값
        {
            _controller.SerializedDictionary.Add(data.Type, data);    // 딕셔너리에 넣음
        }
        
        EditorUtility.SetDirty(_controller);   // 변경사항 Unity 에디터에 알림
        
        window.SaveChanges();    // 에디터 창의 변경사항을 저장
    }
}

 

 

 


 

 

 

 

 

오랜만에 블로그를 왔다.

TIL을 쓰지 않은 기간동안 느낀 건데,

아무리 블로그 쓰는 시간이 오래 걸린다고 해도 쓰는 게 좋은 것 같다.

코드를 아무리 쳐다봐도 나중에는 블로그로 정리한 것만 기억에 남았다.

티아엘 열심히 쓰자.