Unity GUI实践小项目

文章介绍了如何在Unity中使用ExecuteAlways特性让代码在编辑模式下运行,详细讲解了CustomGUIPos和CustomGUIControl类的设计,包括九宫格布局、控件位置计算、自适应分辨率以及解决按钮层级问题的方法,同时展示了自定义文本、按钮、滑块等控件的实现。
摘要由CSDN通过智能技术生成

必备知识点-编辑模式下让指定代码运行

在类前加入特性[ExecuteAlways],可以让该类在编辑模式下也运行

[ExecuteAlways]
public class Lesson_ExecuteAlways : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        Debug.Log("update");
    }
}

需求分析

九宫格布局概念

控件位置信息类

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 对齐方式枚举
/// </summary>
public enum E_Alignment_Type
{
    Up,
    Down,
    Left,
    Right,
    Center,
    Left_Up,
    Left_Down,
    Right_Up,
    Right_Down
}
/// <summary>
/// 该类 是用来表示位置 计算位置相关信息的 不需要继承Mono
/// </summary>
//加特性使得能在Unity面板上看到
[System.Serializable]
public class CustomGUIPos
{
    //主要是处理 空间位置相关的内容
    //要完成 分辨率自适应的相关计算
    
    //该位置信息 会用来返回给外部 用于绘制控件
    //需要对它 进行计算
    private Rect rPos = new Rect(0,0,100,100);

    //屏幕九宫格对齐方式
    public E_Alignment_Type screen_Alignment_Type = E_Alignment_Type.Center;
    //控件中心对齐方式
    public E_Alignment_Type control_Center_Alignment_Type = E_Alignment_Type.Center;
    //偏移位置
    public Vector2 pos;
    //宽高
    public float width = 100;
    public float height = 50;

    //用于计算中心点的成员变量
    private Vector2 centerPos;
    /// <summary>
    /// 计算中心点偏移的方法
    /// </summary>
    private void CalcCenterPos()
    {
        switch (control_Center_Alignment_Type)
        {
            case E_Alignment_Type.Up:
                centerPos.x = -width/2 ;
                centerPos.y = 0 ;
                break;
            case E_Alignment_Type.Down:
                centerPos.x = -width / 2;
                centerPos.y = -height;
                break;
            case E_Alignment_Type.Left:
                centerPos.x = 0;
                centerPos.y = -height/2;
                break;
            case E_Alignment_Type.Right:
                centerPos.x = -width;
                centerPos.y = -height / 2;
                break;
            case E_Alignment_Type.Center:
                centerPos.x = -width / 2;
                centerPos.y = -height / 2;
                break;
            case E_Alignment_Type.Left_Up:
                centerPos.x = 0;
                centerPos.y = 0;
                break;
            case E_Alignment_Type.Left_Down:
                centerPos.x = 0;
                centerPos.y = -height;
                break;
            case E_Alignment_Type.Right_Up:
                centerPos.x = -width;
                centerPos.y = 0;
                break;
            case E_Alignment_Type.Right_Down:
                centerPos.x = -width;
                centerPos.y = -height;
                break;
        }
    }
    //计算最终相对坐标位置的方法
    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;
            case E_Alignment_Type.Down:
                rPos.x = Screen.width / 2 + centerPos.x + pos.x;
                //为了能让pos.y为正时往上走
                rPos.y = Screen.height + centerPos.y - pos.y;
                break;
            case E_Alignment_Type.Left:
                rPos.x = 0 + centerPos.x + pos.x;
                rPos.y = Screen.height/2 + centerPos.y + pos.y;
                break;
            case E_Alignment_Type.Right:
                rPos.x = Screen.width + centerPos.x - pos.x;
                rPos.y = Screen.height/2 + centerPos.y + pos.y;
                break;
            case E_Alignment_Type.Center:
                rPos.x = Screen.width / 2 + centerPos.x + pos.x;
                rPos.y = Screen.height / 2 + centerPos.y + pos.y;
                break;
            case E_Alignment_Type.Left_Up:
                rPos.x = 0 + centerPos.x + pos.x;
                rPos.y = 0 + centerPos.y + pos.y;
                break;
            case E_Alignment_Type.Left_Down:
                rPos.x = 0 + centerPos.x + pos.x;
                rPos.y = Screen.height  + centerPos.y - pos.y;
                break;
            case E_Alignment_Type.Right_Up:
                rPos.x = Screen.width + centerPos.x - pos.x;
                rPos.y = 0 + centerPos.y + pos.y;
                break;
            case E_Alignment_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();
            //宽高直接赋值 返回给外部 别人直接使用来绘制控件
            rPos.width = width;
            rPos.height = height;
            return rPos; 
        }
    }
}

控件父类

public enum E_Style_OnOff
{
    On, Off,
}
public class CustomGUIControl : MonoBehaviour
{
    //提取控件的共同表现
    //位置信息
    public CustomGUIPos guiPos;
    //显示内容信息
    public GUIContent content;
    //自定义样式
    public GUIStyle style;
    //自定义样式是否启用的开关
    public E_Style_OnOff styleOnOrOff = E_Style_OnOff.Off;

    private void OnGUI()
    {
        switch (styleOnOrOff)
        {
            case E_Style_OnOff.On:
                StyleOnDraw();
                break;
            case E_Style_OnOff.Off:
                StyleOffDraw();
                break;
            default:
                break;
        }
    }
    
    protected virtual void StyleOnDraw()
    {
        GUI.Button(guiPos.Pos, content, style);
    }

    protected virtual void StyleOffDraw()
    {
        GUI.Button(guiPos.Pos, content);
    }
}

由此实现了自适应分辨率,但是目前还有问题,当同时绘制多个按钮时,按钮层级无法确定,接下来要解决这个问题。

所见及所得以及控制绘制顺序

解决2个问题:

1.所见即所得(编辑模式下能看到GUI)(使用特性ExecuteAlways)

2.可以控制控件得绘制顺序(前面的子对象先绘制)

创建CustomGUIRoot脚本

新建root对象作为父对象,使用ExecuteAlways特性使所见即所得

修改CustomGUIControl中的OnGUI为DrawGUI,作为提供给外部绘制GUI的方法

补充CustomGUIRoot脚本

[ExecuteAlways]
public class CustomGUIRoot : MonoBehaviour
{
    //用于存储 子对象 所有GUI控件的容器
    private CustomGUIControl[] allControls;
    void Start()
    {
        allControls = this.GetComponentsInChildren<CustomGUIControl>();
    }
    //在这统一绘制子对象控件的内容
    private void OnGUI()
    {
        //通过每一次绘制之前 得到所有子对象控件的 父类脚本
        //这句代码 浪费性能 因为每次 gui 都会来获取所有的 控件对应的脚本
        //allControls = this.GetComponentsInChildren<CustomGUIControl>();
        //这样子只有编辑状态下一直运行
        if (!Application.isPlaying)
        {
            allControls = this.GetComponentsInChildren<CustomGUIControl>();
        }
        //遍历每一个控件 让其 执行绘制  解决层级问题,后面的对象脚本后执行
        for(int i = 0; i < allControls.Length; i++)
        {
            allControls[i].DrawGUI();
        }
    }
}

自定义常用控件

修改CustomGUIControl为抽象类,同时虚方法改为抽象方法。(父类不直接调用)

自定义文本和按钮控件

自定义文本

public class CustomGUILabel : CustomGUIControl
{
    protected override void StyleOffDraw()
    {
        GUI.Label(guiPos.Pos, content);
    }

    protected override void StyleOnDraw()
    {
        GUI.Label(guiPos.Pos, content,style);
    }
}

挂载到对象上后,可以设置为预设体prefab

自定义按钮

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();
        }
    }

}

自定义多选框

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;
        }
    }
}

自定义单选框

public class CustomGUIToggleGroup : MonoBehaviour
{
    public CustomGUIToggle[] toggles;

    public CustomGUIToggle frontTurTog;
    void Start()
    {
        if(toggles.Length == 0)
        {
            return;
        }

        //通过遍历来为多选框 添加 监听事件函数
        //在函数中处理
        //当一个为true时,另外两个变为 false
        for(int i = 0; i < toggles.Length; i++)
        {
            CustomGUIToggle toggle = toggles[i];
            toggle.changeValue += (value) =>
            {
                //当传入的 value 是 true时 需要把另外两个
                //变成false
                if (value)
                {
                    //意味着另外两个要变成false
                    for (int j = 0; j < toggles.Length; j++)
                    {
                        //这里有闭包 toggle就是上一个函数中申明的变量
                        //改变了它的生命周期
                        if (toggles[j] != toggle)
                        {
                            toggles[j].isSel = false;
                        }
                    }
                    //记录上一次为true的toggle
                    frontTurTog = toggle;
                }
                //来判断 当前变成false的这个toggle是不是上一次为true
                //如果是 就不应该让它变成false 因为单选框必须有一个选择
                else  if(toggle == frontTurTog)
                {
                    //强制改成true
                    toggle.isSel = true;
                }
            };
        }
    }

}

自定义输入框和拖动条控件

输入框

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(content.text);
            oldStr = content.text;
        }
    }

    protected override void StyleOnDraw()
    {
        content.text = GUI.TextField(guiPos.Pos, content.text,style);
        if (oldStr != content.text)
        {
            textChange?.Invoke(content.text);
            oldStr=content.text;
        }
    }
}

拖动条

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;
    //小按钮style
    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;
            default:
                break;
        }
        if(oldValue != nowValue)
        {
            changeValue?.Invoke(nowValue);
            oldValue = nowValue;
        }
    }

    protected override void StyleOnDraw()
    {
        switch (type)
        {
            case E_Slider_Type.Horizontal:
                //一个条style 一个按钮
                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;
            default:
                break;
        }
        if (oldValue != nowValue)
        {
            changeValue?.Invoke(nowValue);
            oldValue = nowValue;
        }
    }
}

自定义图片绘制

public class CustomGUITexture : CustomGUIControl
{
    //图片绘制的缩放模式
    public ScaleMode scaleMode = ScaleMode.StretchToFill;
    protected override void StyleOffDraw()
    {
        GUI.DrawTexture(guiPos.Pos, content.image, scaleMode);
    }

    protected override void StyleOnDraw()
    {
        GUI.DrawTexture(guiPos.Pos, content.image, scaleMode);
    }
}

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值