Editor Scripting¶
Overview¶
All editor scripts must be placed in an Editor/ folder (or reference an Editor-only assembly definition). Code in Editor folders is excluded from player builds.
In Unity 6, UI Toolkit is the recommended system for editor UI. The legacy IMGUI (OnGUI/OnInspectorGUI) approach still works but is considered legacy for new tools.
Custom Inspectors¶
UI Toolkit Approach (Recommended)¶
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
[CustomEditor(typeof(EnemyConfig))]
public class EnemyConfigEditor : Editor
{
public override VisualElement CreateInspectorGUI()
{
var root = new VisualElement();
// Automatically creates fields for all serialized properties
InspectorElement.FillDefaultInspector(root, serializedObject, this);
// Add custom elements
var healthBar = new ProgressBar
{
title = "Health Preview",
value = ((EnemyConfig)target).MaxHealth / 100f
};
root.Add(healthBar);
var testButton = new Button(() => Debug.Log("Test!"))
{
text = "Test Enemy"
};
root.Add(testButton);
return root;
}
}
PropertyField for Automatic Serialization¶
PropertyField handles all serialization types automatically:
public override VisualElement CreateInspectorGUI()
{
var root = new VisualElement();
var healthProp = new PropertyField(serializedObject.FindProperty("_maxHealth"));
var speedProp = new PropertyField(serializedObject.FindProperty("_moveSpeed"));
root.Add(healthProp);
root.Add(speedProp);
return root;
}
Editor Windows¶
public class LevelBuilderWindow : EditorWindow
{
[MenuItem("Tools/Level Builder")]
public static void ShowWindow()
{
GetWindow<LevelBuilderWindow>("Level Builder");
}
public void CreateGUI()
{
var root = rootVisualElement;
root.Add(new Label("Level Builder Tool"));
var prefabField = new ObjectField("Prefab")
{
objectType = typeof(GameObject)
};
root.Add(prefabField);
var placeButton = new Button(() => PlacePrefab(prefabField.value as GameObject))
{
text = "Place in Scene"
};
root.Add(placeButton);
}
private void PlacePrefab(GameObject prefab)
{
if (!prefab) return;
var instance = (GameObject)PrefabUtility.InstantiatePrefab(prefab);
instance.transform.position = SceneView.lastActiveSceneView.pivot;
Selection.activeGameObject = instance;
Undo.RegisterCreatedObjectUndo(instance, "Place Prefab");
}
}
Property Drawers¶
Custom rendering for a specific type or attribute in the inspector:
[CustomPropertyDrawer(typeof(RangedFloat))]
public class RangedFloatDrawer : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
var container = new VisualElement();
container.style.flexDirection = FlexDirection.Row;
var minField = new FloatField("Min") { bindingPath = "min" };
var maxField = new FloatField("Max") { bindingPath = "max" };
container.Add(minField);
container.Add(maxField);
return container;
}
}
Gizmos¶
Visual debugging in the Scene view:
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, _attackRange);
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, _detectionRange);
}
private void OnDrawGizmos()
{
// Always visible, even when not selected
Gizmos.color = Color.green;
Gizmos.DrawLine(transform.position, transform.position + transform.forward * 2f);
}
Scene View Overlays¶
Overlays are the modern replacement for floating IMGUI windows in the Scene view:
using UnityEditor;
using UnityEditor.Overlays;
using UnityEngine.UIElements;
[Overlay(typeof(SceneView), "Quick Placer", true)]
public class QuickPlacerOverlay : Overlay
{
public override VisualElement CreatePanelContent()
{
var root = new VisualElement();
root.Add(new Label("Quick Placer"));
root.Add(new Button(() => PlaceObject()) { text = "Place Object" });
return root;
}
private void PlaceObject() { /* placement logic */ }
}
Menu Items¶
[MenuItem("Tools/Reset Player Position")]
private static void ResetPlayerPosition()
{
var player = FindFirstObjectByType<PlayerController>();
if (player)
{
Undo.RecordObject(player.transform, "Reset Player Position");
player.transform.position = Vector3.zero;
}
}
[MenuItem("Tools/Reset Player Position", true)]
private static bool ValidateResetPlayerPosition()
{
return FindFirstObjectByType<PlayerController>() != null;
}
Editor Callbacks¶
[InitializeOnLoadMethod]
private static void OnEditorLoad()
{
EditorApplication.playModeStateChanged += OnPlayModeChanged;
}
private static void OnPlayModeChanged(PlayModeStateChange state)
{
if (state == PlayModeStateChange.EnteredPlayMode)
{
Debug.Log("Entered Play Mode");
}
}
AssetDatabase¶
Programmatic asset manipulation: