Token导航 LogoToken导航TokenDH.com
研究检索操作浏览器github未标认证来源可访问许可证需确认审计通过

unity-editor-imgui-designUnity editor imgui 设计

Agent Skill

用于辅助界面设计、视觉规范、排版、配色、布局和交互体验优化。它适合让 Agent 根据产品场景整理页面结构、生成 UI 方案、检查视觉一致性或改进组件层级。使用时需要结合现有品牌、设计系统和用户任务,不应只堆装饰元素;涉及真实页面改动时,应通过截图或浏览器预览检查文本溢出、对齐和响应式表现。

总安装

235

周安装

10

GitHub Stars

27

下载量

82
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:unity-editor-imgui-design(Unity editor imgui 设计)
来源仓库:https://github.com/akiojin/unity-mcp-server
仓库路径:skills/unity-editor-imgui-design
安装命令:
npx skills add https://github.com/akiojin/unity-mcp-server --skill unity-editor-imgui-design
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/akiojin/unity-mcp-server --skill unity-editor-imgui-design

简介

用于辅助 Unity Editor 内 IMGUI 界面设计、视觉规范、排版、配色和布局优化。

  • 适合让 Agent 根据产品场景整理页面结构、生成 UI 方案或检查一致性。
  • 使用时需结合现有品牌和设计系统,避免仅堆砌装饰元素。
  • 涉及真实页面改动时应通过截图或预览检查文本溢出和对齐表现。
  • unity-editor-imgui-design 属于研究检索类 Skill,可作为该场景下的辅助能力补充。

SKILL.md

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

AspectIMGUI (Editor)uGUI / UI Toolkit (Game)
PurposeEditor tools, inspectorsIn-game UI, HUD
Build inclusionEditor onlyIncluded in builds
RenderingImmediate modeRetained mode
PerformanceOK for editorOptimized for runtime
StylingLimited (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

GUILayoutEditorGUILayout
Works everywhereEditor only
Basic controlsRich editor controls
GUILayout.ButtonEditorGUILayout.PropertyField
GUILayout.TextFieldEditorGUILayout.ObjectField
GUILayout.LabelEditorGUILayout.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<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<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();
    }
`
})

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

2. 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();
}

3. 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();
    }
}

4. 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();
}

2. 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

3. 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

TaskRecommended Approach
Custom inspector[CustomEditor] + Editor class
Reusable field UI[CustomPropertyDrawer] + PropertyDrawer class
Standalone toolEditorWindow subclass
Scene visualizationOnSceneGUI + Handles
Menu command[MenuItem] attribute
Context menu[ContextMenu] / [ContextMenuItemAttribute]
Project settingsSettingsProvider

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

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

35.21%
按下载量换算29

Claude

29.74%
按下载量换算24

Cursor

17.34%
按下载量换算14

Gemini CLI

8.59%
按下载量换算7

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

操作浏览器

该 Skill 可能涉及浏览器控制能力,使用时可能读取或操作网页内容,需要在受控环境中确认权限边界。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills