unity-editor-imgui-design

Unity Editor IMGUI Design Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "unity-editor-imgui-design" with this command: npx skills add akiojin/unity-mcp-server/akiojin-unity-mcp-server-unity-editor-imgui-design

Unity Editor IMGUI Design Skill

WARNING: IMGUI is for Unity Editor extensions ONLY. For game UI, use unity-game-ugui-design or unity-game-ui-toolkit-design .

IMGUI vs Game UI

Aspect IMGUI (Editor) uGUI / UI Toolkit (Game)

Purpose Editor tools, inspectors In-game UI, HUD

Build inclusion Editor only Included in builds

Rendering Immediate mode Retained mode

Performance OK for editor Optimized for runtime

Styling Limited (GUISkin) Rich (USS, materials)

Quick Start

Create EditorWindow

// Create editor window script mcp__unity-mcp-server__create_class({ path: "Assets/Editor/MyToolWindow.cs", className: "MyToolWindow", namespace: "MyProject.Editor", baseType: "EditorWindow", usings: "UnityEditor" })

// Add window content mcp__unity-mcp-server__edit_structured({ path: "Assets/Editor/MyToolWindow.cs", symbolName: "MyToolWindow", operation: "insert_after", newText: ` [MenuItem("Tools/My Tool Window")] public static void ShowWindow() { GetWindow<MyToolWindow>("My Tool"); }

private void OnGUI()
{
    GUILayout.Label("My Tool", EditorStyles.boldLabel);

    if (GUILayout.Button("Do Something"))
    {
        Debug.Log("Button clicked!");
    }
}

` })

Create Custom Inspector

// Create custom inspector script mcp__unity-mcp-server__create_class({ path: "Assets/Editor/MyComponentEditor.cs", className: "MyComponentEditor", namespace: "MyProject.Editor", baseType: "Editor", usings: "UnityEditor" })

// Add CustomEditor attribute and OnInspectorGUI mcp__unity-mcp-server__edit_structured({ path: "Assets/Editor/MyComponentEditor.cs", symbolName: "MyComponentEditor", operation: "insert_before", newText: "[CustomEditor(typeof(MyComponent))]\n" })

mcp__unity-mcp-server__edit_structured({ path: "Assets/Editor/MyComponentEditor.cs", symbolName: "MyComponentEditor", operation: "insert_after", newText: ` public override void OnInspectorGUI() { serializedObject.Update();

    EditorGUILayout.PropertyField(serializedObject.FindProperty("myField"));

    if (GUILayout.Button("Custom Button"))
    {
        ((MyComponent)target).DoSomething();
    }

    serializedObject.ApplyModifiedProperties();
}

` })

Core Concepts

IMGUI Lifecycle

OnGUI() called every frame (or on repaint/input) ├── Layout Event: Calculate sizes ├── Repaint Event: Draw controls └── Input Events: Handle mouse/keyboard

Event Types

void OnGUI() { Event e = Event.current;

switch (e.type)
{
    case EventType.Layout:
        // Calculate layout
        break;
    case EventType.Repaint:
        // Draw visuals
        break;
    case EventType.MouseDown:
        // Handle click
        break;
    case EventType.KeyDown:
        // Handle keyboard
        break;
}

}

GUILayout vs EditorGUILayout

GUILayout EditorGUILayout

Works everywhere Editor only

Basic controls Rich editor controls

GUILayout.Button

EditorGUILayout.PropertyField

GUILayout.TextField

EditorGUILayout.ObjectField

GUILayout.Label

EditorGUILayout.HelpBox

EditorWindow Patterns

Basic Window Structure

using UnityEngine; using UnityEditor;

public class MyToolWindow : EditorWindow { // Serialized state (survives recompile) [SerializeField] private string searchText = ""; [SerializeField] private Vector2 scrollPosition;

// Non-serialized state
private GUIStyle headerStyle;

[MenuItem("Tools/My Tool %#t")] // Ctrl+Shift+T
public static void ShowWindow()
{
    var window = GetWindow&#x3C;MyToolWindow>();
    window.titleContent = new GUIContent("My Tool", EditorGUIUtility.IconContent("d_Settings").image);
    window.minSize = new Vector2(300, 200);
    window.Show();
}

private void OnEnable()
{
    // Initialize when window opens
}

private void OnDisable()
{
    // Cleanup when window closes
}

private void OnGUI()
{
    InitStyles();
    DrawToolbar();
    DrawContent();
}

private void InitStyles()
{
    headerStyle ??= new GUIStyle(EditorStyles.boldLabel)
    {
        fontSize = 14,
        alignment = TextAnchor.MiddleCenter
    };
}

private void DrawToolbar()
{
    using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar))
    {
        if (GUILayout.Button("Refresh", EditorStyles.toolbarButton, GUILayout.Width(60)))
        {
            Refresh();
        }

        GUILayout.FlexibleSpace();

        searchText = EditorGUILayout.TextField(searchText, EditorStyles.toolbarSearchField, GUILayout.Width(200));
    }
}

private void DrawContent()
{
    scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
    {
        GUILayout.Label("Content Here", headerStyle);
    }
    EditorGUILayout.EndScrollView();
}

private void Refresh()
{
    Repaint();
}

}

MCP Implementation

// Create the window class mcp__unity-mcp-server__create_class({ path: "Assets/Editor/Tools/AssetBrowserWindow.cs", className: "AssetBrowserWindow", namespace: "MyProject.Editor.Tools", baseType: "EditorWindow", usings: "UnityEditor,System.Collections.Generic,System.Linq" })

// Add complete implementation mcp__unity-mcp-server__edit_structured({ path: "Assets/Editor/Tools/AssetBrowserWindow.cs", symbolName: "AssetBrowserWindow", operation: "insert_after", newText: ` [SerializeField] private string searchFilter = ""; [SerializeField] private Vector2 scrollPos; private List<string> assetPaths = new List<string>();

[MenuItem("Tools/Asset Browser")]
public static void ShowWindow()
{
    GetWindow&#x3C;AssetBrowserWindow>("Asset Browser");
}

private void OnEnable()
{
    RefreshAssets();
}

private void OnGUI()
{
    DrawSearchBar();
    DrawAssetList();
}

private void DrawSearchBar()
{
    using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar))
    {
        EditorGUI.BeginChangeCheck();
        searchFilter = EditorGUILayout.TextField(searchFilter, EditorStyles.toolbarSearchField);
        if (EditorGUI.EndChangeCheck())
        {
            RefreshAssets();
        }

        if (GUILayout.Button("Refresh", EditorStyles.toolbarButton, GUILayout.Width(60)))
        {
            RefreshAssets();
        }
    }
}

private void DrawAssetList()
{
    scrollPos = EditorGUILayout.BeginScrollView(scrollPos);

    foreach (var path in assetPaths)
    {
        using (new EditorGUILayout.HorizontalScope())
        {
            var icon = AssetDatabase.GetCachedIcon(path);
            GUILayout.Label(icon, GUILayout.Width(20), GUILayout.Height(20));

            if (GUILayout.Button(System.IO.Path.GetFileName(path), EditorStyles.linkLabel))
            {
                Selection.activeObject = AssetDatabase.LoadAssetAtPath&#x3C;Object>(path);
                EditorGUIUtility.PingObject(Selection.activeObject);
            }
        }
    }

    EditorGUILayout.EndScrollView();
}

private void RefreshAssets()
{
    var filter = string.IsNullOrEmpty(searchFilter) ? "t:Object" : searchFilter;
    assetPaths = AssetDatabase.FindAssets(filter)
        .Select(AssetDatabase.GUIDToAssetPath)
        .Take(100)
        .ToList();
    Repaint();
}

` })

Custom Inspector Patterns

Basic Inspector

using UnityEngine; using UnityEditor;

[CustomEditor(typeof(EnemySpawner))] public class EnemySpawnerEditor : Editor { private SerializedProperty spawnPrefab; private SerializedProperty spawnInterval; private SerializedProperty maxEnemies;

private void OnEnable()
{
    spawnPrefab = serializedObject.FindProperty("spawnPrefab");
    spawnInterval = serializedObject.FindProperty("spawnInterval");
    maxEnemies = serializedObject.FindProperty("maxEnemies");
}

public override void OnInspectorGUI()
{
    serializedObject.Update();

    EditorGUILayout.PropertyField(spawnPrefab);

    EditorGUILayout.Space();
    EditorGUILayout.LabelField("Spawn Settings", EditorStyles.boldLabel);

    EditorGUILayout.PropertyField(spawnInterval);
    EditorGUILayout.PropertyField(maxEnemies);

    EditorGUILayout.Space();

    // Custom button
    if (GUILayout.Button("Spawn Test Enemy"))
    {
        var spawner = (EnemySpawner)target;
        spawner.SpawnEnemy();
    }

    serializedObject.ApplyModifiedProperties();
}

}

MCP Implementation

// Create inspector class mcp__unity-mcp-server__create_class({ path: "Assets/Editor/Inspectors/WeaponDataEditor.cs", className: "WeaponDataEditor", namespace: "MyProject.Editor", baseType: "Editor", usings: "UnityEditor" })

// Add CustomEditor attribute mcp__unity-mcp-server__edit_structured({ path: "Assets/Editor/Inspectors/WeaponDataEditor.cs", symbolName: "WeaponDataEditor", operation: "insert_before", newText: "[CustomEditor(typeof(WeaponData))]\n" })

// Add inspector implementation mcp__unity-mcp-server__edit_structured({ path: "Assets/Editor/Inspectors/WeaponDataEditor.cs", symbolName: "WeaponDataEditor", operation: "insert_after", newText: ` private SerializedProperty weaponName; private SerializedProperty damage; private SerializedProperty attackSpeed; private SerializedProperty weaponType;

private bool showStats = true;

private void OnEnable()
{
    weaponName = serializedObject.FindProperty("weaponName");
    damage = serializedObject.FindProperty("damage");
    attackSpeed = serializedObject.FindProperty("attackSpeed");
    weaponType = serializedObject.FindProperty("weaponType");
}

public override void OnInspectorGUI()
{
    serializedObject.Update();

    // Header
    EditorGUILayout.LabelField("Weapon Configuration", EditorStyles.boldLabel);
    EditorGUILayout.Space();

    EditorGUILayout.PropertyField(weaponName);
    EditorGUILayout.PropertyField(weaponType);

    EditorGUILayout.Space();

    // Foldout section
    showStats = EditorGUILayout.Foldout(showStats, "Combat Stats", true);
    if (showStats)
    {
        EditorGUI.indentLevel++;
        EditorGUILayout.PropertyField(damage);
        EditorGUILayout.PropertyField(attackSpeed);

        // Calculated DPS
        float dps = damage.floatValue * attackSpeed.floatValue;
        EditorGUILayout.HelpBox($"DPS: {dps:F1}", MessageType.Info);
        EditorGUI.indentLevel--;
    }

    serializedObject.ApplyModifiedProperties();
}

` })

Foldout Groups

private bool showAdvanced = false;

public override void OnInspectorGUI() { serializedObject.Update();

// Basic properties
EditorGUILayout.PropertyField(serializedObject.FindProperty("basicField"));

// Foldout group
showAdvanced = EditorGUILayout.Foldout(showAdvanced, "Advanced Settings", true);
if (showAdvanced)
{
    EditorGUI.indentLevel++;
    EditorGUILayout.PropertyField(serializedObject.FindProperty("advancedField1"));
    EditorGUILayout.PropertyField(serializedObject.FindProperty("advancedField2"));
    EditorGUI.indentLevel--;
}

serializedObject.ApplyModifiedProperties();

}

Conditional Display

public override void OnInspectorGUI() { serializedObject.Update();

var enableFeature = serializedObject.FindProperty("enableFeature");
EditorGUILayout.PropertyField(enableFeature);

// Show only when enabled
if (enableFeature.boolValue)
{
    EditorGUI.indentLevel++;
    EditorGUILayout.PropertyField(serializedObject.FindProperty("featureValue"));
    EditorGUILayout.PropertyField(serializedObject.FindProperty("featureMode"));
    EditorGUI.indentLevel--;
}

serializedObject.ApplyModifiedProperties();

}

Property Drawer Patterns

Basic Property Drawer

using UnityEngine; using UnityEditor;

[CustomPropertyDrawer(typeof(MinMaxRange))] public class MinMaxRangeDrawer : PropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { EditorGUI.BeginProperty(position, label, property);

    position = EditorGUI.PrefixLabel(position, label);

    var minProp = property.FindPropertyRelative("min");
    var maxProp = property.FindPropertyRelative("max");

    float min = minProp.floatValue;
    float max = maxProp.floatValue;

    // MinMax slider
    var sliderRect = new Rect(position.x, position.y, position.width - 100, position.height);
    var minRect = new Rect(position.x + position.width - 95, position.y, 45, position.height);
    var maxRect = new Rect(position.x + position.width - 45, position.y, 45, position.height);

    EditorGUI.MinMaxSlider(sliderRect, ref min, ref max, 0f, 100f);
    min = EditorGUI.FloatField(minRect, min);
    max = EditorGUI.FloatField(maxRect, max);

    minProp.floatValue = min;
    maxProp.floatValue = max;

    EditorGUI.EndProperty();
}

}

MCP Implementation

// Create property drawer mcp__unity-mcp-server__create_class({ path: "Assets/Editor/PropertyDrawers/ColorRangeDrawer.cs", className: "ColorRangeDrawer", namespace: "MyProject.Editor", baseType: "PropertyDrawer", usings: "UnityEditor" })

// Add attribute and implementation mcp__unity-mcp-server__edit_structured({ path: "Assets/Editor/PropertyDrawers/ColorRangeDrawer.cs", symbolName: "ColorRangeDrawer", operation: "insert_before", newText: "[CustomPropertyDrawer(typeof(ColorRange))]\n" })

mcp__unity-mcp-server__edit_structured({ path: "Assets/Editor/PropertyDrawers/ColorRangeDrawer.cs", symbolName: "ColorRangeDrawer", operation: "insert_after", newText: ` public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { EditorGUI.BeginProperty(position, label, property);

    position = EditorGUI.PrefixLabel(position, label);

    var startColor = property.FindPropertyRelative("startColor");
    var endColor = property.FindPropertyRelative("endColor");

    float halfWidth = position.width / 2 - 2;
    var startRect = new Rect(position.x, position.y, halfWidth, position.height);
    var endRect = new Rect(position.x + halfWidth + 4, position.y, halfWidth, position.height);

    EditorGUI.PropertyField(startRect, startColor, GUIContent.none);
    EditorGUI.PropertyField(endRect, endColor, GUIContent.none);

    EditorGUI.EndProperty();
}

public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
    return EditorGUIUtility.singleLineHeight;
}

` })

Multi-Line Property Drawer

[CustomPropertyDrawer(typeof(DialogLine))] public class DialogLineDrawer : PropertyDrawer { public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { // 3 lines + spacing return EditorGUIUtility.singleLineHeight * 3 + 4; }

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
    EditorGUI.BeginProperty(position, label, property);

    float lineHeight = EditorGUIUtility.singleLineHeight;

    var speakerRect = new Rect(position.x, position.y, position.width, lineHeight);
    var textRect = new Rect(position.x, position.y + lineHeight + 2, position.width, lineHeight * 2);

    EditorGUI.PropertyField(speakerRect, property.FindPropertyRelative("speaker"));
    EditorGUI.PropertyField(textRect, property.FindPropertyRelative("text"));

    EditorGUI.EndProperty();
}

}

Attribute-Based Drawers

ReadOnly Attribute

// Runtime attribute public class ReadOnlyAttribute : PropertyAttribute { }

// Editor drawer [CustomPropertyDrawer(typeof(ReadOnlyAttribute))] public class ReadOnlyDrawer : PropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { GUI.enabled = false; EditorGUI.PropertyField(position, property, label); GUI.enabled = true; } }

Range with Label Attribute

// Runtime attribute public class LabeledRangeAttribute : PropertyAttribute { public float Min { get; } public float Max { get; } public string MinLabel { get; } public string MaxLabel { get; }

public LabeledRangeAttribute(float min, float max, string minLabel, string maxLabel)
{
    Min = min;
    Max = max;
    MinLabel = minLabel;
    MaxLabel = maxLabel;
}

}

// Editor drawer [CustomPropertyDrawer(typeof(LabeledRangeAttribute))] public class LabeledRangeDrawer : PropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { var attr = (LabeledRangeAttribute)attribute;

    EditorGUI.BeginProperty(position, label, property);
    position = EditorGUI.PrefixLabel(position, label);

    // Min label
    var minLabelRect = new Rect(position.x, position.y, 30, position.height);
    GUI.Label(minLabelRect, attr.MinLabel);

    // Slider
    var sliderRect = new Rect(position.x + 35, position.y, position.width - 70, position.height);
    property.floatValue = GUI.HorizontalSlider(sliderRect, property.floatValue, attr.Min, attr.Max);

    // Max label
    var maxLabelRect = new Rect(position.x + position.width - 30, position.y, 30, position.height);
    GUI.Label(maxLabelRect, attr.MaxLabel);

    EditorGUI.EndProperty();
}

}

SceneView GUI (Handles)

Gizmo Drawing

[CustomEditor(typeof(SpawnArea))] public class SpawnAreaEditor : Editor { private void OnSceneGUI() { var spawner = (SpawnArea)target;

    // Draw spawn area bounds
    Handles.color = new Color(0, 1, 0, 0.3f);
    Handles.DrawSolidDisc(spawner.transform.position, Vector3.up, spawner.radius);

    // Draw editable radius handle
    Handles.color = Color.green;
    EditorGUI.BeginChangeCheck();
    float newRadius = Handles.RadiusHandle(Quaternion.identity, spawner.transform.position, spawner.radius);
    if (EditorGUI.EndChangeCheck())
    {
        Undo.RecordObject(spawner, "Change Spawn Radius");
        spawner.radius = newRadius;
    }

    // Draw label
    Handles.Label(spawner.transform.position + Vector3.up * 2, $"Spawn Area\nRadius: {spawner.radius:F1}");
}

}

Position Handle

private void OnSceneGUI() { var waypoint = (WaypointPath)target;

for (int i = 0; i &#x3C; waypoint.points.Count; i++)
{
    EditorGUI.BeginChangeCheck();
    Vector3 newPos = Handles.PositionHandle(waypoint.points[i], Quaternion.identity);
    if (EditorGUI.EndChangeCheck())
    {
        Undo.RecordObject(waypoint, "Move Waypoint");
        waypoint.points[i] = newPos;
    }

    // Draw connection lines
    if (i > 0)
    {
        Handles.DrawLine(waypoint.points[i - 1], waypoint.points[i]);
    }

    // Draw index label
    Handles.Label(waypoint.points[i], $"Point {i}");
}

}

Common UI Patterns

Horizontal Layout

using (new EditorGUILayout.HorizontalScope()) { GUILayout.Label("Name:", GUILayout.Width(60)); value = EditorGUILayout.TextField(value); if (GUILayout.Button("Clear", GUILayout.Width(50))) { value = ""; } }

Vertical Layout with Box

using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) { GUILayout.Label("Section Title", EditorStyles.boldLabel); EditorGUILayout.PropertyField(prop1); EditorGUILayout.PropertyField(prop2); }

Toolbar

private int selectedTab = 0; private string[] tabs = { "General", "Advanced", "Debug" };

private void OnGUI() { selectedTab = GUILayout.Toolbar(selectedTab, tabs);

switch (selectedTab)
{
    case 0: DrawGeneralTab(); break;
    case 1: DrawAdvancedTab(); break;
    case 2: DrawDebugTab(); break;
}

}

Progress Bar

// Simple progress bar EditorGUI.ProgressBar(rect, progress, $"{progress * 100:F0}%");

// Progress bar in inspector public override void OnInspectorGUI() { var healthProp = serializedObject.FindProperty("health"); var maxHealthProp = serializedObject.FindProperty("maxHealth");

float ratio = healthProp.floatValue / maxHealthProp.floatValue;

Rect rect = GUILayoutUtility.GetRect(18, 18, "TextField");
EditorGUI.ProgressBar(rect, ratio, $"Health: {healthProp.floatValue}/{maxHealthProp.floatValue}");

}

Drag and Drop

private void OnGUI() { var dropArea = GUILayoutUtility.GetRect(100, 100); GUI.Box(dropArea, "Drop Asset Here");

Event evt = Event.current;

switch (evt.type)
{
    case EventType.DragUpdated:
    case EventType.DragPerform:
        if (!dropArea.Contains(evt.mousePosition))
            break;

        DragAndDrop.visualMode = DragAndDropVisualMode.Copy;

        if (evt.type == EventType.DragPerform)
        {
            DragAndDrop.AcceptDrag();

            foreach (var obj in DragAndDrop.objectReferences)
            {
                Debug.Log($"Dropped: {obj.name}");
            }
        }
        break;
}

}

Undo Support

Recording Changes

// Single object Undo.RecordObject(target, "Change Value"); myComponent.value = newValue;

// Multiple objects Undo.RecordObjects(targets, "Change Values"); foreach (var t in targets) { ((MyComponent)t).value = newValue; }

// Create object with undo var newObj = new GameObject("New Object"); Undo.RegisterCreatedObjectUndo(newObj, "Create Object");

// Destroy with undo Undo.DestroyObjectImmediate(obj);

Property Change Callback

private void OnEnable() { Undo.undoRedoPerformed += OnUndoRedo; }

private void OnDisable() { Undo.undoRedoPerformed -= OnUndoRedo; }

private void OnUndoRedo() { Repaint(); }

EditorPrefs (Persistent Settings)

public class MyToolWindow : EditorWindow { private const string PREF_KEY = "MyTool_";

private bool showAdvanced;
private int selectedMode;

private void OnEnable()
{
    // Load settings
    showAdvanced = EditorPrefs.GetBool(PREF_KEY + "ShowAdvanced", false);
    selectedMode = EditorPrefs.GetInt(PREF_KEY + "SelectedMode", 0);
}

private void OnDisable()
{
    // Save settings
    EditorPrefs.SetBool(PREF_KEY + "ShowAdvanced", showAdvanced);
    EditorPrefs.SetInt(PREF_KEY + "SelectedMode", selectedMode);
}

}

Message Types

// Help boxes EditorGUILayout.HelpBox("Info message", MessageType.Info); EditorGUILayout.HelpBox("Warning message", MessageType.Warning); EditorGUILayout.HelpBox("Error message", MessageType.Error); EditorGUILayout.HelpBox("No icon message", MessageType.None);

// Dialog boxes if (EditorUtility.DisplayDialog("Confirm", "Are you sure?", "Yes", "No")) { // User clicked Yes }

// Progress dialog EditorUtility.DisplayProgressBar("Processing", "Please wait...", 0.5f); // ... do work ... EditorUtility.ClearProgressBar();

Best Practices

  1. Always Use SerializedProperty

// Good - supports undo, multi-edit, prefab overrides serializedObject.Update(); EditorGUILayout.PropertyField(serializedObject.FindProperty("myField")); serializedObject.ApplyModifiedProperties();

// Bad - loses undo support ((MyComponent)target).myField = EditorGUILayout.FloatField(((MyComponent)target).myField);

  1. Cache SerializedProperty References

private SerializedProperty myProp;

private void OnEnable() { myProp = serializedObject.FindProperty("myField"); }

public override void OnInspectorGUI() { serializedObject.Update(); EditorGUILayout.PropertyField(myProp); // Use cached reference serializedObject.ApplyModifiedProperties(); }

  1. Support Multi-Object Editing

[CustomEditor(typeof(MyComponent))] [CanEditMultipleObjects] // Enable multi-select editing public class MyComponentEditor : Editor { public override void OnInspectorGUI() { serializedObject.Update();

    // This automatically handles multiple selection
    EditorGUILayout.PropertyField(serializedObject.FindProperty("value"));

    // For custom buttons, iterate targets
    if (GUILayout.Button("Reset All"))
    {
        foreach (var t in targets)
        {
            Undo.RecordObject(t, "Reset");
            ((MyComponent)t).Reset();
        }
    }

    serializedObject.ApplyModifiedProperties();
}

}

  1. Repaint on Changes

private void OnGUI() { EditorGUI.BeginChangeCheck();

// ... draw controls ...

if (EditorGUI.EndChangeCheck())
{
    Repaint();  // Force immediate redraw
}

}

Common Mistakes

  1. Forgetting serializedObject.Update/ApplyModifiedProperties

// Wrong - changes won't be saved public override void OnInspectorGUI() { EditorGUILayout.PropertyField(serializedObject.FindProperty("value")); }

// Correct public override void OnInspectorGUI() { serializedObject.Update(); EditorGUILayout.PropertyField(serializedObject.FindProperty("value")); serializedObject.ApplyModifiedProperties(); }

  1. Using IMGUI for Game UI

// Wrong - OnGUI is for editor only public class GameHUD : MonoBehaviour { void OnGUI() { GUI.Label(new Rect(10, 10, 100, 20), "Health: 100"); } }

// Correct - use uGUI or UI Toolkit for game UI

  1. Not Caching GUIStyle

// Wrong - creates new style every frame void OnGUI() { var style = new GUIStyle(EditorStyles.boldLabel); style.fontSize = 14; GUILayout.Label("Title", style); }

// Correct - cache and reuse private GUIStyle titleStyle;

void OnGUI() { titleStyle ??= new GUIStyle(EditorStyles.boldLabel) { fontSize = 14 }; GUILayout.Label("Title", titleStyle); }

Tool Selection Guide

Task Recommended Approach

Custom inspector [CustomEditor]

  • Editor class

Reusable field UI [CustomPropertyDrawer]

  • PropertyDrawer class

Standalone tool EditorWindow subclass

Scene visualization OnSceneGUI

  • Handles

Menu command [MenuItem] attribute

Context menu [ContextMenu] / [ContextMenuItemAttribute]

Project settings SettingsProvider

Reference

Useful Classes

  • EditorGUILayout

  • Layout-based editor controls

  • EditorGUI

  • Position-based editor controls

  • GUILayout

  • Layout-based basic controls

  • GUI

  • Position-based basic controls

  • Handles

  • Scene view 3D handles

  • EditorStyles

  • Built-in editor styles

  • EditorGUIUtility

  • Editor utilities

  • AssetDatabase

  • Asset operations

  • SerializedObject / SerializedProperty

  • Serialization access

MenuItem Shortcuts

[MenuItem("Tools/My Tool %#t")] // Ctrl+Shift+T [MenuItem("Tools/My Tool %&t")] // Ctrl+Alt+T [MenuItem("Tools/My Tool #t")] // Shift+T [MenuItem("Tools/My Tool _t")] // T only [MenuItem("Tools/My Tool %LEFT")] // Ctrl+Left Arrow

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

mcp-server-development

No summary provided by upstream source.

Repository SourceNeeds Review
General

opentui-design

No summary provided by upstream source.

Repository SourceNeeds Review
General

drawio

No summary provided by upstream source.

Repository SourceNeeds Review