通过GUI封装了解高级UI插件原理

通过GUI封装了解高级UI插件原理

结果演示

在这里插入图片描述

这里我将成品打包,下面是下载地址:

https://download.csdn.net/download/BraveRunTo/15347380

目的

通过对Unity自带GUI的封装训练来更好的理解NGUI,UGUI的原理。

需求分析

我们需要提取所有控件的共性部分来实现封装。所有的控件都会含有一个位置信息Rect,一个内容信息GUIContent和样式信息GUIStyle(不填样式信息默认使用Unity的预设样式),所以我们对GUI的封装就是要封装这些共性部分,并通过一个父类来代表所有的控件。

由于位置信息Rect需要做UI自适应逻辑,而内容信息和样式信息属于可以直接面板赋值的字段,不需要处理额外逻辑,所以我们需要将Rect单独提成一个类来处理逻辑。这样体现了类的单一原则思想。

RectCalculator类

我们为Rect逻辑类起名为RectCalculator,意为Rect计算器,负责计算Rect方位,实现UI位置自适应。RectCalculator作为数据处理类不需要继承Monobehaviour。

在进行逻辑处理之前我们需要了解九宫格的概念。

九宫格:

九宫格就是将一块屏幕分成9个部分:中,左,右,上,下,左上,左下,右上,右下。每一个部分都一个相对UI屏幕原点的原点,此原点是通过屏幕宽和高计算出来的。如下图所示:

在这里插入图片描述

其中彩色方块中的坐标为每个相对原点的坐标信息,简称相对屏幕位置。NGUI,UGUI的UI自适应都用到了九宫格的概念。

除了屏幕有九宫格的概念,控件的中心点也有九宫格的概念,如下图:

在这里插入图片描述

这里由于当屏幕原点确定后,改变控件原点位置,视觉上控件会向相反的方向移动,所以这里的坐标信息都是负数。这里我们简称为控件原点偏移坐标

由屏幕九宫格和控件九宫格就组成了我们NGUI,UGUI中的锚点anchor和中心点Priot。这里我们直接得出控件坐标的计算公式:

控件坐标计算公式 = 相对屏幕坐标 + 控件原点偏移坐标 + 偏移位置。

代码:

所以我们的RectCalculator首先需要一个枚举来列出九宫格布局如下:

public enum AnchorPositionType
    {
        Center,
        Up,
        Down,
        Left,
        Right,
        LeftUp,
        RightUp,
        LeftDown,
        RightDown, 
    }

利用九宫格的计算公式我们可以书写以下代码:

//使用此特性来使关联此类的对象可以在inspector面板中看到此类的信息
[System.Serializable]
public class RectCaculator
{
    //当前控件的Rect信息
   private Rect _widgetRealRect = Rect.zero;
    //控件锚点,控件原点相对位置
   public AnchorPositionType widgetAnchor = AnchorPositionType.Center;
   //父类锚点,这里是屏幕相对位置
   public AnchorPositionType parentAnchor = AnchorPositionType.Center;
    //控件偏移量
   public Vector2 offset;
   //计算完后的控件原点相对位置
   private Vector2 widgetPosition;
    //控件宽度
   public float widgetWidth;
   //控件高度
   public float widgetHeight;
    //屏幕宽度
   private float screenWidth;
   //屏幕高度
   private float screenHeight;
    //对外公开的Rect信息只读属性,外部直接调用此属性就可以得到当前控件的Rect信息
   public Rect WidgetRealRect
   {
       get
       {
           //对控件的宽和高进行赋值
           _widgetRealRect.width = widgetWidth;
           _widgetRealRect.height = widgetHeight;
           //计算控件原点相对位置
           CalculateWidgetPos();
           //计算控件坐标
           CalculateParentPosition();
           //返回计算结果
           return _widgetRealRect;
       }
   }
    /// <summary>
    /// 计算控件原点相对位置
    /// </summary>
   private void CalculateWidgetPos()
   {
       switch (widgetAnchor)
       {
           case AnchorPositionType.Center:
               widgetPosition.x = -widgetWidth / 2;
               widgetPosition.y = -widgetHeight / 2;
               break;
           case AnchorPositionType.Down:
               widgetPosition.x = -widgetWidth / 2;
               widgetPosition.y = -widgetHeight;
               break;
           case AnchorPositionType.Up:
               widgetPosition.x = -widgetWidth / 2;
               widgetPosition.y = 0;
               break;
           case AnchorPositionType.Left:
               widgetPosition.x = 0;
               widgetPosition.y = -widgetHeight / 2;
               break;
           case AnchorPositionType.Right:
               widgetPosition.x = -widgetWidth;
               widgetPosition.y = -widgetHeight / 2;
               break;
           case AnchorPositionType.LeftUp:
               widgetPosition.x = 0;
               widgetPosition.y = 0;
               break;
           case AnchorPositionType.RightUp:
               widgetPosition.x = -widgetWidth;
               widgetPosition.y = 0;
               break;
           case AnchorPositionType.LeftDown:
               widgetPosition.x = 0;
               widgetPosition.y = -widgetHeight;
               break;
           case AnchorPositionType.RightDown:
               widgetPosition.x = -widgetWidth;
               widgetPosition.y = -widgetHeight;
               break;
       }
   }
    /// <summary>
    /// 控件坐标计算公式 = 相对屏幕坐标 + 控件原点偏移坐标 + 偏移位置,
    /// 根据此公式得出结果
    /// </summary>
   private void CalculateParentPosition()
   {
       screenWidth = Screen.width;
       screenHeight = Screen.height;
       switch (parentAnchor)
       {
           case AnchorPositionType.Center:
               _widgetRealRect.x = screenWidth / 2 + widgetPosition.x + offset.x;
               _widgetRealRect.y = screenHeight / 2 + widgetPosition.y + offset.y;
               break;
           case AnchorPositionType.Down:
               _widgetRealRect.x = screenWidth / 2 + widgetPosition.x + offset.x;
               _widgetRealRect.y = screenHeight + widgetPosition.y + offset.y;
               break;
           case AnchorPositionType.Up:
               _widgetRealRect.x = screenWidth / 2 + widgetPosition.x + offset.x;
               _widgetRealRect.y = 0 + widgetPosition.y + offset.y;
               break;
           case AnchorPositionType.Left:
               _widgetRealRect.x = 0 + widgetPosition.x + offset.x;
               _widgetRealRect.y = screenHeight / 2 + widgetPosition.y + offset.y;
               break;
           case AnchorPositionType.Right:
               _widgetRealRect.x = screenWidth + widgetPosition.x + offset.x;
               _widgetRealRect.y = screenHeight / 2 + widgetPosition.y + offset.y;
               break;
           case AnchorPositionType.LeftUp:
               _widgetRealRect.x = 0 + widgetPosition.x + offset.x;
               _widgetRealRect.y = 0 + widgetPosition.y + offset.y;
               break;
           case AnchorPositionType.RightUp:
               _widgetRealRect.x = screenWidth + widgetPosition.x + offset.x;
               _widgetRealRect.y = 0 + widgetPosition.y + offset.y;
               break;
           case AnchorPositionType.LeftDown:
               _widgetRealRect.x = 0 + widgetPosition.x + offset.x;
               _widgetRealRect.y = screenHeight + widgetPosition.y + offset.y;
               break;
           case AnchorPositionType.RightDown:
               _widgetRealRect.x = screenWidth + widgetPosition.x + offset.x;
               _widgetRealRect.y = screenHeight + widgetPosition.y + offset.y;
               break;
       }
   }
}

ContentClass类

完成了Rect计算类,接下来我们就可以书写控件基类ContentClass了,当然这个名字起的不太好。

作为所有控件的基类,此类需要有Rect信息,内容信息,样式信息,和控件的绘制方法。

代码:

public abstract class ContentClass : MonoBehaviour
{
    public RectCaculator rectCalculator;

    public GUIContent content;

    public GUIStyle style;

    public bool useCustomStyle = false;
    /// <summary>
    /// UI创建类,Draw其实就是CreateUI成员的过程
    /// </summary>
    public void DrawGui()
    {
        if (useCustomStyle)
        {
            UseStyle();
        }
        else
        {
            DoNotUseStyle();
        }
    }
    /// <summary>
    /// 具体创建什么UI由子类决定
    /// </summary>
    protected abstract void UseStyle();

    protected abstract void DoNotUseStyle();
}

所有属性都设置为public是为了可以在inspector面板看到,当然也可以使用特性来实现。

到这里我们就实现了GUI的基本封装,但还存在着问题:

  1. GUI绘制的顺序是不确定的。
  2. 编辑模式下不能看到GUI。

这里我们模仿NGUI和UGUI创建一个画布类或者叫UI根类来解决这两个问题。

UIRoot类

  1. 顺序不能确定,使用GetComponentsInChildren获得控件数组,从而有序绘制。
  2. [ExecuteAlways],此特性保证unity生命周期在编译模式下也可以被调用。
//此特性保证unity生命周期在编译模式下也可以被调用。
[ExecuteAlways]
public class UIRoot : MonoBehaviour
{
    //子类控件ContentClass数组。这里利用了多态的思想。
    private ContentClass[] _childUi;

    private void Awake()
    {
        SreachChild();
    }
    //搜索子类中所有的控件
    private void SreachChild()
    {
        _childUi = GetComponentsInChildren<ContentClass>();
    }

    private void OnGUI()
    {
        //如果当前为编辑模式,则持续搜索子类控件,来达到对于控件的实时控制
        if (!Application.isPlaying)
        {
            SreachChild();
        }
        //按照搜索的结果依次调用绘制方法。
        for (int i = 0; i < _childUi.Length; i++)
        {
            if (_childUi[i].gameObject.activeInHierarchy)
            {
                _childUi[i].DrawGui(); 
            }
        }
    }
}

这样我们的自定义GUI封装,CGUI就完成了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值