一、背景

在一个Unity项目中或多或少需要一些UI,如设置页面,登录页面等,当页面过多时,使用一个通用的UI框架来进行针对性开发会大大减少造轮子的过程。
以下即为参考之前做过的一些项目整理出来的一个易于实现且扩展性比较强的UI界面管理的简易框架的实现思路。

二、思路概述

将所有的UI界面统一继承一个基类,在基类中实现UI的显示隐藏等UI通用功能,然后定义一个管理类将所有的界面信息放入字典中进行统一管理。重写系统的监听方法,实现对按钮的等UI的监听,并开放一个接口来给需要监听的UI来调用添加监听与相应委托事件。

三、具体实现代码及其思路梳理

1.UI路径信息

将所有的UI提前制作好预制体保存在相应路径中,建立一个UIPathData类来封装UI的路径和信息。在类中用一个字典来保存路径和名称,另设几个基本变量来保存当前UI的状态。

public class UIPathData
{//路径保存和名称保存public static Dictionary<string, UIPathData> dicName = new Dictionary<string, UIPathData>();//资源名称public string dName;//是否入栈public bool IsPush = true;//是否关闭所有栈中的界面,用于不需要返回弹出栈中的界面,主要是一,二级界面用public bool IsCloseAllUI = false;//默认UI缩放应该根据分辨率缩放public float UIRootScale = 1;
}

在类的构造方法中设置界面加载所需的参数如界面资源名称、所在层级、父节点锚点类型等。

public UIPathData(string uiName)
{dName = uiName;if (!dicName.ContainsKey(dName)){dicName.Add(dName, this);//将所有实例化的UI都添加进字典方便管理}
}

然后在类中实现一些静态方法方便对字典的查询

public static UIPathData GetByName(string name)
{UIPathData ret = null;if (!dicName.TryGetValue(name, out ret)){return null;}return ret;
}
public static void ClearData()
{dicName.Clear();
}

经过以上步骤我们便有了一个保存有UI的路径等信息的字典,并且通过实例化即可将UI加入字典来进行管理。所以在类中创建一个新的类,在类中将所有的UI进行实例化便可直接获得保存有所有UI的字典。当有新的UI需要加入项目时也只需要在此进行实例化即可。

public class UIPathInfo
{public static UIPathData UICanvas1 = new UIPathData("UICanvas1");public static UIPathData UICanvas2 = new UIPathData("UICanvas2");
}
2.UI的基类

对于每个UI预制体的逻辑,我们先创建一个UI基类BaseUI。在该类中用一个字典来存放当前UI界面用到的所有物体。

public class BaseUI : MonoBehaviour
{//描述:保存当前UI界面用到的所有物体public Dictionary<string, GameObject> UI = new Dictionary<string, GameObject>();//保存当前资源路径信息public UIPathData CurPath { get; set; }
}

每个UI都需要显示、隐藏、销毁及初始化的功能,于是把它们添加进基类并定义为虚方法

//初始化方法(可重写)
public virtual void Init(){}
//隐藏
public void DisActiveUI(){ gameObject.SetActive(false); }
//显示
public void ActiveUI(){ gameObject.SetActive(true); }
//销毁UI
public void DestroyUI(){ GameObject.Destroy(gameObject); }

添加获取所有子物体的初始化方法UIinit(),将它和Init方法在Awake()方法中调用,这样当UI资源被实例化后都能获取到所有的子物体并存入字典,接着将执行每个UI界面各自的初始化方法。

void Awake()
{UIInit();Init();
}
//初始化UI, 继承该基类时, 此方法必须在Awake中调用
private void UIInit()
{Transform[] childsAll = GetComponentsInChildren<Transform>(true);UI.Clear();for (int i = 0; i < childsAll.Length; i++){if (childsAll[i].CompareTag("UI")){if (UI.ContainsKey(childsAll[i].name)){continue;}UI.Add(childsAll[i].name, childsAll[i].gameObject);}}
}

因为只有基类直接继承了MonoBehaver,所以在基类中将系统方法根据需要定义成虚方法,方便子类重写

public virtual void Start(){}
public virtual void Update(){}
public virtual void OnEnable(){}
public virtual void OnDisable(){}
public virtual void OnDestroy(){}
public virtual void LateUpdate(){}
3.界面的基类

再写一个界面基类BasePanel来继承BaseUI,所有界面的UI逻辑脚本都将继承该基类。在这里重载基类的显示隐藏与销毁方法,在界面初始化时获取或添加CanvasGroup组件,用来控制界面显示与隐藏。

public class BasePanel : BaseUI
{bool isActived;//用来记录显示状态public bool IsActive{get => isActived;}CanvasGroup curRootCanvasGp;//预留处理界面包含的方法public override void Init(){base.Init();curRootCanvasGp = gameObject.GetComponent<CanvasGroup>();if (curRootCanvasGp==null){curRootCanvasGp= gameObject.AddComponent<CanvasGroup>();}}
}

在该类中实现显示、隐藏与销毁的方法

//显示界面
public virtual void OnShow()
{if (curRootCanvasGp != null){curRootCanvasGp.alpha = 1;curRootCanvasGp.blocksRaycasts = true;curRootCanvasGp.interactable = true;isActived = true;}
}
//隐藏不销毁
public virtual void OnHide()
{if (curRootCanvasGp != null){curRootCanvasGp.alpha = 0;curRootCanvasGp.blocksRaycasts = false;curRootCanvasGp.interactable = false;isActived = false; }
}
//关闭界面
public virtual void OnClose()
{DestroyUI();
}
4.UI的加载管理

用单例模式定义一个UI加载管理类,在类中定义两个字典用于保存所有UI及其对应预制体资源,用以对所有的UI界面进行管理

public class UILoadManager : MonoBehaviour
{private static UILoadManager instance;public static UILoadManager Instance{get { return instance; }}//保存所有UIprivate Dictionary<string, BasePanel> mallDicBasePanel = new Dictionary<string, BasePanel>();private Dictionary<string, GameObject> mallDicGameObject = new Dictionary<string, GameObject>();private Transform UIParentObj;private void Awake(){instance = this;UIParentObj = this.transform;}void OnDestroy(){instance = null;}
}

在类中实现界面的打开,定义一个委托,当界面打开后需要执行回调函数时将作为参数传入加载UI的方法。
在加载UI时先去查找字典中是否存在该UI的记录,如果能查得到但资源丢失则直接清除记录,资源存在则直接打开。
当不存在资源时则调用资源管理层的方法来加载UI资源(这里示例调用了系统接口加载Resource文件夹里的预制体)。

public delegate void OnOpenUIDelegate(bool bSuccess, object param);public static void ShowUI(UIPathData pathdata, OnOpenUIDelegate Callback = null, object param = null)
{Instance.LoadUI(pathdata, Callback, param);
}void LoadUI(UIPathData uiData, OnOpenUIDelegate Callback = null, object param = null, UnityEngine.Object curAssetObj = null)
{if (Instance == null){return;}GameObject uiobj = null;BasePanel uipanel = null;//加载或获取当前界面if (mallDicGameObject.TryGetValue(uiData.dName, out uiobj))//字典中已经记录了该UI物体{if (uiobj == null)//资源不存在了{mallDicGameObject.Remove(uiData.dName);//移除该记录if (Instance.mallDicBasePanel.ContainsKey(uiData.dName)){Instance.mallDicBasePanel.Remove(uiData.dName);}}if (Instance.mallDicBasePanel.TryGetValue(uiData.dName, out uipanel)){if (uipanel == null){Instance.mallDicBasePanel.Remove(uiData.dName);}}}if (uiobj == null){//资源参数赋值UnityEngine.Object tempw = curAssetObj;if (tempw == null){//Res加载//tempw = ResLoadManager.LoadResAsset(uiData.dName, ResLoadManager.AssetsEnum.UIPrefab);tempw=Resource.Load(uiData.dName,ResLoadManager)}if (tempw != null){uiobj = Instantiate(tempw) as GameObject;BasePanel temppanel = uiobj.GetComponent<BasePanel>();temppanel.CurPath = uiData;uiobj.transform.parent = UIParentObj;mallDicGameObject.Add(uiData.dName, uiobj);if (!Instance.mallDicBasePanel.ContainsKey(uiData.dName)){Instance.mallDicBasePanel.Add(uiData.dName, temppanel);uipanel = temppanel;}else{uipanel = Instance.mallDicBasePanel[uiData.dName];}}}Instance.WndUIPush(uipanel);if (Callback != null){Callback(uiobj != null, param);}
}
//UI入栈
void WndUIPush(BasePanel uiPanel)
{if (Instance != null){uiPanel.ActiveUI();uiPanel.OnShow();}
}

关闭界面的方法:

//处理自身界面关闭,关闭后对应类型的栈弹出显示
public void OnCloseUI(BasePanel uiPanel)
{//根据当前界面类型 处理关闭界面if (Instance == null)return;//移除对应集合中的数据Debug.Log(uiPanel.CurPath.dName);if (mallDicBasePanel.ContainsKey(uiPanel.CurPath.dName)){mallDicBasePanel.Remove(uiPanel.CurPath.dName);}List<BasePanel> tempStack = null;if (uiPanel.CurPath.IsCloseAllUI){//关闭所有for (int i = 0; i < tempStack.Count; i++){BasePanel temp = tempStack[i];if (mallDicBasePanel.ContainsKey(temp.CurPath.dName)){mallDicBasePanel.Remove(temp.CurPath.dName);}DestroyUI(temp.gameObject);}tempStack.Clear();}else{if (mallDicBasePanel.ContainsKey(uiPanel.CurPath.dName)){mallDicBasePanel.Remove(uiPanel.CurPath.dName);}DestroyUI(uiPanel.gameObject);}
}
void DestroyUI(GameObject obj)
{Destroy(obj);
}
IEnumerator Dispose(GameObject obj)
{yield return new WaitForSeconds(1);Resources.UnloadUnusedAssets();
}
5.界面的打开与关闭

当需要关闭某个界面时只需要在界面基类的关闭界面方法添加对该静态方法的调用

UILoadManager.Instance.OnCloseUI(this);

对于管理类中打开界面方法的调用可以新建一个类来作为所有界面打开的入口

public class LinkManager
{//无参数时:public static void OnOpenCanvas1(){UILoadManager.ShowUI(UIPathInfo.MainUICanvas);}//有参数时public static void OnOpenCanvas2(object param1){//UILoadManager.ShowUI(UIPathInfo.KnowledgeCatalogUICanvas);UILoadManager.ShowUI(UIPathInfo.KnowledgeCatalogUICanvas, (bool b, object param) =>{if (b){if (Canvas1.Instance){Debug.Log("执行了回调方法");}}});}
}
6.UI事件的监听

对于UI事件的监听,先定义一个事件触发器,该类继承EventTrigger,在类中定义所需要监听的事件的委托,然后重写EventTrigger里相应的方法

public class MUIEventListener : EventTrigger
{public delegate void VoidDelegate(GameObject go);public VoidDelegate onClick;public VoidDelegate onDown;public override void OnPointerClick(PointerEventData eventData){if (onClick != null) onClick(gameObject);}public override void OnPointerDown(PointerEventData eventData){if (onDown != null) onDown(gameObject);}
}

然后在该类中定义一个方法来添加监听即可

public static MUIEventListener Get(GameObject go)
{MUIEventListener listener = go.GetComponent<MUIEventListener>();if (listener == null)listener = go.AddComponent<MUIEventListener>();return listener;
}

当需要的时候使用以下形式给UI组件添加委托

MUIEventListener.Get(Button).onClick = Func;
void Func(GameObject go)
{Debug.Log("Button被点击了");
}

四、说明

1.该框架需要将需要加载的UI界面提前制作成预制体的形式进行存储,然后每个界面的逻辑可在各自的UI上挂载各自的逻辑脚本
2.该模板只是一个初步整理的简易框架,可能描述会有不清晰,框架结构不完善考虑不周到等问题,后续会不断完善优化。

Unity项目UI管理简易框架相关推荐

  1. 【逆水寒三周年】大型MMO项目UI管理的价值与责任

    <逆水寒>作为一款大型MMO端游,UI工作的复杂和压力相当巨大,虽然最艰难紧张的时间是17年到19年,但是如今想来还是心有余悸.大型MMO端游流程复杂.资源繁多.开发过程中,参与人员多.开 ...

  2. 逆水寒大型MMO项目UI管理的价值与责任

    <逆水寒>作为一款大型MMO端游,UI工作的复杂和压力相当巨大,虽然最艰难紧张的时间是17年到21年,但是如今想来还是心有余悸.大型MMO端游流程复杂.资源繁多.开发过程中,参与人员多.开 ...

  3. Unity消息简易框架 Advanced C# messenger

    Unity消息简易框架 Advanced C# messenger Unity C# 消息机制  [转载 雨凇MOMO博客] https://www.xuanyusong.com/archives/2 ...

  4. [团队] 在Unity项目中使用FMOD来管理你的音效

    Hello各位小伙伴, 今天来聊一下Unity项目中的音效管理. 我目前正在研发的项目, 使用了FMOD的来管理音效和背景音乐. FMOD本身是一款免费的音频集成工具. 自身的音效编辑器还拥有强大的编 ...

  5. 【狂云歌之unity_vr】unity项目持续集成dailybuild以及多平台打包管理

    [狂云歌之unity_vr]unity项目持续集成dailybuild以及多平台打包管理 前言  持续集成的意义就不多说了.unity通常打包一般就直接build&run,但是在实际项目中,往 ...

  6. Unity程序开发框架——UI管理模块

    UI基类BasePanel负责帮助我门通过代码快速的找到所有的子控件,方便我们在子类中处理逻辑,节约找控件的工作量. public class BasePanel : MonoBehaviour {/ ...

  7. Unity项目中UI美术必须知道的程序要点

    原文地址:http://youxiputao.com/articles/4820 本文转载自IndieACE(游戏葡萄),是开发者DonaldW写给UI美术同事的一篇文章,原文题为<Unity项 ...

  8. Unity的UI框架

    UI框架 UI框架的含义 含义:UI框架用于管理场景中所有的面板,负责控制面板之间的跳转 UI框架的意义 1.随着游戏系统的复杂化,UI控件越来越多,各个UI之间的直接通讯,已经UI与GameObje ...

  9. Vite + Vue3 + Antd + Typescript 管理后台前端简易框架

    Vite + Vue3 + Antd + Typescript 管理后台前端简易框架 这里是antd版本,如果你更倾向于使用element-plus,请点击这里. ✨ 最新版本 v1.0.5 新增环境 ...

最新文章

  1. sift论文_卷积神经网络设计相关论文
  2. Database Appliance并非Mini版的Exadata-还原真实的Oracle Unbreakable Database Appliance
  3. 懂得了这些才可以说学习Python入门了
  4. 小程序直播间页面路径怎么访问直播间_以小程序为例,在线教育产品的直播间有哪些功能设计?...
  5. 音视频技术开发周刊 67期
  6. Angular 自定义 structural 指令的一个例子
  7. 程序员,软件测试知多少?
  8. HTTPS请求实现框架
  9. html 文本溢出,确定HTML元素的内容是否溢出
  10. 《软件需求分析(第二版)》第 2 章——客户眼中的需求 重点部分总结
  11. 使用ogg实现oracle到kafka的增量数据实时同步
  12. 40-400-040-运维-优化-MySQL File Sort 原理以及优化
  13. ArcGIS API for JavaScript——绘制工具(Draw)
  14. LeetCode 368. 最大整除子集(动态规划)
  15. python rarfile_Python中zipfile压缩文件模块的基本使用教程
  16. 程序员博客是否应该晒代码(由摄影而感)
  17. vue 组件开发 ---- rui-vue-poster 海报制作
  18. C语言:IF-ELSE的配对问题
  19. 大学三方协议约定的服务器,大学生签订三方协议要注意
  20. 苹果不创新,库克有道理

热门文章

  1. java ISO 8601 日期格式进行转换
  2. 城市排水管网系统如何实现在线监测和自动预警
  3. vs2017的文件组成
  4. 初中数学知识点总结_全部初中数学知识点总结(最全).pdf
  5. 修改自己电脑上的DNS域名解析
  6. 禅道配置企业微信应用webhook通知消息企业微信到个人
  7. NVIDIA Jetson TX2 System-on-Module中文简介
  8. L1-081 今天我要赢 L1-082 种钻石 C语言
  9. php 怎么获取中文首字母排序,利用PHP怎么获取第一个中文首字母并进行排序
  10. ELK搭建及基础使用(docker版)