基于ET的UI框架--EGUI项目导入与代码理解
前言
拿到ET框架后发现原生的UI框架非常简陋,所以萌生了基于ET去设计UI框架的想法,后续也确实实现了UI的sortingOrder分级、UICamera管理以及生命生命周期的新增、UI栈引入等等([ET6.0框架初步()]),但是近期在查看ET大佬所写框架时发现自己还是小巫见大巫了,因此还是想先去将比较成熟的EGUI导入项目中使用并且记录下方法、思路,同时对EGUI框架加上一些自己的理解。
同时也放上框架作者字母哥的B站视频链接:
1 | 【ET框架】01-EUI介绍与UI控件获取--字母哥Binaray:https://www.bilibili.com/video/BV12F411e7bP?spm_id_from=333.999.0.0 |
项目导入
项目框架主要区分为五个部分:
- ModelView/GameLogic中对于UI表现层数据的存储,抛弃了传统ET自带Demo中传统UIName+Component的思路,将其进一步细化为UIName+ViewComponent中UIBehaviour的两部分以及UIItemBehaviour,同时将ViewComponent作为组件加入到UI对应的Entity实体中。
- UIBehaviour
- CommonUI,表示通用的子UI组件,其能嵌套在不同的UIPrefab中
- ViewComponent,表示主界面展示元素的数据组件,内部存有打上标记的UnityEngine.UI组件
- UIItemBehaviour
- 滑动列表数据处理
- UI Entity实体
- 其可通过View获取到对应UI的ViewComponent,同时也可用于存储UI中其他逻辑数据,相当于进一步把UI层View与Model区分开来
- UIBehaviour
- HotfixView中对于UI表现层的逻辑处理,对于每个UI来说具有一个用于实现具体UI函数的System.cs以及实现UI生命周期的接口的Handler.cs
- ModelView/Module中存有对于UI中的基础数据模块,包括用于表示UI类型的AUIEventAttribute、供逻辑层handler实现的IAUIEventHandler接口、红点处理的数据层等等,这部分会在之后细讲。
- HotfixView/Module中对于ModelView层Module数据的具体逻辑实现,同时也包括最上层UI管理器组件UIComponent的逻辑代码实现(ShowWindow、HideWindow等)
- Editor/UI中的UI代码生成拓展,可以对已有的Prefab通过右键生成对应的UI代码
在分析好基本的UI关联代码结构之后,我们就可以去将关联文件导入到我们自己的项目中。
下面我们来进行测试:
在Bundles/UI中创建Common、Dlg、Item目录分别对应三种不同类型的UI
创建一个DlgLogin的UIPrefab,在View需要获取的GO中打上标签
修改AssetBundle信息,这个后续会用Editor右键来方便处理。
在WindowID枚举类中新增对应WindowID_{UIName}的枚举
通过UIComponent.Instance.ShowWindow(WindowID windowId)进行展示同步显示,ShowWindowAsync(WindowId windowId)进行异步显示。
Tips:如果对应的UIPrefab中有Canvas组件,其Render Mode需要先设置为World Space
框架理解
整体架构
EGUI的框架本身比起GF的通用性更面向于实际逻辑的开发,其根据实际窗口使用类型已经分为了不同的UIGroup,包括Normal、Fixed、PopUp、Other和Invalid,并根据不同类型在UI生命周期中进行了相应的适配。
UI窗口类型:
本文的框架图和结构是结合了自身的修改以及拓展后的版本
ET的基础思路是用组件代替继承,分发代替多态,因此在EGUI的设计中没有Entity类间的继承关系,所有的UI类都是继承自Entity,通过Component的拆装实现类似于OOP中的继承效果。同时ET中的所有Component默认在池中获取,方便管理内存。
查询接口
- IsWindowVisible(),判断Window是否处于显示状态
- GetDlgLogic(),获取UIBaseWindow中对应类型数据
操作接口
- showWindow(),同步展示窗口
- showWindowAsync(),异步展示窗口
- closeWindow(),关闭窗口
- HideWindow(),隐藏窗口
- HideLastWindow(),隐藏popUp窗口类型的最后一个UI
- CloseLastWindow(),关闭popUp窗口类型的最后一个UI
- HideAndShowWindowStack(),将当前UI窗口隐藏并压入栈中,展示新的窗口,在新窗口隐藏或关闭时重新展示
- HideAllShownWindows(),隐藏所有正在展示的窗口
- CloseAllWindow(),清空所有UI
- RemoveAllStackNode(),清空UI栈中所有结点(栈已经拓展为链表)
- RemoveParentStackUI(),递归清除该UI的所有父亲UI,相当于从栈UI窗口(如角色信息、背包等)中回到主界面
- CancelAllLoadingUI(),清除所有加载中的UI,和GF相同,EGUI中的UI无法在加载时就取消,只能在加载完成后清除
生命周期
将上面提到的IAUIEventHandler中各个接口函数的过程进行具体解析。
- OnInitWindowCoreData():在UIComponent中的show()生命周期中的ReadyToShowBaseWindow()以及ShowAsync()生命周期的showBaseWindowAsync()调用,在过程中若未加载对应的UIData资源则调用该函数
- OnInitComponent():过程同上,若过程中UIBaseWindow未进行加载或者未进行UITransform的preload操作则调用该函数
- OnRegisterComponent():过程同上,若过程中UIBaseWindow未进行加载或者未进行UITransform的preload操作则调用该函数
- OnShowWindow():在UIComponent中的show()和showAsync()生命周期中RealShowWindow()中调用,即在窗口展示时进行调用
- OnHideWindow():在UIComponent中的hide()和CheckDirectlyHide()调用,同时也在UIComponent中HideAllShownWindow()调用,即在窗口隐藏/关闭时调用
- BeforeUnload():在UIComponent中的CloseWindow()以及UIComponent组件被移除时的Destroy()回调中调用,即在窗口关闭/被销毁前调用
UI窗口打开的生命周期:
UI隐藏时的生命周期:
自身拓展
将原本框架中的UI栈由Stack数据类型改为链表类型,方便后续可删操作
新增操作接口
WindowCoreData中新增各种状态参数,方便获取状态
UIComponentSystem的Update()中增加对删除加载中UI的处理函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25private static void HandleCancelLoadingUI(this UIComponent self)
{
//MARKER:控制Loading中UI取消
if (self.LoadingWindowsCancelCache != null && self.LoadingWindowsCancelCache.Count > 0)
{
foreach (var windowID in self.LoadingWindowsCancelCache)
{
UIBaseWindow window = self.GetUIBaseWindow(windowID);
if (window != null&&window.WindowData.isLoaded)
{
self.CloseWindow(windowID);
self.ReadyToRemove.Add(windowID);
}
}
if (self.ReadyToRemove.Count > 0)
{
foreach (var id in self.ReadyToRemove)
{
self.LoadingWindowsCancelCache.Remove(id);
}
self.ReadyToRemove.Clear();
}
}
}更改部分生命周期代码,提供可将WindowID生成在任意WindowType的接口,如ShowWindowInWindowType(),ShowWindowInWindowTypeAsync()
新增Editor中对于快捷赋值ABName和清空ABName的小轮子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35[MenuItem("Assets/AssetBundle/NameUIPrefab")]
public static void NameAllUIPrefab()
{
string suffix = ".unity3d";
UnityEngine.Object[] selectAsset = Selection.GetFiltered<UnityEngine.Object>(SelectionMode.DeepAssets);
for (int i = 0; i < selectAsset.Length; i++)
{
string prefabName = AssetDatabase.GetAssetPath(selectAsset[i]);
//MARKER:判断是否是.prefab
if (prefabName.EndsWith(".prefab"))
{
Debug.Log(prefabName);
AssetImporter importer=AssetImporter.GetAtPath(prefabName);
importer.assetBundleName = selectAsset[i].name.ToLower() + suffix;
}
}
AssetDatabase.Refresh();
AssetDatabase.RemoveUnusedAssetBundleNames();
}
[MenuItem("Assets/AssetBundle/ClearABName")]
public static void ClearABName()
{
UnityEngine.Object[] selectAsset = Selection.GetFiltered<UnityEngine.Object>(SelectionMode.DeepAssets);
for (int i = 0; i < selectAsset.Length; i++)
{
string prefabName = AssetDatabase.GetAssetPath(selectAsset[i]);
AssetImporter importer=AssetImporter.GetAtPath(prefabName);
importer.assetBundleName = string.Empty;
Debug.Log(prefabName);
}
AssetDatabase.Refresh();
AssetDatabase.RemoveUnusedAssetBundleNames();
}11/23更新:生命周期中新增OnStart,OnResume两个周期
- OnStart 在第一次show且在OnShow前调用
- OnResume在非第一次show且在OnShow前调用
11/23更新:将EGUI的code生成中Behaviour部分(View)的生成抽离出来,单独作为一个右键扩展,方便更改UIPrefab内部GO后的代码再生成。
11/28更新:更改子UI代码生成函数,适配ET6.0新版,在一个ViewComponent中可以有多个同类型的子UI
使用步骤:
先设计子UI的UIPrefab,并右键点击SpawnEGUICode
在需要添加这个子UI的Dlg中,通过”子UI名_实际名称”的方法做标识,并右键Dlg点击SpawnEGUICode生成对应的EGUI代码
DlgTest的测试例子:
若后续有对这个Dlg的View进行更改,则只需要重新右键Dlg点击SpawnEGUIBehaviourCode即可