找回密码
 立即注册
查看: 244|回复: 6

Unity游戏开发笔记-UI框架

[复制链接]
发表于 2022-9-21 14:26 | 显示全部楼层 |阅读模式
谈到UI,所有游戏前端程序员都深恶痛绝。因为这是每个开发的必经之路。拼UI,接协议,写逻辑,做动画,各种模块之前的相互调用。嘤嘤嘤~想想都觉得麻烦,但是这又是不可避免的,毕竟UI也是游戏很重要的一部分啊。一个合理的UI框架的设计,不仅能提高程序的开发效率,在应对后续策划五花八门的需求的时候,也能表现的从容不迫。UI框架的设计真的很重要~大家不要小瞧了这件事情~
       下面步入正题,我们通过简单的图来梳理下UI框架是什么,需要实现那些东西:


上面是一个简单的Ui基类设计!提供了一些常用的属性以及方法!我们每一个界面都应该继承这个类实现基础接口!这里我们对UI的生命周期做了一个简单的划分,方便我们在实现功能的时候有一个明确的思路,粗暴一点直接上代码:
// **************************************
//
// 文件名(MUIBase.cs):
// 功能描述("UI基类"):
// 作者(Max1993):
// 日期(2019/5/19  21:26):
//
// **************************************
//
using UnityEngine;
using System.Collections;

namespace MFrameWork
{
    //GameObject Loaded CallBack 物体加载回掉
    public delegate void GameObjectLoadedCallBack(GameObject obj);

    /// <summary>
    /// UI层级
    /// </summary>
    public enum MUILayerType
    {
        Top,
        Upper,
        Normal,
        Hud
    }

    /// <summary>
    /// 加载UI的方式
    /// </summary>
    public enum MUILoadType
    {
        SyncLoad,
        AsyncLoad,
    }

    public abstract class MUIBase
    {
        /// <summary>
        /// 是否加载完成的标志位
        /// </summary>
        protected bool m_isInited;

        /// <summary>
        /// UI名字
        /// </summary>
        protected string m_uiName;

        /// <summary>
        /// 在关闭的时候是否缓存UI 默认不缓存
        /// </summary>
        protected bool m_isCatchUI=false;

        /// <summary>
        /// UI的实例化GamObejct
        /// </summary>
        protected GameObject m_uiGameObject;

        /// <summary>
        /// 设置UI可见性状态
        /// </summary>
        protected bool m_active = false;

        /// <summary>
        /// 加载完成的回调
        /// </summary>
        protected GameObjectLoadedCallBack m_callBack;

        /// <summary>
        /// UI的资源全路径
        /// </summary>
        protected string m_uiFullPath = "";

        //UILayerType UI层类型
        protected MUILayerType m_uiLayerType;

        //UI的加载方式
        protected MUILoadType m_uiLoadType;

        public string UIName
        {
            get { return m_uiName;  }
            set
            {
                m_uiName = value.EndsWith(MPathUtils.UI_PREFAB_SUFFIX) ? m_uiName = value : m_uiName = value + MPathUtils.UI_PREFAB_SUFFIX;
                m_uiFullPath = MPathUtils.UI_MAINPATH + "/" + m_uiName;
            }
        }

        public bool IsCatchUI
        {
            get { return m_isCatchUI; }
            set
            {
                m_isCatchUI = value;
            }
        }

        public GameObject UIGameObject
        {
            get { return m_uiGameObject; }
            set { m_uiGameObject = value; }
        }

        public bool Active
        {
            get { return m_active; }
            set
            {
                m_active = value;
                if(m_uiGameObject!=null)
                {
                    m_uiGameObject.SetActive(value);
                    if(m_uiGameObject.activeSelf)
                    {
                        OnActive();   
                    }
                    else
                    {
                        OnDeActive();
                    }
                }
            }
        }

        public bool IsInited { get { return m_isInited; } }

        protected MUIBase(string uiName,MUILayerType layerType,MUILoadType loadType = MUILoadType.SyncLoad)
        {
            UIName = uiName;
            m_uiLayerType = layerType;
            m_uiLoadType = loadType;
        }

        public virtual void Init()
        {
            if(m_uiLoadType == MUILoadType.SyncLoad)
            {
                GameObject go = MObjectManager.singleton.InstantiateGameObeject(m_uiFullPath);
                OnGameObjectLoaded(go);
            }
            else
            {
                MObjectManager.singleton.InstantiateGameObejectAsync(m_uiFullPath,delegate (string resPath, MResourceObjectItem mResourceObjectItem,object[] parms)
                {
                    GameObject go = mResourceObjectItem.m_cloneObeject;
                    OnGameObjectLoaded(go);
                },LoadResPriority.RES_LOAD_LEVEL_HEIGHT);
            }
        }

        private void OnGameObjectLoaded(GameObject uiObj)
        {
            if(uiObj == null)
            {
                MDebug.singleton.AddErrorLog("UI加载失败了老铁~看看路径 ResPath: " + m_uiFullPath);
                return;
            }
            m_uiGameObject = uiObj;
            m_isInited = true;
            SetPanetByLayerType(m_uiLayerType);
            m_uiGameObject.transform.localPosition = Vector3.zero;
            m_uiGameObject.transform.localScale = Vector3.one;
        }

        public virtual void Uninit()
        {
            m_isInited = false;
            m_active = false;
            if (m_isCatchUI)
            {
                //资源并加入到资源池
                MObjectManager.singleton.ReleaseObject(m_uiGameObject);
            }
            else
            {
                //彻底清除Object资源
                MObjectManager.singleton.ReleaseObject(m_uiGameObject, 0, true);
            }
        }

        protected abstract void OnActive();

        protected abstract void OnDeActive();

        public virtual void Update(float deltaTime)
        {
            
        }

        public virtual void LateUpdate(float deltaTime)
        {
            
        }

        public virtual void OnLogOut()
        {
            
        }

        protected void SetPanetByLayerType(MUILayerType layerType)
        {
            switch(m_uiLayerType)
            {
                case MUILayerType.Top:
                    m_uiGameObject.transform.SetParent(MUIManager.singleton.MTransTop);
                    break;
                case MUILayerType.Upper:
                    m_uiGameObject.transform.SetParent(MUIManager.singleton.MTransUpper);
                    break;
                case MUILayerType.Normal:
                    m_uiGameObject.transform.SetParent(MUIManager.singleton.MTransNormal);
                    break;
                case MUILayerType.Hud:
                    m_uiGameObject.transform.SetParent(MUIManager.singleton.MTransHUD);
                    break;
            }
        }
    }
}
UI的管理类,说白了就是对UIBase的一个管理类。提供一些常用的接口函数,管理UI层级,创建,销毁,缓存等,加单来说如下图,上图:


// **************************************
//
// 文件名(MUIManager.cs):
// 功能描述("UI核心管理类"):
// 作者(Max1993):
// 日期(2019/5/19  21:26):
//
// **************************************
//
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using System.Collections.Generic;

namespace MFrameWork
{
    public class MUIManager : MSingleton<MUIManager>
    {
        /// <summary>
        /// 核心的管理所有UI的Dictionary
        /// </summary>
        private Dictionary<string, MUIBase> m_uiDict;

        /// <summary>
        /// 摄像机UICamera
        /// </summary>
        private Camera m_uiCamera;

        private GameObject m_uiRoot;
        private Transform m_transNormal;
        private RectTransform m_rectTransNormal;
        private Transform m_transTop;
        private RectTransform m_rectTransTop;
        private Transform m_transUpper;
        private RectTransform m_rectTransUpper;
        private Transform m_transHUD;
        private RectTransform m_rectTransHUD;

        //以下是一些基础的层级位置信息
        public GameObject MUIRoot { get { return m_uiRoot; } }
        public Transform MTransNormal { get { return m_transNormal; } }
        public RectTransform MRectTransNormal { get { return m_rectTransNormal; } }
        public Transform MTransUpper { get { return m_transUpper; } }
        public RectTransform MRectTransUpper { get { return m_rectTransUpper; } }
        public Transform MTransTop { get { return m_transTop; } }
        public RectTransform MRectTransTop { get { return m_rectTransTop; } }
        public Transform MTransHUD { get { return m_transHUD; } }
        public RectTransform MRectTransHUD { get { return m_rectTransHUD; } }

        public Camera MUICamera
        {
            get
            {
                return m_uiCamera;
            }
        }

        public override bool Init()
                {
            return InitUIInfo() && UIRegister();
        }

        /// <summary>
        /// 初始化一些常用的UI信息
        /// </summary>
        /// <returns></returns>
        public bool InitUIInfo()
        {
            m_uiDict = new Dictionary<string, MUIBase>();
            m_uiRoot = MObjectManager.singleton.InstantiateGameObeject(MPathUtils.UI_ROOTPATH);
            if (m_uiRoot == null)
            {
                MDebug.singleton.AddErrorLog("初始化UIManager 失败了~");
                return false;
            }
            m_uiRoot.name = "UIRoot";
            m_uiRoot.SetActive(true);
            m_transNormal = m_uiRoot.transform.Find("NormalLayer");
            m_rectTransNormal = m_transNormal.gameObject.GetComponent<RectTransform>();
            m_transTop = m_uiRoot.transform.Find("TopLayer");
            m_rectTransTop = m_transTop.gameObject.GetComponent<RectTransform>();
            m_transUpper = m_uiRoot.transform.Find("UpperLayer");
            m_rectTransUpper = m_transUpper.gameObject.GetComponent<RectTransform>();
            m_transHUD = m_uiRoot.transform.Find("HudLayer");
            m_rectTransHUD = m_transHUD.gameObject.GetComponent<RectTransform>();
            m_uiCamera = m_uiRoot.transform.Find("Camera").GetComponent<Camera>();
            GameObject.DontDestroyOnLoad(m_uiRoot);
            return true;
        }

        public const string LOGON_CONTROLLER = "LoginPanel.prefab";
        /// <summary>
        /// 在C#层实现逻辑的UI进行注册注册
        /// </summary>
        /// <returns></returns>
        private bool UIRegister()
        {
            m_uiDict.Add(LOGON_CONTROLLER, new LogonController());
            return true;
        }

        public override void UnInit()
                {
            if (m_uiRoot)
            {
                MObjectManager.singleton.ReleaseObjectComopletly(m_uiRoot);
                m_uiRoot = null;
                m_transNormal = null;
                m_rectTransNormal = null;
                m_transTop = null;
                m_rectTransTop = null;
                m_transUpper = null;
                m_rectTransUpper = null;
                m_transHUD = null;
                m_rectTransHUD = null;
                m_uiCamera = null;
            }
                }

                public override void OnLogOut()
                {
            base.OnLogOut();
                }

        /// <summary>
        /// 打开一个UI的接口
        /// </summary>
        /// <param name="uiName"></param>
        /// <returns></returns>
        public MUIBase ActiveUI(string uiName)
        {
            MUIBase mUIBase = GetUI(uiName);
            if (mUIBase == null)
            {
                Debug.LogError("UIDic里面没有这个UI信息 UIName:"+ uiName);
                return null;
            }

            if (!mUIBase.IsInited)
            {
                mUIBase.Init();
            }

            return mUIBase;
        }

        /// <summary>
        /// 关闭一个UI的接口
        /// </summary>
        /// <param name="uiName"></param>
        public void DeActiveUI(string uiName)
        {
            MUIBase mUIBase = GetUI(uiName);
            if (mUIBase == null)
            {
                Debug.LogError("UIDic里面没有这个UI信息 UIName:" + uiName);
                return;
            }

            if(mUIBase.IsInited)
            {
                if (mUIBase.Active)
                {
                    mUIBase.Active = false;
                }
                mUIBase.Uninit();
            }

        }

        /// <summary>
        /// 获取一个UI的接口
        /// </summary>
        /// <param name="uiName"></param>
        /// <returns></returns>
        public MUIBase GetUI(string uiName)
        {
            MUIBase mUIBase = null;
            m_uiDict.TryGetValue(uiName, out mUIBase);
            return mUIBase;
        }

        public T GetUI<T>(string uiName) where T : MUIBase
        {
            MUIBase mUIBase = null;
            if (m_uiDict.TryGetValue(uiName, out mUIBase))
            {
                if (mUIBase is T)
                {
                    return (T)mUIBase;
                }
            }
            return null;
        }

        /// <summary>
        /// 关闭所有UI的接口
        /// </summary>
        public void DeActiveAll()
        {
            foreach (KeyValuePair<string, MUIBase> pair in m_uiDict)
            {
                DeActiveUI(pair.Key);
            }
        }
        
        /// <summary>
        /// Update方法
        /// </summary>
        /// <param name="delta"></param>
        public void Update(float delta)
        {
            foreach (var mUIBase in m_uiDict.Values)
            {
                mUIBase.Update(delta);
            }
        }

        /// <summary>
        /// LateUpdate方法
        /// </summary>
        /// <param name="delta"></param>
        public void LateUpdate(float delta)
        {
            foreach (var mUIBase in m_uiDict.Values)
            {
                mUIBase.LateUpdate(delta);
            }
        }

        /// <summary>
        /// 注销方法
        /// </summary>
        public void OnLogout()
        {
            foreach (var mUIBase in m_uiDict.Values)
            {
                mUIBase.OnLogOut();
            }
            if (m_uiCamera)
            {
                m_uiCamera.enabled = false;
            }
        }
    }
}
然后就是每一个独立的UI,创建类继承自MuiBase,实现一些核心接口,完成业务相关功能就可以了。如下所示:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;

namespace MFrameWork
{
    public class LogonController : MUIBase
    {
        public GameObject BtnStart;
        public GameObject BtnExit;

        public LogonController() : base("LoginPanel", MUILayerType.Normal)
        {

        }

                public override void Init()
                {
            base.Init();
            BtnStart = m_uiGameObject.transform.Find("BtnLogin").gameObject;
            BtnExit  = m_uiGameObject.transform.Find("BtnExit").gameObject;

            BtnStart.GetComponent<Button>().onClick.AddListener(delegate
            {
                Debug.Log("Start");
            });

            BtnExit.GetComponent<Button>().onClick.AddListener(delegate
            {
                Debug.Log("Exit");
            });
                }

                protected override void OnActive()
        {
            Debug.Log("Active");
        }

        protected override void OnDeActive()
        {
            Debug.Log("OnDeActive");
        }

    }
}嗯,UI框架基本上介绍的差不多了。其实有很多接口需求,都是需要在实际项目开发中结合实际遇到的问题,慢慢加进去的,但是基础的东西就这些了。这里其实还有几点没有介绍到,就是对数据层的管理还有复用组件的封装。
复用组件其实不是一个独立的UI,他就是一个节点下的Object,但是这个Object可能在游戏中的各个地方都可能会用到,那么我们就很有必要,对这个做下逻辑的封装管理,提高业务开发的速度,以及避免重复代码的编写。
数据和显示层的剥离也是一个比较必要的框架设计部分,各司其职,这样代码结构看起来就会清晰很多。
其实当前商业化游戏,业务逻辑基本都是在Lua层实现的。下一篇文章,我会写一下在Lua层的封装,以及上面提到的两个点的做一个具体的解释。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2022-9-21 14:27 | 显示全部楼层
多谢
发表于 2022-9-21 14:34 | 显示全部楼层
嗯,简单的思路介绍~希望能帮到你~
发表于 2022-9-21 14:37 | 显示全部楼层
这个类图是用什么软件画的?
发表于 2022-9-21 14:43 | 显示全部楼层
你那个在UI管理类中去Update 是有点多余了,首先UI加载出来,必须是继承了MonoMehaviou类的,也是必须有GameObject 的, 完全可以根据自己的UI弹窗自己去管理的,比如你的UIBase类就是一个基类也是必须要继承MonoMehaviou否则你UI怎么显示,怎么调取SetAction函数。 所以UI管理类中的Update完全是多余,而且你还用foreach遍历,不找死么,无形增加开销。
发表于 2022-9-21 14:51 | 显示全部楼层
为什么一定要继承mono?照你你这样说那些用lua的,ui都做不了了?
发表于 2022-9-21 14:56 | 显示全部楼层
作者这明显是一套不继承mono的ui解决方案,看清楚再说
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-24 10:28 , Processed in 0.210889 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表