UnityCsReference编辑器扩展:MenuItem与自定义窗口开发指南

UnityCsReference编辑器扩展:MenuItem与自定义窗口开发指南

【免费下载链接】UnityCsReference Unity C# reference source code. 【免费下载链接】UnityCsReference 项目地址: https://gitcode.com/gh_mirrors/un/UnityCsReference

在Unity开发过程中,编辑器扩展是提升工作效率的重要手段。本文将详细介绍如何使用MenuItem(菜单项)和创建自定义窗口,帮助开发者快速定制符合自身需求的编辑器功能。通过本文,你将掌握从简单菜单命令到复杂交互窗口的完整开发流程,以及如何将这些扩展集成到Unity编辑器的工作流中。

MenuItem基础:快速添加编辑器命令

MenuItem是Unity编辑器扩展的基础组件,它允许开发者在编辑器菜单栏中添加自定义命令。通过MenuItem特性,可以将静态方法直接绑定到菜单项,实现一键执行复杂操作的功能。

基本用法与参数解析

MenuItem特性的核心定义位于Editor/Mono/MenuItem.cs文件中。其构造函数提供了丰富的参数配置:

// 创建菜单项并在选中时调用静态函数
public MenuItem(string itemName, bool isValidateFunction = false, int priority = 1000)
  • itemName:菜单路径,使用/分隔层级,如"Tools/MyTool"
  • isValidateFunction:是否为验证函数,用于控制菜单项的启用状态
  • priority:优先级,决定菜单项在菜单中的显示顺序

以下是一个基础示例,演示如何创建一个简单的菜单项:

using UnityEditor;
using UnityEngine;

public static class MyMenuItems
{
    // 在Tools菜单下创建"Hello World"项,优先级100
    [MenuItem("Tools/Hello World", false, 100)]
    private static void SayHello()
    {
        Debug.Log("Hello Unity Editor!");
    }
    
    // 验证函数,控制菜单项是否可用
    [MenuItem("Tools/Hello World", true)]
    private static bool ValidateSayHello()
    {
        // 只有选中物体时菜单项才可用
        return Selection.activeObject != null;
    }
}

快捷键配置与菜单分组

MenuItem支持通过特殊语法为菜单项添加快捷键,常用格式包括:

  • %:Ctrl (Windows) / Cmd (Mac)
  • &:Alt
  • #:Shift
  • F1-F12:功能键

例如添加Ctrl+Shift+H快捷键:

[MenuItem("Tools/Quick Action %#h")]
private static void QuickAction()
{
    // 执行快速操作
}

菜单分组通过优先级控制,相邻菜单项优先级相差超过10会自动创建分隔线:

[MenuItem("Tools/Group 1/Item 1", false, 10)]
private static void Group1Item1() { }

[MenuItem("Tools/Group 1/Item 2", false, 11)]
private static void Group1Item2() { }

// 优先级相差11,创建分隔线
[MenuItem("Tools/Group 2/Item 1", false, 22)]
private static void Group2Item1() { }

自定义窗口开发:从基础到进阶

自定义窗口是更复杂编辑器扩展的基础,可实现丰富的用户交互界面。Unity通过EditorWindow类提供了窗口创建的完整框架,其核心实现位于Editor/Mono/ContainerWindow.cs

窗口创建基础

创建自定义窗口的基本步骤:

  1. 创建继承EditorWindow的类
  2. 添加MenuItem用于打开窗口
  3. 实现OnGUI方法绘制界面

基础示例代码:

using UnityEditor;
using UnityEngine;

public class MyCustomWindow : EditorWindow
{
    private string inputText = "Hello World";
    private float sliderValue = 1.0f;
    
    // 添加菜单项用于打开窗口
    [MenuItem("Window/My Custom Window")]
    public static void ShowWindow()
    {
        // 显示窗口,标题为"My Custom Window"
        GetWindow<MyCustomWindow>("My Custom Window");
    }
    
    // 绘制窗口内容
    private void OnGUI()
    {
        GUILayout.Label("Basic Settings", EditorStyles.boldLabel);
        
        // 文本输入框
        inputText = EditorGUILayout.TextField("Text Field", inputText);
        
        // 滑动条
        sliderValue = EditorGUILayout.Slider("Slider", sliderValue, 0.0f, 10.0f);
        
        // 按钮
        if (GUILayout.Button("Click Me"))
        {
            Debug.Log($"Input: {inputText}, Slider: {sliderValue}");
        }
    }
}

窗口布局与高级UI控件

Unity编辑器提供了丰富的布局控件,用于创建专业的窗口界面:

private void OnGUI()
{
    // 垂直布局组
    using (new EditorGUILayout.VerticalScope("box"))
    {
        GUILayout.Label("Vertical Layout", EditorStyles.boldLabel);
        
        // 水平布局组
        using (new EditorGUILayout.HorizontalScope())
        {
            GUILayout.Label("Horizontal Group");
            if (GUILayout.Button("Left")) { }
            if (GUILayout.Button("Right")) { }
        }
        
        // 滚动视图
        using (var scrollScope = new EditorGUILayout.ScrollViewScope(Vector2.zero))
        {
            for (int i = 0; i < 20; i++)
            {
                GUILayout.Label($"Item {i}");
            }
        }
    }
}

常用高级控件包括:

  • PropertyField:显示和编辑Unity对象属性
  • ObjectField:选择对象引用
  • LayerField:层选择下拉框
  • TagField:标签选择下拉框
public GameObject targetObject;
public LayerMask layerMask;

private void OnGUI()
{
    targetObject = EditorGUILayout.ObjectField(
        "Target Object", targetObject, typeof(GameObject), true) as GameObject;
        
    layerMask = EditorGUILayout.LayerField("Layer", layerMask);
}

窗口状态保存与事件处理

为了提升用户体验,自定义窗口应支持状态保存和响应编辑器事件:

// 保存窗口状态
private void OnEnable()
{
    // 从EditorPrefs加载保存的状态
    inputText = EditorPrefs.GetString("MyCustomWindow_InputText", "Hello World");
    sliderValue = EditorPrefs.GetFloat("MyCustomWindow_SliderValue", 1.0f);
    
    // 注册选择变更事件
    Selection.selectionChanged += OnSelectionChanged;
}

// 保存窗口状态
private void OnDisable()
{
    // 将状态保存到EditorPrefs
    EditorPrefs.SetString("MyCustomWindow_InputText", inputText);
    EditorPrefs.SetFloat("MyCustomWindow_SliderValue", sliderValue);
    
    // 注销事件
    Selection.selectionChanged -= OnSelectionChanged;
}

// 响应选择变更事件
private void OnSelectionChanged()
{
    Repaint(); // 刷新窗口
}

// 响应窗口焦点变化
private void OnFocus()
{
    Debug.Log("Window focused");
}

// 响应窗口大小变化
private void OnResize()
{
    Debug.Log($"Window resized to {position.size}");
}

实际应用案例:资源批量处理器

下面我们将结合MenuItem和自定义窗口,创建一个实用的资源批量处理工具。该工具能够批量重命名选中的资源,并支持添加前缀、后缀和序号。

完整实现代码

using System.IO;
using UnityEditor;
using UnityEngine;

public class AssetBatchProcessor : EditorWindow
{
    private string prefix = "";
    private string suffix = "";
    private int startIndex = 1;
    private bool includeSubfolders = false;
    private string searchPattern = "*.prefab";
    
    // 添加菜单项
    [MenuItem("Tools/Asset Batch Processor")]
    public static void ShowWindow()
    {
        GetWindow<AssetBatchProcessor>("Asset Processor");
    }
    
    private void OnGUI()
    {
        GUILayout.Label("Asset Batch Renamer", EditorStyles.boldLabel);
        
        // 输入设置区域
        using (new EditorGUILayout.VerticalScope("box"))
        {
            prefix = EditorGUILayout.TextField("Prefix", prefix);
            suffix = EditorGUILayout.TextField("Suffix", suffix);
            startIndex = EditorGUILayout.IntField("Start Index", startIndex);
            includeSubfolders = EditorGUILayout.Toggle("Include Subfolders", includeSubfolders);
            searchPattern = EditorGUILayout.TextField("Search Pattern", searchPattern);
        }
        
        // 操作按钮区域
        using (new EditorGUILayout.HorizontalScope())
        {
            if (GUILayout.Button("Rename Selected"))
            {
                RenameSelectedAssets();
            }
            
            if (GUILayout.Button("Batch Process Folder"))
            {
                BatchProcessFolder();
            }
        }
        
        // 帮助信息
        GUILayout.Label("Instructions:", EditorStyles.miniBoldLabel);
        GUILayout.Label("- Rename Selected: 重命名选中的资源");
        GUILayout.Label("- Batch Process Folder: 批量处理文件夹中的资源");
    }
    
    // 重命名选中的资源
    private void RenameSelectedAssets()
    {
        if (Selection.objects.Length == 0)
        {
            EditorUtility.DisplayDialog("提示", "请先选择资源", "确定");
            return;
        }
        
        int index = startIndex;
        foreach (var obj in Selection.objects)
        {
            string path = AssetDatabase.GetAssetPath(obj);
            string directory = Path.GetDirectoryName(path);
            string extension = Path.GetExtension(path);
            string newName = $"{prefix}{index}{suffix}{extension}";
            
            AssetDatabase.RenameAsset(path, newName);
            index++;
        }
        
        AssetDatabase.Refresh();
        EditorUtility.DisplayDialog("完成", $"已重命名 {Selection.objects.Length} 个资源", "确定");
    }
    
    // 批量处理文件夹
    private void BatchProcessFolder()
    {
        string folderPath = EditorUtility.OpenFolderPanel("选择文件夹", Application.dataPath, "");
        if (string.IsNullOrEmpty(folderPath)) return;
        
        if (!folderPath.StartsWith(Application.dataPath))
        {
            EditorUtility.DisplayDialog("错误", "请选择项目Assets目录下的文件夹", "确定");
            return;
        }
        
        string[] files = Directory.GetFiles(
            folderPath, searchPattern, 
            includeSubfolders ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
        
        int index = startIndex;
        foreach (string file in files)
        {
            string assetPath = file.Substring(Application.dataPath.Length - "Assets".Length);
            string directory = Path.GetDirectoryName(assetPath);
            string extension = Path.GetExtension(assetPath);
            string newName = $"{prefix}{index}{suffix}{extension}";
            
            AssetDatabase.RenameAsset(assetPath, newName);
            index++;
        }
        
        AssetDatabase.Refresh();
        EditorUtility.DisplayDialog("完成", $"已处理 {files.Length} 个资源", "确定");
    }
}

功能扩展建议

该工具可以进一步扩展以下功能:

  1. 资源类型筛选:添加对不同资源类型的筛选功能
  2. 重命名规则保存:允许保存和加载重命名规则
  3. 预览功能:在实际重命名前显示预览效果
  4. 撤销支持:添加撤销功能,防止误操作

高级技巧与性能优化

菜单命令的验证与优先级管理

复杂菜单系统需要合理管理菜单项的可见性和启用状态。通过验证函数可以实现上下文相关的菜单行为:

// 只有选中Transform组件时才显示此菜单项
[MenuItem("CONTEXT/Transform/Reset Position", true)]
private static bool ValidateResetPosition(MenuCommand command)
{
    Transform transform = (Transform)command.context;
    return transform.localPosition != Vector3.zero;
}

[MenuItem("CONTEXT/Transform/Reset Position")]
private static void ResetPosition(MenuCommand command)
{
    Transform transform = (Transform)command.context;
    Undo.RecordObject(transform, "Reset Position");
    transform.localPosition = Vector3.zero;
}

自定义窗口的性能优化

对于包含大量UI元素或频繁刷新的窗口,需要注意性能优化:

  1. 减少OnGUI调用:避免在OnGUI中执行复杂计算
  2. 使用延迟刷新:对于频繁变化的数据,使用EditorApplication.delayCall延迟刷新
  3. 缓存资源引用:缓存频繁使用的GUIStyle和纹理
  4. 分批处理数据:处理大量数据时使用协程分批处理
// 使用延迟刷新
private void UpdateData()
{
    EditorApplication.delayCall += () =>
    {
        // 执行数据更新
        Repaint();
    };
}

// 缓存GUIStyle
private GUIStyle _customStyle;
private GUIStyle customStyle
{
    get
    {
        if (_customStyle == null)
        {
            _customStyle = new GUIStyle(EditorStyles.label)
            {
                fontSize = 12,
                fontStyle = FontStyle.Bold
            };
        }
        return _customStyle;
    }
}

扩展编辑器窗口样式

通过自定义GUIStyle和使用GUISkin,可以创建更美观的窗口界面:

// 自定义GUIStyle
private void OnGUI()
{
    GUIStyle headerStyle = new GUIStyle(GUI.skin.label)
    {
        fontSize = 16,
        fontStyle = FontStyle.Bold,
        normal = { textColor = new Color(0.2f, 0.5f, 0.8f) }
    };
    
    GUILayout.Label("Custom Styled Window", headerStyle);
    
    // 使用水平线分隔
    GUILayout.Box("", GUILayout.ExpandWidth(true), GUILayout.Height(1));
    
    // 自定义按钮样式
    GUIStyle buttonStyle = new GUIStyle(GUI.skin.button)
    {
        fixedHeight = 30,
        fontSize = 14
    };
    
    if (GUILayout.Button("Custom Button", buttonStyle))
    {
        // 按钮点击事件
    }
}

总结与扩展学习

本文详细介绍了Unity编辑器扩展中MenuItem和自定义窗口的开发方法,从基础概念到实际应用,涵盖了创建编辑器命令、设计交互界面和优化性能等多个方面。通过这些技术,开发者可以根据项目需求定制高效的编辑器工具,显著提升开发效率。

进阶学习资源

后续建议

  1. 学习EditorWindow生命周期:深入理解窗口的创建、显示、刷新和销毁过程
  2. 掌握Undo系统:为自定义工具添加撤销功能,提高用户体验
  3. 研究内置编辑器窗口:通过分析Unity内置窗口的实现,学习专业的UI设计
  4. 探索Package开发:将编辑器扩展打包为Package,便于在多个项目中复用

通过不断实践和探索,你可以创建出功能强大、界面专业的编辑器扩展,为Unity开发工作流带来更多可能性。

【免费下载链接】UnityCsReference Unity C# reference source code. 【免费下载链接】UnityCsReference 项目地址: https://gitcode.com/gh_mirrors/un/UnityCsReference

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值