본문 바로가기

공부/유니티

[Unity] Custom Editor - ReorderableList

ReorderableList는 Inspector창에서 리스트를 시각적으로 편리하게 관리할 수 있도록 도와주는 클래스다.


ReorderableList는 UnityEditorInternal 네임스페이스에 정의되어 있다.

또한 인스펙터 혹은 윈도우에서 사용이 가능하기 때문에 UnityEditor 네임스페이스도 정의해 주어야한다.

따라서 사용하려면

using UnityEditor;
using UnityEditorInternal;

[CustomEditor(typeof(Tistory))]
public class TistoryEditor : Editor
{
    private void OnEnable()
    {
    
    }
    
    public override void OnInspectorGUI()
    {
    
    }
}

를 작성해주어야 한다.

 

이제 OnEnable 메서드에서 ReorderableList를 생성해주고 콜백을 설정해 주면 된다.


우선 ReorderableList를 생성하는 방법에는 2가지가 있다.

1. SerializedObject와 SerializedProperty를 사용하는 방법

var reorderableList = new ReorderableList(
    serializedObject,
    serializedObject.FindProperty("데이터 명"), 
    순서 변경 여부(bool),
    리스트 타이틀 여부(bool),
    추가(+) 버튼 여부(bool),
    삭제(-) 버튼 여부(bool));

 

용어 설명

더보기

SerializedObject란?

Unity 에디터에서 사용되는 클래스로, 에디터에서 직렬화(Serialize)된 필드와 프로퍼티를 편집할 수 있도록 해준다.

즉, Unity 에디터에서 특정 오브젝트를 직렬화된 상태로 다루기 위해서 UnityEngine.Object를 SerializedObject로 변환해서 사용한다.

이렇게 변환된 SerializedObject는 다시 Asset, .meta 파일로 직렬화 할 수 있다.

 

직관적으로 설명하자면

[CustomEditor(typeof(클래스 명))]

과 같이 있을 때, '클래스 명'을 나타낸다.

 

사용할 때에는 Editor에 정의되어 있는 serializedObject 프로퍼티로 사용할 수 있다.

 

SerializedProperty란?

SerializedObject 내의 특정 속성을 나타내는 클래스다.

리스트, 배열, 클래스, 구조체 등의 다양한 데이터를 포함할 수 있다.

 

시리얼라이즈된 필드 혹은 프로퍼티를 찾으려면 

serializedObject.FindProperty("데이터 명")

 으로 찾을 수 있다.

 

2. 리스트를 사용하는 방법

List<Element> elements = new List<Element>();

var reorderableList = new ReorderableList(
    elements,
    typeof(Element),
    순서 변경 여부(bool),
    리스트 타이틀 여부(bool),
    추가(+) 버튼 여부(bool),
    삭제(-) 버튼 여부(bool));

콜백을 설정해주는 방법에도 2가지 방법이 있다.

 

1. ReorderableList의 초기화 블럭에서 콜백을 초기화 시켜주는 방법

List<Element> elements = new List<Element>();

var reorderableList = new ReorderableList(elements, typeof(Element), true, true, true, true)
{
    // 이 부분이 객체 초기화 블럭
    // 객체 생성 후에 객체의 필드나 프로퍼티의 초기값을 설정해주는 블록
    
    drawHeaderCallback = (rect) =>
    {
        EditorGUI.LabelField(rect, "타이틀 텍스트");
    }
}

 

2. ReorderableList를 변수로 받아서 콜백을 초기화 시켜주는 방법

List<Element> elements = new List<Element>();

var reorderableList = new ReorderableList(elements, typeof(Element), true, true, true, true);

reorderableList.drawHeaderCallback = (rect) =>
{
    EditorGUI.LabelField(rect, "타이틀 텍스트");
}

콜백을 초기화 시켜주는 방법을 알았으니, 콜백의 종류에 대해 알아보자.

 

콜백의 종류

콜백 이름 설명
onChangedCallback 리스트의 내용이 변경될 때 호출되는 콜백
onAddCallback 요소를 추가할 때 호출되는 콜백
onAddDropdownCallback 추가 버튼을 클릭하고 드롭다운 메뉴가 표시될 때 호출되는 콜백
onRemoveCallback 요소를 제거할 때 호출되는 콜백
onSelectCallback 요소를 선택했을 때 호출되는 콜백
onReorderCallback 요소의 순서가 변경될 때 호출되는 콜백
onReorderCallbackWithDetails 요소의 순서가 변경될 때 호출되는 콜백 (자세한 정보 포함)
onMouseDragCallback 마우스 드래그 중일 때 호출되는 콜백
onMouseUpCallback 마우스 버튼을 놓았을 때 호출되는 콜백
drawElementCallback 각 요소를 그리는 콜백
drawElementBackgroundCallback 각 요소의 배경을 그리는 콜백
drawNoneElementCallback 요소가 없을 때 표시되는 콜백
drawHeaderCallback 리스트의 헤더(타이틀)를 그리는 콜백
drawFooterCallback 리스트의 하단 푸터(부가 설명)를 그리는 콜백
elementHeightCallback 각 요소의 높이를 결정하는 콜백
각 요소마다 다른 높이를 설정할 때 사용
onCanAddCallback 요소를 추가할 수 있는지 여부를 결정하는 콜백
onCanRemoveCallback 요소를 제거할 수 있는지 여부를 결정하는 콜백

새로운 요소를 추가하기

ReorderableList에서는 GenericMenu 클래스를 사용하여 요소를 추가해 줄 수 있다.

private Tistory _target;
private ReorderableList reorderableList;

private void OnEnable()
{
    _target = target as Tistory;

    List<Element> elements = new List<Element>();

    reorderableList = new ReorderableList(elements, typeof(Element), true, true, true, true);

    reorderableList.onAddDropdownCallback = (buttonRect, l) =>
    {
        InitMenu();
    };
}

private void InitMenu()
{
    var menu = new GenericMenu();

    menu.AddItem(new GUIContent("상태이상"), false, CreateElementCallback, typeof(AbnormalStatus));

    menu.ShowAsContext();
}

private void CreateElementCallback(object obj)
{
    // 새로운 ScriptableObject 인스턴스 생성 후, Element 타입으로 캐스팅
    var element = ScriptableObject.CreateInstance((Type)obj) as Element;

    if (element != null)
    {
        // 새롭게 만든 인스턴스가 Hierarchy 창에 보이지 않도록 설정
        element.hideFlags = HideFlags.HideInHierarchy;
        // Tistory의 elements 리스트에 추가
        _target.elements.Add(element);
        
        // 해당 Tistory ScriptableObject 에셋의 경로를 가져오기
        var path = AssetDatabase.GetAssetPath(_target);
        // element를 Tistory ScriptableObject 에셋에 추가하기
        AssetDatabase.AddObjectToAsset(element, path);
        // Tistory ScriptableObject가 변경되었음을 Unity 에디터에 알리며, 변경 사항을 저장한다.
        EditorUtility.SetDirty(_target);
    }
}

이제 리스트를 보여주기 위해, 

reorderableList?.DoLayoutList();

를 OnInspectorGUI 메서드 내에 작성해주면 된다.

※ OnInspectorGUI 메서드는 인스펙터 창이 활성화되어 있을 때, 매 프레임마다 호출되어 GUI를 업데이트 한다.

 

또한 Inspector에서 값을 변경하는 등의 GUI 이벤트가 발생하면 지정된 오브젝트가 수정되었음을 알려주기 위해

if (GUI.changed)
{
    EditorUtility.SetDirty(this);
}

를 작성해준다.

이러한 과정을 통해 지정된 오브젝트의 변경 사항이 저장된다.