본문 바로가기
Unity/스크립터블오브젝트

Scriptable Object - 응용: EditorWithSubEditors, 확장 메서드 (11)

by PlaneK 2020. 6. 8.

EditorWithSubEditors<TEditor, TTarget> : Editor

Editor의 인스턴스는 어떻게 생성될까?

MonoBehaviour의 에디터는 해당 MonoBehaviour가 생성되면 같이 자동 생성된다.

ScriptableObject의 에디터도 .asset 파일이 생성되면 같이 자동 생성된다.

그러나 ScriptableObject를 코드 상에서 인스턴스만 생성하면 해당 에디터는 자동 생성되지 않고 직접 생성해줘야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
using UnityEngine;
using UnityEditor;
 
// 제네릭 타입의 추상 클래스. Editor를 상속한다.
public abstract class EditorWithSubEditors<TEditor, TTarget> : Editor
    where TEditor : Editor    // 해당 타입 매개변수의 타입을 Editor로 제한한다.
    where TTarget : Object    // 해당 타입 매개변수의 타입을 Object로 제한한다.
{
    protected TEditor[] subEditors;
 
    // subEditorTargets에는 target의 프로퍼티 값이 넘어온다.
    protected void CheckAndCreateSubEditors (TTarget[] subEditorTargets)
    {
        // Editor는 Update문에 의해 계속 호출된다.
        // subEditors가 이미 초기화됐으면
        if (subEditors != null && subEditors.Length == subEditorTargets.Length)
            // 생략한다.
            return;
 
        // 배열은 길이 동적 할당이 불가능하므로 클린을 수행한 후
        CleanupEditors ();
 
        // 지정한 길이만큼 해당 타입의 빈 배열을 새로 생성한다.
        subEditors = new TEditor[subEditorTargets.Length];
 
        // 배열에 요소를 할당한다.
        for (int i = 0; i < subEditors.Length; i++)
        {
            // 해당 타입의 에디터를 생성한다.
            // CreateEditor([Target Instance])
            subEditors[i] = CreateEditor (subEditorTargets[i]) as TEditor;
            // 서브 에디터를 전달하여 서브 에디터의 프로퍼티 초기화를 맡긴다.
            SubEditorSetup (subEditors[i]);
        }
    }
 
 
    protected void CleanupEditors ()
    {
        if (subEditors == null)
            return;
 
        for (int i = 0; i < subEditors.Length; i++)
        {
            DestroyImmediate (subEditors[i]);
        }
 
        subEditors = null;
    }
 
 
    protected abstract void SubEditorSetup (TEditor editor);
}
 

EditorWithSubEditors.cs

서브에디터가 필요한 에디터들은 이 클래스를 상속한다.

SO 인스턴스를 생성하고 해당 인스턴스의 Editor 인스턴스도 생성, 서브에디터로써 참조한다.

 

확장 메서드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
using UnityEngine;
using UnityEditor;
 
// 확장 메서드는 static 클래스에 정의된다.
public static class SerializedPropertyExtensions
{    
    // 확장 메서드는 static이며, 확장하려는 타입의 매개변수를 this 예약어와 함께 명시해야 한다.
    public static void AddToObjectArray<T> (this SerializedProperty arrayProperty, T elementToAdd)
        where T : Object
    {
        // 해당 프로퍼티가 컨테이너(배열, 리스트)가 아니면
        if (!arrayProperty.isArray)
            // 예외 발생
            throw new UnityException("SerializedProperty " + arrayProperty.name + " is not an array.");
 
        arrayProperty.serializedObject.Update();
 
        // 빈 요소 삽입
        arrayProperty.InsertArrayElementAtIndex(arrayProperty.arraySize);
        // 빈 요소 초기화
        arrayProperty.GetArrayElementAtIndex(arrayProperty.arraySize - 1).objectReferenceValue = elementToAdd;
 
        arrayProperty.serializedObject.ApplyModifiedProperties();
    }
 
 
    public static void RemoveFromObjectArrayAt (this SerializedProperty arrayProperty, int index)
    {
        if(index < 0)
            throw new UnityException("SerializedProperty " + arrayProperty.name + " cannot have negative elements removed.");
 
        if (!arrayProperty.isArray)
            throw new UnityException("SerializedProperty " + arrayProperty.name + " is not an array.");
 
        if(index > arrayProperty.arraySize - 1)
            throw new UnityException("SerializedProperty " + arrayProperty.name + " has only " + arrayProperty.arraySize + " elements so element " + index + " cannot be removed.");
 
        arrayProperty.serializedObject.Update();
        // 해당 인덱스의 요소가 null이 아니면
        if (arrayProperty.GetArrayElementAtIndex(index).objectReferenceValue)
            // 요소의 값 제거
            arrayProperty.DeleteArrayElementAtIndex(index);
        // 요소 값만 제거 되서 arraySize가 줄지 않음. 다시 실행시켜서 요소 제거.
        // Sometimes a single delete only deletes the value and not the actual element
        arrayProperty.DeleteArrayElementAtIndex(index);
        arrayProperty.serializedObject.ApplyModifiedProperties();
    }
 
 
    public static void RemoveFromObjectArray<T> (this SerializedProperty arrayProperty, T elementToRemove)
        where T : Object
    {
        if (!arrayProperty.isArray)
            throw new UnityException("SerializedProperty " + arrayProperty.name + " is not an array.");
 
        // 지우려는 요소가 null 이면 예외 발생
        if(!elementToRemove)
            throw new UnityException("Removing a null element is not supported using this method.");
 
        arrayProperty.serializedObject.Update();
 
        // 순회 하면서 지우려는 요소 추출
        for (int i = 0; i < arrayProperty.arraySize; i++)
        {
            // 요소 순차 추출
            SerializedProperty elementProperty = arrayProperty.GetArrayElementAtIndex(i);
    
            // 찾으면
            if (elementProperty.objectReferenceValue == elementToRemove)
            {
                // 해당 인덱스의 요소 제거
                arrayProperty.RemoveFromObjectArrayAt (i);
                return;
            }
        }
 
        throw new UnityException("Element " + elementToRemove.name + "was not found in property " + arrayProperty.name);
    }
}
 

SerializedPropertyExtensions.cs

배열이나 리스트 같은 Collection 프로퍼티들의 요소 삽입 또는 제거할 때 사용하는 api들이다.

(DeleteArrayElementAtIndex의 샘플 코드, hot example)
https://csharp.hotexamples.com/examples/UnityEditor/SerializedProperty/DeleteArrayElementAtIndex/php-serializedproperty-deletearrayelementatindex-method-examples.html

 

댓글