컴포넌트 GUI 커스터 마이징
유니티에서 인스팩터창에 있는 컴포넌트에 GUI를 커스터 마이징 할 수 있습니다.

만드는 방법은 커스터 마이징 할 스크립트 파일을 만들고 MonoBehaviour를 상속 받는 대신 Editor를 상속 시키고, 어트리뷰트로 [CustomEditor(커스텀할 클래스)] 작성해주면 됩니다.
[CustomEditor(typeof(CustomScript))]
public class CustromEditor : Editor
{
...
}
그리고 커스터마이징할 클래스 타입을 지정한 뒤, 그 클래스에서 수정할 필드들을 SerializedProperty로 받을 변수를 선언해줍니다.
[CustomEditor(typeof(CustomScript))]
public class CustromEditor : Editor
{
CustomScript targetRef;
SerializedProperty targetObjectProp;
SerializedProperty nameProp;
SerializedProperty hpProp;
}
다음으로 OnEnable() 메서드에서는 게임 오브젝트가 활성화될 때 실행됩니다.
이때 serializedObject.FindProperty()를 사용하여 씬에 있는 CustomScript 컴포넌트의 필드(otherObject, myName, myHP)를 찾아 각각의 SerializedProperty 변수에 할당합니다.
마지막으로 target을 CustomScript 타입으로 캐스팅해 targetRef 변수에 저장합니다.
private void OnEnable()
{
targetObjectProp = serializedObject.FindProperty($"{nameof(CustomScript.otherObject)}");
nameProp = serializedObject.FindProperty($"{nameof(CustomScript.myName)}");
hpProp = serializedObject.FindProperty($"{nameof(CustomScript.myHP)}");
targetRef = (CustomScript)base.target;
}
마지막으로 OnInspectorGUI() 메서드에서, 이전에 OnGUI()에서 작성했던 것처럼 인스펙터 창에 표시될 GUI를 재정의합니다.
먼저 serializedObject.Update();를 호출하여 변경된 값을 동기화해주고, 인스펙터 내에서 값이 수정된 후에는 serializedObject.ApplyModifiedProperties();를 호출해 변경 내용을 실제 객체에 반영합니다.
이렇게 하면 다른 곳에서 해당 값을 사용할 때도 데이터가 일치하게 유지됩니다.
public override void OnInspectorGUI()
{
//base.OnInspectorGUI();
serializedObject.Update();
if (hpProp.intValue < 500)
{
GUI.color = Color.red;
}
else
{
GUI.color = Color.green;
}
hpProp.intValue = EditorGUILayout.IntSlider("HP 값", hpProp.intValue, 0, 10000);
EditorGUILayout.BeginHorizontal();
{
GUI.color = Color.blue;
EditorGUILayout.PrefixLabel("이름");
GUI.color = Color.white;
nameProp.stringValue = EditorGUILayout.TextArea(nameProp.stringValue);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.PropertyField(targetObjectProp);
serializedObject.ApplyModifiedProperties();
}
여기서 중요한 용어
- Editor
- CustomEditor
- SerializedProperty
- SerializedObject
EditorWindow에 응용
이번에는 위에서 커스터마이징한 컴포넌트의 GUI를 EditorWindow에 연결해보겠습니다.
먼저, SerializedObject를 키로 하고 SerializedProperty 리스트를 값으로 가지는 Dictionary를 선언합니다.
Dictionary<SerializedObject, List<SerializedProperty>> Targets = new Dictionary<SerializedObject, List<SerializedProperty>>();
그다음 OnGUI() 메서드에서 CustomScript 컴포넌트를 가진 모든 오브젝트를 찾아, 각 오브젝트의 필드(otherObject, myName, myHP)를 SerializedProperty로 가져와 Targets 딕셔너리에 추가합니다.
var allCustom = FindObjectsOfType<CustomScript>();
if (allCustom != null)
{
for (int i = 0; i < allCustom.Length; i++)
{
var so = new SerializedObject(allCustom[i]);
var props = new List<SerializedProperty>()
{
so.FindProperty(nameof(CustomScript.otherObject)),
so.FindProperty(nameof(CustomScript.myName)),
so.FindProperty(nameof(CustomScript.myHP)),
};
Targets.Add(so, props);
}
}
그리고 다음과 같이 foreach문을 사용해 EditorWindow에 그려줍니다. 여기서 중요한 점은 각 키에 들어 있는 필드가 그려지고 나면 마지막에 반드시 ApplyModifiedProperties(); 로 동기화 해주어야 합니다.
foreach (var pair in Targets)
{
EditorGUI.BeginChangeCheck();
{
EditorGUILayout.LabelField(pair.Key.targetObject.name, EditorStyles.boldLabel);
EditorGUI.indentLevel++;
{
foreach (var prop in pair.Value)
{
EditorGUILayout.PropertyField(prop);
}
}
EditorGUI.indentLevel--;
EditorGUILayout.LabelField("",GUI.skin.horizontalSlider);
}
if (EditorGUI.EndChangeCheck())
{
pair.Key.ApplyModifiedProperties();
}
}
EditorWindow에서 수정한 내용과 Inspector에서 수정한 내용이 항상 일치하도록 만들어야 합니다. 이를 위해 Update() 메서드에서, EditorWindow에 포커스가 없더라도 Targets에 등록된 SerializedObject를 갱신하여 EditorWindow와 Inspector의 값이 동일하게 유지되도록 합니다.
private void Update()
{
if (isFocused == false)
{
foreach (var item in Targets)
{
item.Key.Update();
}
}
Repaint();
}
private void OnFocus()
{
isFocused = true;
foreach (var item in Targets)
{
item.Key.Update();
}
}
프로퍼티 GUI 재정의하기
위에서는 클래스 안에 필드에 대한 GUI를 수정했습니다. 이번에는 커스텀 클래스 안에 있는 필드에 GUI를 수정하는 방법에 대해 알아보겠습니다.

먼저, 커스텀 클래스의 필드를 인스펙터에서 접근할 수 있도록 [Serializable]로 선언해줍니다.
[Serializable]
public class PlayerData
{
public int hp;
public string name;
public PlayerClass thisClass;
}
그리고 인스펙터 창에서 PlayerData의 표시 방식을 커스터마이징하기 위해, PropertyDrawer를 상속받는 클래스를 새로 만들고,
[CustomPropertyDrawer(typeof(PlayerData))] 어트리뷰트를 사용해 해당 클래스를 PlayerData에 연결합니다.
[CustomPropertyDrawer(typeof(PlayerData))]
public class PlayerDataDrawer : PropertyDrawer
{
}
PropertyDrawer는 Unity가 “필드 단위로 그릴 때” 좌표(Rect) 와 프로퍼티 정보(SerializedProperty) 를 함께 전달하기 때문에
일반적인 OnGUI() 대신 다음과 같이 정해진 시그니처(Rect, SerializedProperty, GUIContent)를 반드시 사용해야 합니다.
그리고 ProeperttyDrawer: OnGUI()에서는 GUILayout/EditorGUILayout 류의 자동 레이아웃 클래스 지원 안합니다.
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
}
SceneView에 응용
씬뷰에서 커스텀 에디터를 구현 할 수 있습니다. 하는 방법은 씬 뷰에 다가 드롱잉을 할 수 있는 콜백 함수를 연결 시켜주면 됩니다.
// OnSceneGUI는 로컬 함수
SceneView.duringSceneGui += OnSceneGUI;
그리고 Handles 클래스를 사용하면 씬(Scene) 뷰에서 라인, 텍스트, 버튼 등을 GUI처럼 원하는 형태로 그릴 수 있습니다.
private void OnSceneGUI(SceneView obj)
{
// Handles는 씬 뷰에 어떤 것을 드로잉 하기 위해서 사용하는 클래스
Handles.Label(targetRef.transform.position, $"I am : {targetRef.gameObject.name}");
var otherObjs = FindObjectsOfType<CustomScript>();
for (int i = 0; i < otherObjs.Length; i++)
{
if (this.targetRef != otherObjs[i])
{
var pos = otherObjs[i].transform.position;
Handles.DrawLine(this.targetRef.transform.position, pos);
Handles.color = Color.red;
Handles.DrawWireCube(pos, Vector3.one);
Handles.color = Color.white;
}
}
Handles.DrawWireCube(targetRef.transform.position, new Vector3(2,3,2));
Handles.BeginGUI();
{
if (GUILayout.Button("Move Right!"))
{
targetRef.transform.position += Vector3.right;
}
if (GUILayout.Button("Move Left!"))
{
targetRef.transform.position += Vector3.left;
}
}
Handles.EndGUI();
}
에셋 파일 관리 및 조작하는 방법
- AssetDatabase 클래스는 Unity 프로젝트 내 에셋 파일을 관리하고 조작하는 데 사용됩니다.
주요 기능은 다음과 같습니다.- AssetDatabase.FindAssets("검색어")
→ 현재 프로젝트에서 검색어와 일치하는 모든 에셋의 GUID를 반환합니다.- 검색어
- 타입기반 검색 ex) "t:Texture", "t:Material", "t:ScriptableObject"
- 특정 문자열 ex) "Player"
- 타입과 이름 조합 ex)"Player t:Texture"
- 레이블기반 검색 ex) "l:UI"
- 검색어
- AssetDatabase.GUIDToAssetPath("에셋GUID")
→ 해당 GUID에 연결된 에셋의 경로(Path)를 반환합니다. - AssetDatabase.CreateAsset(Object asset, string path)
→ 새로운 에셋을 지정한 경로에 생성합니다.
- AssetDatabase.FindAssets("검색어")
유저의 오브젝트 선택 직접 제어하기
Selection 클래스는 에디터에서 오브젝트를 선택하거나 현재 선택 상태를 제어할 수 있는 기능을 제공합니다.
예를 들어, EditorWindow에서 다음과 같은 작업이 가능합니다.
- 프로젝트나 씬(Scene)에 있는 모든 오브젝트를 선택
- 특정 타입의 오브젝트만 선택
- 현재 선택된 오브젝트를 기반으로 핑(Ping) 표시
if (GUILayout.Button("모든 Scene Obejct 선택하기"))
{
var targets = FindObjectsOfType<GameObject>(true);
if (targets != null)
{
Selection.objects = targets;
}
}
if (GUILayout.Button("모든 Text 선택하기"))
{
var targets = FindObjectsOfType<UnityEngine.UI.Text>();
if (targets != null)
{
GameObject[] gos = new GameObject[targets.Length];
for (int i = 0; i< targets.Length; i++)
{
gos[i] = targets[i].gameObject;
}
Selection.objects = gos;
}
}
if (GUILayout.Button("선택한 오브젝트 핑 찍기"))
{
if (Selection.objects != null && Selection.objects.Length == 1)
{
EditorGUIUtility.PingObject(Selection.objects[0]);
}
}
타 Editor 훔쳐오기
EditorWindow에서 선택한 오브젝트의 컴포넌트를 가져와 표시할 수 있습니다. 방법은 다음과 같습니다. 먼저 Selection 클래스를 사용하여 현재 선택된 오브젝트를 가져오고, 그 오브젝트를 기반으로 미리 선언한 Editor 변수에 Editor.CreateEditor()를 이용해 Editor 인스턴스를 생성하여 할당합니다.
Editor duplicatedEditor;
if (Selection.objects != null && Selection.objects.Length == 1)
{
var target = Selection.objects[0];
duplicatedEditor = Editor.CreateEditor(target);
}
그 다음에 선택한 오브젝트에 모든 컴포넌트를 가져와서 미리 선언한 Editor[] 배열에 Editor.CreateEditor()를 이용해 Editor 인스턴스를 생성하여 할당합니다.
var allComps = go.GetComponents(typeof(Component));
if (allComps != null)
{
dupulicatedDetailEditor = new Editor[allComps.Length];
for (int i = 0; i < allComps.Length; i++)
{
detailFoldOut.Add(false);
dupulicatedDetailEditor[i] = Editor.CreateEditor(allComps[i]);
}
}
마지막으로 duplicatedEditor != null이 아니면 다음과 같이 EditorWindow에 해당 오브젝트에 GUI를 그려줍니다.
if (duplicatedEditor != null)
{
duplicatedEditor.DrawHeader();
duplicatedEditor.OnInspectorGUI();
if (dupulicatedDetailEditor != null)
{
for (int i = 0; i < dupulicatedDetailEditor.Length; i++)
{
detailFoldOut[i] = EditorGUILayout.Foldout(detailFoldOut[i], $"{dupulicatedDetailEditor[i].GetType()}");
if (detailFoldOut[i])
{
dupulicatedDetailEditor[i].OnInspectorGUI();
}
}
}
}
에디터 전용 비휘발성 값 관리하기
- EditorWindow에서는 값을 저장하고 불러올 수 있습니다.
이때 EditorPrefs를 사용하면 PlayerPrefs와 유사하게 키-값(Key-Value) 형식으로 데이터를 관리할 수 있습니다.- 저장: EditorPrefs.SetXXX(key, value)
- 불러오기: EditorPrefs.GetXXX(key)
- 삭제: EditorPrefs.DeleteKey(key)
'유니티 공부' 카테고리의 다른 글
| 유니티에 유용한 기능 에디터 프로그래밍 - 기본편 (1) | 2025.10.13 |
|---|---|
| UIToolkit 튜토리얼 (0) | 2025.07.04 |
| UI Toolkit (0) | 2025.06.29 |
| Object Pooling (2) | 2025.06.09 |
| 유니티 게임 WebGL 빌드 하기 (0) | 2025.06.06 |