Unity IMGUI (Immediate Mode GUI) for editor tools and custom inspectors. Use for EditorWindow, Custom Inspector, Property Drawer development. NOT for game UI - use unity-game-ugui-design or unity-game-ui-toolkit-design instead.
/plugin marketplace add akiojin/unity-mcp-server/plugin install unity-mcp-server@unity-mcp-serverThis skill is limited to using the following tools:
WARNING: IMGUI is for Unity Editor extensions ONLY. For game UI, use
unity-game-ugui-designorunity-game-ui-toolkit-design.
| 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) |
// 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 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();
}
`
})
OnGUI() called every frame (or on repaint/input)
├── Layout Event: Calculate sizes
├── Repaint Event: Draw controls
└── Input Events: Handle mouse/keyboard
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 | EditorGUILayout |
|---|---|
| Works everywhere | Editor only |
| Basic controls | Rich editor controls |
GUILayout.Button | EditorGUILayout.PropertyField |
GUILayout.TextField | EditorGUILayout.ObjectField |
GUILayout.Label | EditorGUILayout.HelpBox |
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<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();
}
}
// 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<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<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();
}
`
})
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();
}
}
// 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();
}
`
})
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();
}
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();
}
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();
}
}
// 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;
}
`
})
[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();
}
}
// 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;
}
}
// 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();
}
}
[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}");
}
}
private void OnSceneGUI()
{
var waypoint = (WaypointPath)target;
for (int i = 0; i < 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}");
}
}
using (new EditorGUILayout.HorizontalScope())
{
GUILayout.Label("Name:", GUILayout.Width(60));
value = EditorGUILayout.TextField(value);
if (GUILayout.Button("Clear", GUILayout.Width(50)))
{
value = "";
}
}
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
{
GUILayout.Label("Section Title", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(prop1);
EditorGUILayout.PropertyField(prop2);
}
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;
}
}
// 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}");
}
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;
}
}
// 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);
private void OnEnable()
{
Undo.undoRedoPerformed += OnUndoRedo;
}
private void OnDisable()
{
Undo.undoRedoPerformed -= OnUndoRedo;
}
private void OnUndoRedo()
{
Repaint();
}
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);
}
}
// 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();
// 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);
private SerializedProperty myProp;
private void OnEnable()
{
myProp = serializedObject.FindProperty("myField");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(myProp); // Use cached reference
serializedObject.ApplyModifiedProperties();
}
[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();
}
}
private void OnGUI()
{
EditorGUI.BeginChangeCheck();
// ... draw controls ...
if (EditorGUI.EndChangeCheck())
{
Repaint(); // Force immediate redraw
}
}
// 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();
}
// 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
// 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);
}
| 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 |
EditorGUILayout - Layout-based editor controlsEditorGUI - Position-based editor controlsGUILayout - Layout-based basic controlsGUI - Position-based basic controlsHandles - Scene view 3D handlesEditorStyles - Built-in editor stylesEditorGUIUtility - Editor utilitiesAssetDatabase - Asset operationsSerializedObject / SerializedProperty - Serialization access[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
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.