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