1. EditorWindow的核心价值与基础搭建
第一次接触Unity编辑器扩展时,我被Scene窗口里那些可以自由拖拽的坐标轴吸引,但真正让我效率翻倍的却是自定义EditorWindow。这就像给木匠打造专属工具箱——把每天重复的手动操作变成一键完成的智能按钮。
创建基础窗口只需要三步:新建继承EditorWindow的类、添加MenuItem标记、调用Show方法。但实际开发中我推荐更健壮的写法:
using UnityEditor; using UnityEngine; public class CustomToolkit : EditorWindow { [MenuItem("Tools/高级工具面板")] static void Init() { var window = GetWindow<CustomToolkit>(); window.titleContent = new GUIContent("效率工具", EditorGUIUtility.IconContent("d_Prefab Icon").image); window.minSize = new Vector2(300, 400); window.Show(); } }这里有几个实用细节:
- titleContent同时设置窗口标题和图标(使用内置图标资源)
- minSize避免窗口被缩得过小导致UI错乱
- 使用Tools而非自定义菜单路径,符合Unity官方规范
2. 窗口事件的高级响应机制
2.1 实时交互监听
很多教程只教OnGUI绘制,但实际项目更需要事件响应。比如我们需要在场景中选择物体时自动同步数据:
void OnSelectionChange() { if(Selection.activeGameObject != null) { currentTarget = Selection.activeGameObject; Repaint(); // 强制刷新UI } } void OnGUI() { if(currentTarget) { EditorGUILayout.LabelField($"当前选中: {currentTarget.name}"); } }这里有个坑要注意:直接修改EditorWindow字段不会自动触发UI更新,必须手动调用Repaint()。我曾在项目中因为忘记这个导致数据显示延迟,排查了半天。
2.2 智能场景响应
做场景编辑器时,常需要根据场景变化自动更新。比起每帧检测的笨办法,更优雅的方式是:
void OnEnable() { EditorApplication.hierarchyChanged += OnHierarchyChange; SceneView.duringSceneGui += OnSceneGUI; } void OnDisable() { EditorApplication.hierarchyChanged -= OnHierarchyChange; SceneView.duringSceneGui -= OnSceneGUI; } void OnHierarchyChange() { // 当层级变化时执行(新增/删除物体等) } void OnSceneGUI(SceneView sceneView) { // 实时响应场景视图操作 Handles.BeginGUI(); if(Event.current.type == EventType.MouseDrag) { Debug.Log($"鼠标拖动坐标:{Event.current.mousePosition}"); } Handles.EndGUI(); }这种事件订阅模式比老式的autoRepaintOnSceneChange更灵活,还能减少不必要的性能开销。
3. 专业级窗口布局技巧
3.1 多面板协同工作
复杂工具往往需要分栏设计,比如材质编辑器那样的左右布局。通过EditorGUILayout.BeginHorizontal/EndHorizontal可以实现:
void OnGUI() { EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true)); // 左侧面板(30%宽度) EditorGUILayout.BeginVertical(GUILayout.Width(position.width * 0.3f)); DrawObjectList(); EditorGUILayout.EndVertical(); // 右侧面板(70%宽度) EditorGUILayout.BeginVertical(); DrawDetailInspector(); EditorGUILayout.EndVertical(); EditorGUILayout.EndHorizontal(); }实际项目中我发现,给每个区域添加GUILayout.ExpandHeight参数可以避免内容过多时的显示异常。
3.2 智能停靠控制
窗口停靠直接影响用户体验。通过如下代码可以检测窗口状态:
void OnGUI() { if(docked) { EditorGUILayout.HelpBox("窗口已停靠", MessageType.Info); if(GUILayout.Button("浮动窗口")) { this.SetDocked(false); } } else { if(GUILayout.Button("停靠到主窗口")) { var mainWindow = EditorWindow.GetWindow<SceneView>(); this.AddTab(mainWindow); } } }这里有个隐藏技巧:调用ShowTab方法可以实现类似Visual Studio的标签式窗口管理,让多个工具窗口共享同一区域。
4. 性能优化实战方案
4.1 按需刷新机制
EditorWindow默认每帧调用OnGUI,这在复杂界面中会造成性能浪费。我的优化方案是:
private bool needRefresh; void Update() { // 只有标记为需要刷新时才重绘 if(needRefresh) { Repaint(); needRefresh = false; } } void OnSelectionChange() { needRefresh = true; // 外部事件触发刷新 }对于数据看板类工具,可以结合EditorApplication.update实现定时刷新:
void OnEnable() { EditorApplication.update += IntervalUpdate; } void IntervalUpdate() { if(Time.realtimeSinceStartup - lastUpdateTime > 1.0f) { needRefresh = true; lastUpdateTime = Time.realtimeSinceStartup; } }4.2 资源加载优化
工具窗口经常需要加载预览图等资源。错误示范是直接使用Resources.Load:
// 不推荐(每次OnGUI都加载) var tex = Resources.Load<Texture2D>("icon");正确做法是在OnEnable预加载,并在OnDisable释放:
private Texture2D cachedIcon; void OnEnable() { cachedIcon = AssetDatabase.LoadAssetAtPath<Texture2D>( "Assets/Editor/Icons/tool_icon.png"); } void OnDisable() { if(cachedIcon != null) { Resources.UnloadAsset(cachedIcon); } }在最近参与的MMO项目里,通过这种优化使编辑器内存占用下降了40%。