为什么要使用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框架其实也万变不离其中,主要目的还是解耦