[Unity] EditorGUI를 활용한 커스텀 에디터 만들기
개념
커스텀 에디터를 제작하면 단순 반복 작업을 일괄 처리 할 수도 있고, 필요로 하는 기능을 추가할 수도 있으며, 더 나아가 난잡해진 Inspector 창을 깔끔하게 정리할 수도 있다
public class Inspector : MonoBehaviour
{
public bool showValue = true;
public int val = 100;
}
예를 들어 위와 같은 스크립트를 작성하면 아래와같이 되지만
이렇게 만들 수도 있다
대부분 두 가지로 구현하는데
첫번째는 MonoBehaviour를 상속한 클래스의 Inspector를 조작하는 것
두번째는 Lighting, Project Settings, Preference 및 Propiler와 같은 EditorWindow를 생성하는 것
Inspector
CustomClass에는 다양한 자료형의 변수들을 두었다, 이 클래스의 Inspector를 제작하기 위해선
public class CustomClass : MonoBehaviour
{
public bool boolVar = true;
public int intVar = 100;
public float floatVar = 0.1f;
public int[] arrayVar;
public CustomStruct structVar;
public struct CustomStruct
{
public string stringVar;
}
}
[CustomEditor(typeof("클래스명"))]으로 커스텀할 클래스를 지정했고, OnInspectorGUI 함수에 작성된 구문들이 실제 Inspector에 표시되는 GUI가 된다.
/* 에디터에서만 사용한다는 뜻으로, #endif 까지의 구문들은 빌드에 포함되지 않는다 */
#if UNITY_EDITOR
[CustomEditor(typeof(CustomClass))] //CustomClass는 커스텀 Inspector로 표시할 것
public class CustomInspector : Editor
{
/* Inspector를 그리는 함수 */
public override void OnInspectorGUI()
{
}
}
#endif
이제 CustomClass의 있는 변수들을 참조할 수 있어야 한다. 유니티에서는 초기화 가능한 변수들을 감싸서 직력화한 오브젝트를 제공하는데, 그것이 SerializedObject이다. 현재 에디터에 표시되고 있는 클래스의 SerializedObject를 상속(Editor)받은 클래스에 제공하고 있어 즉시 사용이 가능하다.
FindProperty 함수를 통해 변수의 이름으로 SerializedProperty 클래스를 참조할 수 있다. 이 클래스는 찾은 변수의 값을 지니고 있다. 단, SerializedProperty를 활용하여 변수를 참조할 때에는 각 자료형에 맞는 접근이 필요하다.(bool 자료형인데 boolValue 참조가 아닌 floatValue 참조 시 null 에러 발생)
/* Inspector를 그리는 함수 */
public override void OnInspectorGUI()
{
SerializedProperty boolVar = serializedObject.FindProperty("boolVar"); //Bool
Debug.Log(boolVar.boolValue);
SerializedProperty intVar = serializedObject.FindProperty("intVar"); //Int
Debug.Log(intVar.intValue);
SerializedProperty floatVar = serializedObject.FindProperty("floatVar"); //Float
Debug.Log(floatVar.floatValue);
SerializedProperty arrayVar = serializedObject.FindProperty("arrayVar"); //배열
for (int i = 0; i < arrayVar.arraySize; ++i)
{
SerializedProperty arrayElementVar = arrayVar.GetArrayElementAtIndex(i);
Debug.Log(arrayElementVar.intValue);
}
SerializedProperty structVar = serializedObject.FindProperty("structVar"); //구조체
}
변경된 프로퍼티 값을 실제 변수를 적용하기 위해서는 ApplyModifiedProperties 함수를 실행해주어야 한다. (참고로 CUstomInspector 클래스에서 선언한 변수는 유니티에서 지정해주지 않는다.)
/* Inspector를 그리는 함수 */
public override void OnInspectorGUI()
{
SerializedProperty intVar = serializedObject.FindProperty("intVar");
++intVar.intValue; //값 변경 (그저 예시일 뿐입니다)
serializedObject.ApplyModifiedProperties(); //변경된 프로퍼티 값을 적용
}
여기에서는 Init 함수이고, 창을 생성한 후 여는 과정이 포함되어 있다. OnGUI에 작성된 구문이 실제로 나타나는 GUI이다.
/* 에디터에서만 사용한다는 뜻으로, #endif 까지의 구문들은 빌드에 포함되지 않는다 */
#if UNITY_EDITOR
using UnityEditor;
public class CustomEditorWindow : EditorWindow
{
[MenuItem("Custom/EditorWindow")] //해당 버튼을 누르면 Init 함수가 실행
private static void Init()
{
CustomEditorWindow editorWindow = (CustomEditorWindow)GetWindow(typeof(CustomEditorWindow)); //Window 생성
editorWindow.Show(); //Window 열기
}
/* GUI를 생성하는 함수 */
private void OnGUI()
{
}
}
#endif
EditorWindow에서는 Selection 클래스를 자주 참조하게 될 것이다. 선택된 오브젝트를 반환해주는 유용한 클래스이기 때문이다.
/* GUI를 생성하는 함수 */
private void OnGUI()
{
Debug.Log(Selection.activeGameObject);
Debug.Log(Selection.gameObjects.Length);
Debug.Log(Selection.activeObject);
Debug.Log(Selection.objects);
}
이를 활용하여 선택된 오브젝트 클래스의 변수를 참조할 수도 있다. 이전에 설명했던 SerializedObject로 변환하는 방식이다.
/* GUI를 생성하는 함수 */
private void OnGUI()
{
Debug.Log(Selection.activeGameObject);
Debug.Log(Selection.gameObjects.Length);
Debug.Log(Selection.activeObject);
Debug.Log(Selection.objects);
}
GUILayout
1. LabelField
boldLabel을 조작하여 색상 변경도 가능하다
EditorGUILayout.LabelField("My Label");
EditorGUILayout.LabelField("My BoldLabel", EditorStyles.boldLabel);
2. ObjectField
GameObject 뿐만 아니라 원하는 타입의 컴포넌트를 지정하기 위한 Field이다. GUI에 표시되기를 원하는 변수를 넣어주고, 타입을 인자로 넣는다. GUI를 그릴 때마다 값을 반환한다.
gameObjectVar = (GameObject)EditorGUILayout.ObjectField("My GameObject", gameObjectVar, typeof(GameObject), true);
3. IntField, FloatField, Vector2Field, Vector3Field, ColorField
값을 지정하기 위한 Field이다. 표시하고자 하는 변수를 넣어준다, GUI를 그릴 때마다 값을 반환한다.
intVar = EditorGUILayout.IntField("My Int", intVar);
floatVar = EditorGUILayout.FloatField("My Float", floatVar);
vector2Var = EditorGUILayout.Vector2Field("My Vector2", vector2Var);
vector3Var = EditorGUILayout.Vector3Field("My Vector3", vector3Var);
colorVar = EditorGUILayout.ColorField("My Color", colorVar);
4. TextsField, TextArea
Text를 지정하기 위한 Field이다. TextField의 크기를 가변 또는 고정하는 TextArea를 활용할 수도 있다.
stringVar = EditorGUILayout.TextField("My String", stringVar);
stringVar = EditorGUILayout.TextArea(stringVar, EditorStyles.textArea);
stringVar = EditorGUILayout.TextArea(stringVar, EditorStyles.textArea, GUILayout.Height(50f));
5. Toggle
Bool 타입 변수를 지정하기 위한 Toggle이다. GUI를 그릴 때마다 값을 반환한다. ToggleGroup로 묶어서 사용가능
boolVar = EditorGUILayout.Toggle("My Bool", boolVar);
6. Button
버튼이 클릭되는 순간 True를 반환한다
if (GUILayout.Button("My Button")) Debug.Log("Click");
7. EnumPopup
Enum 타입을 지정하는 Popup이다
myEnum = (Number)EditorGUILayout.EnumPopup("My Enum", myEnum);
8. IntPopup
Enum 타입을 만들지 않아도 string 배열로 만들 수 있는 Popup이다. (optionValues 인자는 아직 필요성을 못느낀다)
string[] displayedOptions = new string[] { "One", "Two", "Three", "Zero" };
int[] optionValues = new int[] { 1, 2, 3, 0 };
intVar = EditorGUILayout.IntPopup("My IntPopup", intVar, displayedOptions, optionValues);
9. Toolbar
본 방식은 Enum을 사용했다. GUI를 그릴 때마다 값을 반환한다
intVar = GUILayout.Toolbar(intVar, System.Enum.GetNames(typeof(Number)));
10. Foldout
펼침을 나타낼 수 있으며, Label 클릭 시에도 토글될 것인지를 지정할 수 있다
boolVar = EditorGUILayout.Foldout(boolVar, "My FoldOut", true);
11. IndentLevel
들여쓰기를 나타낼 수 있다
EditorGUILayout.LabelField("My Label");
++EditorGUI.indentLevel;
EditorGUILayout.LabelField("My Label2");
++EditorGUI.indentLevel;
EditorGUILayout.LabelField("My Label3");
--EditorGUI.indentLevel;
EditorGUILayout.LabelField("My Label4");
--EditorGUI.indentLevel;
EditorGUILayout.LabelField("My Label5");
12. Space
간격을 조절할 수 있다
GUILayout.Label("My Label");
EditorGUILayout.Space();
GUILayout.Label("My Label2");
EditorGUILayout.Space(20f);
GUILayout.Label("My Label3");
13. BeginHorizontal, EndHorizontal
수평으로 나열할 수 있다
GUILayout.BeginHorizontal();
GUILayout.Button("My Button1");
GUILayout.Label("My Label");
GUILayout.Button("My Button2");
GUILayout.EndHorizontal();
14.FlexibleSpace
유연하게 공간을 만들어낸다
GUILayout.BeginHorizontal();
GUILayout.Button("My Button1");
GUILayout.Label("My Label");
GUILayout.Button("My Button2");
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();