🏠 HaroFramework Project | 📂 Skill | ⬆️ Skill
Unity Editor Extension Builder
Expert skill for creating Unity Editor scripts that enhance the development workflow.
When to Use This Skill
Activate this skill when the user requests:
- •Custom Inspector for MonoBehaviour or ScriptableObject
- •Property Drawer for custom serializable classes
- •Editor Window for custom tools
- •Menu items and shortcuts
- •Scene view tools and handles
- •Asset post-processors
- •Build automation
Editor Script Requirements
- •Must be in
Editorfolder or have Editor-only assembly definition - •Uses
UnityEditornamespace - •Never included in builds
- •Can access editor-only APIs
Custom Inspector Template
csharp
using UnityEditor;
using UnityEngine;
namespace HaroFramework.Editor
{
/// <summary>
/// Custom inspector for [TargetClass]
/// </summary>
[CustomEditor(typeof(TargetClass))]
[CanEditMultipleObjects]
public class TargetClassEditor : Editor
{
private SerializedProperty _propertyName;
private void OnEnable()
{
// Cache serialized properties
_propertyName = serializedObject.FindProperty("_fieldName");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
// Option 1: Draw default inspector
DrawDefaultInspector();
// Option 2: Custom layout
EditorGUILayout.PropertyField(_propertyName);
// Custom buttons
if (GUILayout.Button("Custom Action"))
{
foreach (var t in targets)
{
var target = (TargetClass)t;
target.DoSomething();
}
}
serializedObject.ApplyModifiedProperties();
}
private void OnSceneGUI()
{
var target = (TargetClass)this.target;
// Draw handles in scene view
Handles.color = Color.blue;
Handles.DrawWireDisc(target.transform.position, Vector3.up, 1f);
}
}
}
Property Drawer Template
csharp
using UnityEditor;
using UnityEngine;
namespace HaroFramework.Editor
{
/// <summary>
/// Custom property drawer for [CustomType]
/// </summary>
[CustomPropertyDrawer(typeof(CustomType))]
public class CustomTypeDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
// Draw label
position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
// Calculate rects
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
var fieldRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
// Draw fields
SerializedProperty fieldProp = property.FindPropertyRelative("fieldName");
EditorGUI.PropertyField(fieldRect, fieldProp, GUIContent.none);
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
// Return height needed for the property
return EditorGUIUtility.singleLineHeight;
}
}
}
Editor Window Template
csharp
using UnityEditor;
using UnityEngine;
namespace HaroFramework.Editor
{
/// <summary>
/// Custom editor window for [purpose]
/// </summary>
public class CustomToolWindow : EditorWindow
{
private Vector2 _scrollPosition;
private string _textValue = "";
[MenuItem("HaroFramework/Custom Tool Window")]
public static void ShowWindow()
{
var window = GetWindow<CustomToolWindow>("Custom Tool");
window.minSize = new Vector2(300, 200);
window.Show();
}
private void OnGUI()
{
EditorGUILayout.LabelField("Custom Tool", EditorStyles.boldLabel);
EditorGUILayout.Space();
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
// Tool UI
_textValue = EditorGUILayout.TextField("Text Field:", _textValue);
EditorGUILayout.Space();
if (GUILayout.Button("Execute Action"))
{
ExecuteAction();
}
EditorGUILayout.EndScrollView();
}
private void ExecuteAction()
{
// Tool logic
Debug.Log($"Action executed with value: {_textValue}");
}
private void OnEnable()
{
// Initialize when window opens
}
private void OnDisable()
{
// Cleanup when window closes
}
}
}
Menu Items
csharp
using UnityEditor;
using UnityEngine;
namespace HaroFramework.Editor
{
public static class CustomMenuItems
{
// Simple menu item
[MenuItem("HaroFramework/Do Something")]
private static void DoSomething()
{
Debug.Log("Menu action executed");
}
// Menu item with validation
[MenuItem("HaroFramework/Process Selected")]
private static void ProcessSelected()
{
foreach (var obj in Selection.objects)
{
Debug.Log($"Processing {obj.name}");
}
}
[MenuItem("HaroFramework/Process Selected", true)]
private static bool ValidateProcessSelected()
{
return Selection.objects.Length > 0;
}
// Context menu for GameObject
[MenuItem("GameObject/HaroFramework/Add Custom Component", false, 10)]
private static void AddCustomComponent(MenuCommand command)
{
var go = command.context as GameObject;
if (go != null)
{
Undo.AddComponent<CustomComponent>(go);
}
}
// Assets menu
[MenuItem("Assets/HaroFramework/Create Special Asset")]
private static void CreateSpecialAsset()
{
var asset = ScriptableObject.CreateInstance<CustomScriptableObject>();
AssetDatabase.CreateAsset(asset, "Assets/NewSpecialAsset.asset");
AssetDatabase.SaveAssets();
EditorUtility.FocusProjectWindow();
Selection.activeObject = asset;
}
// Hotkey (% = Ctrl, # = Shift, & = Alt)
[MenuItem("HaroFramework/Quick Action %#q")]
private static void QuickAction()
{
Debug.Log("Quick action with Ctrl+Shift+Q");
}
}
}
Scene View Tools
csharp
using UnityEditor;
using UnityEngine;
namespace HaroFramework.Editor
{
[InitializeOnLoad]
public static class SceneViewTools
{
static SceneViewTools()
{
SceneView.duringSceneGui += OnSceneGUI;
}
private static void OnSceneGUI(SceneView sceneView)
{
Handles.BeginGUI();
// Draw UI in scene view
var rect = new Rect(10, 10, 200, 50);
GUI.Box(rect, "Custom Scene Tool");
if (GUI.Button(new Rect(20, 30, 180, 20), "Scene Action"))
{
Debug.Log("Scene action executed");
}
Handles.EndGUI();
// Draw handles
if (Selection.activeGameObject != null)
{
var pos = Selection.activeGameObject.transform.position;
Handles.color = Color.green;
Handles.DrawWireCube(pos, Vector3.one);
}
}
}
}
Asset Post-Processor
csharp
using UnityEditor;
using UnityEngine;
namespace HaroFramework.Editor
{
/// <summary>
/// Automatically processes assets on import
/// </summary>
public class CustomAssetPostprocessor : AssetPostprocessor
{
private void OnPreprocessTexture()
{
var importer = assetImporter as TextureImporter;
if (assetPath.Contains("Sprites"))
{
importer.textureType = TextureImporterType.Sprite;
importer.spritePixelsPerUnit = 100;
}
}
private void OnPostprocessTexture(Texture2D texture)
{
Debug.Log($"Processed texture: {assetPath}");
}
private static void OnPostprocessAllAssets(
string[] importedAssets,
string[] deletedAssets,
string[] movedAssets,
string[] movedFromAssetPaths)
{
foreach (var path in importedAssets)
{
if (path.EndsWith(".prefab"))
{
Debug.Log($"Prefab imported: {path}");
}
}
}
}
}
Editor Utilities
csharp
using UnityEditor;
using UnityEngine;
namespace HaroFramework.Editor
{
public static class EditorUtilities
{
// Undo support
public static void RecordUndo(Object obj, string operationName)
{
Undo.RecordObject(obj, operationName);
EditorUtility.SetDirty(obj);
}
// Progress bar
public static void ShowProgress(string title, string info, float progress)
{
EditorUtility.DisplayProgressBar(title, info, progress);
}
public static void ClearProgress()
{
EditorUtility.ClearProgressBar();
}
// Dialog boxes
public static bool ShowDialog(string title, string message, string ok = "OK", string cancel = "Cancel")
{
return EditorUtility.DisplayDialog(title, message, ok, cancel);
}
// Save file panel
public static string SaveFilePanel(string title, string defaultName, string extension)
{
return EditorUtility.SaveFilePanel(title, Application.dataPath, defaultName, extension);
}
// Find assets of type
public static T[] FindAssetsOfType<T>() where T : Object
{
var guids = AssetDatabase.FindAssets($"t:{typeof(T).Name}");
var assets = new T[guids.Length];
for (int i = 0; i < guids.Length; i++)
{
var path = AssetDatabase.GUIDToAssetPath(guids[i]);
assets[i] = AssetDatabase.LoadAssetAtPath<T>(path);
}
return assets;
}
}
}
Best Practices
Performance
- •Cache SerializedProperties in
OnEnable() - •Use
EditorGUILayoutfor automatic layouts - •Use
EditorGUIfor manual rect-based layouts - •Avoid expensive operations in
OnGUI()orOnSceneGUI()
Undo/Redo
csharp
// Record object before modification Undo.RecordObject(target, "Operation Name"); target.value = newValue; EditorUtility.SetDirty(target);
Multi-Object Editing
csharp
[CanEditMultipleObjects]
public class MyEditor : Editor
{
public override void OnInspectorGUI()
{
foreach (var t in targets)
{
var target = (MyComponent)t;
// Apply to all selected objects
}
}
}
File Organization
code
Assets/
Scripts/
Editor/
Inspectors/
*Editor.cs
PropertyDrawers/
*Drawer.cs
Windows/
*Window.cs
Tools/
*Tools.cs
Common Patterns
Reorderable List
csharp
using UnityEditorInternal;
private ReorderableList _list;
private void OnEnable()
{
var prop = serializedObject.FindProperty("_items");
_list = new ReorderableList(serializedObject, prop, true, true, true, true);
_list.drawHeaderCallback = (Rect rect) =>
{
EditorGUI.LabelField(rect, "Items");
};
_list.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
{
var element = prop.GetArrayElementAtIndex(index);
EditorGUI.PropertyField(rect, element, GUIContent.none);
};
}
public override void OnInspectorGUI()
{
serializedObject.Update();
_list.DoLayoutList();
serializedObject.ApplyModifiedProperties();
}
Questions to Ask
Before creating editor tools:
- •What workflow needs to be improved?
- •Should it be an Inspector, Window, or MenuItem?
- •Does it need to support Undo/Redo?
- •Should it work with multiple selected objects?
- •Are there performance considerations?
Output Format
- •Create .cs file in Assets/Scripts/Editor/ or appropriate subfolder
- •Include complete, production-ready code
- •Add XML documentation
- •Support Undo/Redo where applicable
- •Handle multi-object editing if relevant
- •Include usage instructions in comments