콘텐츠로 이동

Unity Editor 스크립팅 — 커스텀 툴 제작 가이드

Unity Editor 스크립팅은 UnityEditor 네임스페이스를 활용해 에디터 자체를 확장하는 기술입니다. 커스텀 Inspector, 툴 창, 에셋 파이프라인 훅을 만들어 팀 워크플로를 자동화할 수 있습니다.

Editor 스크립트는 반드시 Editor/ 폴더 안에 위치해야 빌드에 포함되지 않습니다.


using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(EnemyConfig))]
public class EnemyConfigEditor : Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
var config = (EnemyConfig)target;
EditorGUILayout.Space();
EditorGUILayout.LabelField("미리보기", EditorStyles.boldLabel);
GUI.color = Color.red;
if (GUILayout.Button("스탯 초기화"))
{
config.ResetStats();
EditorUtility.SetDirty(config);
}
GUI.color = Color.white;
}
}

[CustomEditor(typeof(WeaponData))]
public class WeaponDataEditor : Editor
{
SerializedProperty _damage;
SerializedProperty _range;
SerializedProperty _type;
void OnEnable()
{
_damage = serializedObject.FindProperty("damage");
_range = serializedObject.FindProperty("range");
_type = serializedObject.FindProperty("weaponType");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(_type);
var type = (WeaponType)_type.enumValueIndex;
if (type == WeaponType.Ranged)
EditorGUILayout.PropertyField(_range);
EditorGUILayout.PropertyField(_damage);
serializedObject.ApplyModifiedProperties();
}
}

using UnityEditor;
using UnityEngine;
public class LevelBuilderWindow : EditorWindow
{
[MenuItem("Tools/Level Builder")]
public static void Open() => GetWindow<LevelBuilderWindow>("Level Builder");
private int _gridSize = 10;
private GameObject _prefab;
void OnGUI()
{
GUILayout.Label("레벨 빌더 설정", EditorStyles.boldLabel);
_gridSize = EditorGUILayout.IntSlider("그리드 크기", _gridSize, 1, 50);
_prefab = (GameObject)EditorGUILayout.ObjectField(
"타일 프리팹", _prefab, typeof(GameObject), false);
EditorGUI.BeginDisabledGroup(_prefab == null);
if (GUILayout.Button("그리드 생성"))
GenerateGrid();
EditorGUI.EndDisabledGroup();
}
void GenerateGrid()
{
for (int x = 0; x < _gridSize; x++)
for (int z = 0; z < _gridSize; z++)
{
var obj = (GameObject)PrefabUtility.InstantiatePrefab(_prefab);
obj.transform.position = new Vector3(x, 0, z);
Undo.RegisterCreatedObjectUndo(obj, "Generate Grid");
}
}
}

public static class MenuItems
{
// 메뉴 추가
[MenuItem("Tools/씬 정리")]
static void CleanupScene() { /* ... */ }
// 우클릭 컨텍스트 메뉴
[MenuItem("GameObject/Custom/빈 부모 생성", false, 0)]
static void CreateEmptyParent()
{
var selected = Selection.activeGameObject;
var parent = new GameObject("Group");
parent.transform.SetParent(selected?.transform.parent);
parent.transform.position = selected?.transform.position ?? Vector3.zero;
selected?.transform.SetParent(parent.transform);
Undo.RegisterCreatedObjectUndo(parent, "Create Parent");
}
// 유효성 검사 (회색 처리)
[MenuItem("Tools/씬 정리", true)]
static bool CleanupSceneValidate() => !EditorApplication.isPlaying;
}

[ExecuteAlways]
public class PatrolRoute : MonoBehaviour
{
public Transform[] waypoints;
void OnDrawGizmos()
{
if (waypoints == null || waypoints.Length < 2) return;
Gizmos.color = Color.cyan;
for (int i = 0; i < waypoints.Length; i++)
{
Gizmos.DrawSphere(waypoints[i].position, 0.3f);
Gizmos.DrawLine(
waypoints[i].position,
waypoints[(i + 1) % waypoints.Length].position);
}
}
}

6. AssetPostprocessor — 에셋 파이프라인 훅

섹션 제목: “6. AssetPostprocessor — 에셋 파이프라인 훅”
public class TexturePostprocessor : AssetPostprocessor
{
void OnPreprocessTexture()
{
var importer = (TextureImporter)assetImporter;
// UI 폴더의 텍스처는 자동으로 Sprite 타입으로 설정
if (assetPath.Contains("/UI/"))
{
importer.textureType = TextureImporterType.Sprite;
importer.maxTextureSize = 512;
}
}
}

Editor 스크립팅은 팀의 반복 작업을 자동화하는 핵심 도구입니다. 커스텀 Inspector로 데이터 입력 오류를 줄이고, EditorWindow로 레벨 디자인 툴을 만들며, AssetPostprocessor로 에셋 규칙을 강제하면 프로젝트 품질과 생산성이 동시에 향상됩니다.