커스텀 에디터를 제작하면 단순 반복 작업을 일괄처리 할 수도 있고, 필요로 하는 기능을 추가할 수도 있으며,
더 나아가 난잡해진 Inspector 창을 깔끔하게 정리할 수도 있다.
public class Inspector : MonoBehaviour
{
public bool showValue = true;
public int val = 100;
}
예를 들어 위와 같은 스크립트를 작성하면,

원래 이러한 Inspector 창을 생성하지만,

이렇게 만들 수도 있다는 것이다.
대부분 두 가지로 구현하는데,
첫 번째는 위에서 예시로 든 것과 같이 MonoBehavior를 상속한 클래스의 Inspector를 조작하는 것이고.
두 번째는 Lighting, Project Settings, Preference 및 Propiler와 같은 EditorWindow를 생성하는 것이다.
일단 각 방식을 구성해보고, 공통으로 사용되는 GUILayout에 대해 알아보겠다.
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;
}
}
CustomClass에는 다양한 자료형의 변수들을 두었다. 이 클래스의 Inspector를 제작하기 위해서는
/* 에디터에서만 사용한다는 뜻으로, #endif 까지의 구문들은 빌드에 포함되지 않는다 */
#if UNITY_EDITOR
[CustomEditor(typeof(CustomClass))] //CustomClass는 커스텀 Inspector로 표시할 것
public class CustomInspector : Editor
{
/* Inspector를 그리는 함수 */
public override void OnInspectorGUI()
{
}
}
#endif
이러한 형식을 갖춘 클래스가 필요한데, [CustomEditor(typeof("클래스명"))] 으로 커스텀할 클래스를 지정했고,
OnInspectorGUI 함수에 작성된 구문들이 실제 Inspector에 표시되는 GUI가 된다.
※ 해당 클래스를 따로 스크립트를 생성해서 작성하는 것이 일반적이나, 같은 스크립트에 작성해도 된다.
이제 CustomClass의 있는 변수들을 참조할 수 있어야 한다.
유니티에서는 초기화 가능한 변수들을 감싸서 직렬화한 오브젝트를 제공하는데, 그것이 SerializedObject 이다.
현재 에디터에 표시되고 있는 클래스의 SerializedObject를 상속(Editor)받은 클래스에 제공하고 있어
즉시 사용이 가능하다.
※ 초기화 가능한 변수는 접근 지정자 public으로 지정하거나, [SerializeField]로 선언한 것이다.
CustomClass에서는 그냥 public으로만 선언했다.
/* 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"); //구조체
}
바로 serializedObject 프로퍼티가 위에서 언급한 것이고,
FindProperty 함수를 통해 변수의 이름으로 SerializedProperty 클래스를 참조할 수 있다.
이 클래스는 찾은 변수의 값을 지니고 있다.
단, SerializedProperty를 활용하여 변수를 참조할 때에는 각 자료형에 맞는 접근이 필요하다.
※ bool 자료형인데 boolValue 참조가 아닌 floatValue 참조 시 null 에러 발생
/* Inspector를 그리는 함수 */
public override void OnInspectorGUI()
{
SerializedProperty intVar = serializedObject.FindProperty("intVar");
++intVar.intValue; //값 변경 (그저 예시일 뿐입니다)
serializedObject.ApplyModifiedProperties(); //변경된 프로퍼티 값을 적용
}
변경된 프로퍼티 값을 실제 변수를 적용하기 위해서는 ApplyModifiedProperties 함수를 실행해주어야 한다.
※ 참고로 CustomInspector 클래스에서 선언한 변수는 유니티에서 저장해주지 않는다.
EditorWindow

유니티에서는 특정 기능을 담은 이러한 Window들을 열 수 있는데, 우리도 직접 만들어본다.
/* 에디터에서만 사용한다는 뜻으로, #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
아까와 구조는 비슷하나, 메뉴 아이템을 클릭했을 때 실행되는 함수를 설정해주어야 한다.
여기에서는 Init 함수이고, 창을 생성한 후 여는 과정이 포함되어 있다.
OnGUI에 작성된 구문이 실제로 나타나는 GUI이다.

아직 GUILayout이 없기 때문에 허전하지만, 창은 잘 나타나는 것을 볼 수 있다.
/* GUI를 생성하는 함수 */
private void OnGUI()
{
Debug.Log(Selection.activeGameObject);
Debug.Log(Selection.gameObjects.Length);
Debug.Log(Selection.activeObject);
Debug.Log(Selection.objects);
}
EditorWindow에서는 Selection 클래스를 자주 참조하게 될 것이다.
선택된 오브젝트를 반환해주는 유용한 클래스이기 때문이다.
※ activeGameObject(복수:gameObjects)는 Hierarchy에서 선택된 게임 오브젝트고,
activeObject(복수:objects)는 Hierarchy 뿐만 아니라 Asset에 있는 모든 오브젝트다.
/* GUI를 생성하는 함수 */
private void OnGUI()
{
CustomClass customClass = Selection.activeGameObject.GetComponent<CustomClass>(); //CustomClass 클래스 참조
SerializedObject serializedObject = new SerializedObject(customClass); //SerializedObject로 변환
serializedObject.FindProperty("intVar").intValue = 1004; //프로퍼티로 참조
serializedObject.ApplyModifiedProperties(); //프로퍼티 값 변경을 적용
}
이를 활용하여 선택된 오브젝트 클래스의 변수를 참조할 수도 있다.
이전에 설명했던 SerializedObject로 변환하는 방식이다.
※ EditorWindow는 초기화 가능한 클래스와 연관되지 않기 때문에 자체 SerializedObject가 없다.
※ CustomEditorWindow 클래스에서 선언한 변수도 유니티에 저장되지 않는다.
그러나, EditorWindow 지정한 값들은 저장이 필요할 수도 있기 때문에
Xml이나 Json 방식 등으로 파싱하여 OnEnable 및 OnDisable 함수에서 로드 및 저장할 수도 있다.
GUILayout
본격적으로 GUI에 나타내보자.
단, 모든 예제는 상황에 맞게 바꿔야 한다.
① LabelField
EditorGUILayout.LabelField("My Label");
EditorGUILayout.LabelField("My BoldLabel", EditorStyles.boldLabel);

라벨을 나타내기 위한 Field이다.
boldLabel을 조작하여 색상 변경도 가능하다.
② ObjectField
gameObjectVar = (GameObject)EditorGUILayout.ObjectField("My GameObject", gameObjectVar, typeof(GameObject), true);

GameObject 뿐만 아니라 원하는 타입의 컴포넌트를 지정하기 위한 Field이다.
GUI에 표시되기를 원하는 변수를 넣어주고, 타입을 인자로 넣는다.
GUI를 그릴 때마다 값을 반환한다.
③ IntField, FloatField, Vector2Field, Vector3Field, ColorField
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);

값을 지정하기 위한 Field이다.
표시하고자 하는 변수를 넣어준다. GUI를 그릴 때마다 값을 반환한다.
④ TextField, TextArea
stringVar = EditorGUILayout.TextField("My String", stringVar);
stringVar = EditorGUILayout.TextArea(stringVar, EditorStyles.textArea);
stringVar = EditorGUILayout.TextArea(stringVar, EditorStyles.textArea, GUILayout.Height(50f));

Text를 지정하기 위한 Field이다.TextField의 크기를 가변 또는 고정하는 TextArea를 활용할 수도 있다.
⑤ Toggle
boolVar = EditorGUILayout.Toggle("My Bool", boolVar);

Bool 타입 변수를 지정하기 위한 Toggle이다.
GUI를 그릴 때마다 값을 반환한다.
※ ToggleGroup으로 묶어서 사용가능
⑥ Button
if (GUILayout.Button("My Button")) Debug.Log("Click");

버튼이 클릭되는 순간 True를 반환한다.
※ 나머지는 False
⑦ EnumPopup
myEnum = (Number)EditorGUILayout.EnumPopup("My Enum", myEnum);

Enum 타입을 지정하는 Popup이다.
⑧ IntPopup
string[] displayedOptions = new string[] { "One", "Two", "Three", "Zero" };
int[] optionValues = new int[] { 1, 2, 3, 0 };
intVar = EditorGUILayout.IntPopup("My IntPopup", intVar, displayedOptions, optionValues);

Enum 타입을 만들지 않아도 string 배열로 만들 수 있는 Popup이다.
(optionValues 인자는 아직 필요성을 못 느낀다)
⑨ Toolbar
intVar = GUILayout.Toolbar(intVar, System.Enum.GetNames(typeof(Number)));

본 방식은 Enum을 사용했다. GUI를 그릴 때마다 값을 반환한다.
⑩ Foldout
boolVar = EditorGUILayout.Foldout(boolVar, "My FoldOut", true);

펼침을 나타낼 수 있으며, Label 클릭 시에도 토글될 것인지를 지정할 수 있다.
⑪ 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");

들여 쓰기를 나타낼 수 있다.
⑫ Space
GUILayout.Label("My Label");
EditorGUILayout.Space();
GUILayout.Label("My Label2");
EditorGUILayout.Space(20f);
GUILayout.Label("My Label3");

간격을 조절할 수 있다.
⑬ BeginHorizontal, EndHorizontal
GUILayout.BeginHorizontal();
GUILayout.Button("My Button1");
GUILayout.Label("My Label");
GUILayout.Button("My Button2");
GUILayout.EndHorizontal();

수평으로 나열할 수 있다.
⑭ FlexibleSpace
GUILayout.BeginHorizontal();
GUILayout.Button("My Button1");
GUILayout.Label("My Label");
GUILayout.Button("My Button2");
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();

유연하게 공간을 만들어낸다.
아직 설명하지 못한 종류가 많지만, 나머지는 응용 수준이므로
감을 쉽게 잡을 수 있을 것으로 본다.
조금이나마 도움이 되길 바라며 이만 글을 마친다.
※ 마우스를 올리기만 해도 실행되는 것이 OnInspectorGUI 함수와 OnGUI 함수이다.
그래서 조금만 화면이 복잡해져도 성능이 급격히 떨어진다. 최대한 간단하게 나타내야 한다.
※ 참고로 거의 모든 GUILayout 예제에 Label(My ...)을 넣었는데 안 넣어도 된다.
'[Unity]' 카테고리의 다른 글
[Unity] GPS 기능으로 위도와 경도 불러오기 (0) | 2023.08.20 |
---|---|
[Unity] 안드로이드(Android) 카메라 연동하기 (23) | 2023.08.05 |
[Unity] GameObject의 경로를 반환하는 함수 (0) | 2023.02.26 |
[Unity] 구글 애드몹 보상형 광고 구현 (0) | 2023.01.17 |
[Unity] 구글 플레이 인앱 결제(IAP) 구현 (0) | 2023.01.14 |
댓글