为什么要使用UI框架呢?
在我刚使用Unity开发UI界面时,根本没想过用什么UI框架,都是想到要什么界面就通过UGUI拖动什么界面。如果需要实现交互功能,就会绑定对应的监听函数,这样的做法固然是非常的简单直接,但是也会留下一定的弊端。
当你的项目不在简单时,UI界面和控件越来越多时,你有时候会找不到哪个对象和哪个对象关联,要是团队合作的话,别人找你的UI接口更是找半天,耦合度非常之高,经常会牵一发而动全身,代码混乱,所以这个时候有一个好的UI框架,会更便于我们整合和使用UI。
设计UI框架的要点
1.设计一个UI的基类BaseUI,里面有UI的一些基本属性,显示方式等,所有UI的脚本都继承它
2.设计一个管理UI的框架UIManager,为UI的显示、销毁、切换等提供方法接口,并把该脚本设为单例模式,每个UI间互不干扰
3.UI间通信、UI和游戏层的通信通过一个消息分发的脚本去实现,即利用观察者模式,都通过MessageCenter来联系
部分伪代码:
建立一个定义UI类型的脚本
//窗体ID
public enum E_UiId
{
MainUI,//主界面
PlayUI,//游戏界面
}
//窗体的层级
public enum E_UIRootType
{
KeepAbove,// 保持在最前方的窗体
Normal// 普通窗体
}
//窗体的显示方式
public enum E_ShowUIMode
{
// 窗体显示出来的时候,不会去隐藏任何窗体
DoNothing,
// 窗体显示出来的时候,会隐藏掉所有的普通窗体,但是不会隐藏保持在最前方的窗体
HideOther,
// 窗体显示出来的时候,会隐藏所有的窗体,不管是普通的还是保持在最前方的
HideAll
}
// 消息类型
public enum E_MessageType
{
ImageChange,// 图片更换
textChange,// 文字更换
RewardChange,// 奖励更换
WeaponChange,// 武器更换
BulletTips// 弹药提示
}
public class GameDefine
{
// 导入UI预制体文件(名字,路径)
public static Dictionary<E_UiId, string> dicPath = new Dictionary<E_UiId, string>()
{
{ E_UiId.MainUI,"UIPrefab/"+"MainPanel"},
{ E_UiId.PlayUI,"UIPrefab/"+"PlayPanel"}
};
// 自动挂载UI脚本
public static Type GetUIScriptType(E_UiId uiId)
{
Type scriptType = null;
switch (uiId)
{
case E_UiId.NullUI:
break;
case E_UiId.MainUI:
scriptType = typeof(MainUI);
break;
case E_UiId.PlayUI:
scriptType = typeof(PlayUI);
break;
default:
break;
}
return scriptType;
}
}
定义UI基类
//窗体类型
public class UIType
{
//显示方式
public E_ShowUIMode showMode = E_ShowUIMode.HideOther;
//父节点的类型
public E_UIRootType uiRootType = E_UIRootType.Normal;
}
public class BaseUI : MonoBehaviour
{
//窗体类型
public UIType uiType;
//缓存窗体的RectTransform组件
protected RectTransform thisTrans;
//当前窗体的ID
protected E_UiId uiId = E_UiId.NullUI;
//上一个窗体的ID
protected E_UiId beforeUiId = E_UiId.NullUI;
//获取当前窗体的ID
public E_UiId GetUiId
{
get
{
return uiId;
}
//为什么没有set?
//因为每个窗体的ID都是固定的,不能被外界随意修改,外界只能获取它的值
//只有在子类才能对该窗体的ID进行赋值或修改
//set
//{
// uiId = value;
//}
}
protected virtual void Awake()
{
if (uiType == null)
{
uiType = new UIType();
}
thisTrans = this.GetComponent<RectTransform>();
InitUiOnAwake();
InitDataOnAwake();
}
//用于判断窗体显示出来的时候,是否需要去隐藏其他窗体
public bool IsHideOtherUI()
{
if (this.uiType.showMode == E_ShowUIMode.DoNothing)
{
return false;//不需要隐藏其他窗体
}
else
{
//需要去处理隐藏其他窗体的逻辑
return true;// E_ShowUIMode.HideOther与 E_ShowUIMode.HideAll
}
}
//初始化界面元素
protected virtual void InitUiOnAwake()
{
}
//初始化数据
protected virtual void InitDataOnAwake()
{
}
protected virtual void Start()
{
InitOnStart();
}
//初始化相关逻辑
protected virtual void InitOnStart()
{
}
//窗体的显示
public virtual void ShowUI()
{
this.gameObject.SetActive(true);
}
//窗体额隐藏
public virtual void HideUI(Del_AfterHideUI del = null)
{
this.gameObject.SetActive(false);
if (del != null)
{
del();
}
}
定义UIManager框架
//这里我把单例模式写成了一个基类UnitySingleton并继承
public class UIManager : UnitySingleton<UIManager>
{
//缓存所有打开过的窗体
private Dictionary<E_UiId, BaseUI> dicAllUI;
//缓存正在显示的窗体
private Dictionary<E_UiId, BaseUI> dicShowUI;
//缓存最近显示出来的窗体
private BaseUI currentUI = null;
//缓存上一个窗体
// private BaseUI beforeUI = null;
private E_UiId beforeUiId = E_UiId.NullUI;
//缓存画布
private Transform canvas;
//缓存保持在最前方的窗体的父节点
private Transform keepAboveUIRoot;
//缓存普通窗体的父节点
private Transform normalUIRoot;
private void Awake()
{
//Test.Instance.Show();
dicAllUI = new Dictionary<E_UiId, BaseUI>();
dicShowUI = new Dictionary<E_UiId, BaseUI>();
InitUIManager();
}
//初始化UI管理类
private void InitUIManager()
{
canvas = this.transform.parent;
//设置画布在场景切换的时候不被销毁,因为整个游戏共用唯一的一个画布
DontDestroyOnLoad(canvas);
if (keepAboveUIRoot==null)
{
keepAboveUIRoot = GameTool.FindTheChild(canvas.gameObject, "KeepAboveUIRoot");
}
if (normalUIRoot==null)
{
normalUIRoot= GameTool.FindTheChild(canvas.gameObject, "NormalUIRoot");
}
}
//供外界调用,销毁窗体
public void DestroyUI(E_UiId uiId)
{
if (dicAllUI.ContainsKey(uiId))
{
//存在该窗体,去销毁
Destroy( dicAllUI[uiId].gameObject);
dicAllUI.Remove(uiId);
}
}
//供外界调用的,显示窗体的方法
public BaseUI ShowUI(E_UiId uiId,bool isSaveBeforeUiId=true)
{
if (uiId == E_UiId.NullUI)
{
uiId = E_UiId.MainUI;
}
BaseUI baseUI=JudgeShowUI(uiId);
if (baseUI!=null)
{
baseUI.ShowUI();
}
if (isSaveBeforeUiId)
{
baseUI.BeforeUiId = beforeUiId;
}
return baseUI;
}
//供外界调用,反向切换窗体的方法
public void ReturnBeforeUI(E_UiId uiId)
{
ShowUI(uiId,false);
}
//供外界调用的,隐藏单个窗体的方法
public void HideSingleUI(E_UiId uiId, Del_AfterHideUI del=null)
{
if (!dicShowUI.ContainsKey(uiId))
{
return;
}
dicShowUI[uiId].HideUI(del);
dicShowUI.Remove(uiId);
}
private BaseUI JudgeShowUI(E_UiId uiId)
{
//判断将要显示的窗体是否已经正在显示了
if (dicShowUI.ContainsKey(uiId))
{
//如果已经正在显示了,就不需要处理其他逻辑了
return null;
}
//判断窗体是否有加载过
BaseUI baseUI = GetBaseUI(uiId);
if (baseUI == null)
{
//说明这个窗体没显示过(没有加载过),要去动态加载
string path = GameDefine.dicPath[uiId];
GameObject theUI = Resources.Load<GameObject>(path);
if (theUI != null)
{
//把该窗体生成出来
GameObject willShowUI = Instantiate(theUI);
//窗体生成出来后,要确保有挂对应的UI脚本
baseUI = willShowUI.GetComponent<BaseUI>();
if (baseUI == null)
{
//说明生成出来的这个窗体上面没有挂载对应的UI脚本
//那么就需要给这个窗体自动添加对应的脚本
Type type = GameDefine.GetUIScriptType(uiId);
baseUI = willShowUI.AddComponent(type) as BaseUI;
}
//判断这个窗体是属于哪个父节点的
Transform uiRoot = GetTheUIRoot(baseUI);
GameTool.AddChildToParent(uiRoot, willShowUI.transform);
willShowUI.GetComponent<RectTransform>().sizeDelta = Vector2.zero;
//这个窗体是第一次加载显示出来的,那么就需要缓存起来
dicAllUI.Add(uiId, baseUI);
}
else
{
Debug.LogError("指定路径下面找不到对应的预制体");
}
}
UpdateDicShowUIAndHideUI(baseUI);
return baseUI;
}
//更新缓存正在显示的窗体的字典并且隐藏对应的窗体
private void UpdateDicShowUIAndHideUI(BaseUI baseUI)
{
//判断是否需要隐藏其他窗体
if (baseUI.IsHideOtherUI())
{
//如果返回值为true, E_ShowUIMode.HideOther与 E_ShowUIMode.HideAll
//需要隐藏其他窗体
if (dicShowUI.Count>0)
{
//有窗体正在显示,就要隐藏对应的窗体
if (baseUI.uiType.showMode == E_ShowUIMode.HideOther)
{
HideAllUI(false, baseUI);
}
else
{
HideAllUI(true, baseUI);
}
}
}
//更新缓存正在显示的窗体的字典
dicShowUI.Add(baseUI.GetUiId, baseUI);
}
public void HideAllUI(bool isHideAboveUI,BaseUI baseUI)
{
if (isHideAboveUI)
{
//1、隐藏所有的窗体,不管是普通窗体还是保持在最前方的窗体,都需要全部隐藏
foreach (KeyValuePair<E_UiId,BaseUI> uiItem in dicShowUI)
{
uiItem.Value.HideUI();
}
dicShowUI.Clear();
}
else
{
//2、隐藏所有的窗体,但是不包含保持在最前方的窗体
//缓存所有被隐藏的窗体
List<E_UiId> list = new List<E_UiId>();
foreach (KeyValuePair<E_UiId, BaseUI> uiItem in dicShowUI)
{
//如果不是保持在最前方的窗体
if (uiItem.Value.uiType.uiRootType != E_UIRootType.KeepAbove)
{
uiItem.Value.HideUI();
//存储上一个窗体的ID
beforeUiId = uiItem.Key;
// baseUI.BeforeUiId= uiItem.Key;
list.Add(uiItem.Key);
}
}
for (int i = 0; i < list.Count; i++)
{
dicShowUI.Remove(list[i]);
}
}
}
//判断窗体的父物体
private Transform GetTheUIRoot(BaseUI baseUI)
{
if (baseUI.uiType.uiRootType == E_UIRootType.KeepAbove)
{
return keepAboveUIRoot;
}
else
{
return normalUIRoot;
}
}
private BaseUI GetBaseUI(E_UiId UiId)
{
if (dicAllUI.ContainsKey(UiId))
{
return dicAllUI[UiId];
}
else
{
return null;
}
}
}
定义通讯中心MessageCenter
public class MessageCenter : MonoBehaviour
{
public delegate void CallBack(object obj);
//一个用来存放所有监听的字典<消息类型,监听到消息后所要处理的逻辑>
public static Dictionary<E_MessageType, CallBack> dicMessageType = new Dictionary<E_MessageType, CallBack>();
//添加监听
public static void AddMessageListener(E_MessageType messageType, CallBack callBack)
{
if (!dicMessageType.ContainsKey(messageType))
{
dicMessageType.Add(messageType, null);
}
dicMessageType[messageType] += callBack;
}
//取消监听
public static void RemoveListener(E_MessageType messageType, CallBack callBack)
{
if (dicMessageType.ContainsKey(messageType))
{
dicMessageType[messageType] -= callBack;
}
}
//取消所有的监听
public static void RemoveAllMessage()
{
dicMessageType.Clear();
}
//广播消息
public static void SendMessage(E_MessageType messageType, object obj = null)
{
CallBack callBack;
if (dicMessageType.TryGetValue(messageType, out callBack))
{
if (callBack != null)
{
callBack(obj);
}
}
}
}
以上就是一个基本的UI框架了,接下来只要自己去创建对应的UI界面并处理好继承问题和初始化一些基础的UI属性即可,其他的一些UI框架其实也万变不离其中,主要目的还是解耦,便于处理,如果有好用的框架欢迎私信或者底下留言我,大家一起分享!