news 2026/8/24 15:19:35

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Unity 编辑器之 EditorTool、EditorToolBar、Overlay属性

Unity面板栏

EditorTool

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

全局工具示例

// 放在 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; } } }

组件工具示例

// 运行时组件(普通脚本,不在 Editor 文件夹) using UnityEngine; public class PlatformPath : MonoBehaviour { public Vector3 start = new Vector3(-5, 0, 0); public Vector3 end = new Vector3( 5, 0, 0); }
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,一次性添加(浮动条)

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属性,主动添加(面板)

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)
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

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类,辅助

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/24 15:19:04

单片机毕设选题推荐:基于 STM32 单片机的阈值可调智能快递柜软硬件设计 基于 STM32 的短信验证码存取件快递柜控制系统研究(017104)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/8/24 15:17:02

Rapier.js 物理引擎快速上手指南

Rapier.js 物理引擎快速上手指南 【免费下载链接】rapier.js Official JavaScript bindings for the Rapier physics engine ⚠️ MIGRATED TO https://github.com/dimforge/rapier/tree/master/typescript ⚠️ 项目地址: https://gitcode.com/gh_mirrors/ra/rapier.js …

作者头像 李华
网站建设 2026/8/24 15:15:37

搭建elk环境并接入frostmourne,实现监控报警效果(五)

#修改权限目录,id 1000的账号chown -R meng.meng data*chmod -R 777 data*#docker-compose.yamlversion: 3.8 services:filebeat:# 镜像名称image: docker.elastic.co/beats/filebeat:8.6.0# 容器名称hostname: filebeatcontainer_name: filebeat#ports:# - 5044:5044volumes:…

作者头像 李华
网站建设 2026/8/24 15:07:13

小程序面试指南

一、为什么会出现小程序&#xff1f; App太重&#xff0c;H5网页太糙 小程序是 App 和 H5 网页之间的“黄金平衡点”。它既保留了 App 流畅的体验和强大的功能&#xff0c;又拥有了 H5 网页无需下载、极其轻便的优势。 App、H5、小程序对比 对比维度原生AppH5网页小程序获取…

作者头像 李华