오늘은 강의를 듣고 배운 유니티에 유용한 기능인 에디터 프로그래밍에 대해 공부한 내용을 정리할려고 합니다.
가장 기본적인 요소 EditorWindow, MenuItem, OnGUI
기본적으로 에디터 프로그래밍에 가장 기본적인 틀은 아래와 같습니다.
public class MyEditorWindow : EditorWindow
{
private void OnGUI()
{
//여기에 그릴 요소들 들어옴
}
[MenuItem("MyTool/Open MyTool %g")]
static void Open()
{
var window = GetWindow<MyEditorWindow>();
window.title = "MyTool";
}
}
- EditorWindow: 유니티 에디터 내에서 새로운 창 형태의 도구를 만들 때 사용하는 클래스입니다. 일반 스크립트에서 MonoBehaviour를 상속 받는 대신, 에디터 확장을 위해 EditorWindow를 상속받아 사용합니다.
- MenuItem: 에디터 상단 메뉴에 커스텀 윈도우를 열 수 있는 메뉴 항목을 추가할 때 사용하는 어트리뷰트입니다. [MenuItem("메뉴이름/항목이름")] 형태로 지정하며, 위와 같이 작성하면 "MyTool > Open MyTool" 메뉴가 생성됩니다. %g는 Ctrl + g 단축키로 해당 항목을 실행 할 수 있습니다.
- OnGUI: 에디터 윈도우가 그렬질 때 호출되는 함수로, 이 안에서 GUI 요소(버튼, 텍스트, 슬라이더 등)를 직접 배치하고 그릴 수 있습니다. 즉, 에디터 창에 표시되는 모든 UI 구성은 OnGUI() 함수 내부에서 구현됩니다.
GUI, EditorGUI, GUILayout, EditorGUILayout
유니티 에디터 확장에 자주 사용되는 GUI, GUILayout, EditorGUI, EditorGUILayout의 차이를 표로 정리해보았습니다.
| 구분 | GUI | GUILayout | EditorGUI | EditorGUILayout |
| 사용 환경 | 런타임 & 에디터 | 에디터 전용 | ||
| 자동 레이아웃 지원 | x | o | x | o |
자주 사용되는 GUI 요소들
유니티 에디터 확장에서 자주 사용하는 EditorGUILayout의 다양한 UI 요소들을 예시로 정리해보았습니다.
private void OnGUI()
{
//int 필드
intValue = EditorGUILayout.IntField("Int 값", intValue);
//float 필드
floatValue = EditorGUILayout.FloatField("Float 값", floatValue);
//컬러 필드
colorValue = EditorGUILayout.ColorField("Color 값", colorValue);
//그래디언트 필드
gradientValue = EditorGUILayout.GradientField("Gradient 값", gradientValue);
//벡터 필드
vector3Value = EditorGUILayout.Vector3Field("Vector3 값", vector3Value);
//백터 Int 필드
vector3IntValue = EditorGUILayout.Vector3IntField("Vector3Int 값", vector3IntValue);
//Rect 필드
rectValue = EditorGUILayout.RectField("Rect 값", rectValue);
//오브젝트 필드(ex. 프리팹, 이미지)
objectValue = EditorGUILayout.ObjectField("Object 값", objectValue, typeof(UnityEngine.Object), false);
//패스워드 필드
passwordValue = EditorGUILayout.PasswordField("Password 값", passwordValue);
//태그 필드(인스펙터창에 태크와 동일함)
tagValue = EditorGUILayout.TagField("tag값", tagValue);
EditorGUILayout.Space(15);
//Enum 필드
enumValue = (UnityEngine.ParticleSystemCollisionType)EditorGUILayout.EnumFlagsField("Enum 값", enumValue);
//슬라이더
floatValue = EditorGUILayout.Slider("Slider 값", floatValue, 0, 100);
//빨강 느낌표 박스(Debug.Log에 찍히는게 이걸 사용하는 듯)
EditorGUILayout.HelpBox("Help Box!", MessageType.Error);
//토글
toggleValue = EditorGUILayout.Toggle("Toggle 값",toggleValue);
//라벨
EditorGUILayout.LabelField("",GUI.skin.horizontalSlider);
//ToolBar
selectionValue = GUILayout.Toolbar(selectionValue, stringArr);
GUILayout.Space(15);
//Selection Grid
selectionValue = GUILayout.SelectionGrid(selectionValue, stringArr,2);
//Box
GUILayout.Box(EditorGUIUtility.IconContent("Animation.Record"));
}
Layout 시스템에 대한 이해
유니티 에디터 프로그래밍에서 Layout 계열 메서드(GUILayout, EditorGUILayout 등) 는 보통 Begin과 End로 짝을 이루어 사용됩니다.
예를 들어, 아래 코드와 같이 BeginVertical()을 호출하면 그 안에 작성된 UI 요소들이 수직으로 차례대로 정렬되어 표시됩니다. 그리고 EndVertical()로 끝을 알리면서 수직으로 정렬되는게 종료 됩니다.
private void OnGUI()
{
EditorGUILayout.BeginVertical(); // 시작해서
{
for (int i = 0; i < 3; i++)
{
EditorGUILayout.LabelField("VerticalLabel!!");
}
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space(5);
EditorGUILayout.BeginVertical();
{
for (int i = 0; i < 3; i++)
{
EditorGUILayout.LabelField("Label02!!!!");
}
}
EditorGUILayout.EndVertical(); // 끝
}
그래서 아래와 같이 수직 정렬을 먼저 시작하고 그 안에 다시 수평 정렬을 시작해서 수평 정렬이 된 목록들이 수직으로 정렬 됩니다.
private void OnGUI()
{
for(int i = 0; i <5;i++)
{
EditorGUILayout.BeginVertical(); // 수직 정렬
{
EditorGUILayout.LabelField($"Title:{i}");
var backUp01 = EditorGUIUtility.fieldWidth;
var backUp02 = EditorGUIUtility.labelWidth;
EditorGUIUtility.fieldWidth = 50;
EditorGUIUtility.labelWidth = 50;
EditorGUILayout.BeginHorizontal(); // 수평 정렬
{
for (int j = 0; j < 5; j++)
{
EditorGUILayout.TextField(j.ToString(), "put data here");
bool isLastData = j == 5;
if (!isLastData)
{
EditorGUILayout.Space(6);
}
}
}
EditorGUILayout.EndHorizontal(); // 수평정렬 끝
EditorGUIUtility.fieldWidth = backUp01;
EditorGUIUtility.labelWidth = backUp02;
}
EditorGUILayout.EndVertical(); //수직 정렬 끝
EditorGUILayout.Space(10);
if(i < 4)
{
EditorGUILayout.LabelField("", GUI.skin.horizontalScrollbar);
}
}
}
위와 같이 BeginVertical, BeginHorizontal로 그리지 않고 VerticalScope(),HorizontalScope()를 사용해 그릴 수도 있습니다.
private void OnGUI()
{
using (var scope = new EditorGUILayout.HorizontalScope())
{
// 위에서 정의한 scope의 크기로 버튼의 크기를 할당
if (GUI.Button(scope.rect,GUIContent.none))
{
Debug.Log("clicked");
}
// 버튼 안에서 수평으로 라벨과 박스를 정렬함
for (int i = 0; i < 5; i++)
{
GUILayout.Label("Label!!");
GUILayout.Box(EditorGUIUtility.FindTexture("BuildSettings.Editor"));
}
}
}
GUIContent, GUIStyle
- GUIContent: 요소에 표시될 텍스트, 이밎, 툴팁 등 무엇을 그릴지를 정의 합니다. (=> HTML)
- GUIStyle: 글꼴, 색상, 배경 등 어떻게 그릴지 모양을 정의 합니다. (=> CSS)
유니티에서 자주 사용되는 스타일들은 대부분 EditorStyles 와 GUI.skin 안에 포함되어 있습니다.
하지만 이 스타일들은 유니티 에디터 전역에서 공유되기 때문에, 직접 수정하면 전체 에디터 스타일이 함께 변경됩니다.
따라서, 아래와 같이 복사(백업) 해서 사용하는 것이 안전합니다.
❌ var targetStyle = EditorStyles.label;
// 이렇게 하면 유니티 에디터 내의 모든 라벨 스타일과 공유되어, 수정 시 전체에 영향을 줍니다.
⭕ var targetStyle = new GUIStyle(EditorStyles.label);
// 새로 복사한 라벨 스타일을 사용하므로, 개별적으로 커스터마이징할 수 있습니다.
OnGUI의 작동 원리
에디터 윈도우에서 UI 요소를 그릴 때는 OnGUI() 내부에서 바로 화면에 그리는 것이 아니라, 먼저 Layout 이벤트를 통해 “어떤 요소들이 그려질지”와 “각 요소의 배치 정보”를 계산한 뒤, 이후 Repaint 이벤트에서 실제로 화면에 UI를 렌더링합니다.
즉, OnGUI()는 한 번만 호출되는 함수가 아니라, 여러 이벤트(Layout, Repaint, MouseDown, KeyDown 등) 에 대해 반복적으로 호출되며, 그 중 Layout → Repaint 순서로 UI가 완성됩니다.
그래서 Debug.log로 확인해보면 아래와 같이 Layout 이벤트를 실행하고 다시 한번 repaint 이벤트를 실행 하는 것을 확인 할 수 있습니다.

배운 내용 정리
오늘은 다음과 같은 내용을 학습했습니다.
- 에디터 프로그래밍의 기본 구성 요소
- 자주 사용되는 GUI 요소
- Layout 시스템의 동작 순서
- 에디터 윈도우에서 UI가 그려지는 방식
이번 학습을 통해 에디터 프로그래밍의 기초적인 개념과 사용법을 익혔습니다. 다음 TIL에서는 에디터 윈도우를 응용하는 방법에 대해 다뤄보겠습니다.
'유니티 공부' 카테고리의 다른 글
| 유니티에 유용한 기능 에디터 프로그래밍 - 응용편 (1) | 2025.10.27 |
|---|---|
| UIToolkit 튜토리얼 (0) | 2025.07.04 |
| UI Toolkit (0) | 2025.06.29 |
| Object Pooling (2) | 2025.06.09 |
| 유니티 게임 WebGL 빌드 하기 (0) | 2025.06.06 |