大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。
一、前言
在开发中总是会控制UI界面,如何优雅的控制UI界面是每一个Unity3D程序员的必修课。
这篇文章就总结了一下博主在实际开发中用到的几种控制UI的方式,分享出来以供批评指正。
在文章的最后,也根据UI控制做了一些延展,比如说:
- 控制UI顺序
- 控制UI层级
- 控制初始化的先后顺序
- 显示隐藏的堆栈
二、正文
2-1、讨论UI控制的解决方案
先说一下痛点吧,隐藏UI面板很简单,xx.SetActive(false);
就行,但是这个管理的脚本放在哪里是个问题。
因为这个挂载的对象一旦隐藏,那么这个脚本就失灵了,所以一般不能挂载在UI面板自己身上,因为一旦隐藏就不管用了。
但是UI统一管理耦合性太高,不适合组件开发,但是也是一种控制方法。
挂载在UI面板自己身上,就需要一些技巧,避开隐藏自身这种行为。
由此为基础,有下面几种方案的讨论。
2-1-1、用一个脚本统一管理脚本的方式
实现方式
先搭建UI,然后新建一个对象挂载控制UI的脚本,然后这个脚本里面控制所有的UI事件。
例子
效果图:
代码参考:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UIControl : MonoBehaviour
{
public GameObject Panel1;
public Button Btn1;
public GameObject Panel2;
public Text TextTitle;
public Text TextContent;
public GameObject Panel3;
public GameObject Panel4;
void Start()
{
// 按钮绑定
Btn1.onClick.AddListener(ClickSure);
}
void ClickSure()
{
}
public void ShowPanel()
{
Panel1.SetActive(true);
}
}
用这个脚本去控制所有的对象,保存隐藏显示、文字赋值、按钮绑定事件等。
优缺点
优点:
脚本一般都在一个独立对象上,脚本容易找。控制显示隐藏不容易报NULL对象错误。
缺点:
耦合性太高,所有的UI对象都在一个脚本中,脚本代码比较拥杂,并且无法独立出来形成组件进行复用。
2-1-2、面板自身挂载脚本,通过控制所有子对象来隐藏面板
实现方式
UI自身带有控制的脚本,通过控制所有子对象来实现隐藏或显示。
例子
效果图:
优缺点
优点:组件化开发,可以形成预制体复用,不必隐藏面板。
缺点:需要获取所有子对象,并且父节点身上不能添加Image,不然隐藏所有子对象也不行。
2-1-3、面板自身挂载脚本,通过控制UI界面缩放来隐藏面板
实现方式
UI界面挂载脚本,控制UI界面的缩放为0即可隐藏脚本,算是视觉隐藏,但是实际没有隐藏。
例子
效果图:
优缺点
优点:不必获取子对象,使用缩放控制。
缺点:不确定面板是否要显示,没法控制显示顺序。
2-1-4、面板自身挂载脚本,通过控制UI子节点来隐藏面板
实现方式
算是第一种和第二种方法的一种优化和升级。
在UI界面下面再设置一个节点用来控制所有的UI对象,随意控制隐藏和显示都没有问题,也不用获取所有的子对象,非常好用。
例子
效果图:
优缺点
优点:控制子对象,不用直接控制UI界面,避免脚本禁用情况,方便管理,也可以默认隐藏,更加灵活。
缺点:搭建UI的时候需要按照一定的规则搭建。比如UI界面是根节点,下一个节点是控制UI界面隐藏和显示的节点,再下面才是真正的UI搭建。
2-2、方法改良及可行性演示
2-1小结分析的几种解决方案都有优点和缺点,再次基础上,总结了一个比较完善的改良型方案。
UI面板自身挂载脚本,下面一个子节点是所有UI的父节点,也就是:
这样的话,一个UI界面的父节点是固定的,然后子节点用同一个名字方便脚本控制。
代码参考:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Diagnostics;
using UnityEngine.UI;
public class Panel3Logic : MonoBehaviour
{
GameObject currentObj;
Button BtnSure;
Action endAction;
void Start()
{
currentObj = transform.Find("UIControl").gameObject;
currentObj.SetActive(false);
BtnSure = currentObj.transform.Find("BtnSure").GetComponent<Button>();
BtnSure.onClick.AddListener(BtnSureEvent);
}
/// <summary>
/// 显示隐藏面板
/// </summary>
/// <param name="ison"></param>
void ShowInfo(bool ison)
{
currentObj.SetActive(ison);
}
/// <summary>
/// 设置委托函数
/// </summary>
/// <param name="ison"></param>
/// <param name="endAction"></param>
void ShowInfo(bool ison, Action endAction)
{
currentObj.SetActive(ison);
this.endAction = endAction;
}
/// <summary>
/// 点击确定的时候,关闭面板,并且执行委托函数
/// </summary>
void BtnSureEvent()
{
currentObj.SetActive(false);
endAction?.Invoke();
}
}
2-3、延展内容
这一节就将UI控制解决方案再做一下延展,包括:
- 控制初始化的先后顺序
- 控制UI层级和顺序
- 显示隐藏的堆栈
新建一个UI基类UIBase.cs,双击编辑代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class UIBase : MonoBehaviour
{
/// <summary>
/// 初始化顺序
/// </summary>
public int StartOrder;
/// <summary>
/// 层级顺序
/// </summary>
public int LayerOrder;
/// <summary>
/// 唯一标识符
/// </summary>
[HideInInspector]public int UniqueID;
public virtual void OnStart() { }
public virtual void ShowInfo(bool ison) { }
}
UI界面控制UI继承与这个基类,比如说Panel1Logic.cs,编辑代码:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Panel1Logic : MonoBehaviour
{
GameObject currentObj;
Button BtnSure;
Action endAction;
/// <summary>
/// 初始化顺序
/// </summary>
public int StartOrder;
/// <summary>
/// 层级顺序
/// </summary>
public int LayerOrder;
/// <summary>
/// 唯一标识符
/// </summary>
public int UniqueID;
public void OnStart()
{
currentObj = transform.Find("UIControl").gameObject;
currentObj.SetActive(false);
BtnSure = currentObj.transform.Find("BtnSure").GetComponent<Button>();
BtnSure.onClick.AddListener(BtnSureEvent);
}
/// <summary>
/// 显示隐藏面板
/// </summary>
/// <param name="ison"></param>
public void ShowInfo(bool ison)
{
currentObj.SetActive(ison);
}
/// <summary>
/// 设置委托函数
/// </summary>
/// <param name="ison"></param>
/// <param name="endAction"></param>
public void ShowInfo(bool ison, Action endAction)
{
currentObj.SetActive(ison);
this.endAction = endAction;
}
/// <summary>
/// 点击确定的时候,关闭面板,并且执行委托函数
/// </summary>
void BtnSureEvent()
{
currentObj.SetActive(false);
endAction?.Invoke();
}
}
UI控制脚本UIControl.cs,编辑代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UIControl : MonoBehaviour
{
public List<UIBase> UIList;
// 使用栈的后进先出的特性,实现UI的后显示先隐藏的功能
Stack<GameObject> uiQueue;
void Start()
{
// 初始化UI
UIInit();
// 初始化参数
uiQueue = new Stack<GameObject>();
}
void UIInit()
{
// 排序
UIList.Sort((x, y) => x.StartOrder.CompareTo(y.StartOrder));
// 设置初始化顺序
for (int i = 0; i < UIList.Count; i++)
{
Debug.Log(UIList[i].StartOrder);
UIList[i].OnStart();
UIList[i].UniqueID = 1000 + i;
}
// 排序
UIList.Sort((x, y) => x.LayerOrder.CompareTo(y.LayerOrder));
// 设置层级顺序
for (int i = 0; i < UIList.Count; i++)
{
Debug.Log(UIList[i].LayerOrder);
UIList[i].transform.SetSiblingIndex(UIList[i].LayerOrder);
}
}
/// <summary>
/// 显示UI
/// </summary>
/// <param name="UniqueID"></param>
public void ShowPanel(int UniqueID)
{
UIBase uiObj = UIList.Find(value => value.UniqueID == UniqueID);
if (uiObj != null)
{
uiObj.ShowInfo(true);
uiQueue.Push(uiObj.gameObject);
}
}
/// <summary>
/// 隐藏UI 适用于多个UI重叠 点击任意位置关闭UI的情况
/// </summary>
public void HidePanel()
{
GameObject ui = uiQueue.Pop();
ui.GetComponent<UIBase>().ShowInfo(false);
}
}
三、后记
如果觉得本篇文章有用别忘了点个关注,关注不迷路,持续分享更多Unity干货文章。
你的点赞就是对博主的支持,有问题记得留言:
博主主页有联系方式。
博主还有跟多宝藏文章等待你的发掘哦: