2025GUI项目实践:Unity编辑模式下GUI运行

一、需求分析

二、九宫格布局

三、空间位置信息类

1.对齐方式枚举(E_Alignment_Type)

2.核心属性

3.位置计算核心逻辑

[步骤 1:计算控件中心点偏移(CalcCenterPos)](#步骤 1:计算控件中心点偏移(CalcCenterPos))

[步骤 2:计算屏幕坐标位置(CalcPos)](#步骤 2:计算屏幕坐标位置(CalcPos))

4.最终位置获取

四、控件基类设计

1.样式开关枚举(E_Style_OnOff)

[2.自定义 GUI 控件基类(CustomGUIControl)](#2.自定义 GUI 控件基类(CustomGUIControl))

(1)核心属性

(2)核心方法

3.自定义位置计算类(CustomGUIPos)

五、编辑模式GUI实时预览和显示顺序

[1. 编辑模式实时预览](#1. 编辑模式实时预览)

2.显示顺序控制

六、自定义按钮控件(CustomGUIButton)

1.按钮控件的核心特性

2.按钮控件的使用方法

七、自定义标签控件(CustomGUILabel)

1.标签控件的核心特性

2.标签控件的应用场景

八、自定义复选框控件

1.代码实现

2.控件核心特性解析

(1)状态管理

(2)事件机制

(3)样式支持

[九、自定义ToggleGroup 单选框控件](#九、自定义ToggleGroup 单选框控件)

1.代码实现

2.实现原理解析

(1)核心功能

(2)数组管理

(3)事件监听

(4)互斥逻辑

(5)至少一项选中

十、自定义输入框控件(CustomGUIInput)

1.代码实现

2.输入框控件核心特性

十一、自定义滑动条控件(CustomGUISlider)


一、需求分析


二、九宫格布局


三、空间位置信息类

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

数值范围

  • 通过minValuemaxValue定义数值范围
  • nowValue存储当前选中的数值,始终保持在范围内

样式定制

  • 支持轨道(style)和滑块(styleThumb)的分别样式定制
  • 提供更精细的视觉效果控制

变化检测

  • 使用oldValue记录上一帧的数值
  • 仅在数值实际变化时触发事件,优化性能

事件机制

  • 定义changeValue事件,传递当前数值
  • 方便外部系统响应数值变化
相关推荐
wanhengidc2 小时前
本机网速会影响到云手机的运行吗
运维·服务器·安全·游戏·智能手机
王源骏3 小时前
Unity实现网页端 打开本地文件选择图片上传到阿里云的OSS
unity·阿里云·游戏引擎
爱吃小胖橘4 小时前
Unity-角色控制器
3d·unity·c#·游戏引擎
绀目澄清10 小时前
unity3d PuppetMaster 布娃娃插件在学习
unity
PaoloBanchero12 小时前
Unity 虚拟仿真实验中设计模式的使用 ——状态模式(State Pattern)
unity·设计模式·状态模式
SmalBox12 小时前
【光照】[PBR][镜面反射]实现方法解析
unity·渲染
高山有多高13 小时前
C语言实战项目:贪吃蛇(1)
c语言·开发语言·数据结构·c++·算法·游戏
wanhengidc14 小时前
云手机:云计算的灵动化身
运维·服务器·人工智能·游戏·智能手机·云计算
Robbie丨Yang14 小时前
【Unity 入门教程】四、如何制作一个 Perfab
unity·游戏引擎