Unity 编辑器之 EditorTool、EditorToolBar、Overlay属性

Unity面板栏

EditorTool

  • 注意
    • 全局工具,默认收纳在全局工具的图标内
    • 组件工具,按照组件类型,在全局工具图标后边,依次排列

全局工具示例

cs 复制代码
// 放在 Assets/Editor/ 下
using UnityEngine;
using UnityEditor;
using UnityEditor.EditorTools;

[EditorTool("My Slider Tool")]
class MySliderTool : EditorTool
{
    GUIContent m_Icon;

    public override GUIContent toolbarIcon
    {
        get
        {
            if (m_Icon == null)
                m_Icon = new GUIContent("📏", "My Slider Tool");
            return m_Icon;
        }
    }

    public override void OnToolGUI(EditorWindow window)
    {
        if (Selection.transforms.Length == 0) return;

        EditorGUI.BeginChangeCheck();
        Vector3 pos = Tools.handlePosition;

        using (new Handles.DrawingScope(Color.green))
        {
            pos = Handles.Slider(pos, Vector3.right);
        }

        if (EditorGUI.EndChangeCheck())
        {
            Vector3 delta = pos - Tools.handlePosition;
            Undo.RecordObjects(Selection.transforms, "Slider Tool Move");
            foreach (var t in Selection.transforms)
                t.position += delta;
        }
    }
}

组件工具示例

cs 复制代码
// 运行时组件(普通脚本,不在 Editor 文件夹)
using UnityEngine;

public class PlatformPath : MonoBehaviour
{
    public Vector3 start = new Vector3(-5, 0, 0);
    public Vector3 end   = new Vector3( 5, 0, 0);
}
cs 复制代码
using UnityEngine;
using UnityEditor;
using UnityEditor.EditorTools;

// ★ 第二个参数让它变成"组件工具",只有选中带 PlatformPath 的物体才出现
[EditorTool("Path Tool", typeof(PlatformPath))]
class PathTool : EditorTool // 可选实现 IDrawSelectedHandles
{
    public override GUIContent toolbarIcon
        => new GUIContent("⟿", "Edit Platform Path");

    public override void OnActivated()
        => SceneView.lastActiveSceneView?
            .ShowNotification(new GUIContent("Path Tool Active"), 1f);

    public override void OnToolGUI(EditorWindow window)
    {
        if (!(window is SceneView)) return;

        // targets 来自 EditorTool,类型是 Object[]
        foreach (var o in targets)
        {
            if (!(o is PlatformPath p)) continue;

            EditorGUI.BeginChangeCheck();

            var ws = p.transform.TransformPoint(p.start);
            var we = p.transform.TransformPoint(p.end);

            ws = Handles.PositionHandle(ws, Quaternion.identity);
            we = Handles.PositionHandle(we, Quaternion.identity);

            // 画连线便于观察
            Handles.DrawLine(ws, we);

            if (EditorGUI.EndChangeCheck())
            {
                Undo.RecordObject(p, "Edit Path");
                p.start = p.transform.InverseTransformPoint(ws);
                p.end   = p.transform.InverseTransformPoint(we);
                EditorUtility.SetDirty(p);
            }
        }
    }
}

代码中需要注意的点

  • OnToolGui 默认是在3D空间,使用世界坐标绘制,如果想切换2D空间,需要使用Handles.BeginGui/EndGui 开启关闭2D空间
  • TransformPoint 把坐标转换成世界坐标,InverseTransformPoint 把世界坐标转换成局部坐标
  • Handles.DrawingScope:这是一个"状态管理器"。当创建一个它的实例时,它会保存当前的 Gizmos/Handles 绘制状态(包括颜色 Handles.color、矩阵 Handles.matrix 等)
    using ( ... ) { ... }:这是 C# 的语法。当代码执行进入 { } 时,DrawingScope 生效;当代码执行离开 { }(即使是因为报错跳出),Dispose() 方法会被自动调用,DrawingScope 会把之前保存的状态恢复回去。

EditorToolBar

这四个类和EditorTool类似,都是防止在Scene工具栏中,但是可以自定义浮动栏,一般和overlay配合使用

继承自ToolBarOverlay,一次性添加(浮动条)

cs 复制代码
using UnityEditor;
using UnityEditor.Overlays;
using UnityEditor.Toolbars;
using UnityEngine;


[EditorToolbarElement("CustomEditorToolbarButton", typeof(SceneView))]
public class CustomEditorToolbarButton : EditorToolbarButton
{
    public const string ID = "CustomEditorToolbarButton";

    public CustomEditorToolbarButton()
    {
        text = "Custom Editor Toolbar Button";
        clickable.clicked += () => Debug.Log("Custom Editor Toolbar Button Clicked!");
    }
}


[Overlay(typeof(SceneView), "EditorToolBarOverlayExample", true)]
public class EditorToolBarOverlayExample : ToolbarOverlay
{
    public EditorToolBarOverlayExample() : base(CustomEditorToolbarButton.ID)
    {
    }
}

使用Overlay属性,主动添加(面板)

cs 复制代码
using UnityEditor;
using UnityEditor.Overlays;
using UnityEditor.Toolbars;
using UnityEngine;
using UnityEngine.UIElements;


[Overlay(typeof(SceneView), "EditorToolBarExample", true)]
public class EditorToolBarExample : Overlay
{
    public override VisualElement CreatePanelContent()
    {
        base.CreatePanelContent();

        var root = new VisualElement();
        
        // 添加button
        root.Add(new EditorToolbarButton(() => { Debug.Log("Editor Toolbar Button Clicked!"); }));

        // 添加dropdown
        var dropdownButton = new EditorToolbarDropdown(() => { Debug.Log("Editor Toolbar Dropdown Clicked!"); });
        dropdownButton.text = "Dropdown";
        dropdownButton.icon = EditorGUIUtility.IconContent("d_winbtn_win_max").image as Texture2D;
        dropdownButton.clicked += () =>
        {
            GenericMenu menu = new GenericMenu();
            menu.AddItem(new GUIContent("Option 1"), false, () => { Debug.Log("Option 1 Selected"); });
            menu.AddItem(new GUIContent("Option 2"), false, () => { Debug.Log("Option 2 Selected"); });
            menu.AddItem(new GUIContent("Option 3"), false, () => { Debug.Log("Option 3 Selected"); });
            menu.DropDown(dropdownButton.worldBound);
        };
        root.Add(dropdownButton);

        // 添加toggle
        var editorToolbarToggle = new EditorToolbarToggle();
        editorToolbarToggle.text = "Toggle";
        editorToolbarToggle.icon = EditorGUIUtility.IconContent("d_winbtn_win_max").image as Texture2D;
        editorToolbarToggle.RegisterValueChangedCallback(evt =>
        {
            Debug.Log($"Editor Toolbar Toggle Value Changed: {evt.newValue}");
        });
        root.Add(editorToolbarToggle);

        return root;
    }
}

EditorTool 与 Overlay的示例

Unity 官方在 Overlay 脚本参考里给出了一个教科书级示例------核心思路是 EditorTool 内部嵌套定义一个 Overlay 子类 ,然后在工具的 OnActivated / OnWillBeDeactivated 里控制这个 Overlay 的显示与移除。

主要方法:

  • SceneView.AddOverlayToActiveView(m_Overlay)
  • SceneView.RomoveOverlayToActiveView(m_Overlay)
cs 复制代码
using System.Linq;
using UnityEngine;
using UnityEditor;
using UnityEditor.EditorTools;
using UnityEditor.Overlays;

[EditorTool("Offset", typeof(Transform))]
public class OffsetTool : EditorTool
{
    // ★ Overlay 作为 EditorTool 的内部类
    // defaultDisplay = true:第一次被加到画布时就可见
    [Overlay(defaultDisplay = true)]
    class OffsetToolOverlay : Overlay
    {
        Transform[] selection;
        public OffsetToolOverlay(Transform[] targets) =>
            selection = targets;

        public override VisualElement CreatePanelContent()
        {
            var root = new VisualElement();
            root.Add(new Label("Offset Tool Settings"));

            var field = new TextField("Offset");
            field.RegisterValueChangedCallback(evt =>
            {
                // 处理用户输入的逻辑
            });
            root.Add(field);
            return root;
        }
    }

    OffsetToolOverlay m_Overlay;

    // ★ 工具被激活时
    public override void OnActivated()
    {
        m_Overlay = new OffsetToolOverlay(targets.Cast<Transform>().ToArray());

        // ★★ 关键 API:把 Overlay 加到当前激活的 SceneView 上
        SceneView.AddOverlayToActiveView(m_Overlay);
    }

    // ★ 工具被停用时
    public override void OnWillBeDeactivated()
    {
        // ★★ 关键 API:从 SceneView 移除 Overlay
        if (m_Overlay != null)
            SceneView.RemoveOverlayFromActiveView(m_Overlay);
    }

    // 3D 手柄绘制
    public override void OnToolGUI(EditorWindow window)
    {
        if (!(window is SceneView)) return;
        // Handles.Slider(...) 等
    }
}

Q&A

Q:除了SceneWindow,还有别的View可以使用吗?

A:目前只支持SceneWindow,以及实现了ISupportsOverlays接口的自定义EditorWindow

cs 复制代码
using UnityEditor;
using UnityEditor.Overlays;
using UnityEngine.UIElements;

public class MyOverlayWindow : EditorWindow, ISupportsOverlays
{
    [MenuItem("Window/My Overlay Window")]
    public static void ShowWindow() => GetWindow<MyOverlayWindow>();
    
    // 实现 ISupportsOverlays 接口
    // (该接口是 marker interface,不需要实现任何方法)
}

// 然后就可以把 Overlay 注册到这个窗口了
[Overlay(typeof(MyOverlayWindow), "My Panel")]
public class MyOverlay : Overlay
{
    public override VisualElement CreatePanelContent()
    {
        return new Label("Hello from MyOverlayWindow!");
    }
}

Q:Handles类都有什么方法,还有别的类,用来在SceneWindow中操作吗?

A:Handles类,负责绘画3D相关的图形,使用世界坐标,除了它,还有HandleUtility\Tool类,辅助

相关推荐
零基础的修炼2 天前
CUDA知识汇总
开发语言·c++·编辑器
Java后端的Ai之路3 天前
Git pull弹出vim编辑器完整排查指南
开发语言·人工智能·git·编辑器·vim
卷土重去3 天前
第 6 章 Linux 实操篇-Vi 和 Vim 编辑器
linux·编辑器·vim
一叶龙洲5 天前
Ubuntu24.04 Cursor导入vscode配置
vscode·编辑器
好悬给我拽开线6 天前
RoboTTT
ide·vscode·编辑器
山峰哥7 天前
造价算量系统SQL优化,慢查询从9秒压到35毫秒‌
大数据·数据库·sql·编辑器·深度优先
-To be number.wan8 天前
编译原理第三章:编译器是怎么“认字“的?——词法分析全解析
学习·编辑器
gzkuijing8 天前
MDI1PRD23B7‑EQ|Novanta IMS一体化RS-485/422步进电机参数、选型要点与典型应用
人工智能·机器学习·编辑器
晚风叙码9 天前
Linux(三)apt/yum 软件安装与 vim 编辑器入门指南
linux·编辑器·vim
民乐团扒谱机9 天前
【超详细】PyQt6 实现 AU 风格波形图编辑器:多级缩放与采样点逐级渲染,附全过程代码
开发语言·python·编辑器·pyqt·audition·时域·频谱图