UnityCsReference编辑器扩展:MenuItem与自定义窗口开发指南
在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#:ShiftF1-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。
窗口创建基础
创建自定义窗口的基本步骤:
- 创建继承
EditorWindow的类 - 添加
MenuItem用于打开窗口 - 实现
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} 个资源", "确定");
}
}
功能扩展建议
该工具可以进一步扩展以下功能:
- 资源类型筛选:添加对不同资源类型的筛选功能
- 重命名规则保存:允许保存和加载重命名规则
- 预览功能:在实际重命名前显示预览效果
- 撤销支持:添加撤销功能,防止误操作
高级技巧与性能优化
菜单命令的验证与优先级管理
复杂菜单系统需要合理管理菜单项的可见性和启用状态。通过验证函数可以实现上下文相关的菜单行为:
// 只有选中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元素或频繁刷新的窗口,需要注意性能优化:
- 减少OnGUI调用:避免在OnGUI中执行复杂计算
- 使用延迟刷新:对于频繁变化的数据,使用EditorApplication.delayCall延迟刷新
- 缓存资源引用:缓存频繁使用的GUIStyle和纹理
- 分批处理数据:处理大量数据时使用协程分批处理
// 使用延迟刷新
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和自定义窗口的开发方法,从基础概念到实际应用,涵盖了创建编辑器命令、设计交互界面和优化性能等多个方面。通过这些技术,开发者可以根据项目需求定制高效的编辑器工具,显著提升开发效率。
进阶学习资源
- 官方文档:LICENSE.md
- 菜单系统源码:Editor/Mono/MenuItem.cs
- 窗口系统源码:Editor/Mono/ContainerWindow.cs
- UI控件库:Editor/Mono/GUI/
后续建议
- 学习EditorWindow生命周期:深入理解窗口的创建、显示、刷新和销毁过程
- 掌握Undo系统:为自定义工具添加撤销功能,提高用户体验
- 研究内置编辑器窗口:通过分析Unity内置窗口的实现,学习专业的UI设计
- 探索Package开发:将编辑器扩展打包为Package,便于在多个项目中复用
通过不断实践和探索,你可以创建出功能强大、界面专业的编辑器扩展,为Unity开发工作流带来更多可能性。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



