1.界面的加载、卸载
2.打开、关闭、隐藏、显示界面,这边隐藏是指界面被遮挡的意思,一般来说,界面被遮住时,应该关闭界面的更新
3.界面栈的管理,主要是用于场景切换时需要回到上一个场景打开的界面栈
4.需要的功能:图片镜像(节省资源)、滑动列表(复用)、模糊背景等
注意点:
1.界面的生成:class的生成、预制体的实例化,类和实例的关联。
业务打开一个界面需要传入界面的标识(枚举、或者字符串),如何通过这个标识找到预制体并实例化go、如何生成指定的界面view,如何绑定view和go
如何销毁一个界面,清除ab缓存、清除引用关系、destory go
2.界面的层级关系:每次打开一个新的UI,都将它堆入栈,关闭时出栈。这个栈是一个特殊的栈,例如它可以实现,某个不在栈顶的UI,可以“TOP”到栈顶。
打开一个界面:1.从已打开界面搜索,避免重复打开界面。2.从缓存界面搜索,避免重复加载。3.隐藏栈顶界面。4.打开新界面
关闭一个界面:1.关闭界面并加入缓存。2.从已打开界面栈顶取出一个界面,显示该界面。3.重复步骤2直到打开一个全屏界面。
退出场景:1.所有界面入栈,当再次回到场景时可以恢复界面栈。2.关闭界面
进入场景:1.如果需要恢复界面栈,从界面栈取出界面并打开显示该界面。2.重复1步骤直到打开一个全屏界面。3.不需要恢复:打开当前场景的界面。
3.界面间的通信:最好不要有界面之间的通信,界面的更新通过数据(逻辑)类发送消息通知给界面。例如使用道具后界面的更新,数据类接收到服务器道具使用成功后,发送道具更新消息BAG_DATA_UPDATE,需要更新的界面监听BAG_DATA_UPDATE消息并刷新界面。
4.界面打开动画控制。统一使用Animator(Animation),且每个界面最多只有一个Animator(Animation),界面获取焦点时调用Animator的Play方法播放动画。
5.界面特效控制:游戏会有很多进入界面播放一次的特效,如果你的界面关闭不是使用SetActive处理的(例如设置layer、移到屏幕外等),那么在你的界面再次打开时特效不会再次被播放。
6.界面内使用的对象怎么获取?(例如要修改界面的某个text)
每个需要在脚本内加载的对象都挂载一个UIExportItem对象,在界面初始化时统一收集这些对象,并存储在一个map里给界面使用
UI模块分以下几部分:
1.界面类:负责界面的逻辑,提供生命周期方法供业务使用,如OnOpen、OnClose等。负责界面的生成、销毁(以及界面ab包的加载、卸载)。子窗口、子item的管理(生成、缓存、销毁)
2.管理类:提供界面的生命周期管理,如打开、关闭、显示(获得焦点,显示在最上层)、隐藏(失去焦点,可以理解成被其他界面挡住了)一个界面、打开、关闭一堆界面(场景切入、切出时)。缓存界面。维护窗体中间的层级关系。
3.配置类:负责配置界面的预制体路径、类、界面类型等参数。
4.功能类:滑动列表、图片镜像、模糊、弹窗适配、通用的标题、通用的tab等。
5.item类,例如界面的滑动列表的子项。
详细代码如下:
一、ViewDefine:定义类的配置:这边主要是一些界面定义以及界面的配置,通过配置类名可以用反射实例化界面类,通过配置的路径可以加载并实例化预制体。
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 using System.Collections.Generic; using UnityEngine; public class ViewDefine { public enum ViewType { MAIN = 1, // 主窗口(全屏) POPUP = 2, // 弹窗 FIXED = 3, // 固化窗口 SCENE = 4, // 场景UI窗口 GUIDE = 5, // 引导UI窗口 } public enum ViewPopModal { Blur = 1, // 带有模糊效果的模态弹窗 Lucency_ImPenetrable = 2, // 无模糊,不可穿透 Lucency_Penetrate = 3, // 无模糊, 可穿透 } public enum ViewLoadStateDefine { NONE = 0, LOADING = 1, LOADED = 2, } public enum ViewOnLoadDefine { Cache = 1, Destroy = 2, } public enum ViewAlignmentType { UpperLeft = 0, UpperCenter = 1, UpperRight = 2, MiddleLeft = 3, MiddleCenter = 4, MiddleRight = 5, LowerLeft = 6, LowerCenter = 7, LowerRight = 8, } public enum ViewID { TOAST, // 吐司界面 TOAST_BATTLE, // 战斗中吐司界面 NETWAIT, // 网络等待界面 NETWORK_TIPS, // 网络异常提示 } private static Dictionary<int, string> _viewConfig = new Dictionary<int, string> { { (int)ViewID.TOAST, "ToastView,Prefab/Common/ToastPanel" }, { (int)ViewID.TOAST_BATTLE, "ToastBattleView,Prefab/Common/ToastBattlePanel" }, { (int)ViewID.NETWAIT, "NetwaitView,Prefab/Common/NetwaitPanel" } }; public static string GetViewType(ViewID viewID) { string config = _viewConfig[(int)viewID]; if (string.IsNullOrEmpty(config)) { Debug.LogErrorFormat("未配置界面路径 : {0}", viewID); return null; } string[] split = config.Split(','); return split[0]; } public static string GetViewPath(ViewID viewID) { string config = _viewConfig[(int)viewID]; if (string.IsNullOrEmpty(config)) { Debug.LogErrorFormat("未配置界面路径 : {0}", viewID); return null; } string[] split = config.Split(','); return split[1]; } }
二、UIBase ,ui元素基类,主要提供go的销毁以及导入界面需要引用的对象并保存在_viewObj里,业务可以通过_viewObj[“对象名”]访问对象而不用去定义参数。
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 using System.Collections.Generic; using UnityEngine; /// <summary> /// item、view的基类,提供预制体的生成、卸载(ab包的维护),提供子节点的生成 /// </summary> public class UIBase { protected GameObject gameObject; protected Transform transform; public string Name { get; set; } protected Dictionary<string, Object> _viewObj = new Dictionary<string, Object>();//导出的界面对象 public virtual void Ctor(GameObject obj, Transform parent) { if (obj != null) { gameObject = obj; transform = obj.transform; ExportHierarchy(); if (parent != null) { transform.SetParent(parent); transform.localPosition = Vector3.zero; transform.localScale = Vector3.zero; } } } protected virtual void OnLoad() { } public void SetActive(bool isShow) { gameObject.SetActive(isShow); } public virtual void Dispose() { if (gameObject != null) { GameObject.Destroy(gameObject); transform = null; gameObject = null; } } protected void ExportHierarchy() { if (gameObject) { UIHierarchy hierarchy = gameObject.GetComponent<UIHierarchy>(); if (hierarchy) { foreach (var item in hierarchy.widgets) { _viewObj.Add(item.name, item.item); } foreach (var item in hierarchy.externals) { _viewObj.Add(item.name, item.item); } } } } }
三、UIItemBase ,item类,主要是提供OnItemOpen、OnItemClose方法,方便item在界面打开和关闭时监听(移除)事件
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 /// <summary> /// 界面item /// </summary> public class UIItemBase:UIBase { protected ViewBase parentView; protected bool isItemOpen = false; public virtual void OnItemOpen() { isItemOpen = true; } public virtual void OnItemClose() { isItemOpen = false; } public bool IsItemOpen() { return isItemOpen; } public void SetParentView(ViewBase parent) { parentView = parent; } }
四、PanelBase ,子界面、界面的基类。主要是提供子item的生成、维护。
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 using System.Collections.Generic; using UnityEngine; /// <summary> /// 所有界面的基类,包括子窗口、所有的界面 /// 这个类主要功能是:提供给子界面生个生成、维护item的接口 /// </summary> public class PanelBase:UIBase { protected bool isOpen = false; protected Dictionary<string, Queue<UIItemBase>> childItemPool = new Dictionary<string, Queue<UIItemBase>>(); //缓存item的池子 protected List<UIBase> subItems = new List<UIBase>();//维护子对象,包括子窗口、子item /// <summary> /// UIModule调用,用于打开一个界面。如果该界面还未加载,会调用Load加载界面 /// </summary> /// <param name="args"></param> 界面参数 public virtual void Open(params object[] args) { } public virtual void Close() { } public bool IsOpen() { return isOpen; } /// <summary> /// 生成一个子item /// </summary> /// <param name="className"></param> 子item的类名 /// <param name="prefabs"></param> 子item的预制体 /// <param name="parent"></param> 子item的父节点 /// <returns></returns> protected UIItemBase GenerateItem(string className, GameObject prefabs, Transform parent) { Queue<UIItemBase> pool = childItemPool[className]; if (pool != null && pool.Count > 0) { return pool.Dequeue(); } UIItemBase item = InstantiateItem(className, prefabs, parent); AddSubItem(item); return item; } protected void GenerateItemList(string className, GameObject prefabs, Transform parent, int count, ref List<UIItemBase> container) { while (container.Count < count) { UIItemBase item = GenerateItem(className, prefabs, transform); container.Add(item); } while (container.Count > count) { UIItemBase item = container[container.Count]; container.Remove(item); RecyleItem(item); } } protected UIItemBase InstantiateItem(string className, GameObject prefabs, Transform parent) { GameObject obj = GameObject.Instantiate(prefabs, parent); UIItemBase item = (UIItemBase)UIModule.Instance.CreateUIClass(className); item.Ctor(obj, parent); item.Name = className; return item; } /// <summary> /// 回收子item /// </summary> /// <param name="item"></param> protected void RecyleItem(UIItemBase item) { RemoveSubItem(item); Queue<UIItemBase> cachePool = childItemPool[item.Name]; if (cachePool == null) { cachePool = new Queue<UIItemBase>(); } cachePool.Enqueue(item); item.SetActive(false); if (item.IsItemOpen()) { item.OnItemClose(); } } protected void RecyleItemList(int count, ref List<UIItemBase> container) { while (container.Count > count) { UIItemBase item = container[container.Count]; RecyleItem(item); container.Remove(item); } } protected void ClearCache() { foreach (var pool in childItemPool.Values) { foreach (var item in pool) { item.Dispose(); } pool.Clear(); } childItemPool.Clear(); } /// <summary> /// 添加子对象,包括item、childview /// </summary> /// <param name="item"></param> protected void AddSubItem(UIBase item) { if (subItems.Contains(item)) { Debug.Log("item已存在"); return; } subItems.Add(item); } protected void RemoveSubItem(UIBase item) { if (subItems.Contains(item)) { subItems.Remove(item); } } protected void RemoveAllSubItem() { foreach (var item in subItems) { item.Dispose(); } subItems.Clear(); } protected void OnOpenSubItem() { foreach (var item in subItems) { if (item is UIItemBase) { (item as UIItemBase).OnItemOpen(); } } } protected void OnCloseSubItem() { foreach (var item in subItems) { if (item is UIItemBase) { (item as UIItemBase).OnItemClose(); } } } public override void Dispose() { base.Dispose(); RemoveAllSubItem(); ClearCache(); } }
五、ChildViewBase :重写了Open、Close方法
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 public class ChildViewBase:PanelBase { /// <summary> /// UIModule调用,用于打开一个界面。如果该界面还未加载,会调用Load加载界面 /// </summary> /// <param name="args"></param> 界面参数 public override void Open(params object[] args) { if (!isOpen) { SetActive(true); OnOpenSubItem(); isOpen = true; } } public override void Close() { if (isOpen) { OnCloseSubItem(); SetActive(false); isOpen = false; } } }
六、ViewBase :所有界面基类,主要是提供了界面的加载、卸载(注意ab的加、卸载),子界面的添加、维护。
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 using System.Collections.Generic; using UnityEngine; /// <summary> /// 界面基类,可以生成ChildView /// </summary> public class ViewBase: PanelBase { protected object[] openParam; protected ViewDefine.ViewType viewType; protected ViewDefine.ViewLoadStateDefine loadState = ViewDefine.ViewLoadStateDefine.NONE; protected bool isHide = false; protected float closeTime = 0;//用于回收计时 public ViewDefine.ViewID ViewID { get; set; } /// <summary> /// 打开界面,一次OnOpen对应一次OnClose,子类实现 /// </summary> /// <param name="args"></param> protected virtual void OnOpen(params object[] args){ } /// <summary> /// 用于刷新界面,每次调用OpenView都会调用,避免业务重复打开界面 /// </summary> /// <param name="args"></param> protected virtual void OnRefreshView(params object[] args) { } /// <summary> /// 获得焦点。1.打开界面。2.上一层界面被关闭,重新获得焦点 /// </summary> protected virtual void OnEnabled() { } public virtual void Update(float dt) { } /// <summary> /// 失去焦点。1.关闭界面。2.有新的界面打开。 /// </summary> protected virtual void OnDisable() { } protected virtual void OnClose() { } /// <summary> /// UIModule调用,用于打开一个界面。如果该界面还未加载,会调用Load加载界面 /// </summary> /// <param name="args"></param> 界面参数 public override void Open(params object[] args) { openParam = args; if (loadState == ViewDefine.ViewLoadStateDefine.LOADED) { DoRealOpen(); } else { Load(); } } /// <summary> /// 加载界面,包括界面的ab包,ab的依赖包,最终返回一个Prefab用于实例化界面 /// </summary> private void Load() { if (loadState != ViewDefine.ViewLoadStateDefine.NONE) return; loadState = ViewDefine.ViewLoadStateDefine.LOADING; ResManager.Instance.LoadPrefab(ViewDefine.GetViewPath(ViewID), "", LoadFinish); } private void LoadFinish(object prefab) { loadState = ViewDefine.ViewLoadStateDefine.LOADED; gameObject = GameObject.Instantiate((GameObject)prefab, UIModule.Instance.GetViewRoot(viewType)); transform = gameObject.transform; ExportHierarchy(); OnLoad(); DoRealOpen(); } private void DoRealOpen() { if (!isOpen) { SetActiveEx(true); OnOpen(openParam); OnOpenSubItem(); } OnRefreshView(openParam); ShowView(); } /// <summary> /// 显示、隐藏界面,这边使用的方式是将界面移到屏幕外。 /// 另外几种做法是:1.SetActive直接隐藏go。2.设置Scale为0。3.设置layer out /// </summary> /// <param name="isActive"></param> 是否显示 public void SetActiveEx(bool isActive) { if (transform) { if (isActive) { transform.localPosition = Vector3.zero; } else { transform.localPosition = new Vector3(10000, 10000, 0); } } } public virtual void ShowView() { SetActiveEx(true); transform.SetAsFirstSibling(); if (!isHide) { isHide = true; OnEnabled(); } } /// <summary> /// 界面失去焦点,如果是打开弹窗,不隐藏该界面。 /// </summary> /// <param name="keepShow"></param> public virtual void HideView(bool keepShow = false) { SetActiveEx(keepShow); if (isHide) { isHide = false; OnDisable(); } } public override void Close() { if (isOpen) { UIModule.Instance.CloseView(ViewID); } } public virtual void CloseView() { HideView(); if (isOpen) { OnClose(); OnCloseSubItem(); CloseSubPanel(); closeTime = Time.realtimeSinceStartup; } } public float GetCloseTime() { return closeTime; } public bool IsPopView() { return viewType == ViewDefine.ViewType.POPUP; } public bool IsMainView() { return viewType == ViewDefine.ViewType.MAIN; } public bool IsFixedView() { return viewType == ViewDefine.ViewType.FIXED; } public bool IsShow() { return !isHide; } public bool IsOnLoadDestroy() { return loadState == ViewDefine.ViewLoadStateDefine.LOADING; } protected List<ChildViewBase> childView = new List<ChildViewBase>(); protected ChildViewBase AddChildPanel(string className, GameObject obj, Transform parent) { ChildViewBase view = (ChildViewBase)UIModule.Instance.CreateUIClass(className); view.Ctor(obj, parent); childView.Add(view); AddSubItem(view); return view; } protected void CloseSubPanel() { foreach (var item in childView) { if (item.IsOpen()) { item.Close(); } } } }
七、UIHierarchy:保存了业务导出的引用对象。ExportPanelHierarchy寻找UIExportItem 元素并保存到UIHierarchy
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 36 37 public class UIHierarchy : MonoBehaviour { [System.Serializable] public class ItemInfo { public string name; public Object item; public ItemInfo() { } public ItemInfo(string _name, Object _item) { name = _name; item = _item; } } // 控件 public List<ItemInfo> widgets; public void SetWidgets(List<ItemInfo> data) { if (data.Count == 0) return; if (widgets == null) { widgets = new List<ItemInfo>(); } widgets.Clear(); widgets.AddRange(data); } // 外部引用 public List<ItemInfo> externals; } using UnityEngine; [DisallowMultipleComponent] public class UIExportItem : MonoBehaviour { public string FieldName; }
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 using UnityEngine; using UnityEngine.UI; using UnityEditor; using System.Collections.Generic; public class ExportPanelHierarchy { /// <summary> /// 导出组件优先级 /// </summary> private static System.Type[] ms_componentTypes = { typeof(Button), typeof(InputField), typeof(ScrollRect), typeof(Dropdown), typeof(Image), typeof(RawImage), typeof(Scrollbar), typeof(Slider), typeof(Text), typeof(Toggle), typeof(GridLayoutGroup), typeof(HorizontalOrVerticalLayoutGroup), typeof(LayoutElement), typeof(CanvasGroup), typeof(ToggleGroup), typeof(TextMesh), typeof(Animation), typeof(Camera), typeof(SpriteRenderer), }; static Object FindComponent(GameObject go) { Object component = null; for (int i = 0; i < ms_componentTypes.Length; ++i) { component = go.GetComponent(ms_componentTypes[i]); if (component != null) { break; } } return component; } //生成嵌套UI层级 public static void ExportNested(Object obj) { GameObject root = obj as GameObject; if (root == null) return; UIHierarchy hierarchy = root.GetComponent<UIHierarchy> (); if(hierarchy==null) { hierarchy = root.AddComponent<UIHierarchy>(); } //生成根节点层级 List<UIHierarchy.ItemInfo> fields = new List<UIHierarchy.ItemInfo>(); GetChildComponentUtilHierarchy (root.transform, fields); hierarchy.SetWidgets(fields); //生成子panel层级 UIHierarchy[] childHierarchys = root.GetComponentsInChildren<UIHierarchy>(true); for(int i=1; i<childHierarchys.Length; i++) { UIHierarchy childHrcy = childHierarchys [i]; List<UIHierarchy.ItemInfo> childUIItem = new List<UIHierarchy.ItemInfo>(); GetChildComponentUtilHierarchy (childHrcy.transform, childUIItem); childHrcy.SetWidgets(childUIItem); } EditorUtility.SetDirty(root); AssetDatabase.SaveAssets(); } //导出传入节点的层级,直到某个子节点挂有UIHierarchy组件 private static void GetChildComponentUtilHierarchy(Transform transRoot, List<UIHierarchy.ItemInfo> fields) { for(int i=0; i<transRoot.childCount; i++) { Transform trans = transRoot.GetChild (i); UIHierarchy hrchy = trans.GetComponent<UIHierarchy> (); if(hrchy!=null) { fields.Add (new UIHierarchy.ItemInfo(hrchy.name, hrchy)); continue; } UIExportItem uiItem = trans.GetComponent<UIExportItem>(); if (uiItem != null) { Object fieldItem = FindComponent(uiItem.gameObject); if (fieldItem == null) { fieldItem = uiItem.transform; } fields.Add(new UIHierarchy.ItemInfo(uiItem.name, fieldItem)); } GetChildComponentUtilHierarchy (trans, fields); } } }
八、UIModule:核心管理类,提供给全局唯一的打开界面方法,维护层级栈。维护界面缓存。
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 using System.Collections.Generic; using System.Reflection; using UnityEngine; public class UIModule { #region Instance private static UIModule m_Instance; public static UIModule Instance { get { return m_Instance ?? (m_Instance = new UIModule()); } } #endregion private const float CACHE_TIME = 30;//界面缓存时间 private int curSceneID = 0;//当前打开的场景id,每个场景都有自己的层级栈 private int curViewID = 0;//当前打开的界面id private int lastViewID = 0;//上一个界面id private List<int> openViewList;//当前打开的界面列表,按顺序 private Dictionary<int, ViewBase> cacheView;//缓存区,等待销毁,从缓存区取要移除 private Dictionary<int, List<int>> naviStack;//场景的层级栈,key为场景id,用于维护场景 private Dictionary<int, ViewBase> viewPool;//保存所有ViewBase的引用,包括缓存区的,界面销毁时需要移除。 private Dictionary<ViewDefine.ViewType, Transform> viewRoot;//界面实例化出来的父节点,不同界面的父节点不一样。 private Transform uiRoot;//ui根节点 private float lastCheckCacheTime;//上次检查缓存时间,一秒检查一次 public void Init() { openViewList = new List<int>(); cacheView = new Dictionary<int, ViewBase>(); naviStack = new Dictionary<int, List<int>>(); viewPool = new Dictionary<int, ViewBase>(); uiRoot = GameObject.Find("UIRoot").transform; viewRoot = new Dictionary<ViewDefine.ViewType, Transform> { { ViewDefine.ViewType.MAIN, uiRoot.Find("main") }, { ViewDefine.ViewType.POPUP, uiRoot.Find("main") }, { ViewDefine.ViewType.FIXED, uiRoot.Find("fixed") }, { ViewDefine.ViewType.GUIDE, uiRoot.Find("guide") } }; lastCheckCacheTime = Time.realtimeSinceStartup; } public Transform GetViewRoot(ViewDefine.ViewType viewType) { if (viewType == ViewDefine.ViewType.SCENE) { return null; } else { return viewRoot[viewType]; } } public void Update(float dt) { int openCount = openViewList.Count; ViewBase view; for (int i = 0; i < openCount; i++) { int viewId = openViewList[i]; if (viewPool.TryGetValue(viewId, out view)) { view.Update(dt); } } //清楚缓存时间到了的界面 float curTime = Time.realtimeSinceStartup; if (curTime - lastCheckCacheTime > 1) { foreach (var item in cacheView) { if (curTime - item.Value.GetCloseTime() > CACHE_TIME) { item.Value.Dispose(); cacheView.Remove(item.Key); viewPool.Remove(item.Key); } } } lastCheckCacheTime = curTime; } /// <summary> /// 外部调用 打开一个窗口的唯一方式 /// 如果上一个界面lastView存在,需要把lastView入栈 /// </summary> /// <param name="viewID"></param> /// <param name="param"></param> 界面打开参数,给业务使用的 /// <returns></returns> public ViewBase OpenView(ViewDefine.ViewID viewID, params object[] param) { int viewKey = (int)viewID; ViewBase view = FindOpenView(viewKey); if (view == null) { view = CreateView(viewID); if (view == null) { Debug.LogErrorFormat("OpenView CreateView Fail..viewID:", viewID); return null; } view.ViewID = viewID; AddOpenView(viewKey); } if (curViewID == viewKey) { view.Open(param); return view; } if (view.IsPopView() || view.IsMainView()) { lastViewID = curViewID; curViewID = viewKey; if (lastViewID > 0) { OnBackstage(lastViewID); } } view.Open(param); return view; } private ViewBase FindOpenView(int viewKey) { ViewBase view = null; if (openViewList.Contains(viewKey)) { viewPool.TryGetValue(viewKey, out view); } return view; } private void AddOpenView(int viewKey) { if (openViewList.Contains(viewKey)) { Debug.LogFormat("界面已打开:{0}", viewKey); return; } openViewList.Add(viewKey); } private void RemoveOpenView(int viewKey) { if (openViewList.Contains(viewKey)) { openViewList.Remove(viewKey); } } /// <summary> /// 创建新的view,先从缓存里面找 /// </summary> /// <param name="viewID"></param> /// <returns></returns> private ViewBase CreateView(ViewDefine.ViewID viewID) { int viewKey = (int)viewID; ViewBase view = GetViewFromCache(viewKey); if (view == null) { string viewName = ViewDefine.GetViewType(viewID); //加载程序集,创建程序集里面的 命名空间.类型名 实例 object ect = CreateUIClass(viewName); view = (ViewBase)ect;//类型转换并返回 viewPool.Add((int)viewID, view); } return view; } public object CreateUIClass(string calssName) { return Assembly.GetExecutingAssembly().CreateInstance(calssName); } private ViewBase GetViewFromCache(int viewKey) { ViewBase view = null; if (cacheView.TryGetValue(viewKey, out view)) { cacheView.Remove(viewKey); } return view; } private void AddViewToCache(int viewKey, ViewBase view) { cacheView[viewKey] = view; } private int GetTopViewOfStack() { List<int> stack = naviStack[curSceneID]; if (stack == null || stack.Count == 0) { return 0; } return stack[stack.Count]; } private ViewBase PopViewFormStack() { List<int> stack = naviStack[curSceneID]; if (stack == null || stack.Count == 0) { return null; } int index = stack.Count; int viewId = stack[index]; ViewBase view = GetViewByKey(viewId); stack.RemoveAt(index); if (view.IsPopView()) { for (int i = index-1; i > 0; i--) { ViewBase temp = GetViewByKey(stack[i]); temp.SetActiveEx(true); if (temp.IsMainView()) break; } } curViewID = viewId; view.ShowView(); AddOpenView(viewId); return view; } /// <summary> /// 向栈里添加元素 /// </summary> /// <param name="viewKey"></param> /// <param name="isForce"></param>true时,栈里存在会先移除在加入,否则栈里存在就不处理了 private void AddViewToStack(int viewKey, bool isForce = true) { List<int> stack = naviStack[curSceneID]; if (stack == null) { stack = new List<int>(); } if (stack.Contains(viewKey)) { if (isForce) { stack.Remove(viewKey); } else { return; } } stack.Add(viewKey); } private void RemoveFromStack(int viewKey) { List<int> stack = naviStack[curSceneID]; if (stack == null || stack.Count == 0) { return; } if (stack.Contains(viewKey)) { stack.Remove(viewKey); } } /// <summary> /// 进入后台 /// </summary> /// <param name="viewKey"></param> private void OnBackstage(int viewKey) { AddViewToStack(viewKey); ViewBase lastView = GetViewByKey(lastViewID); if (lastView.IsPopView()) { lastView.HideView(true); } else { lastView.HideView(false); } } /// <summary> /// 关闭界面,如果是当前打开界面,需要从栈顶弹出新的界面 /// </summary> /// <param name="viewKey"></param> private void InsertClose(int viewKey) { RemoveView(viewKey); if (viewKey == curViewID) { curViewID = 0; PopViewFormStack(); } else { RemoveFromStack(viewKey); } } /// <summary> /// 移除界面,从打开列表移除,添加到缓存 /// </summary> /// <param name="viewKey"></param> private void RemoveView(int viewKey) { RemoveOpenView(viewKey); RemoveFromStack(viewKey); ViewBase view = GetViewByKey(viewKey); view.CloseView(); AddViewToCache(viewKey, view); } private ViewBase GetViewByKey(int viewKey) { return viewPool[viewKey]; } public void CloseView(ViewDefine.ViewID viewID) { int viewKey = (int)viewID; if (openViewList.Contains(viewKey)) { InsertClose(viewKey); } } public void CloseCurView() { if (curViewID > 0) { CloseView((ViewDefine.ViewID)curViewID); } } /// <summary> /// 进入新的场景 /// </summary> /// <param name="sceneID"></param> 场景id /// <param name="isNative"></param> 是否需要打开ui栈,isBack=true时,从当前场景的栈顶弹出界面 public void EnterScene(int sceneID, bool isBack) { curSceneID = sceneID; if (naviStack[sceneID] == null) { naviStack[sceneID] = new List<int>(); } if (isBack) { PopViewFormStack(); } } /// <summary> /// 退出当前场景 /// </summary> /// <param name="pushToStack"></param> 是否压栈,用于场景返回时恢复ui层级 public void ExitScene(bool pushToStack) { int count = openViewList.Count; ViewBase temp = null; int viewKey = 0; for (int i = count; i > 0; i--) { viewKey = openViewList[i]; temp = GetViewByKey(viewKey); if (temp != null && (temp.IsMainView() || temp.IsPopView())) { if (pushToStack) { RemoveOpenView(viewKey); AddViewToStack(viewKey, false); } else { RemoveView(viewKey); } } } List<int> stack = naviStack[curSceneID]; if (stack != null) { for (int i = 0; i < stack.Count; i++) { ViewBase view = GetViewByKey(stack[i]); view.HideView(); } } curViewID = 0; lastViewID = 0; } }