콘텐츠로 이동

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;
}
}
}

7. EditorPrefs — 에디터 설정 영구 저장

섹션 제목: “7. EditorPrefs — 에디터 설정 영구 저장”
public class LevelBuilderPrefs : EditorWindow
{
private const string KeyGridSize = "LevelBuilder.GridSize";
private const string KeyLastPrefab = "LevelBuilder.LastPrefab";
private int _gridSize;
private string _lastPrefabGuid;
void OnEnable()
{
// 에디터 재시작 후에도 유지
_gridSize = EditorPrefs.GetInt(KeyGridSize, 10);
_lastPrefabGuid = EditorPrefs.GetString(KeyLastPrefab, "");
}
void OnGUI()
{
EditorGUI.BeginChangeCheck();
_gridSize = EditorGUILayout.IntSlider("그리드 크기", _gridSize, 1, 50);
if (EditorGUI.EndChangeCheck())
EditorPrefs.SetInt(KeyGridSize, _gridSize);
}
}

8. ScriptedImporter — 커스텀 파일 형식

섹션 제목: “8. ScriptedImporter — 커스텀 파일 형식”
using UnityEditor.AssetImporters;
using System.IO;
[ScriptedImporter(1, "csv")]
public class CsvDataImporter : ScriptedImporter
{
public override void OnImportAsset(AssetImportContext ctx)
{
var text = File.ReadAllText(ctx.assetPath);
var asset = ScriptableObject.CreateInstance<CsvDataAsset>();
asset.rows = text.Split('\n');
ctx.AddObjectToAsset("main", asset);
ctx.SetMainObject(asset);
}
}
// 이제 .csv 파일을 Project 창에서 드래그하면 자동 임포트됨

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