🎯 Unity 插件开发实战:编辑器扩展核心技巧

🎯 Unity 插件开发实战:编辑器扩展核心技巧

image.png

 

Unity 拥有强大的编辑器扩展能力,让开发者可以为团队或自己量身打造工具。在本文中,我们将以一个“场景标注工具”的开发思路为例,总结 Unity 编辑器中的高频实用技巧,包括:

  • Scene 视图右键菜单
  • EditorWindow 界面分页
  • Scene 中绘制图标与文字
  • 支持 Undo 的删除逻辑
  • 捕捉编辑器视图截图
  • 导出文档与持久化配置

示例源码


1️⃣ Scene 视图右键菜单的实现

image.png

Unity 不原生支持 Scene 右键菜单,但我们可以借助 SceneView.duringSceneGuiGenericMenu 自定义逻辑:

SceneView.duringSceneGui这是一个事件,在SceneView绘制期间每一帧都会被调用。我们通过+=订阅了OnSceneGUI方法,这样就能在SceneView中处理GUI事件。

 
 
 
x
 
 
 
 
[InitializeOnLoad]
public static class SceneContextMenu {
    static SceneContextMenu() {
        SceneView.duringSceneGui += OnSceneGUI;
    }

    static void OnSceneGUI(SceneView view) {
        if (Event.current.type == EventType.ContextClick) {
            Vector2 mouse = Event.current.mousePosition;
            Vector3 world = HandleUtility.GUIPointToWorldRay(mouse).origin;

            GenericMenu menu = new GenericMenu();
            menu.AddItem(new GUIContent("Add Menu Here"), false, () => {
                Debug.Log("Add Menu at " + world);
            });
            menu.ShowAsContext();
            Event.current.Use();
        }
    }
}
 

2️⃣ EditorWindow 分页系统

image.png

你可以用 GUILayout.Toolbar 快速实现简易分页:

 
 
 
xxxxxxxxxx
 
 
 
 
int tabIndex = GUILayout.Toolbar(currentTab, new[] { "Markers", "Settings" });
switch (tabIndex) {
    case 0: DrawMarkerList(); break;
    case 1: DrawSettings(); break;
}
 

再配合:

 
 
 
xxxxxxxxxx
 
 
 
 
[MenuItem("Tools/Scene Marker Manager")]
public static void OpenWindow() {
    GetWindow<YourWindowClass>("Marker Manager");
}
 

3️⃣ SceneView 中绘制 GUI 内容

image.png

标记信息如何实时显示?可使用:

 
 
 
xxxxxxxxxx
 
 
 
 
Handles.BeginGUI();
Vector2 guiPos = HandleUtility.WorldToGUIPoint(markerWorldPos);
GUI.Label(new Rect(guiPos.x, guiPos.y, 200, 20), "📍 Important Marker");
Handles.EndGUI();
 

4️⃣ 删除与 Undo 操作

支持 Undo 非常关键:

 
 
 
xxxxxxxxxx
 
 
 
 
Undo.RecordObject(target, "Update Marker");
target.note = "Updated";

Undo.DestroyObjectImmediate(target.gameObject);
 

这会让你的工具“Ctrl+Z” 友好,减少误删风险。


5️⃣ 捕捉 Scene 视图截图

如果你想保存当前 Scene 视图(不是 Game 视图),可以这么做:

 
 
 
xxxxxxxxxx
 
 
 
 
SceneView sv = SceneView.lastActiveSceneView;
Camera cam = sv.camera;

RenderTexture rt = new RenderTexture(1024, 768, 24);
cam.targetTexture = rt;
cam.Render();

RenderTexture.active = rt;
Texture2D tex = new Texture2D(1024, 768, TextureFormat.RGB24, false);
tex.ReadPixels(new Rect(0, 0, 1024, 768), 0, 0);
tex.Apply();

byte[] bytes = tex.EncodeToPNG();
File.WriteAllBytes(path, bytes);

// 清理资源
cam.targetTexture = null;
RenderTexture.active = null;
Object.DestroyImmediate(rt);
 

6️⃣ 保存配置参数(EditorPrefs)

想保存设置值供下次启动使用?可用:

 
 
 
xxxxxxxxxx
 
 
 
 
EditorPrefs.SetFloat("SceneMarker_MaxIconSize", 36f);
float iconSize = EditorPrefs.GetFloat("SceneMarker_MaxIconSize", 28f);
 

适合存储像“图标大小”、“显示范围”等偏好值。


7️⃣ API详解

1. 核心结构

 
 
 
xxxxxxxxxx
 
 
 
 
[InitializeOnLoad]
public static class SceneContextMenu
 
  • [InitializeOnLoad]: Unity编辑器特性,在编辑器启动或脚本重新编译后自动调用静态构造函数
  • static class: 静态类确保只存在一个实例,适合编辑器工具

2. 事件订阅

 
 
 
xxxxxxxxxx
 
 
 
 
SceneView.duringSceneGui += OnSceneGUI;
 
  • SceneView.duringSceneGui: Unity编辑器事件,在Scene视图每次渲染GUI时触发
  • 订阅模式:通过+=添加事件处理函数

3. 事件处理

 
 
 
xxxxxxxxxx
 
 
 
 
if (Event.current.type == EventType.MouseDown && Event.current.button == 1)
 
  • Event.current: 当前正在处理的GUI事件
  • EventType.MouseDown: 鼠标按下事件类型
  • Event.current.button == 1: 检测右键点击(0=左键, 1=右键)

4. 坐标转换

 
 
 
xxxxxxxxxx
 
 
 
 
Vector3 worldPosition = GetWorldPosition(mousePosition, view);
 
 
 
 
xxxxxxxxxx
 
 
 
 
Ray ray = HandleUtility.GUIPointToWorldRay(mousePosition);
 
  • HandleUtility.GUIPointToWorldRay: 将屏幕坐标转换为世界空间中的射线

  • 射线检测流程:

    1. 优先检测场景中的碰撞体
    2. 使用场景视图的基准平面作为备用
    3. 最后使用默认距离

5. 上下文菜单

 
 
 
xxxxxxxxxx
 
 
 
 
GenericMenu menu = new GenericMenu();
 
  • GenericMenu: Unity编辑器API,用于创建自定义上下文菜单

菜单项添加:

 
 
 
xxxxxxxxxx
 
 
 
 
menu.AddItem(new GUIContent("Create/Marker"), false, () => CreateMarker(...));
 
  • 参数1: GUIContent - 定义菜单项显示文本和路径(使用/创建子菜单)
  • 参数2: bool isChecked - 是否显示选中标记
  • 参数3: GenericMenu.MenuFunction - 点击时的回调函数

菜单分隔符:

 
 
 
xxxxxxxxxx
 
 
 
 
menu.AddSeparator("Utilities/");
 

6. 对象创建功能

 
 
 
xxxxxxxxxx
 
 
 
 
GameObject.CreatePrimitive(PrimitiveType.Cube);
 
  • 创建Unity内置原始几何体
  • 支持的类型:Cube, Sphere, Cylinder, Plane等

7. 世界坐标计算

 
 
 
xxxxxxxxxx
 
 
 
 
Plane groundPlane = new Plane(Vector3.up, view.pivot);
 
  • 创建XZ平面(Y轴向上)
  • view.pivot: 当前场景视图的基准点位置
 
 
 
xxxxxxxxxx
 
 
 
 
groundPlane.Raycast(ray, out distance);
 
  • 计算射线与平面的交点

8. 编辑器操作集成

 
 
 
xxxxxxxxxx
 
 
 
 
Undo.RegisterCreatedObjectUndo(createdObject, undoMessage);
 
  • Undo系统:注册创建操作到Unity撤销栈
  • 允许用户按Ctrl+Z撤销对象创建
 
 
 
xxxxxxxxxx
 
 
 
 
Selection.activeObject = createdObject;
view.FrameSelected();
 
  • 自动选择新创建的对象
  • 将场景视图镜头对准选中对象

9. 自定义编辑器组件

 
 
 
xxxxxxxxxx
 
 
 
 
obj.AddComponent<Gizmo>();
 
 
 
 
xxxxxxxxxx
 
 
 
 
void OnDrawGizmos() 
{
    Gizmos.DrawWireSphere(transform.position, 0.5f);
}
 
  • OnDrawGizmos: Unity回调方法,在场景视图中绘制辅助图形
  • Gizmos类:提供绘制基本形状的API
 
 
 
xxxxxxxxxx
 
 
 
 
void OnGUI() 
{
    Vector3 pos = Camera.current.WorldToScreenPoint(transform.position);
    GUI.Label(...);
}
 
  • OnGUI: 在场景视图中绘制2D GUI
  • WorldToScreenPoint: 将3D世界坐标转换为屏幕坐标

✅ 总结:编辑器开发 = 自定义你的工作方式

通过上面这些实战片段你会发现,Unity 编辑器的可玩性极强。无论是团队协作、可视化调试,还是日常效率提升,学会使用编辑器扩展能力将是中高级程序员的关键跳板

posted @ 2025-07-17 15:09  世纪末の魔术师  阅读(50)  评论(0)    收藏  举报