前言
刚开始做游戏时,大家就说入行了都是UI仔,证明了UI的重要性,导致我对UI框架一直有一种畏惧的心理。
这次就从GF的UI加载来简单看看GF的UI框架以及它的使用
在这里给大家推荐我们群主写的GF解析,写的非常好
传送门
打开UI界面
第一视角分析,找到ProcedureMenu.cs
首先订阅一个UI成功打开的事件,接下来就是一个OpenUIForm函数
我们继续往上,还需要往上。此处int?代表可空类型,如果没有赋值会返回null而不是0
此处我们通过读取配置表,并且加上前置路径,继续往上看
此处是UI组件调用UIManager.OpenUIForm方法
UIManager属于逻辑层也就是GF层面的了,我们继续看源码。此处的逻辑是从对象池获取UI实例,如果对象池中没有
就先在 m_UIFormsBeingLoaded字典中记录,这个字典记录的应该是正在加载的UIForm,然后通过ResourceManager来异步加载对应资源,我们在LoadAsset时还传入了加载成功时的回调方法
此回调的逻辑是从m_UIFormsBeingLoaded移除该UI的id
再通过UIFormInstanceObject.Create()创建出对应UI实例,通过对象池进行注册,最后调用InternalOpenUIForm方法
如果一开始对象池没有记录,就直接调用InternalOpenUIForm方法
/// <summary>
/// 打开界面。
/// </summary>
/// <param name="uiFormAssetName">界面资源名称。</param>
/// <param name="uiGroupName">界面组名称。</param>
/// <param name="priority">加载界面资源的优先级。</param>
/// <param name="pauseCoveredUIForm">是否暂停被覆盖的界面。</param>
/// <param name="userData">用户自定义数据。</param>
/// <returns>界面的序列编号。</returns>
public int OpenUIForm(string uiFormAssetName, string uiGroupName, int priority, bool pauseCoveredUIForm, object userData)
{
if (m_ResourceManager == null)
{
throw new GameFrameworkException("You must set resource manager first.");
}
if (m_UIFormHelper == null)
{
throw new GameFrameworkException("You must set UI form helper first.");
}
if (string.IsNullOrEmpty(uiFormAssetName))
{
throw new GameFrameworkException("UI form asset name is invalid.");
}
if (string.IsNullOrEmpty(uiGroupName))
{
throw new GameFrameworkException("UI group name is invalid.");
}
UIGroup uiGroup = (UIGroup)GetUIGroup(uiGroupName);
if (uiGroup == null)
{
throw new GameFrameworkException(Utility.Text.Format("UI group '{0}' is not exist.", uiGroupName));
}
int serialId = ++m_Serial;
//从对象池获取UI实例
UIFormInstanceObject uiFormInstanceObject = m_InstancePool.Spawn(uiFormAssetName);
//如果对象池不存在就loadAsset
if (uiFormInstanceObject == null)
{
//在字典中记录
m_UIFormsBeingLoaded.Add(serialId, uiFormAssetName);
//异步加载资源
m_ResourceManager.LoadAsset(uiFormAssetName, priority, m_LoadAssetCallbacks, OpenUIFormInfo.Create(serialId, uiGroup, pauseCoveredUIForm, userData));
}
else
{
InternalOpenUIForm(serialId, uiFormAssetName, uiGroup, uiFormInstanceObject.Target, pauseCoveredUIForm, false, 0f, userData);
}
return serialId;
}
InternalOpenUIForm
此函数的作用是调用UIForm.OnInit,然后加入对应的UIGroup中,然后调用UIForm.OnOpen,最后UIGroup.Refresh即刷新界面组
关闭UI界面
从UIGroup移除,调用UIForm.OnClose方法,调用Refresh
最后分别是调用关闭完成的回调、将此UIForm加入m_RecycleQueue中,加入此队列的原因是在下一次Update中会取出所有UIForm,调用其界面回收函数以及让对象池回收
GF的UI架构
- IUIManager
- IUIGroup
- IUIForm
- UIManager
- UIGroup
- UIForm
- UIFormLogic
- UguiForm
- UIFormInfo
这只是一部分,其中继承、组合、相互引用就已经比较复杂了
和Form相关I开头的接口均属于GF层,直接继承自这些接口类属于UGF层,最后UguiForm以及继承自UguiForm均属于游戏业务层(根据需求也可以自己写为GuiForm)
和Manager相关的包括UIGroup都属于GF层,需要看源码才能学习到
UIGroup
继承自IUIGroup,UIManager的内部类,当有多个UIForm处于同一Group,可以用UIGroup来控制它们的层级、轮询每个Form的Update。
虽然没有用过,但我认为如果有BagForm和ItemForm也就是背包和物品的话,就可以归为一个Group,因为它们同一时刻就一个处于最上层且两者之间有关联。
UIGroup直接受UIManager管理,各个UIGroup管理各自的UIForm。(开头传送门写的,写的真好)
其中有一个链表,里面记录了属于此UIGroup的UIFormInfo(和UIForm相互引用)
对外开放的属性
- Name:名字
- Depth:深度,Set也开放,完成后Refresh()
- Pause:是否暂停,Set也开放,完成后Refresh()
- UIFormCount:包含的界面数量
- CurrentUIForm:当前界面
- Helper:界面辅助器
RefocusUIForm
直接将此UIForm放在链表头
Refresh
代码太长啦就不截图啦
刷新界面组,不难理解就是比较繁琐。举个栗子就是如果当前UIGroup暂停的但是当前链表第一个UIForm没有暂停就将其暂停,如果UIGroup没被覆盖但是UIForm被覆盖了就将它取消覆盖
UIFormInfo
UIGroup的内部类,UIGroup链表中记录的类就是UIFormInfo而不是UIForm,因为UIFormInfo中有包含UIForm的引用,其中有UIForm没有记录的两个状态Paused和Coverd,相当于对UIForm又封装了一层,所以我们拿到UIFormInfo就等于拿到了UIForm
UIForm
UI界面类,被UIGroup管理,属于UGF层(引用了Unityengine)可以看得到代码。
- SerialId:界面序列编号(不理解干啥的,大佬的文章是说用于标识相同UI的不同实例和用于区别放回对象池前和再次取出的情况)
- UIFormAssetName:资源名称
- Handle:界面实例,Object类,因为UIManager也需要对其操作,如果是GameObj则无法操作因为其属于GF层
- UIGroup:界面所属的界面组
- DepthInUIGroup:界面深度(在所属Group中的)
- PauseCoveredUIForm:为Ture则代表打开时在其下面的UI全部Pause,关闭时下面的UI全部Resume
- Logic:UIFormLogic类,界面逻辑。需要被UguiForm所继承
然后还有些生命周期方法和深度相关的方法
UguiForm
继承自UIFormLogic,也就是用于编写逻辑的,如果要自己编写UI界面的逻辑,就要继承自UguiForm
结构就是如此,一个Form上又要挂载UI界面类记录obj又要挂载逻辑类。这些Form均为预制物,比如button的点击事件都是手动设置而不是靠代码设置的。