🎨 Unity EditorGUI 控件完全手册:构建专业编辑器工具的核心技能

💡 编辑器工具的核心

  • 想开发自己的编辑器窗口,却不知道该用什么控件?
  • EditorGUI 和 EditorGUILayout 有什么区别,该怎么选?
  • 如何实现拖拽、弹出菜单等高级交互?
  • 怎样让工具界面既美观又易用?

这篇文章! 将系统介绍 Unity 编辑器 GUI 控件体系,从基础按钮到高级交互,助你构建专业级编辑器工具!

一、EditorGUI 与 EditorGUILayout 概述

Unity 提供了两套编辑器 GUI 系统:

系统 区别 使用场景
EditorGUI 需要手动指定 Rect 精确布局控制
EditorGUILayout 自动布局,带标签 快速开发编辑器工具

1.1 创建 EditorWindow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using UnityEngine;
using UnityEditor;

public class ExampleWindow : EditorWindow
{
[MenuItem("Tools/Example Window")]
static void ShowWindow()
{
var window = GetWindow<ExampleWindow>("示例窗口");
window.minSize = new Vector2(300, 200);
}

private void OnGUI()
{
EditorGUILayout.LabelField("Hello, Editor!", EditorStyles.boldLabel);
}
}

二、基础控件

2.1 Label 标签

1
2
3
4
5
6
7
8
9
10
11
// 普通标签
EditorGUILayout.LabelField("这是一个标签");

// 带样式的标签
EditorGUILayout.LabelField("粗体标题", EditorStyles.boldLabel);
EditorGUILayout.LabelField("白色标签", EditorStyles.whiteLabel);
EditorGUILayout.LabelField("灰色小字", EditorStyles.miniLabel);

// 可选择标签(可复制文本)
string readOnlyText = "这段文本可以选择和复制";
EditorGUILayout.SelectableLabel(readOnlyText);

2.2 Toggle 开关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bool showButton = true;
bool toggleValue = false;

private void OnGUI()
{
// 基本开关
showButton = EditorGUILayout.Toggle("显示按钮", showButton);

// 左侧标签开关
toggleValue = EditorGUILayout.ToggleLeft("启用功能", toggleValue);

// 检测值变化
EditorGUI.BeginChangeCheck();
toggleValue = EditorGUILayout.ToggleLeft("监听变化的开关", toggleValue);
if (EditorGUI.EndChangeCheck())
{
Debug.Log($"开关状态: {toggleValue}");
}

// 按钮样式开关
bool buttonState = GUILayout.Toggle(true, "开", "Button");
}

2.3 ToggleGroup 分组开关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bool groupEnabled = false;
float sliderValue = 0.5f;
string text = "";

private void OnGUI()
{
groupEnabled = EditorGUILayout.BeginToggleGroup("可选设置", groupEnabled);
{
EditorGUI.BeginDisabledGroup(!groupEnabled);
{
sliderValue = EditorGUILayout.Slider("滑动条", sliderValue, 0, 1);
text = EditorGUILayout.TextField("文本", text);
}
EditorGUI.EndDisabledGroup();
}
EditorGUILayout.EndToggleGroup();
}

三、文本输入控件

3.1 TextField 文本框

1
2
3
4
5
6
7
8
9
10
11
string textValue = "";
string multiLineText = "";

private void OnGUI()
{
// 单行文本
textValue = EditorGUILayout.TextField("单行文本:", textValue);

// 密码字段
string password = EditorGUILayout.PasswordField("密码:", password);
}

3.2 TextArea 多行文本

1
2
3
4
5
6
7
8
string description = "";

private void OnGUI()
{
// 最小3行,最大10行
description = EditorGUILayout.TextArea(description,
GUILayout.Height(EditorGUIUtility.singleLineHeight * 5));
}

四、数值控件

4.1 Slider 滑动条

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
float floatValue = 0.5f;
int intValue = 10;

private void OnGUI()
{
// 浮点滑动条
floatValue = EditorGUILayout.Slider("浮点值", floatValue, 0, 1);

// 整数滑动条
intValue = EditorGUILayout.IntSlider("整数值", intValue, 0, 100);

// 最小最大滑动条
float minVal = -10f;
float maxVal = 10f;
EditorGUILayout.MinMaxSlider(ref minVal, ref maxVal, -20f, 20f);
EditorGUILayout.LabelField($"范围: {minVal:F1} ~ {maxVal:F1}");
}

4.2 MultiFloatField 多数值输入

1
2
3
4
5
6
7
8
9
10
11
12
13
float[] values = new float[] { 0, 1, 2 };
GUIContent[] labels = new GUIContent[]
{
new GUIContent("X"),
new GUIContent("Y"),
new GUIContent("Z")
};

private void OnGUI()
{
Rect rect = EditorGUILayout.GetControlRect();
EditorGUI.MultiFloatField(rect, "坐标", labels, values);
}

五、选择控件

5.1 Popup 下拉菜单

1
2
3
4
5
6
7
string[] options = { "立方体", "球体", "平面" };
int selectedIndex = 0;

private void OnGUI()
{
selectedIndex = EditorGUILayout.Popup("选择形状", selectedIndex, options);
}

5.2 EnumPopup 枚举菜单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum PrimitiveType
{
Cube = 0,
Sphere = 1,
Cylinder = 2,
Plane = 3
}

PrimitiveType selectedType = PrimitiveType.Cube;

private void OnGUI()
{
selectedType = (PrimitiveType)EditorGUILayout.EnumPopup("基础形状", selectedType);
}

5.3 IntPopup 整数选择

1
2
3
4
5
6
7
8
int selectedSize = 1;
string[] names = { "普通", "双倍", "四倍" };
int[] values = { 1, 2, 4 };

private void OnGUI()
{
selectedSize = EditorGUILayout.IntPopup("缩放比例", selectedSize, names, values);
}

5.4 Toolbar 工具栏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int toolbarIndex = 0;

private void OnGUI()
{
// 工具栏
toolbarIndex = GUILayout.Toolbar(toolbarIndex, new string[] { "选项1", "选项2", "选项3" });

// 选择网格
int gridIndex = 0;
gridIndex = GUILayout.SelectionGrid(gridIndex,
new string[] { "A", "B", "C", "D" },
4, // 每行4个
EditorStyles.toolbarButton);
}

六、对象引用控件

6.1 ObjectField 对象字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GameObject selectedObject;
Material selectedMaterial;
Texture selectedTexture;

private void OnGUI()
{
// 通用对象字段
selectedObject = EditorGUILayout.ObjectField("游戏对象", selectedObject, typeof(GameObject), true);

// 指定类型对象
selectedMaterial = EditorGUILayout.ObjectField("材质", selectedMaterial, typeof(Material), false);

// 带尺寸的对象预览
var options = new[] { GUILayout.Width(64), GUILayout.Height(64) };
selectedTexture = EditorGUILayout.ObjectField("贴图", selectedTexture, typeof(Texture), false, options);
}

6.2 TagField 和 LayerField

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
string selectedTag = "Untagged";
int selectedLayer = 0;

private void OnGUI()
{
// 标签字段
selectedTag = EditorGUILayout.TagField("标签", selectedTag);

// 层字段
selectedLayer = EditorGUILayout.LayerField("层", selectedLayer);

// 批量设置
if (GUILayout.Button("应用到选中物体"))
{
foreach (GameObject obj in Selection.gameObjects)
{
obj.tag = selectedTag;
obj.layer = selectedLayer;
}
}
}

七、向量与颜色控件

7.1 Vector2Field / Vector3Field

1
2
3
4
5
6
7
8
Vector2 position = Vector2.zero;
Vector3 rotation = Vector3.zero;

private void OnGUI()
{
position = EditorGUILayout.Vector2Field("位置", position);
rotation = EditorGUILayout.Vector3Field("旋转", rotation);
}

7.2 ColorField 颜色字段

1
2
3
4
5
6
7
8
9
Color colorValue = Color.white;

private void OnGUI()
{
colorValue = EditorGUILayout.ColorField("颜色", colorValue);

// 预览颜色
EditorGUI.DrawRect(EditorGUILayout.GetControlRect(false, 50), colorValue);
}

八、Knob 旋钮控件

圆形旋钮控件,常用于角度调整。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
float angle = 270f;

private void OnGUI()
{
angle = EditorGUILayout.Knob(
Vector2.one * 64, // 旋钮大小
angle, // 当前值
0, // 最小值
360, // 最大值
"°", // 单位
Color.gray, // 背景色
Color.red, // 激活色
true // 显示当前值
);

EditorGUILayout.LabelField($"角度: {angle:F1}°");
}

九、布局系统

9.1 IndentLevel 缩进层级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void OnGUI()
{
EditorGUILayout.LabelField("父级内容");

// 增加缩进
EditorGUI.indentLevel++;

EditorGUILayout.LabelField("子级内容 1");
EditorGUILayout.LabelField("子级内容 2");

// 恢复缩进
EditorGUI.indentLevel--;

EditorGUILayout.LabelField("父级内容");
}

9.2 BeginHorizontal / BeginVertical

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void OnGUI()
{
// 水平布局
EditorGUILayout.BeginHorizontal();
{
if (GUILayout.Button("按钮1", GUILayout.Width(100)))
Debug.Log("按钮1");
if (GUILayout.Button("按钮2", GUILayout.Width(100)))
Debug.Log("按钮2");
GUILayout.FlexibleSpace();
}
EditorGUILayout.EndHorizontal();

// 垂直布局
EditorGUILayout.BeginVertical();
{
EditorGUILayout.LabelField("垂直布局内容1");
EditorGUILayout.LabelField("垂直布局内容2");
}
EditorGUILayout.EndVertical();
}

9.3 using 语法糖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void OnGUI()
{
// 使用 using 自动调用 EndHorizontal
using (new EditorGUILayout.HorizontalScope())
{
GUILayout.Button("左按钮");
GUILayout.Button("右按钮");
}

// 自定义 Scope
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
{
EditorGUILayout.LabelField("这是一个带边框的区域");
}
}

9.4 自定义 Scope

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class BoxScope : GUI.Scope
{
public BoxScope()
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
}

protected override void CloseScope()
{
EditorGUILayout.EndVertical();
}
}

// 使用
private void OnGUI()
{
using (new BoxScope())
{
EditorGUILayout.LabelField("框内内容");
}
}

十、Foldout 折叠面板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
bool showWeapons = true;
bool showSettings = false;

private void OnGUI()
{
// 折叠面板
showWeapons = EditorGUILayout.Foldout(showWeapons, "武器设置", true);
if (showWeapons)
{
EditorGUI.indentLevel++;
EditorGUILayout.FloatField("伤害1", 10);
EditorGUILayout.FloatField("伤害2", 20);
EditorGUI.indentLevel--;
}

showSettings = EditorGUILayout.BeginFoldoutHeaderGroup(showSettings, "高级设置");
if (showSettings)
{
EditorGUILayout.Toggle("启用调试", false);
EditorGUILayout.Toggle("显示FPS", true);
}
EditorGUILayout.EndFoldoutHeaderGroup();
}

十一、ScrollView 滚动视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Vector2 scrollPosition = Vector2.zero;
string[] logMessages = new string[50];

private void OnGUI()
{
// 滚动视图
scrollPosition = EditorGUILayout.BeginScrollView(
scrollPosition,
GUILayout.Height(200)
);
{
foreach (var log in logMessages)
{
EditorGUILayout.LabelField(log);
}
}
EditorGUILayout.EndScrollView();
}

十二、文件/文件夹选择

12.1 打开文件夹面板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
string savePath = "Assets/";

private void OnGUI()
{
EditorGUILayout.LabelField("保存路径", EditorStyles.boldLabel);

EditorGUILayout.BeginHorizontal();
{
savePath = EditorGUILayout.TextField(savePath);
if (GUILayout.Button("浏览", GUILayout.Width(80)))
{
savePath = EditorUtility.SaveFolderPanel(
"选择保存文件夹",
savePath,
Application.dataPath
);
}
}
EditorGUILayout.EndHorizontal();
}

12.2 打开文件面板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
string filePath = "";

private void OnGUI()
{
if (GUILayout.Button("选择文件"))
{
filePath = EditorUtility.OpenFilePanel(
"选择文件",
Application.dataPath,
"png;jpg;txt" // 文件扩展名过滤
);
}

if (!string.IsNullOrEmpty(filePath))
{
EditorGUILayout.LabelField($"已选: {filePath}");
}
}

十三、拖拽操作 DragAndDrop

实现从 Project 窗口拖拽资源到 EditorWindow。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
string draggedPath = "";
Object draggedObject;

private void OnGUI()
{
EditorGUILayout.LabelField("拖拽区域");

// 获取拖拽区域
Rect dropArea = EditorGUILayout.GetControlRect(false, 100);
GUI.Box(dropArea, "将资源拖拽到这里");

// 处理拖拽事件
Event currentEvent = Event.current;

if (dropArea.Contains(currentEvent.mousePosition))
{
if (currentEvent.type == EventType.DragUpdated)
{
// 改变鼠标样式
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
currentEvent.Use();
}
else if (currentEvent.type == EventType.DragPerform)
{
// 接受拖拽
DragAndDrop.AcceptDrag();
draggedObject = DragAndDrop.objectReferences[0];
draggedPath = AssetDatabase.GetAssetPath(draggedObject);
currentEvent.Use();
}
}

// 显示拖拽结果
if (!string.IsNullOrEmpty(draggedPath))
{
EditorGUILayout.HelpBox($"已拖拽: {draggedPath}", MessageType.Info);
}
}

拖拽事件类型

事件类型 说明
DragUpdated 拖拽进入区域,持续触发
DragPerform 释放鼠标,完成拖拽
DragExited 拖拽离开区域

VisualMode 选项

模式 说明
None 不接受拖拽
Copy 复制操作
Move 移动操作
Link 链接操作
Generic 通用操作

十四、Box 和 Notification

14.1 绘制盒子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void OnGUI()
{
// 使用现有样式
EditorGUILayout.HelpBox("这是一个信息提示框", MessageType.Info);
EditorGUILayout.HelpBox("这是一个警告框", MessageType.Warning);
EditorGUILayout.HelpBox("这是一个错误框", MessageType.Error);

// 自定义盒子
Rect boxRect = EditorGUILayout.GetControlRect(false, 60);
GUI.Box(boxRect, "自定义盒子");

// 使用 EditorGUI 绘制
EditorGUI.DrawRect(EditorGUILayout.GetControlRect(false, 30), Color.gray);
}

14.2 窗口通知

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void OnGUI()
{
if (GUILayout.Button("显示通知"))
{
ShowNotification(new GUIContent("这是一个临时通知消息"));
}

if (GUILayout.Button("显示持久通知"))
{
ShowNotification(new GUIContent("点击关闭按钮移除"), 5f);
}
}

// 手动移除通知
private void RemoveNotification()
{
RemoveNotification();
}

十五、完整示例:属性编辑器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
using UnityEngine;
using UnityEditor;

public class PlayerProfileWindow : EditorWindow
{
private string playerName = "Player";
private int level = 1;
private float health = 100f;
private Color playerColor = Color.white;
private bool showAdvanced = false;
private Vector2 scrollPosition;

[MenuItem("Tools/Player Profile")]
static void ShowWindow()
{
var window = GetWindow<PlayerProfileWindow>("玩家配置");
window.Show();
}

private void OnGUI()
{
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
{
// 基础信息
EditorGUILayout.LabelField("基础信息", EditorStyles.boldLabel);
playerName = EditorGUILayout.TextField("玩家名称", playerName);
level = EditorGUILayout.IntSlider("等级", level, 1, 100);

EditorGUILayout.Space(10);

// 属性值
EditorGUILayout.LabelField("属性值", EditorStyles.boldLabel);
health = EditorGUILayout.Slider("生命值", health, 0, 100);
playerColor = EditorGUILayout.ColorField("角色颜色", playerColor);

EditorGUILayout.Space(10);

// 高级设置(折叠面板)
showAdvanced = EditorGUILayout.Foldout(showAdvanced, "高级设置");
if (showAdvanced)
{
EditorGUI.indentLevel++;
EditorGUILayout.Toggle("启用调试", false);
EditorGUILayout.Toggle("显示伤害数字", true);
EditorGUILayout.Toggle("自动存档", true);
EditorGUI.indentLevel--;
}

EditorGUILayout.Space(10);

// 预览区域
EditorGUILayout.LabelField("预览", EditorStyles.boldLabel);
Rect previewRect = EditorGUILayout.GetControlRect(false, 60);
EditorGUI.DrawRect(previewRect, playerColor);
GUI.Label(previewRect, $"Lv.{level} {playerColor}", EditorStyles.centeredGreyMiniLabel);

EditorGUILayout.Space(10);

// 操作按钮
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("保存配置", GUILayout.Height(30)))
{
SaveProfile();
}
if (GUILayout.Button("重置", GUILayout.Height(30)))
{
ResetProfile();
}
}
}
EditorGUILayout.EndScrollView();
}

private void SaveProfile()
{
Debug.Log($"保存: {playerName}, Lv.{level}");
}

private void ResetProfile()
{
playerName = "Player";
level = 1;
health = 100f;
playerColor = Color.white;
}
}

十六、常用控件速查表

控件 方法 说明
标签 LabelField 显示文本
文本 TextField, TextArea 单行/多行输入
开关 Toggle, ToggleLeft 布尔值开关
滑动条 Slider, IntSlider 数值滑动条
下拉 Popup, EnumPopup 下拉选择菜单
对象 ObjectField 对象引用
颜色 ColorField 颜色选择器
向量 Vector2Field, Vector3Field 向量输入
折叠 Foldout 可折叠面板
分组 BeginToggleGroup 条件禁用组
滚动 BeginScrollView 滚动视图
布局 BeginHorizontal/Vertical 水平/垂直布局

十七、总结

本文介绍了 Unity 编辑器 GUI 的常用控件:

类别 内容
基础控件 Label, Toggle, TextField
数值控件 Slider, IntSlider, MinMaxSlider
选择控件 Popup, EnumPopup, Toolbar
对象控件 ObjectField, TagField, LayerField
布局系统 Horizontal, Vertical, ScrollView, Foldout
交互操作 DragAndDrop, Notification

💡 开发建议

  • 优先使用 EditorGUILayout 快速开发
  • 使用 using 语法简化布局代码
  • 合理使用 BeginChangeCheck 监听值变化
  • HelpBox 显示提示信息

下一篇将详细介绍 EditorWindow 的开发技巧


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1487842110@qq.com