UnityUI系统--GUI
认识GUI
IMGUI 是 Unity 中一种独特、强大且历史悠久的 GUI 系统,它的“即时”性质使其在代码驱动、快速迭代和状态透明的场景中表现出色,尤其是在 Unity 编辑器扩展开发中扮演着核心角色。然而,其每帧重绘的模型带来的性能开销和可视化/复杂布局能力的欠缺,使它不适合构建现代游戏中主流的、性能敏感的、设计精美的用户界面。对于游戏 UI,UGUI 和更新推出的 UI Toolkit 是更合适的选择。理解 IMGUI 的核心概念对于扩展 Unity 编辑器和创建调试工具至关重要。
IMGUI 与传统 GUI 系统(如 UGUI 的事件驱动模式)有本质区别:
- 无持久状态对象: IMGUI 不像 UGUI 那样预先创建并维护
Button
、Label
、Slider
等持久的 UI 对象存在于场景中。UI 元素是“即时”绘制的。 - 函数驱动: 你通过在
OnGUI()
方法(或类似的编辑器窗口OnGUI
方法)中调用一系列GUI
或GUILayout
类的函数(如GUI.Button()
,GUILayout.Label()
,GUI.Toggle()
)来声明每一帧要显示的 UI。 - 响应即是返回值: 这些函数不仅绘制控件,更重要的是它们直接返回用户的交互状态。例如:
bool clicked = GUI.Button(rect, "Click Me");
在绘制按钮的同时,clicked
会在按钮被点击的那一帧返回true
。string enteredText = GUI.TextField(rect, currentText);
在绘制输入框的同时,返回用户输入/更新后的文本。
- 基于每帧:
OnGUI()
方法在每一帧都会被调用(在渲染之前)。整个 UI 是在每一帧中从头开始“绘制”(或更准确地说,声明应该如何绘制)并处理交互的。这意味着 UI 逻辑需要高效。
核心特性与优缺点
- 优点:
- 简单快速原型: 无需设置场景、预制体或编辑器拖拽,几行代码就能创建出调试按钮、滑块或文本字段。非常适合临时信息显示和快速测试。
- 工具开发的黄金标准: IMGUI 是 Unity 编辑器本身扩展(如自定义 Inspector 面板、Editor 窗口、Scene 视图工具)的官方首选 API。UnityEditor 命名空间下的
EditorGUI
/EditorGUILayout
类都是基于 IMGUI 构建的扩展,提供了大量编辑器专用控件。 - 无状态管理: 因为 UI 是即时绘制的,控件不保存自己的状态(开/关,当前值等)。状态完全由你的代码管理(存储为 MonoBehaviour 的成员变量或 EditorWindow 的变量)。这简化了一些复杂逻辑,状态逻辑完全透明。
- 深度集成: 在编辑器扩展中,与 Unity 原生对象(如
SerializedObject
/SerializedProperty
)的集成非常紧密和直接。 - 完全代码控制: 对程序化生成高度动态或极其复杂的界面有独特优势(只要性能不是瓶颈)。
- 缺点:
- 性能: 最大的缺点。每帧都要重新绘制和处理整个 UI 层次结构(即使没有变化),在 UI 复杂或游戏运行在较低性能设备(移动端)时,可能带来显著开销。绝对不适合游戏内复杂UI或移动端。
- 难以构建复杂、动态 UI: 创建具有复杂布局、动画、动态添加/移除元素的大型、可维护的用户界面非常笨拙和困难。UGUI/UI Toolkit 更擅长于此。
- 缺乏可视化设计: 所有 UI 元素的位置、大小、样式都通过代码定义(矩形
Rect
),没有所见即所得(WYSIWYG)的编辑器。调整布局需要修改代码并重新运行。 - 样式有限: 内置控件的默认样式比较基础。虽然可以自定义外观(如使用自定义纹理
GUIStyle
,GUISkin
),但这需要额外代码工作,且不如 UGUI/UI Toolkit 的图形化样式设置直观。 - 布局挑战: 手动计算所有
Rect
(Rect(x, y, width, height)
) 来精确定位是繁琐且不灵活的。虽然GUILayout
系列函数提供了一种基于“自动布局区域”的方式(类似于 HTML 的流式布局),节省了手动计算Rect
的工作,但其布局控制仍然不如 UGUI 的 RectTransform 锚点系统和布局组件强大和直观。复杂的GUILayout
嵌套也可能导致意外行为。
核心类和方法
-
GUI
类: 核心静态类。包含手动定位 (Rect
) 的基本控件函数。你需要精确指定每个控件的位置和大小。GUI.Button(Rect position, string text/Texture)
: 绘制按钮,点击返回true
。GUI.Label(Rect position, string text)
: 绘制只读文本标签。GUI.Box(Rect position, string text/Texture)
: 绘制背景框。GUI.TextField(Rect position, string text)
: 绘制单行文本输入框,返回输入文本。GUI.Toggle(Rect position, bool value, string text/Texture)
: 绘制开关按钮,返回开关状态。GUI.HorizontalSlider/Rect position, float value, float leftValue, float rightValue)
: 绘制水平滑动条,返回滑块值。GUI.DrawTexture(Rect position, Texture2D texture)
: 在指定位置绘制纹理。GUI.Window(int id, Rect clientRect, GUI.WindowFunction func, string title)
: 绘制一个可拖动的窗口。func
是一个委托方法,内部绘制该窗口的内容。
-
GUILayout
类: 基于GUI
的静态类。提供自动布局版本的控件。你只需指定控件内容和一些选项(如宽度/高度约束),系统会自动计算位置和尺寸(从上到下,从左到右排列)。GUILayout.Button(string text/Texture, params GUILayoutOption[] options)
GUILayout.Label(string text, params GUILayoutOption[] options)
GUILayout.Box(string text/Texture, params GUILayoutOption[] options)
GUILayout.TextField(string text, params GUILayoutOption[] options)
GUILayout.Toggle(bool value, string text/Texture, params GUILayoutOption[] options)
GUILayout.HorizontalSlider(float value, float leftValue, float rightValue, params GUILayoutOption[] options)
- 布局控制函数:
GUILayout.BeginHorizontal() / GUILayout.EndHorizontal()
: 开始/结束一个水平自动布局组。GUILayout.BeginVertical() / GUILayout.EndVertical()
: 开始/结束一个垂直自动布局组。GUILayout.FlexibleSpace()
: 插入一个可伸缩的空间,将控件推到一边。GUILayout.Space(float pixels)
: 插入固定像素高度的空格。GUILayoutOption
: 如GUILayout.Width(float)
,GUILayout.Height(float)
,GUILayout.MinWidth(float)
,GUILayout.ExpandWidth(bool)
等,用于在GUILayout
控件中约束大小。
-
GUIStyle
: 定义单个控件的外观(字体、对齐方式、颜色、边距、背景图等)。可以创建并应用于特定的GUI
/GUILayout
调用。 -
GUISkin
: 包含一组预定义的GUIStyle
(如button
,label
,textField
的默认样式)。可以一次性应用到整个 UI 层级。 -
GUIUtility
和GUIContent
: 提供实用工具(如获取鼠标在 GUI 坐标中的位置)和一种将文本、图像、工具提示封装在一起传递给控件的方式。
何时使用 IMGUI?
- Unity 编辑器扩展开发 (Editor Scripting): 这是 IMGUI 目前最主要且推荐的使用场景。自定义 Inspector、自定义 Editor 窗口、Scene 视图控件(Handle)、Game 视图工具栏扩展等都离不开它。
- 调试界面与运行时工具:
- 快速在游戏视图顶部叠加调试信息(如 FPS、内存、内部状态变量)。
- 临时添加控制面板(开关特效、调整参数、触发测试事件)。
- 内置作弊控制台 (Cheat Console)。
- 游戏原型设计: 在项目早期,快速搭建起简单的菜单或设置界面以测试核心玩法逻辑,而无需投入 UI 美术资源或搭建 UGUI 结构。
- 极其动态或程序化 UI: 如果 UI 是高度动态生成且结构极其复杂(难以用 UGUI 预制体管理),完全由算法控制,且性能不是首要关注点(如 PC 上的工具软件)。
何时避免使用 IMGUI?
- 游戏内主要的、复杂的 UI: 如主菜单、HUD、设置菜单、背包系统、对话系统等。请使用 UGUI 或 UI Toolkit。
- 性能敏感场景: 尤其是移动端游戏。
- 需要丰富动画和视觉效果的 UI。
- 需要可视化编辑器和所见即所得设计体验的项目。
- 需要大量布局控制且期望自动适配不同分辨率/屏幕比例的 UI。
封装GUI中常见的组件,让GUI可以所见即所得
系统总结
这些脚本共同构建了一个自定义GUI框架:
- 基础结构:
CustomGUIControl
提供抽象基类,CustomGUIPos
解决布局问题,CustomGUIRoot
实现统一绘制。 - 控件库:
包含按钮、输入框、标签、滑动条、图片、开关等常用UI组件。 - 事件驱动:
控件通过UnityEvent
暴露交互事件(点击、文本变化、值变化等)。 - 扩展性:
通过继承CustomGUIControl
可快速创建新控件。
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.Rendering;public enum E_Aligent_Type
{Up,Down,Left, Right,Center,Left_Up,Right_Up,Left_Down,Right_Down,
}
[System.Serializable]
public class CustomGUIPos
{private Rect rPos = new Rect(0, 0, 100, 100);public E_Aligent_Type screen_Aligent_Type;public E_Aligent_Type control_Aligent_Type;//用户自行偏移位置public Vector2 pos;//屏幕的宽高public float Width=100;public float Height=50;private Vector2 centerPos;private void CalcCenterPos(){switch (control_Aligent_Type){case E_Aligent_Type.Up:centerPos .x=-Width /2;centerPos .y=0;break;case E_Aligent_Type.Down:centerPos .x=-Width /2;centerPos .y=Height;break;case E_Aligent_Type.Left:centerPos .x=0;centerPos .y=-Height /2;break;case E_Aligent_Type.Right:centerPos .x=-Width;centerPos .y=-Height/2;break;case E_Aligent_Type.Center:centerPos .x=-Width /2;centerPos .y=-Height/2 ;break;case E_Aligent_Type.Left_Up:centerPos .x=0;centerPos .y=0;break;case E_Aligent_Type.Right_Up:centerPos .x=-Width;centerPos .y=0;break;case E_Aligent_Type.Left_Down:centerPos .x=0;centerPos .y=-Height;break;case E_Aligent_Type.Right_Down:centerPos .x=-Width;centerPos .y=-Height;break;}}private void CalcControlPos(){switch (screen_Aligent_Type){case E_Aligent_Type.Up:rPos.x = Screen.width / 2 + centerPos.x + pos.x;rPos.y = centerPos.y + pos.y;break;case E_Aligent_Type.Down:rPos.x = Screen.width / 2 + centerPos.x + pos.x;rPos.y = Screen.height + centerPos.y - pos.y;break;case E_Aligent_Type.Left:rPos.x = centerPos.x + pos.x;rPos.y = Screen.height / 2 + centerPos.y + pos.y;break;case E_Aligent_Type.Right:rPos.x = Screen.width + centerPos.x - pos.x;rPos.y = Screen.height / 2 + centerPos.y + pos.y;break;case E_Aligent_Type.Center:rPos.x = Screen.width / 2 + centerPos.x + pos.x;rPos.y = Screen.height / 2 + centerPos.y + pos.y;break;case E_Aligent_Type.Left_Up:rPos.x = centerPos.x + pos.x;rPos.y = centerPos .y+pos.y;break;case E_Aligent_Type.Right_Up:rPos .x=Screen .width +centerPos .x - pos.x;rPos .y =centerPos .y+pos.y;break;case E_Aligent_Type.Left_Down:rPos .x = centerPos.x + pos.x;rPos .y =Screen .height + centerPos .y - pos.y;break;case E_Aligent_Type.Right_Down:rPos .x=Screen .width +centerPos .x - pos.x;rPos .y=Screen .height + centerPos .y - pos.y;break;}}public Rect Pos{get{CalcCenterPos();CalcControlPos();rPos.width = Width;rPos.height = Height;return rPos;}}}
CustomGUIPos.cs
- 功能:屏幕位置计算工具。
- 核心特性:
- 通过
E_Aligent_Type
枚举定义控件在屏幕上的对齐方式(9种,如左上、居中)。 - 动态计算控件矩形区域 (
Rect
),考虑屏幕尺寸、控件尺寸、自定义偏移 (pos
)。 - 通过
Pos
属性返回最终位置(自动调用CalcCenterPos()
和CalcControlPos()
)。
- 通过
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using UnityEngine;public enum E_Style_OnOff
{On,Off
}public abstract class CustomGUIControl : MonoBehaviour
{public CustomGUIPos guiPos;public GUIStyle guiStyle;public GUIContent guiContent;public E_Style_OnOff styleOnOff=E_Style_OnOff.Off;public void DrawGUI(){switch (styleOnOff){case E_Style_OnOff.On:StyleOnDraw();break;case E_Style_OnOff.Off:StyleOffDraw();break;default:break;}}protected abstract void StyleOnDraw();protected abstract void StyleOffDraw();
}
CustomGUIControl.cs
- 功能:自定义GUI控件的抽象基类。
- 核心特性:
- 管理控件位置 (
guiPos
)、样式 (guiStyle
)、内容 (guiContent
)。 - 通过
E_Style_OnOff
枚举控制是否启用GUIStyle。 - 提供
DrawGUI()
方法,根据样式开关调用StyleOnDraw()
或StyleOffDraw()
(需子类实现)。
- 管理控件位置 (
using System.Collections;
using System.Collections.Generic;
using UnityEngine;[ExecuteAlways]
public class CustomGUIRoot : MonoBehaviour
{private CustomGUIControl []allControls;// Start is called before the first frame updatevoid Start(){allControls = this.GetComponentsInChildren<CustomGUIControl>();}private void OnGUI(){if(!Application .isPlaying){allControls = this.GetComponentsInChildren<CustomGUIControl>();}for (int i = 0; i < allControls .Length; i++){allControls[i].DrawGUI();}}
}
CustomGUIRoot.cs
- 功能:GUI控件根节点管理器。
- 核心特性:
- 使用
[ExecuteAlways]
在编辑模式和运行时生效。 - 自动收集子节点的
CustomGUIControl
组件。 - 在
OnGUI()
中统一绘制所有子控件。
- 使用
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class CustomGUILabel : CustomGUIControl
{protected override void StyleOffDraw(){GUI.Label(guiPos.Pos, guiContent);}protected override void StyleOnDraw(){GUI.Label(guiPos.Pos, guiContent, guiStyle);}
}
CustomGUILabel.cs
- 功能:自定义文本标签控件。
- 核心特性:
- 继承
CustomGUIControl
,实现静态文本标签。 - 支持带/不带样式的文本渲染(
GUI.Label
)。
- 继承
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;public class CustomGUIButton : CustomGUIControl
{public event UnityAction clickEvent;protected override void StyleOffDraw(){if(GUI .Button (guiPos .Pos ,guiContent )){clickEvent?.Invoke();}}protected override void StyleOnDraw(){if(GUI .Button (guiPos .Pos , guiContent,guiStyle )){clickEvent?.Invoke();}}
}
CustomGUIButton.cs
- 功能:自定义按钮控件。
- 核心特性:
- 继承
CustomGUIControl
,实现按钮的绘制逻辑。 - 支持带/不带样式的渲染 (
StyleOnDraw/StyleOffDraw
)。 - 提供点击事件
clickEvent
(通过UnityAction
触发)。
- 继承
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;public class CustomGUIToggle : CustomGUIControl
{public bool isSel;public event UnityAction<bool> isSelEvent;private bool isOldSel;protected override void StyleOffDraw(){isSel =GUI.Toggle(guiPos.Pos, isSel, guiContent);//只有在变化时 才告诉外部 执行函数 否则 没必要一直告诉别人同一个值if(isOldSel != isSel){isSelEvent?.Invoke(isSel);isOldSel = isSel;}}protected override void StyleOnDraw(){isSel = GUI.Toggle(guiPos.Pos, isSel, guiContent,guiStyle);if (isOldSel != isSel){isSelEvent?.Invoke(isSel);isOldSel = isSel;}}
}
CustomGUIToggle.cs
- 功能:自定义开关/复选框控件。
- 核心特性:
- 继承
CustomGUIControl
,实现开关逻辑。 - 通过
isSel
存储状态,isSelEvent
事件通知状态变化。 - 优化事件触发:仅在实际状态变化时调用事件。
- 继承
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;public class CustomGUIToggleGroup : MonoBehaviour
{public CustomGUIToggle[] customToggles;//用来记录判断的toggle上一次是不是trueprivate CustomGUIToggle frontToggle;// Start is called before the first frame updatevoid Start(){if (customToggles.Length == 0)return;//通过遍历为多个多选框添加监听事件函数//在函数中做处理//当一个为true是其他为falsefor (int i = 0; i < customToggles .Length; i++){CustomGUIToggle toggle = customToggles[i];toggle.isSelEvent += (value) =>{//当前传入的value是true时,需要将其他的改为falseif (value){//意味着其他的要改为falsefor (int j = 0; j < customToggles.Length; j++){//这里有闭包,toggle是上一个函数中声明的变量//改变了他的生命周期if(customToggles[j]!=toggle){customToggles[j].isSel = false;}}frontToggle = toggle;}//来判断 当前变成false的这个toggle是不是上一次是true//如果是的话,就不应该让他变成falseelse if(toggle== frontToggle){toggle.isSel = true;}};}}}
CustomGUIToggleGroup.cs
- 功能:单选按钮组控制器。
- 核心特性:
- 管理多个
CustomGUIToggle
,实现单选逻辑。 - 通过事件订阅确保组内只有一个开关被选中。
- 防误操作:阻止已选开关被取消(
frontToggle
机制)。
- 管理多个
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;public class CustomGUIInput : CustomGUIControl
{public event UnityAction<string> textChange;private string oldStr = "";protected override void StyleOffDraw(){guiContent .text = GUI.TextField(guiPos.Pos, guiContent.text);if(oldStr !=guiContent .text){textChange?.Invoke(oldStr);oldStr = guiContent.text;}}protected override void StyleOnDraw(){if (oldStr != guiContent.text){guiContent.text = GUI.TextField(guiPos.Pos, guiContent.text,guiStyle);textChange?.Invoke(oldStr);oldStr = guiContent.text;}}}
CustomGUIInput.cs
- 功能:自定义输入框控件。
- 核心特性:
- 继承
CustomGUIControl
,实现文本输入框逻辑。 - 监听文本变化,通过
textChange
事件返回旧文本值。 - 优化性能:仅在文本实际变化时触发事件。
- 继承
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;public enum E_Slider_Type
{Horizontal,Vertical,
}
public class CustomGUISlider : CustomGUIControl
{//最小值public float minValue;//最大值public float maxValue;//当前值public float nowValue;public GUIStyle thumbStyle;public E_Slider_Type slider_Type=E_Slider_Type.Horizontal;public event UnityAction<float> changeValue;private float oldValue;protected override void StyleOffDraw(){switch (slider_Type){case E_Slider_Type.Horizontal:nowValue = GUI.HorizontalSlider(guiPos.Pos, nowValue, minValue, maxValue);break;case E_Slider_Type.Vertical:nowValue = GUI.VerticalSlider(guiPos.Pos, nowValue, minValue, maxValue);break;}if(oldValue !=nowValue ){changeValue?.Invoke(oldValue);oldValue = nowValue;}}protected override void StyleOnDraw(){switch (slider_Type){case E_Slider_Type.Horizontal:nowValue = GUI.HorizontalSlider(guiPos.Pos, nowValue, minValue, maxValue,guiStyle ,thumbStyle );break;case E_Slider_Type.Vertical:nowValue = GUI.VerticalSlider(guiPos.Pos, nowValue, minValue, maxValue,guiStyle ,thumbStyle);break;}if (oldValue != nowValue){changeValue?.Invoke(oldValue);oldValue = nowValue;}}
}
CustomGUISlider.cs
- 功能:自定义滑动条控件。
- 核心特性:
- 支持水平/垂直滑动条(
E_Slider_Type
)。 - 可配置最小值/最大值/当前值 (
minValue/maxValue/nowValue
)。 - 提供滑块样式 (
thumbStyle
) 和值变化事件changeValue
。
- 支持水平/垂直滑动条(
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class CustomGUITexture : CustomGUIControl
{public ScaleMode scaleMode = ScaleMode.StretchToFill;protected override void StyleOffDraw(){GUI.DrawTexture (guiPos.Pos,guiContent .image ,scaleMode);}protected override void StyleOnDraw(){GUI.DrawTexture(guiPos.Pos, guiContent.image, scaleMode);}
}
CustomGUITexture.cs
- 功能:自定义纹理显示控件。
- 核心特性:
- 继承
CustomGUIControl
,实现纹理绘制。 - 支持多种缩放模式 (
ScaleMode
),如拉伸填充 (StretchToFill
)。
- 继承