[步骤 1:计算控件中心点偏移(CalcCenterPos)](#步骤 1:计算控件中心点偏移(CalcCenterPos))
[步骤 2:计算屏幕坐标位置(CalcPos)](#步骤 2:计算屏幕坐标位置(CalcPos))
[2.自定义 GUI 控件基类(CustomGUIControl)](#2.自定义 GUI 控件基类(CustomGUIControl))
[1. 编辑模式实时预览](#1. 编辑模式实时预览)
[九、自定义ToggleGroup 单选框控件](#九、自定义ToggleGroup 单选框控件)
一、需求分析


二、九宫格布局


三、空间位置信息类
1.对齐方式枚举(E_Alignment_Type)
定义了 9 种基础对齐方式,覆盖了屏幕和控件的所有关键位置。
cs
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)
根据控件自身的对齐方式,计算控件左上角相对于其对齐点的偏移量:
cs
private void CalcCenterPos()
{
switch (control_Center_Alignment_Type)
{
case E_Alignment_Type.Up:
centerPos.x = -width / 2; // 水平居中
centerPos.y = 0; // 顶部对齐
break;
// 其他对齐方式的计算...
}
}
步骤 2:计算屏幕坐标位置(CalcPos)
根据屏幕对齐方式,结合屏幕分辨率和偏移量,计算控件在屏幕上的最终位置:
cs
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
属性将计算逻辑封装,外部使用时无需关心内部计算细节:
cs
public Rect Pos
{
get
{
CalcCenterPos(); // 先计算控件自身偏移
CalcPos(); // 再计算屏幕位置
rPos.width = width;
rPos.height = height;
return rPos;
}
}
四、控件基类设计
1.样式开关枚举(E_Style_OnOff)
cs
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 的生命周期方法,作为控件绘制的入口
cs
private void OnGUI()
{
switch (styleOnOrOff)
{
case E_Style_OnOff.On:
StyleOnDraw(); // 使用自定义样式绘制
break;
case E_Style_OnOff.Off:
StyleOffDraw(); // 使用默认样式绘制
break;
}
}
StyleOnDraw():使用自定义样式绘制控件的虚方法
cs
protected virtual void StyleOnDraw()
{
// 子类需重写此方法实现具体绘制逻辑
// 示例: GUI.Button(guiPos.Pos, content, style);
}
StyleOffDraw():使用默认样式绘制控件的虚方法
cs
protected virtual void StyleOffDraw()
{
// 子类需重写此方法实现具体绘制逻辑
// 示例: GUI.Button(guiPos.Pos, content);
}
3.自定义位置计算类(CustomGUIPos)
此类在之前基础上增加了[System.Serializable]
特性,使其可以在 Inspector 面板中显示和编辑,极大提升了可视化编辑体验。
cs
[System.Serializable]
public class CustomGUIPos
{
// 保持原有功能实现...
}
五、编辑模式GUI实时预览和显示顺序
1. 编辑模式实时预览
通过**[ExecuteAlways]
特性**和特殊的更新逻辑,实现了编辑模式下的实时预览:
cs
[ExecuteAlways]
public class CustomGUIRoot : MonoBehaviour
{
// ...
private void OnGUI()
{
// 编辑模式下每次绘制前更新控件列表
if( !Application.isPlaying )
{
allControls = this.GetComponentsInChildren<CustomGUIControl>();
}
// ...
}
}
这使得开发者在编辑模式下无需进入 Play 状态,就能实时看到 GUI 效果,极大提升了开发效率。
2.显示顺序控制
通过根控制器统一管理所有控件的绘制顺序,解决了传统 IMGUI 显示顺序混乱的问题:
cs
// 按数组顺序绘制控件,控制显示层级
for (int i = 0; i < allControls.Length; i++)
{
allControls[i].DrawGUI();
}
数组中靠前的控件先绘制,靠后的控件后绘制(显示在上方),开发者可以通过调整数组顺序精确控制显示层级。
六、自定义按钮控件(CustomGUIButton)
我们的自定义按钮实现了点击事件的封装,方便外部进行事件绑定。
cs
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.按钮控件的使用方法
cs
// 获取按钮控件实例
CustomGUIButton myButton = GetComponent<CustomGUIButton>();
// 绑定点击事件
myButton.clickEvent += OnButtonClicked;
// 事件处理函数
private void OnButtonClicked()
{
Debug.Log("按钮被点击了!");
}
七、自定义标签控件(CustomGUILabel)
cs
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.代码实现
cs
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.代码实现
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CustomGUIToggleGroup : MonoBehaviour
{
// 存储需要管理的所有Toggle控件
public CustomGUIToggle[] toggles;
// 记录上一次选中的Toggle
private 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.代码实现
cs
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)
cs
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
事件,传递当前数值 - 方便外部系统响应数值变化