2025GUI项目实践:Unity编辑模式下GUI运行
一、需求分析
二、九宫格布局
三、空间位置信息类
1.对齐方式枚举(E_Alignment_Type)
2.核心属性
3.位置计算核心逻辑
步骤 1:计算控件中心点偏移(CalcCenterPos)
步骤 2:计算屏幕坐标位置(CalcPos)
4.最终位置获取
四、控件基类设计
1.样式开关枚举(E_Style_OnOff)
2.自定义 GUI 控件基类(CustomGUIControl)
(1)核心属性
(2)核心方法
3.自定义位置计算类(CustomGUIPos)
五、编辑模式GUI实时预览和显示顺序
1. 编辑模式实时预览
2.显示顺序控制
六、自定义按钮控件(CustomGUIButton)
1.按钮控件的核心特性
2.按钮控件的使用方法
七、自定义标签控件(CustomGUILabel)
1.标签控件的核心特性
2.标签控件的应用场景
八、自定义复选框控件
1.代码实现
2.控件核心特性解析
(1)状态管理
(2)事件机制
(3)样式支持
九、自定义ToggleGroup 单选框控件
1.代码实现
2.实现原理解析
(1)核心功能
(2)数组管理
(3)事件监听
(4)互斥逻辑
(5)至少一项选中
十、自定义输入框控件(CustomGUIInput)
1.代码实现
2.输入框控件核心特性
十一、自定义滑动条控件(CustomGUISlider)
一、需求分析
二、九宫格布局
三、空间位置信息类
1.对齐方式枚举(E_Alignment_Type)
定义了 9 种基础对齐方式,覆盖了屏幕和控件的所有关键位置。
public enum E_Alignment_Type
{Up, // 上中Down, // 下中Left, // 左中Right, // 右中Center, // 中心Left_Up, // 左上Left_Down, // 左下Right_Up, // 右上Right_Down // 右下
}
这些枚举值用于描述两个关键对齐关系:
(1)控件相对于屏幕的对齐方式
(2)控件自身中心点的对齐方式
2.核心属性
属性 | 作用 |
---|---|
screen_Alignment_Type | 控件相对于屏幕的九宫格对齐方式 |
control_Center_Alignment_Type | 控件自身的中心点对齐方式 |
pos | 基于对齐点的偏移位置 |
width /height | 控件的宽高 |
Pos (只读属性) | 计算后最终用于绘制的 Rect 位置 |
3.位置计算核心逻辑
步骤 1:计算控件中心点偏移(CalcCenterPos)
根据控件自身的对齐方式,计算控件左上角相对于其对齐点的偏移量:
private void CalcCenterPos()
{switch (control_Center_Alignment_Type){case E_Alignment_Type.Up:centerPos.x = -width / 2; // 水平居中centerPos.y = 0; // 顶部对齐break;// 其他对齐方式的计算...}
}
步骤 2:计算屏幕坐标位置(CalcPos)
根据屏幕对齐方式,结合屏幕分辨率和偏移量,计算控件在屏幕上的最终位置:
private void CalcPos()
{switch (screen_Alignment_Type){case E_Alignment_Type.Up:// 屏幕上中位置 + 控件偏移 + 自定义偏移rPos.x = Screen.width/2 + centerPos.x + pos.x;rPos.y = 0 + centerPos.y + pos.y;break;// 其他屏幕对齐方式的计算...}
}
4.最终位置获取
通过Pos
属性将计算逻辑封装,外部使用时无需关心内部计算细节:
public Rect Pos
{get{CalcCenterPos(); // 先计算控件自身偏移CalcPos(); // 再计算屏幕位置rPos.width = width;rPos.height = height;return rPos;}
}
四、控件基类设计
1.样式开关枚举(E_Style_OnOff)
public enum E_Style_OnOff
{On, // 使用自定义样式Off // 使用默认样式
}
用于控制控件是否启用自定义样式,提供了灵活的样式切换能力。
2.自定义 GUI 控件基类(CustomGUIControl)
这是所有自定义控件的基类,继承自 MonoBehaviour,封装了控件的共性属性和行为。
(1)核心属性
属性 | 作用 |
---|---|
guiPos | 类型为CustomGUIPos ,控制控件的位置和大小,实现自适应布局 |
content | 类型为GUIContent ,存储控件的显示内容(文本、图片、提示等) |
style | 类型为GUIStyle ,存储自定义样式信息 |
styleOnOrOff | 类型为E_Style_OnOff ,控制是否启用自定义样式 |
(2)核心方法
OnGUI():重写 MonoBehaviour 的生命周期方法,作为控件绘制的入口
private void OnGUI()
{switch (styleOnOrOff){case E_Style_OnOff.On:StyleOnDraw(); // 使用自定义样式绘制break;case E_Style_OnOff.Off:StyleOffDraw(); // 使用默认样式绘制break;}
}
StyleOnDraw():使用自定义样式绘制控件的虚方法
protected virtual void StyleOnDraw()
{// 子类需重写此方法实现具体绘制逻辑// 示例: GUI.Button(guiPos.Pos, content, style);
}
StyleOffDraw():使用默认样式绘制控件的虚方法
protected virtual void StyleOffDraw()
{// 子类需重写此方法实现具体绘制逻辑// 示例: GUI.Button(guiPos.Pos, content);
}
3.自定义位置计算类(CustomGUIPos)
此类在之前基础上增加了[System.Serializable]
特性,使其可以在 Inspector 面板中显示和编辑,极大提升了可视化编辑体验。
[System.Serializable]
public class CustomGUIPos
{// 保持原有功能实现...
}
五、编辑模式GUI实时预览和显示顺序
1. 编辑模式实时预览
通过[ExecuteAlways]
特性和特殊的更新逻辑,实现了编辑模式下的实时预览:
[ExecuteAlways]
public class CustomGUIRoot : MonoBehaviour
{// ...private void OnGUI(){// 编辑模式下每次绘制前更新控件列表if( !Application.isPlaying ){allControls = this.GetComponentsInChildren<CustomGUIControl>();}// ...}
}
这使得开发者在编辑模式下无需进入 Play 状态,就能实时看到 GUI 效果,极大提升了开发效率。
2.显示顺序控制
通过根控制器统一管理所有控件的绘制顺序,解决了传统 IMGUI 显示顺序混乱的问题:
// 按数组顺序绘制控件,控制显示层级
for (int i = 0; i < allControls.Length; i++)
{allControls[i].DrawGUI();
}
数组中靠前的控件先绘制,靠后的控件后绘制(显示在上方),开发者可以通过调整数组顺序精确控制显示层级。
六、自定义按钮控件(CustomGUIButton)
我们的自定义按钮实现了点击事件的封装,方便外部进行事件绑定。
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, content ) ){// 安全调用事件(如果有绑定事件处理函数则执行)clickEvent?.Invoke();}}// 重写自定义样式绘制方法protected override void StyleOnDraw(){// 使用自定义样式绘制按钮并检测点击if (GUI.Button(guiPos.Pos, content, style)){// 安全调用事件clickEvent?.Invoke();}}
}
1.按钮控件的核心特性
(1)事件驱动设计:
- 使用
UnityAction
定义点击事件,符合 Unity 的事件处理习惯 - 通过
clickEvent?.Invoke()
实现事件的安全调用,避免空引用异常
(2)样式支持:
- 重写了基类的
StyleOffDraw()
和StyleOnDraw()
方法 - 分别实现了默认样式和自定义样式的按钮绘制逻辑
(3)继承优势:
- 直接使用基类提供的
guiPos
属性实现自适应位置 - 利用基类的
content
属性管理显示内容(文本、图片等) - 自动支持样式开关切换功能
2.按钮控件的使用方法
// 获取按钮控件实例
CustomGUIButton myButton = GetComponent<CustomGUIButton>();// 绑定点击事件
myButton.clickEvent += OnButtonClicked;// 事件处理函数
private void OnButtonClicked()
{Debug.Log("按钮被点击了!");
}
七、自定义标签控件(CustomGUILabel)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class CustomGUILabel : CustomGUIControl
{// 重写默认样式绘制方法protected override void StyleOffDraw(){GUI.Label(guiPos.Pos, content);}// 重写自定义样式绘制方法protected override void StyleOnDraw(){GUI.Label(guiPos.Pos, content, style);}
}
1.标签控件的核心特性
(1)简洁实现:
- 仅重写了绘制方法,没有额外的属性和逻辑
- 专注于文本和图片的展示功能
(2)样式支持:
- 同样实现了默认样式和自定义样式两种绘制方式
- 可通过
GUIStyle
自定义字体、颜色、对齐方式等
(3)内容灵活性:
- 利用
GUIContent
类型的content
属性,支持:- 纯文本显示
- 纯图片显示
- 文本 + 图片组合显示
- 包含提示信息(tooltip)的显示
2.标签控件的应用场景
- 显示标题和说明文字
- 展示状态信息(如生命值、分数等)
- 显示图片或图标
- 作为界面布局的辅助元素
八、自定义复选框控件
1.代码实现
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;public class CustomGUIToggle : CustomGUIControl
{// 复选框当前选中状态public bool isSel;// 状态变化事件,传递新的状态值public event UnityAction<bool> changeValue;// 用于记录上一帧的状态,检测状态变化private bool isOldSel;// 重写默认样式绘制方法protected override void StyleOffDraw(){// 绘制复选框并更新状态isSel = GUI.Toggle(guiPos.Pos, isSel, content);// 仅在状态发生变化时通知外部if(isOldSel != isSel){changeValue?.Invoke(isSel);isOldSel = isSel;}}// 重写自定义样式绘制方法protected override void StyleOnDraw(){// 使用自定义样式绘制复选框并更新状态isSel = GUI.Toggle(guiPos.Pos, isSel, content, style);// 仅在状态发生变化时通知外部if (isOldSel != isSel){changeValue?.Invoke(isSel);isOldSel = isSel;}}
}
2.控件核心特性解析
(1)状态管理
- 当前状态:通过
isSel
变量存储复选框的当前选中状态(true
为选中,false
为未选中) - 状态追踪:使用
isOldSel
变量记录上一帧的状态,用于检测状态是否发生变化 - 状态更新:每次绘制时通过
GUI.Toggle
方法自动更新isSel
的值
(2)事件机制
- 事件定义:使用
UnityAction<bool>
类型的changeValue
事件,当状态变化时会将新状态作为参数传递 - 触发时机:仅在状态实际发生变化时才触发事件,避免不必要的性能消耗
- 安全调用:使用
changeValue?.Invoke(isSel)
语法,确保在没有绑定事件处理函数时不会抛出空引用异常
(3)样式支持
- 继承基类的样式管理机制,支持两种绘制模式:
StyleOffDraw()
:使用默认样式绘制StyleOnDraw()
:使用自定义GUIStyle
绘制
- 两种模式下都保持相同的状态检测和事件触发逻辑,确保行为一致性
九、自定义ToggleGroup 单选框控件
1.代码实现
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class CustomGUIToggleGroup : MonoBehaviour
{// 存储需要管理的所有Toggle控件public CustomGUIToggle[] toggles;// 记录上一次选中的Toggleprivate CustomGUIToggle frontTurTog;void Start(){if (toggles.Length == 0)return;// 为每个Toggle添加状态变化事件监听for (int i = 0; i < toggles.Length; i++){CustomGUIToggle toggle = toggles[i];toggle.changeValue += (value) =>{// 当某个Toggle被选中时if( value ){// 将其他所有Toggle设为未选中for (int j = 0; j < toggles.Length; j++){if( toggles[j] != toggle ){toggles[j].isSel = false;}}// 更新上一次选中的Toggle记录frontTurTog = toggle;}// 当试图取消当前选中的Toggle时else if( toggle == frontTurTog){// 强制保持选中状态(至少保留一个选中项)toggle.isSel = true;}};}}
}
2.实现原理解析
(1)核心功能
CustomGUIToggleGroup
的核心作用是管理一组CustomGUIToggle
控件,确保同一时间只有一个 Toggle 处于选中状态,实现单选功能。
(2)数组管理
- 通过
public CustomGUIToggle[] toggles
数组存储需要管理的所有 Toggle 控件 - 在 Inspector 面板中手动指定需要纳入管理的 Toggle 控件
(3)事件监听
- 在
Start()
方法中为每个 Toggle 绑定changeValue
事件 - 使用 Lambda 表达式处理状态变化逻辑,简洁高效
(4)互斥逻辑
- 当某个 Toggle 被选中(value 为 true)时,遍历所有 Toggle,将其他 Toggle 设为未选中
- 使用
frontTurTog
变量记录当前选中的 Toggle,确保状态一致性
(5)至少一项选中
- 当试图取消当前唯一选中的 Toggle 时,强制保持其选中状态
- 避免出现所有选项都未选中的情况(可根据需求修改此逻辑)
十、自定义输入框控件(CustomGUIInput)
1.代码实现
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(){// 绘制输入框并更新内容content.text = GUI.TextField(guiPos.Pos, content.text);// 仅在文本实际变化时触发事件if(oldStr != content.text){textChange?.Invoke(oldStr);oldStr = content.text;}}// 重写自定义样式绘制方法protected override void StyleOnDraw(){// 使用自定义样式绘制输入框并更新内容content.text = GUI.TextField(guiPos.Pos, content.text, style);// 仅在文本实际变化时触发事件if (oldStr != content.text){textChange?.Invoke(oldStr);oldStr = content.text;}}
}
2.输入框控件核心特性
文本管理:
- 利用基类的
content.text
存储输入的文本内容 - 通过
GUI.TextField
实现文本输入功能
变化检测:
- 使用
oldStr
记录上一帧的文本值 - 仅在文本实际发生变化时触发事件,避免不必要的性能消耗
事件机制:
- 定义
textChange
事件,在文本变化时通知外部 - 事件参数传递旧文本值,方便对比前后变化
样式支持:
- 同时支持默认样式和自定义样式
- 保持与其他控件一致的样式切换机制
十一、自定义滑动条控件(CustomGUISlider)
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 = 0;// 最大值public float maxValue = 1;// 当前值public float nowValue = 0;// 滑动条方向类型public E_Slider_Type type = E_Slider_Type.Horizontal;// 滑块的自定义样式public GUIStyle styleThumb;// 数值变化事件public event UnityAction<float> changeValue;// 用于记录上一次的数值,检测变化private float oldValue = 0;// 重写默认样式绘制方法protected override void StyleOffDraw(){// 根据类型绘制不同方向的滑动条switch (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(nowValue);oldValue = nowValue;}}// 重写自定义样式绘制方法protected override void StyleOnDraw(){// 使用自定义样式绘制不同方向的滑动条switch (type){case E_Slider_Type.Horizontal:nowValue = GUI.HorizontalSlider(guiPos.Pos, nowValue, minValue, maxValue, style, styleThumb);break;case E_Slider_Type.Vertical:nowValue = GUI.VerticalSlider(guiPos.Pos, nowValue, minValue, maxValue, style, styleThumb);break;}// 仅在数值实际变化时触发事件if (oldValue != nowValue){changeValue?.Invoke(nowValue);oldValue = nowValue;}}
}
方向支持:
- 通过
E_Slider_Type
枚举支持水平和垂直两种方向 - 根据方向自动选择对应的绘制方法(
HorizontalSlider
/VerticalSlider
)
数值范围:
- 通过
minValue
和maxValue
定义数值范围 nowValue
存储当前选中的数值,始终保持在范围内
样式定制:
- 支持轨道(
style
)和滑块(styleThumb
)的分别样式定制 - 提供更精细的视觉效果控制
变化检测:
- 使用
oldValue
记录上一帧的数值 - 仅在数值实际变化时触发事件,优化性能
事件机制:
- 定义
changeValue
事件,传递当前数值 - 方便外部系统响应数值变化