Unity编辑器扩展:从EditorStyles到自定义GUIStyle的UI美化实战
1. 项目概述:为什么Unity编辑器样式值得深究?
如果你在Unity里做过编辑器扩展,无论是简单的工具窗口,还是复杂的资产管理器,肯定都遇到过界面美化的问题。Unity自带的默认控件,比如EditorGUILayout.TextField或者EditorGUILayout.Toggle,功能上没问题,但样式上往往千篇一律,和你的项目风格格格不入。更头疼的是,当你需要把多个控件组合成一个复杂的UI块,或者想实现一个带图标、颜色醒目的按钮时,光靠默认控件就显得捉襟见肘了。
这就是EditorStyles和自定义样式登场的时刻。它们不是锦上添花,而是决定你工具专业度和易用性的关键。一个布局清晰、视觉反馈明确、风格统一的编辑器工具,能极大提升开发者的使用体验和工作效率。想象一下,你花一周写了个强大的资源批处理工具,结果因为界面丑陋、操作反直觉,团队没人愿意用,这该多可惜。
网络上关于Unity编辑器扩展的教程很多,但大多集中在功能实现上,对样式的讲解往往浅尝辄止,或者只给出一段“魔法代码”而不解释其所以然。这导致很多开发者要么忍受默认的“灰盒子”界面,要么从网上东拼西凑样式代码,一旦出问题就无从调试。本指南的目的,就是彻底拆解Unity编辑器UI的样式系统,从内置的EditorStyles库开始,一步步深入到完全自定义的GUIStyle,让你不仅能“用”样式,更能“懂”样式,最终打造出独一无二的、高度定制化的编辑器界面。
2. 核心概念解析:GUIStyle与EditorStyles的基石
要玩转编辑器样式,必须先理解两个核心类:GUIStyle和EditorStyles。它们是所有UI绘制的底层支撑。
2.1 GUIStyle:样式的原子单位
你可以把GUIStyle理解为一个UI控件的“皮肤”或“样式模板”。它不仅仅定义了颜色和字体,而是一个包含了控件所有视觉和布局状态的完整描述符。一个GUIStyle对象主要由以下属性构成:
- normal / hover / active / focused / onNormal...: 这些是
GUIStyleState对象,分别定义了控件在不同交互状态(正常、鼠标悬停、按下、获得焦点、开启状态等)下的视觉效果,最核心的是其background(背景纹理)和textColor(文字颜色)。 - border: 一个
RectOffset对象,定义了背景纹理的“九宫格”拉伸边界。这对于创建可伸缩的按钮、窗口边框至关重要。它指定了上、下、左、右四个方向的不拉伸区域。 - margin / padding: 同样是
RectOffset对象。margin是此样式控件外部与其他控件之间的间隔;padding是内部内容(如文字)与控件边缘之间的间隔。 - alignment:
TextAnchor枚举,控制文本在控件矩形区域内的对齐方式(如左上、居中、右下等)。 - fontStyle:
FontStyle枚举,控制文本是否加粗、倾斜等。 - fixedWidth / fixedHeight, stretchWidth / stretchHeight: 控制控件的尺寸行为。是固定大小,还是水平/垂直拉伸以填充可用空间。
- wordWrap: 布尔值,控制文本是否自动换行。
理解GUIStyle的关键在于,Unity的IMGUI(即时模式GUI)系统在绘制每一个控件(如GUI.Button(rect, “text”, style))时,都会查询传入的GUIStyle对象,根据控件当前的状态,选择对应的GUIStyleState,并结合border进行九宫格渲染,最终将文本按照alignment、padding等属性绘制在正确的位置。
2.2 EditorStyles:Unity为你准备好的工具箱
EditorStyles是一个静态类,里面预定义了海量在Unity编辑器内部使用的GUIStyle实例。这些样式保证了编辑器本身(如Inspector、Project窗口)视觉上的一致性。对于我们开发者来说,它是快速构建专业编辑器UI的宝藏库。
EditorStyles中的样式大致可以分为几类:
- 标签类:如
label(普通标签)、boldLabel(粗体标签)、miniLabel(小号标签),用于显示只读文本。 - 按钮类:如
button(普通按钮)、miniButton(小按钮)、toolbarButton(工具栏按钮)。 - 文本框类:如
textField(单行文本框)、textArea(多行文本区域)、numberField(数字输入框)。 - 工具栏与标题类:如
toolbar(工具栏背景)、helpBox(帮助信息框)、largeLabel(大标题)、headingLabel(节标题)。 - 特殊控件类:如
foldout(折叠箭头)、toggle(复选框)、popup(下拉菜单样式)、objectField(对象引用字段)。
直接使用这些样式,能让你的工具窗口瞬间拥有“原生Unity”的观感。例如,使用EditorStyles.toolbarButton来制作工具条,其悬停、按下效果与Unity顶部的工具栏完全一致。
注意:
EditorStyles中的样式是共享的、全局的。你不应该去修改它们(如EditorStyles.label.normal.textColor = Color.red;),这会影响编辑器其他部分的显示,行为是不可预测的。正确的做法是以它们为蓝本,创建你自己的GUIStyle副本进行修改。
3. 内置EditorStyles实战应用与技巧
知道有哪些样式只是第一步,如何在正确的场景下选用合适的样式,并组合它们,才是体现功力的地方。
3.1 常用样式场景化选用指南
不要盲目选择样式。根据UI元素的用途和上下文来选择,能使界面逻辑更清晰。
展示信息时:
- 主标题:使用
EditorStyles.largeLabel或EditorStyles.boldLabel。 - 节标题/分组标签:使用
EditorStyles.label并搭配GUI.backgroundColor设置一个浅色背景,或者使用EditorStyles.helpBox将整个分组框起来。 - 说明性文字:首选
EditorStyles.helpBox。它的灰色背景和边距非常适合展示提示、警告或描述性文本。你也可以使用EditorStyles.miniLabel来显示辅助性的、字号较小的说明。 - 数值/状态显示:如果只是只读显示,用
EditorStyles.label。如果想突出显示,可以创建一个自定义样式,将normal.textColor设置为高亮色(如绿色表示正常,红色表示错误)。
- 主标题:使用
接收输入时:
- 单行文本:
EditorStyles.textField。 - 多行文本:
EditorStyles.textArea。 - 数字输入:虽然可以用
textField,但更专业的做法是使用EditorGUILayout.IntField/FloatField等方法,它们内部使用了优化过的样式。 - 对象拖拽赋值:使用
EditorGUILayout.ObjectField方法,它会自动渲染为带picker按钮的样式,无需手动处理样式。 - 复选框/开关:
EditorStyles.toggle。用于布尔值。 - 下拉菜单:
EditorStyles.popup。通常与EditorGUILayout.Popup方法配合使用。
- 单行文本:
触发操作时:
- 主要操作按钮:
EditorStyles.miniButton或GUI.Button(使用默认或自定义样式)。对于重要操作,可以加大尺寸或使用醒目的背景色。 - 工具栏按钮组:
EditorStyles.toolbarButton。多个按钮并排时,使用这个样式能获得完美的工具栏视觉体验,按钮之间无缝衔接。 - 图标按钮:为
GUIStyle设置normal.background为一个Texture2D图标,并清除fixedWidth/Height,让按钮尺寸适应图标。
- 主要操作按钮:
3.2 样式组合与布局实战
一个好看的UI是样式和布局共同作用的结果。EditorGUILayout和EditorGUI是布局的两大帮手。
示例:创建一个带标题和行动按钮的设置面板
using UnityEditor; using UnityEngine; public class StyleDemoWindow : EditorWindow { [MenuItem("Tools/Style Demo")] static void ShowWindow() { GetWindow<StyleDemoWindow>("样式演示"); } private string inputText = "请输入"; private bool toggleState = false; private int selectedIndex = 0; private string[] options = { "选项A", "选项B", "选项C" }; void OnGUI() { // 1. 主标题区域 EditorGUILayout.LabelField("配置面板", EditorStyles.largeLabel); EditorGUILayout.Space(); // 增加一点间距 // 2. 第一个设置分组 - 使用HelpBox视觉分组 EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.LabelField("基础设置", EditorStyles.boldLabel); inputText = EditorGUILayout.TextField("文本输入:", inputText); toggleState = EditorGUILayout.Toggle("启用功能:", toggleState); selectedIndex = EditorGUILayout.Popup("选择模式:", selectedIndex, options); EditorGUILayout.EndVertical(); EditorGUILayout.Space(10); // 3. 第二个分组 - 使用背景色视觉分组 Color defaultBgColor = GUI.backgroundColor; GUI.backgroundColor = new Color(0.9f, 0.95f, 1.0f); // 浅蓝色背景 EditorGUILayout.BeginVertical(EditorStyles.helpBox); // 这里依然用helpBox,但背景色被覆盖了 GUI.backgroundColor = defaultBgColor; // 恢复默认背景色 EditorGUILayout.LabelField("操作区域", EditorStyles.boldLabel); EditorGUILayout.HelpBox("点击下方按钮执行相应操作。", MessageType.Info); // 4. 按钮行 - 灵活运用布局 EditorGUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); // 将按钮推到右侧 if (GUILayout.Button("取消", EditorStyles.miniButton, GUILayout.Width(60))) { this.Close(); } // 给主要按钮一个不同的样式(这里用加粗标签模拟一个更突出的按钮) GUIStyle primaryButtonStyle = new GUIStyle(EditorStyles.miniButton); primaryButtonStyle.fontStyle = FontStyle.Bold; if (GUILayout.Button("确认保存", primaryButtonStyle, GUILayout.Width(80))) { Debug.Log($"保存配置:{inputText}, 状态:{toggleState}, 模式:{options[selectedIndex]}"); } EditorGUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); } }这段代码的要点解析:
EditorGUILayout.Space():这是最常用的间距控制方法,比硬编码Rect位置灵活得多。BeginVertical(EditorStyles.helpBox):利用helpBox样式自带的边框和内边距,快速创建一个视觉上的分组容器。这是实现分组最简洁有效的方式之一。- 临时修改
GUI.backgroundColor:通过临时改变背景色来高亮某个区域,操作结束后立即恢复,这是一种非常实用的局部样式技巧。 BeginHorizontal()与GUILayout.FlexibleSpace():这是实现右对齐按钮组的经典模式。FlexibleSpace()会填充所有可用空间,从而将后续的控件“推”到右侧。- 动态创建
GUIStyle:代码中基于EditorStyles.miniButton创建了一个新的primaryButtonStyle,并仅修改了其fontStyle。这是自定义样式的安全做法。
4. 从零到一:深度自定义GUIStyle
当内置样式无法满足需求时,就需要亲手打造自定义GUIStyle。这分为两种情况:基于现有样式修改,和从头创建。
4.1 基于现有样式的修改与继承
这是最推荐、最高效的方式。以EditorStyles.label为蓝本,创建一个只属于你当前窗口或控件的样式副本。
// 在类中定义,避免每次OnGUI都重新创建 private GUIStyle customLabelStyle; void OnGUI() { if (customLabelStyle == null) { customLabelStyle = new GUIStyle(EditorStyles.label); customLabelStyle.normal.textColor = Color.blue; customLabelStyle.fontSize = 14; customLabelStyle.fontStyle = FontStyle.BoldAndItalic; customLabelStyle.alignment = TextAnchor.MiddleCenter; // 添加文字阴影(通过绘制两次实现) // customLabelStyle.normal.textColor = Color.white; // customLabelStyle.hover.textColor = Color.white; // 阴影效果通常需要更复杂的绘制,这里仅作示意。 } EditorGUILayout.LabelField("这是一个自定义样式的标签", customLabelStyle); }关键技巧:样式初始化时机
- 避免在
OnGUI内频繁new:OnGUI每帧可能调用多次,频繁创建GUIStyle对象会引发GC(垃圾回收),导致性能问题。最佳实践是在脚本的OnEnable方法中,或者像上面一样使用懒加载模式(检查是否为null再创建)来初始化你的自定义样式。 GUIStyle的构造函数:new GUIStyle(string name)可以按名称查找内置样式;new GUIStyle(GUIStyle other)是复制构造函数,最常用。
4.2 从头创建与九宫格(Border)艺术
当你需要独特的背景,比如圆角按钮、渐变标签时,就需要自己处理背景纹理和九宫格。
步骤1:准备背景纹理在Unity中导入一张背景图(如PNG),确保Texture Type设置为Editor GUI and Legacy,并关闭Generate Mip Maps。假设我们有一张名为custom_button_bg的圆角矩形纹理。
步骤2:在代码中创建样式
private GUIStyle customButtonStyle; private Texture2D buttonNormalTex; private Texture2D buttonActiveTex; void OnEnable() { // 加载纹理资源 buttonNormalTex = EditorGUIUtility.Load("custom_button_bg.png") as Texture2D; buttonActiveTex = EditorGUIUtility.Load("custom_button_bg_active.png") as Texture2D; // 按下状态的纹理 customButtonStyle = new GUIStyle(); // 1. 设置状态纹理 customButtonStyle.normal.background = buttonNormalTex; customButtonStyle.hover.background = buttonNormalTex; // 悬停也可以用同一张,或换色 customButtonStyle.active.background = buttonActiveTex; customButtonStyle.onNormal.background = buttonActiveTex; // 开启状态 // 2. 设置九宫格拉伸边界(Border) // 假设你的圆角矩形纹理的圆角半径为12像素,图片尺寸为100x50 // 那么border的left, right, top, bottom都应设置为12,确保圆角部分在拉伸时不变形 customButtonStyle.border = new RectOffset(12, 12, 12, 12); // 3. 设置内边距(Padding),让文字不要紧贴边缘 customButtonStyle.padding = new RectOffset(15, 15, 8, 8); // 左,右,上,下 // 4. 设置文本属性 customButtonStyle.normal.textColor = Color.white; customButtonStyle.hover.textColor = Color.yellow; customButtonStyle.active.textColor = Color.gray; customButtonStyle.alignment = TextAnchor.MiddleCenter; customButtonStyle.fontSize = 13; // 5. 固定高度,宽度自适应内容 customButtonStyle.fixedHeight = 50; customButtonStyle.stretchWidth = true; // 宽度会拉伸填充可用空间 } void OnGUI() { if (GUILayout.Button("九宫格按钮", customButtonStyle)) { // 按钮逻辑 } }九宫格(Border)原理深度解析:这是自定义样式中最容易出错的部分。RectOffset定义了纹理的四个边界。left、right、top、bottom的值表示从纹理对应边缘向内多少像素的区域是“不拉伸”的。
- 以
border = new RectOffset(12, 12, 12, 12)为例,它将纹理分为9个区域:四个角(各12x12像素)、四个边(可拉伸)、一个中心(可拉伸)。 - 当按钮尺寸变化时,四个角的像素保持不变,四条边只进行单向拉伸,中心区域进行双向拉伸。
- 这样就能保证无论按钮拉多大,它的圆角都能保持原样,只有中间平整的部分被拉伸,视觉上不会失真。
实操心得:确定
border值的最佳方法是在Photoshop等工具中测量你设计的背景图。确保你的圆角或特殊边框位于“不拉伸”区域内。如果设置不当,拉伸时会出现圆角变形或边框粗细不均的问题。
5. 高级样式技巧与性能优化
掌握了基础创建后,一些高级技巧能让你的UI更加出彩和高效。
5.1 动态样式与状态反馈
样式不是一成不变的。根据数据状态动态改变样式,能提供极强的交互反馈。
private bool isWarning = false; private string userInput = ""; void OnGUI() { // 根据输入内容动态改变文本框样式 GUIStyle dynamicTextFieldStyle = new GUIStyle(EditorStyles.textField); if (string.IsNullOrEmpty(userInput)) { dynamicTextFieldStyle.normal.textColor = Color.gray; } else if (userInput.Length < 5) { dynamicTextFieldStyle.normal.textColor = Color.red; // 输入过短警告 isWarning = true; } else { dynamicTextFieldStyle.normal.textColor = EditorStyles.textField.normal.textColor; // 恢复默认 isWarning = false; } userInput = EditorGUILayout.TextField("用户名:", userInput, dynamicTextFieldStyle); // 在输入框下方显示动态提示 if (isWarning) { EditorGUILayout.HelpBox("用户名长度至少为5个字符", MessageType.Warning); } }5.2 使用GUISkin实现样式主题化管理
当你的工具拥有大量自定义样式时,散落在代码各处会难以维护。GUISkin是一个资产文件(.guiskin),可以集中存储和管理多个GUIStyle。虽然它更常用于运行时UI,但也可以用于编辑器扩展。
- 在Project窗口右键
Create -> GUI Skin。 - 将创建好的
.guiskin文件拖入你的编辑器脚本所在目录。 - 在代码中加载并使用:
public GUISkin myEditorSkin; // 在Inspector中赋值 private GUIStyle skinButtonStyle; void OnEnable() { if (myEditorSkin != null) { skinButtonStyle = myEditorSkin.FindStyle("MyCustomButton"); // 或者通过名称直接获取 myEditorSkin.button // 注意:GUISkin中的样式名是其在Inspector中显示的名称 } else { // 备用方案 skinButtonStyle = new GUIStyle(EditorStyles.miniButton); } }使用GUISkin的好处是,美术或设计师可以直接在Unity编辑器中修改这个皮肤文件,调整颜色、字体等,而无需修改代码。
5.3 性能陷阱与优化策略
编辑器扩展的UI性能同样重要,糟糕的代码会导致工具窗口卡顿。
- 陷阱1:在OnGUI中创建GUIStyle。如前所述,这会产生大量垃圾对象。务必在
OnEnable、Awake或静态构造函数中初始化并缓存你的样式。 - 陷阱2:过度复杂的自定义样式。每个使用独特纹理和复杂
border的GUIStyle都会带来额外的绘制开销。尽量复用样式,对于简单颜色变化,优先考虑修改GUI.color或GUI.backgroundColor,它们是轻量级的全局状态设置。 - 陷阱3:不合理的布局计算。
GUILayout系统非常方便,但过度嵌套BeginHorizontal/Vertical以及滥用FlexibleSpace()、GUILayout.Width()会导致布局计算复杂化。在性能关键的循环中,考虑使用GUI而非GUILayout,通过精确计算Rect来定位,可以避免布局系统的开销。 - 优化策略:样式池。对于完全动态生成的UI元素(如列表项),可以创建一个样式池,根据需要启用/禁用,而不是不断创建和销毁。
6. 常见问题排查与调试实录
即使理解了原理,在实际操作中仍会遇到各种诡异的问题。下面是我踩过的一些坑和解决方案。
6.1 样式不生效或渲染异常
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 自定义样式完全没效果,显示为空白或默认样式。 | 1. 纹理加载失败。 2. GUIStyle对象未正确初始化或为null。3. 样式属性(如 normal.background)在绘制后被意外修改。 | 1.检查纹理路径和类型:使用Debug.Log(EditorGUIUtility.Load(“path”))确认纹理是否加载成功。确保纹理类型正确。2.确认初始化时机:在 OnEnable中打断点或打印日志,确认样式已创建。3.使用临时颜色测试:先将 normal.textColor设为Color.red,看文字颜色是否改变,快速判断样式是否被应用。 |
| 背景纹理拉伸后严重失真,圆角变成椭圆。 | GUIStyle.border属性设置错误。 | 1.测量纹理:在图像软件中打开背景图,精确测量圆角或不变形区域的像素宽度。 2.设置正确的RectOffset:确保 border的四个值覆盖了所有不希望拉伸的区域。对于对称圆角,四个值通常相等。3.使用 GUIStyle.clipping:如果只是简单显示,可以尝试设置clipping = TextClipping.Clip,但这不是根本解决方案。 |
| 文字显示不完整,或被截断。 | GUIStyle.padding设置过小,或者fixedWidth/Height太小,不足以容纳文字。 | 1.检查Padding:适当增加padding的左右和上下值。2.检查固定尺寸:如果设置了 fixedWidth/Height,尝试暂时注释掉,让控件自适应大小。3.使用 GUIContent并检查尺寸:GUIStyle.CalcSize(new GUIContent(“your text”))可以计算给定文本所需的最小尺寸。 |
| 按钮等控件没有悬停、按下反馈。 | 只设置了normal.background,未设置hover.background和active.background。 | 为hover和active状态也分配相应的纹理或颜色。如果想用颜色变化,可以只设置normal.textColor和hover.textColor,背景纹理可以共用。 |
6.2 布局与样式冲突
有时样式看起来没问题,但一放到复杂布局里就乱套。
问题:控件重叠或间距诡异。
- 原因:
margin和padding理解混淆,或者GUILayout与绝对Rect混用导致计算冲突。 - 解决:牢记
margin是“外部间距”,影响控件与邻居的关系;padding是“内部间距”,影响控件内容与边框的关系。在GUILayout体系中,margin通常由布局组自动管理,手动设置可能无效。尽量避免在同一UI树中混用GUILayout和基于Rect的GUI方法。
- 原因:
问题:滚动视图(ScrollView)内的自定义样式错位。
- 原因:在
BeginScrollView和EndScrollView之间使用GUILayout时,滚动视图需要计算总高度,而复杂的自定义样式可能让布局计算出现偏差。 - 解决:尝试为滚动区域内的自定义样式明确设置
fixedHeight,或者使用GUILayout.Height()强制指定高度,帮助布局系统进行准确计算。
- 原因:在
6.3 调试利器:EditorWindow的“Inspect”功能
Unity编辑器自带一个强大的调试工具。在游戏运行时这个功能不明显,但在编辑器扩展开发中极其有用。
- 在你的自定义
EditorWindow处于焦点状态时,点击Unity编辑器右上角的“Development”下拉菜单(如果没有,需要在Preferences -> General中开启Developer Mode)。 - 选择
Inspect -> Inspector (Editor)。 - 会弹出一个特殊的Inspector窗口,里面实时显示了当前焦点窗口的整个GUI层级树、每个元素的
Rect、使用的GUIStyle等信息。 - 你可以点击树中的元素,查看其详细的样式属性,这对于定位哪个控件用了什么样式、尺寸位置是否正确,有奇效。
我个人在开发复杂编辑器工具时,几乎离不开这个“Inspect”功能。它能让你像用浏览器开发者工具检查DOM元素一样,来检查你的编辑器UI,是解决样式和布局问题的终极法宝。
7. 实战:构建一个风格统一的编辑器工具窗口
让我们综合运用以上所有知识,从头构建一个简单的“项目笔记”编辑器工具。这个工具将包含标题、可折叠的分组、带样式的文本输入和自定义按钮。
using UnityEditor; using UnityEngine; using System.Collections.Generic; public class ProjectNotesWindow : EditorWindow { [MenuItem("Window/Project Notes")] static void Init() { GetWindow<ProjectNotesWindow>("项目笔记").Show(); } // 自定义样式缓存 private GUIStyle headerStyle; private GUIStyle sectionHeaderStyle; private GUIStyle noteBoxStyle; private GUIStyle saveButtonStyle; // 数据 private Dictionary<string, bool> sectionFoldouts = new Dictionary<string, bool>(); private Dictionary<string, string> sectionNotes = new Dictionary<string, string>(); private Vector2 scrollPos; void OnEnable() { // 初始化数据 string[] sections = { "设计思路", "技术难点", "待办事项", "临时备忘" }; foreach (var sec in sections) { if (!sectionFoldouts.ContainsKey(sec)) sectionFoldouts[sec] = true; if (!sectionNotes.ContainsKey(sec)) sectionNotes[sec] = PlayerPrefs.GetString($"Note_{sec}", ""); } // 创建并缓存样式 CreateStyles(); } void CreateStyles() { // 1. 主标题样式 headerStyle = new GUIStyle(EditorStyles.largeLabel); headerStyle.alignment = TextAnchor.MiddleCenter; headerStyle.fontSize = 18; headerStyle.normal.textColor = new Color(0.2f, 0.3f, 0.6f); // 深蓝色 // 2. 分区标题样式 (可折叠) sectionHeaderStyle = new GUIStyle(EditorStyles.foldout); sectionHeaderStyle.fontStyle = FontStyle.Bold; sectionHeaderStyle.fontSize = 14; // 3. 笔记文本框样式 noteBoxStyle = new GUIStyle(EditorStyles.textArea); noteBoxStyle.wordWrap = true; noteBoxStyle.padding = new RectOffset(10, 10, 10, 10); // 设置一个柔和的背景色(通过修改文本区域背景) noteBoxStyle.normal.background = MakeTex(2, 2, new Color(0.97f, 0.97f, 0.98f, 1f)); // 4. 保存按钮样式 (基于miniButton,但更突出) saveButtonStyle = new GUIStyle(EditorStyles.miniButton); saveButtonStyle.fontStyle = FontStyle.Bold; saveButtonStyle.normal.textColor = Color.white; // 创建一个简单的绿色背景纹理 saveButtonStyle.normal.background = MakeTex(2, 2, new Color(0.3f, 0.7f, 0.3f, 1f)); saveButtonStyle.hover.background = MakeTex(2, 2, new Color(0.4f, 0.8f, 0.4f, 1f)); saveButtonStyle.active.background = MakeTex(2, 2, new Color(0.2f, 0.6f, 0.2f, 1f)); saveButtonStyle.padding = new RectOffset(12, 12, 6, 6); saveButtonStyle.border = new RectOffset(5, 5, 5, 5); // 小圆角效果 } // 辅助方法:创建纯色纹理 private Texture2D MakeTex(int width, int height, Color col) { Color[] pix = new Color[width * height]; for (int i = 0; i < pix.Length; ++i) { pix[i] = col; } Texture2D result = new Texture2D(width, height); result.SetPixels(pix); result.Apply(); return result; } void OnGUI() { // 开始滚动视图 scrollPos = EditorGUILayout.BeginScrollView(scrollPos); // 主标题 EditorGUILayout.LabelField("项目开发笔记", headerStyle, GUILayout.Height(40)); EditorGUILayout.Space(20); // 遍历所有分区 foreach (var section in sectionNotes.Keys) { // 可折叠的分区标题 sectionFoldouts[section] = EditorGUILayout.Foldout(sectionFoldouts[section], section, sectionHeaderStyle); if (sectionFoldouts[section]) { // 分区内容区域,稍微缩进 EditorGUILayout.BeginHorizontal(); GUILayout.Space(20); // 缩进 EditorGUILayout.BeginVertical(); // 使用自定义样式的文本区域 string oldNote = sectionNotes[section]; sectionNotes[section] = EditorGUILayout.TextArea(oldNote, noteBoxStyle, GUILayout.MinHeight(80)); // 如果内容改变,显示保存提示 if (oldNote != sectionNotes[section]) { EditorGUILayout.HelpBox("内容已修改,请记得保存。", MessageType.Info); } EditorGUILayout.EndVertical(); EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(10); } } EditorGUILayout.EndScrollView(); // 底部操作栏 EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); GUILayout.FlexibleSpace(); if (GUILayout.Button("全部折叠", EditorStyles.toolbarButton)) { foreach (var key in sectionFoldouts.Keys) sectionFoldouts[key] = false; } if (GUILayout.Button("全部展开", EditorStyles.toolbarButton)) { foreach (var key in sectionFoldouts.Keys) sectionFoldouts[key] = true; } GUI.enabled = true; // 确保按钮可用 if (GUILayout.Button("保存所有笔记", saveButtonStyle, GUILayout.Width(120))) { SaveAllNotes(); } EditorGUILayout.EndHorizontal(); } void SaveAllNotes() { foreach (var kvp in sectionNotes) { PlayerPrefs.SetString($"Note_{kvp.Key}", kvp.Value); } PlayerPrefs.Save(); Debug.Log("项目笔记已保存!"); // 这里可以替换为更持久的存储方式,如ScriptableObject或文件。 } void OnDestroy() { // 窗口关闭时自动保存 SaveAllNotes(); } }这个综合案例的关键点:
- 样式初始化:所有自定义样式在
OnEnable中创建并缓存,性能最优。 - 样式复用:
noteBoxStyle基于EditorStyles.textArea,只修改了背景色和内边距。saveButtonStyle基于EditorStyles.miniButton,但改变了颜色和边框,创建了更醒目的“主操作按钮”。 - 动态反馈:在文本修改后,动态显示一个
HelpBox提示用户保存。 - 布局组合:综合运用了
ScrollView、Foldout、水平垂直布局组、FlexibleSpace和Space来控制整体结构。 - 工具条风格:底部操作栏使用了
EditorStyles.toolbar和toolbarButton,使其与Unity编辑器其他工具条风格一致。 - 实用功能:实现了折叠/展开、保存等实际功能,使这个示例不仅仅是一个样式演示。
通过这个完整的例子,你应该能够看到,将EditorStyles的便捷性与自定义GUIStyle的灵活性结合起来,完全可以打造出既专业又独特的编辑器工具界面。记住,好的编辑器UI是深思熟虑的样式设计与清晰的信息架构共同作用的结果。多观察、多模仿Unity编辑器自身的界面设计,然后大胆地用代码实现你的想法,这是提升编辑器扩展开发能力的最佳路径。
