Unity游戏开发笔记-UI框架
谈到UI,所有游戏前端程序员都深恶痛绝。因为这是每个开发的必经之路。拼UI,接协议,写逻辑,做动画,各种模块之前的相互调用。嘤嘤嘤~想想都觉得麻烦,但是这又是不可避免的,毕竟UI也是游戏很重要的一部分啊。一个合理的UI框架的设计,不仅能提高程序的开发效率,在应对后续策划五花八门的需求的时候,也能表现的从容不迫。UI框架的设计真的很重要~大家不要小瞧了这件事情~下面步入正题,我们通过简单的图来梳理下UI框架是什么,需要实现那些东西:
上面是一个简单的Ui基类设计!提供了一些常用的属性以及方法!我们每一个界面都应该继承这个类实现基础接口!这里我们对UI的生命周期做了一个简单的划分,方便我们在实现功能的时候有一个明确的思路,粗暴一点直接上代码:
// **************************************
//
// 文件名(MUIBase.cs):
// 功能描述("UI基类"):
// 作者(Max1993):
// 日期(2019/5/1921: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 = &#34;&#34;;
//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 + &#34;/&#34; + 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(&#34;UI加载失败了老铁~看看路径 ResPath: &#34; + 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):
// 功能描述(&#34;UI核心管理类&#34;):
// 作者(Max1993):
// 日期(2019/5/1921: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(&#34;初始化UIManager 失败了~&#34;);
return false;
}
m_uiRoot.name = &#34;UIRoot&#34;;
m_uiRoot.SetActive(true);
m_transNormal = m_uiRoot.transform.Find(&#34;NormalLayer&#34;);
m_rectTransNormal = m_transNormal.gameObject.GetComponent<RectTransform>();
m_transTop = m_uiRoot.transform.Find(&#34;TopLayer&#34;);
m_rectTransTop = m_transTop.gameObject.GetComponent<RectTransform>();
m_transUpper = m_uiRoot.transform.Find(&#34;UpperLayer&#34;);
m_rectTransUpper = m_transUpper.gameObject.GetComponent<RectTransform>();
m_transHUD = m_uiRoot.transform.Find(&#34;HudLayer&#34;);
m_rectTransHUD = m_transHUD.gameObject.GetComponent<RectTransform>();
m_uiCamera = m_uiRoot.transform.Find(&#34;Camera&#34;).GetComponent<Camera>();
GameObject.DontDestroyOnLoad(m_uiRoot);
return true;
}
public const string LOGON_CONTROLLER = &#34;LoginPanel.prefab&#34;;
/// <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=&#34;uiName&#34;></param>
/// <returns></returns>
public MUIBase ActiveUI(string uiName)
{
MUIBase mUIBase = GetUI(uiName);
if (mUIBase == null)
{
Debug.LogError(&#34;UIDic里面没有这个UI信息 UIName:&#34;+ uiName);
return null;
}
if (!mUIBase.IsInited)
{
mUIBase.Init();
}
return mUIBase;
}
/// <summary>
/// 关闭一个UI的接口
/// </summary>
/// <param name=&#34;uiName&#34;></param>
public void DeActiveUI(string uiName)
{
MUIBase mUIBase = GetUI(uiName);
if (mUIBase == null)
{
Debug.LogError(&#34;UIDic里面没有这个UI信息 UIName:&#34; + uiName);
return;
}
if(mUIBase.IsInited)
{
if (mUIBase.Active)
{
mUIBase.Active = false;
}
mUIBase.Uninit();
}
}
/// <summary>
/// 获取一个UI的接口
/// </summary>
/// <param name=&#34;uiName&#34;></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=&#34;delta&#34;></param>
public void Update(float delta)
{
foreach (var mUIBase in m_uiDict.Values)
{
mUIBase.Update(delta);
}
}
/// <summary>
/// LateUpdate方法
/// </summary>
/// <param name=&#34;delta&#34;></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(&#34;LoginPanel&#34;, MUILayerType.Normal)
{
}
public override void Init()
{
base.Init();
BtnStart = m_uiGameObject.transform.Find(&#34;BtnLogin&#34;).gameObject;
BtnExit= m_uiGameObject.transform.Find(&#34;BtnExit&#34;).gameObject;
BtnStart.GetComponent<Button>().onClick.AddListener(delegate
{
Debug.Log(&#34;Start&#34;);
});
BtnExit.GetComponent<Button>().onClick.AddListener(delegate
{
Debug.Log(&#34;Exit&#34;);
});
}
protected override void OnActive()
{
Debug.Log(&#34;Active&#34;);
}
protected override void OnDeActive()
{
Debug.Log(&#34;OnDeActive&#34;);
}
}
}嗯,UI框架基本上介绍的差不多了。其实有很多接口需求,都是需要在实际项目开发中结合实际遇到的问题,慢慢加进去的,但是基础的东西就这些了。这里其实还有几点没有介绍到,就是对数据层的管理还有复用组件的封装。
复用组件其实不是一个独立的UI,他就是一个节点下的Object,但是这个Object可能在游戏中的各个地方都可能会用到,那么我们就很有必要,对这个做下逻辑的封装管理,提高业务开发的速度,以及避免重复代码的编写。
数据和显示层的剥离也是一个比较必要的框架设计部分,各司其职,这样代码结构看起来就会清晰很多。
其实当前商业化游戏,业务逻辑基本都是在Lua层实现的。下一篇文章,我会写一下在Lua层的封装,以及上面提到的两个点的做一个具体的解释。 多谢 嗯,简单的思路介绍~希望能帮到你~ 这个类图是用什么软件画的? 你那个在UI管理类中去Update 是有点多余了,首先UI加载出来,必须是继承了MonoMehaviou类的,也是必须有GameObject 的, 完全可以根据自己的UI弹窗自己去管理的,比如你的UIBase类就是一个基类也是必须要继承MonoMehaviou否则你UI怎么显示,怎么调取SetAction函数。 所以UI管理类中的Update完全是多余,而且你还用foreach遍历,不找死么,无形增加开销。 为什么一定要继承mono?照你你这样说那些用lua的,ui都做不了了? 作者这明显是一套不继承mono的ui解决方案,看清楚再说
页:
[1]