| File was renamed from Main/UI/UIManager.cs |
| | |
| | | using System;
|
| | | using System.Collections;
|
| | | using System.Collections.Generic;
|
| | | using UnityEngine;
|
| | | using System.Linq;
|
| | |
|
| | | /// <summary>
|
| | | /// UI管理器 - 负责管理所有UI界面的显示、隐藏和层级
|
| | | /// </summary>
|
| | | public class UIManager : ManagerBase<UIManager>
|
| | | {
|
| | | #region 常量和枚举
|
| | | |
| | | // 基础排序顺序
|
| | | private const int BASE_SORTING_ORDER = 10;
|
| | | |
| | | #endregion
|
| | |
|
| | | #region 字段和属性
|
| | | |
| | | // UI根节点
|
| | | private Transform uiRoot;
|
| | | |
| | | // 各层级的Transform
|
| | | private Transform staticTrans;
|
| | | private Transform bottomTrans;
|
| | | private Transform midTrans;
|
| | | private Transform topTrans;
|
| | | private Transform systemTrans;
|
| | | |
| | | // UI字典,存储所有已加载的UI,键为UI名称,值为UI实例
|
| | | private Dictionary<string, List<UIBase>> uiDict = new Dictionary<string, List<UIBase>>();
|
| | | |
| | | // 存储关闭但未销毁的UI,键为UI名称,值为UI实例
|
| | | private Dictionary<string, List<UIBase>> closedUIDict = new Dictionary<string, List<UIBase>>();
|
| | |
|
| | | // UI栈,用于管理UI的显示顺序
|
| | | private Stack<UIBase> uiStack = new Stack<UIBase>();
|
| | | |
| | | // 当前最高的排序顺序
|
| | | private int currentHighestSortingOrder = 0;
|
| | | |
| | | // 当前回合数,用于记录UI的使用情况
|
| | | private int currentRound = 0;
|
| | | |
| | | // 缓存层级对应的排序顺序
|
| | | private Dictionary<UILayer, int> layerSortingOrderCache = new Dictionary<UILayer, int>();
|
| | | |
| | | // 缓存层级对应的Transform
|
| | | private Dictionary<UILayer, Transform> layerTransformCache = new Dictionary<UILayer, Transform>();
|
| | | |
| | | // UI实例计数器,用于为同类型UI生成唯一标识
|
| | | private Dictionary<string, int> uiInstanceCounter = new Dictionary<string, int>();
|
| | | |
| | | // 上次检查时间
|
| | | private float lastCheckTime = 0f;
|
| | | // 检查间隔(秒)
|
| | | private const float CHECK_INTERVAL = 5f;
|
| | | |
| | | public Action<UIBase> OnOpenWindow;
|
| | |
|
| | | public Action<UIBase> OnCloseWindow;
|
| | |
|
| | | #endregion
|
| | |
|
| | | #region 初始化
|
| | | |
| | | /// <summary>
|
| | | /// 初始化UI管理器
|
| | | /// </summary>
|
| | | public override void Init()
|
| | | {
|
| | | base.Init();
|
| | | |
| | | // 初始化UI根节点
|
| | | InitUIRoot();
|
| | | |
| | | // 初始化缓存
|
| | | layerSortingOrderCache.Clear();
|
| | | layerTransformCache.Clear();
|
| | | uiInstanceCounter.Clear();
|
| | | |
| | | Debug.Log("UI管理器初始化完成");
|
| | | }
|
| | | |
| | | /// <summary>
|
| | | /// 初始化UI根节点
|
| | | /// </summary>
|
| | | private void InitUIRoot()
|
| | | {
|
| | | // 查找UI根节点
|
| | | GameObject root = GameObject.Find("UIRoot");
|
| | | |
| | | // 如果场景中没有UI根节点,则创建一个
|
| | | if (root == null)
|
| | | {
|
| | | root = GameObject.Instantiate(BuiltInLoader.LoadPrefab("UIRoot"));
|
| | | root.name = "UIRoot";
|
| | | if (root == null)
|
| | | {
|
| | | Debug.LogError("无法找到UI根节点");
|
| | | return;
|
| | | }
|
| | |
|
| | | // 添加DontDestroyOnLoad组件,确保UI根节点在场景切换时不被销毁
|
| | | GameObject.DontDestroyOnLoad(root);
|
| | | }
|
| | |
|
| | | uiRoot = root.transform;
|
| | | uiRoot.position = Vector3.zero;
|
| | |
|
| | | staticTrans = uiRoot.Find("Static");
|
| | | bottomTrans = uiRoot.Find("Bottom");
|
| | | midTrans = uiRoot.Find("Middle");
|
| | | topTrans = uiRoot.Find("Top");
|
| | | systemTrans = uiRoot.Find("System");
|
| | |
|
| | | layerTransformCache.Clear();
|
| | | layerTransformCache.Add(UILayer.Static, staticTrans);
|
| | | layerTransformCache.Add(UILayer.Bottom, bottomTrans);
|
| | | layerTransformCache.Add(UILayer.Mid, midTrans);
|
| | | layerTransformCache.Add(UILayer.Top, topTrans);
|
| | | layerTransformCache.Add(UILayer.System, systemTrans);
|
| | | }
|
| | | |
| | | #endregion
|
| | |
|
| | | #region 辅助方法
|
| | | |
| | | // 获取UI层级对应的基础排序顺序
|
| | | private int GetBaseSortingOrderForLayer(UILayer layer)
|
| | | {
|
| | | // 尝试从缓存中获取排序顺序
|
| | | if (layerSortingOrderCache.TryGetValue(layer, out int order))
|
| | | return order;
|
| | | |
| | | // 如果缓存中没有,使用原来的方法计算并缓存结果
|
| | | int result;
|
| | | switch (layer)
|
| | | {
|
| | | case UILayer.Static:
|
| | | result = BASE_SORTING_ORDER;
|
| | | break;
|
| | | case UILayer.Bottom:
|
| | | result = BASE_SORTING_ORDER * 10;
|
| | | break;
|
| | | case UILayer.Mid:
|
| | | result = BASE_SORTING_ORDER * 100;
|
| | | break;
|
| | | case UILayer.Top:
|
| | | result = BASE_SORTING_ORDER * 1000;
|
| | | break;
|
| | | case UILayer.System:
|
| | | result = BASE_SORTING_ORDER * 10000;
|
| | | break;
|
| | | default:
|
| | | result = BASE_SORTING_ORDER * 10;
|
| | | break;
|
| | | }
|
| | | |
| | | // 将结果存入缓存
|
| | | layerSortingOrderCache[layer] = result;
|
| | | return result;
|
| | | }
|
| | |
|
| | | // 获取层级对应的Transform
|
| | | private Transform GetTransForLayer(UILayer layer)
|
| | | {
|
| | | // 尝试从缓存中获取Transform
|
| | | if (layerTransformCache.TryGetValue(layer, out Transform trans))
|
| | | return trans;
|
| | | |
| | | // 如果缓存中没有,使用原来的方法获取并缓存结果
|
| | | Transform result;
|
| | | switch (layer)
|
| | | {
|
| | | case UILayer.Static:
|
| | | result = staticTrans;
|
| | | break;
|
| | | case UILayer.Bottom:
|
| | | result = bottomTrans;
|
| | | break;
|
| | | case UILayer.Mid:
|
| | | result = midTrans;
|
| | | break;
|
| | | case UILayer.Top:
|
| | | result = topTrans;
|
| | | break;
|
| | | case UILayer.System:
|
| | | result = systemTrans;
|
| | | break;
|
| | | default:
|
| | | result = bottomTrans;
|
| | | break;
|
| | | }
|
| | | |
| | | // 将结果存入缓存
|
| | | layerTransformCache[layer] = result;
|
| | | return result;
|
| | | }
|
| | | |
| | | // 获取UI实例,如果不存在则返回null
|
| | | public T GetUI<T>() where T : UIBase
|
| | | {
|
| | | // 获取UI类型名称
|
| | | string uiName = typeof(T).Name;
|
| | | if (string.IsNullOrEmpty(uiName))
|
| | | {
|
| | | // 记录错误日志
|
| | | Debug.LogError("UI名称为空");
|
| | | return null;
|
| | | }
|
| | |
|
| | | // 尝试从字典中获取UI实例列表
|
| | | if (uiDict.TryGetValue(uiName, out List<UIBase> uiList) && uiList.Count > 0)
|
| | | {
|
| | | // 返回第一个实例
|
| | | return uiList[0] as T;
|
| | | }
|
| | |
|
| | | // 如果不存在,返回null
|
| | | return null;
|
| | | }
|
| | |
|
| | | public List<T> GetUIList<T>() where T : UIBase
|
| | | {
|
| | | List<T> uiList = new List<T>();
|
| | |
|
| | | // 获取UI类型名称
|
| | | string uiName = typeof(T).Name;
|
| | | if (string.IsNullOrEmpty(uiName))
|
| | | {
|
| | | // 记录错误日志
|
| | | Debug.LogError("UI名称为空");
|
| | | return uiList;
|
| | | }
|
| | |
|
| | | // 尝试从字典中获取UI实例列表
|
| | | List<UIBase> tempList = null;
|
| | | uiDict.TryGetValue(uiName, out tempList);
|
| | |
|
| | | if (tempList != null)
|
| | | {
|
| | | for (int i = 0; i < tempList.Count; i++)
|
| | | {
|
| | | UIBase ui = tempList[i];
|
| | | if (null != ui)
|
| | | {
|
| | | uiList.Add(ui as T);
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | return uiList;
|
| | | }
|
| | |
|
| | | public bool IsOpenedInList<T>() where T : UIBase
|
| | | {
|
| | | List<T> uiList = GetUIList<T>();
|
| | |
|
| | | foreach (T ui in uiList)
|
| | | {
|
| | | if (ui.IsActive())
|
| | | {
|
| | | return true;
|
| | | }
|
| | | }
|
| | |
|
| | | return false;
|
| | | }
|
| | |
|
| | | public bool IsOpened<T>() where T : UIBase
|
| | | {
|
| | | T ui = GetUI<T>();
|
| | |
|
| | | if (null != ui)
|
| | | {
|
| | | return ui.IsActive();
|
| | | }
|
| | |
|
| | | return false;
|
| | | }
|
| | | |
| | | // 获取指定类型的所有UI实例
|
| | | public List<T> GetAllUI<T>() where T : UIBase
|
| | | {
|
| | | // 获取UI类型名称
|
| | | string uiName = typeof(T).Name;
|
| | | if (string.IsNullOrEmpty(uiName))
|
| | | {
|
| | | // 记录错误日志
|
| | | Debug.LogError("UI名称为空");
|
| | | return new List<T>();
|
| | | }
|
| | |
|
| | | // 尝试从字典中获取UI实例列表
|
| | | if (uiDict.TryGetValue(uiName, out List<UIBase> uiList) && uiList.Count > 0)
|
| | | {
|
| | | // 将列表中的所有实例转换为指定类型并返回
|
| | | return uiList.Cast<T>().ToList();
|
| | | }
|
| | |
|
| | | // 如果不存在,返回空列表
|
| | | return new List<T>();
|
| | | }
|
| | | |
| | | // 更新父级UI的回合数
|
| | | private void UpdateParentUIRounds(UIBase ui)
|
| | | {
|
| | | // 如果UI为空或不支持父子关系,直接返回
|
| | | if (ui == null || !ui.supportParentChildRelation)
|
| | | return;
|
| | | |
| | | // 获取父级UI
|
| | | UIBase parentUI = ui.parentUI;
|
| | | // 遍历所有父级UI,更新回合数
|
| | | while (parentUI != null)
|
| | | {
|
| | | // 更新父级UI的最后使用回合数
|
| | | parentUI.lastUsedRound = currentRound;
|
| | | // 继续向上查找父级UI
|
| | | parentUI = parentUI.parentUI;
|
| | | }
|
| | | }
|
| | | |
| | | // 递归收集所有子UI
|
| | | private void CollectChildrenUI(UIBase ui, List<UIBase> result)
|
| | | {
|
| | | // 如果UI为空或没有子UI或不支持父子关系,直接返回
|
| | | if (ui == null || !ui.supportParentChildRelation || ui.childrenUI == null || ui.childrenUI.Count == 0)
|
| | | return;
|
| | | |
| | | // 遍历所有子UI
|
| | | foreach (var childUI in ui.childrenUI)
|
| | | {
|
| | | // 如果结果列表中不包含当前子UI,则添加
|
| | | if (!result.Contains(childUI))
|
| | | {
|
| | | // 添加到结果列表
|
| | | result.Add(childUI);
|
| | | // 递归收集子UI的子UI
|
| | | CollectChildrenUI(childUI, result);
|
| | | }
|
| | | }
|
| | | }
|
| | | |
| | | /// <summary>
|
| | | /// 检查并关闭长时间未使用的UI
|
| | | /// </summary>
|
| | | public void CheckAndCloseIdleUI()
|
| | | {
|
| | | // 如果没有UI,直接返回
|
| | | if (uiDict.Count == 0 && closedUIDict.Count == 0)
|
| | | return;
|
| | | |
| | | // 创建需要关闭的UI列表
|
| | | List<UIBase> uiToClose = new List<UIBase>();
|
| | | |
| | | // 遍历所有活跃UI
|
| | | foreach (var uiList in uiDict.Values)
|
| | | {
|
| | | foreach (var ui in uiList)
|
| | | {
|
| | | // 如果UI是持久化的,跳过
|
| | | if (ui.isPersistent)
|
| | | continue;
|
| | | |
| | | if (ui.IsActive())
|
| | | continue;
|
| | |
|
| | | // 计算UI空闲的回合数
|
| | | int idleRounds = currentRound - ui.lastUsedRound;
|
| | | |
| | | // 如果空闲回合数超过最大空闲回合数,添加到关闭列表
|
| | | if (idleRounds > ui.maxIdleRounds)
|
| | | {
|
| | | uiToClose.Add(ui);
|
| | | }
|
| | | }
|
| | | }
|
| | | |
| | | // 遍历所有关闭的UI
|
| | | List<string> emptyKeys = new List<string>();
|
| | | foreach (var kvp in closedUIDict)
|
| | | {
|
| | | string uiName = kvp.Key;
|
| | | List<UIBase> uiList = kvp.Value;
|
| | | List<UIBase> uiToRemove = new List<UIBase>();
|
| | | |
| | | foreach (var ui in uiList)
|
| | | {
|
| | | // 计算UI空闲的回合数
|
| | | int idleRounds = currentRound - ui.lastUsedRound;
|
| | | |
| | | // 如果空闲回合数超过最大空闲回合数,添加到关闭列表
|
| | | if (idleRounds > ui.maxIdleRounds)
|
| | | {
|
| | | uiToClose.Add(ui);
|
| | | uiToRemove.Add(ui);
|
| | | }
|
| | | }
|
| | | |
| | | // 从关闭列表中移除需要销毁的UI
|
| | | foreach (var ui in uiToRemove)
|
| | | {
|
| | | uiList.Remove(ui);
|
| | | }
|
| | | |
| | | // 如果列表为空,记录需要从字典中移除的键
|
| | | if (uiList.Count == 0)
|
| | | {
|
| | | emptyKeys.Add(uiName);
|
| | | }
|
| | | }
|
| | | |
| | | // 从字典中移除空列表
|
| | | foreach (var key in emptyKeys)
|
| | | {
|
| | | closedUIDict.Remove(key);
|
| | | }
|
| | | |
| | | // 销毁所有需要关闭的UI
|
| | | foreach (var ui in uiToClose)
|
| | | {
|
| | | // 记录日志
|
| | | Debug.Log($"销毁长时间未使用的UI: {ui.uiName}, 空闲回合数: {currentRound - ui.lastUsedRound}");
|
| | | // 销毁UI对象
|
| | | GameObject.Destroy(ui.gameObject);
|
| | | }
|
| | | }
|
| | | |
| | | // // 生成UI实例的唯一标识
|
| | | // private string GenerateUIInstanceID(string uiName)
|
| | | // {
|
| | | // // 如果计数器中不存在该UI类型,初始化为0
|
| | | // if (!uiInstanceCounter.ContainsKey(uiName))
|
| | | // {
|
| | | // uiInstanceCounter[uiName] = 0;
|
| | | // }
|
| | | |
| | | // // 递增计数器
|
| | | // uiInstanceCounter[uiName]++;
|
| | | |
| | | // // 返回带有计数的唯一标识
|
| | | // return $"{uiName}_{uiInstanceCounter[uiName]}";
|
| | | // }
|
| | | |
| | | #endregion
|
| | |
|
| | | #region UI资源管理
|
| | |
|
| | | private UIBase LoadUIResource(string uiName)
|
| | | {
|
| | | // 从资源管理器加载UI预制体
|
| | | GameObject prefab = ResManager.Instance.LoadAsset<GameObject>("UI", uiName);
|
| | |
|
| | | // 检查预制体是否加载成功
|
| | | if (prefab == null)
|
| | | {
|
| | | // 记录错误日志
|
| | | Debug.LogError($"加载UI预制体失败: {uiName}");
|
| | | return null;
|
| | | }
|
| | |
|
| | | // 实例化UI对象
|
| | | GameObject uiObject = GameObject.Instantiate(prefab);
|
| | | // 设置对象名称
|
| | | uiObject.name = uiName;
|
| | |
|
| | | // 通过uiName映射Type
|
| | | Type uiType = Type.GetType(uiName);
|
| | | if (uiType == null)
|
| | | {
|
| | | Debug.LogError($"找不到UI类型: {uiName}");
|
| | | return null;
|
| | | }
|
| | |
|
| | | // 获取UI基类组件
|
| | | UIBase uiBase = uiObject.GetComponent(uiType) as UIBase;
|
| | |
|
| | | // 检查UI基类组件是否存在
|
| | | if (uiBase == null)
|
| | | {
|
| | | // 记录错误日志
|
| | | Debug.LogError($"UI预制体 {uiName} 没有 UIBase 组件或类型不匹配");
|
| | | return null;
|
| | | }
|
| | |
|
| | | // 设置UI名称
|
| | | uiBase.uiName = uiName;
|
| | |
|
| | | // 设置父节点为UI根节点
|
| | | Transform parentTrans = GetTransForLayer(uiBase.uiLayer);
|
| | |
|
| | | uiObject.transform.SetParent(parentTrans, false);
|
| | |
|
| | | // 设置排序顺序
|
| | | int baseSortingOrder = GetBaseSortingOrderForLayer(uiBase.uiLayer);
|
| | | uiBase.SetSortingOrder(baseSortingOrder);
|
| | |
|
| | | return uiBase;
|
| | | }
|
| | |
|
| | | // 加载UI预制体
|
| | | private T LoadUIResource<T>(string uiName) where T : UIBase
|
| | | {
|
| | | return LoadUIResource(uiName) as T;
|
| | | }
|
| | | |
| | | #endregion
|
| | |
|
| | | #region UI排序管理
|
| | | |
| | | // 更新UI排序顺序
|
| | | private void UpdateUISortingOrder()
|
| | | {
|
| | | // 重置当前最高排序顺序
|
| | | currentHighestSortingOrder = 0;
|
| | | |
| | | // 遍历UI栈,设置排序顺序
|
| | | UIBase[] uiArray = new UIBase[uiStack.Count];
|
| | | uiStack.CopyTo(uiArray, 0);
|
| | | |
| | | // 先按照UILayer进行排序,然后再按照栈顺序排序
|
| | | Array.Sort(uiArray, (a, b) => {
|
| | | // 比较UI层级
|
| | | int layerCompare = a.uiLayer.CompareTo(b.uiLayer);
|
| | | if (layerCompare != 0)
|
| | | return layerCompare;
|
| | | |
| | | // 同层级内,按照栈中的顺序排序
|
| | | return Array.IndexOf(uiArray, a).CompareTo(Array.IndexOf(uiArray, b));
|
| | | });
|
| | | |
| | | // 遍历排序后的UI数组,设置排序顺序
|
| | | foreach (var ui in uiArray)
|
| | | {
|
| | | // 获取基础排序顺序
|
| | | int baseSortingOrder = GetBaseSortingOrderForLayer(ui.uiLayer);
|
| | | // 计算当前UI的排序顺序
|
| | | int sortingOrder = baseSortingOrder + currentHighestSortingOrder;
|
| | | // 设置UI的排序顺序
|
| | | ui.SetSortingOrder(sortingOrder);
|
| | | // 更新当前最高排序顺序
|
| | | currentHighestSortingOrder += 10;
|
| | | }
|
| | | }
|
| | | |
| | | #endregion
|
| | |
|
| | | #region UI操作
|
| | |
|
| | |
|
| | |
|
| | | private UIBase GetLastSupportParentChildRelationUI()
|
| | | {
|
| | | List<UIBase> tempList = new List<UIBase>();
|
| | |
|
| | | UIBase target = null;
|
| | |
|
| | | while (target == null && uiStack.Count > 0)
|
| | | {
|
| | | UIBase uiBase = uiStack.Pop();
|
| | |
|
| | | if (uiBase != null && uiBase.supportParentChildRelation && !uiBase.isMainUI)
|
| | | {
|
| | | target = uiBase;
|
| | | }
|
| | |
|
| | | tempList.Add(uiBase);
|
| | | }
|
| | |
|
| | | for (int i = tempList.Count - 1; i >= 0; i--)
|
| | | {
|
| | | uiStack.Push(tempList[i]);
|
| | | }
|
| | |
|
| | | return target;
|
| | | }
|
| | |
|
| | | public UIBase OpenWindow(string uiName)
|
| | | {
|
| | | // 优先从closedUIDict中获取
|
| | | UIBase parentUI = null;
|
| | |
|
| | | UIBase returnValue = null;
|
| | |
|
| | | if (closedUIDict.TryGetValue(uiName, out List<UIBase> closedUIList) && closedUIList.Count > 0)
|
| | | {
|
| | | returnValue = closedUIList[0] as UIBase;
|
| | | closedUIList.RemoveAt(0);
|
| | | |
| | | if (closedUIList.Count == 0)
|
| | | {
|
| | | closedUIDict.Remove(uiName);
|
| | | }
|
| | | }
|
| | | else
|
| | | {
|
| | | returnValue = LoadUIResource(uiName);
|
| | | if (returnValue == null)
|
| | | {
|
| | | // 记录错误日志
|
| | | Debug.LogError($"打开UI失败: {uiName}");
|
| | | return null;
|
| | | }
|
| | | }
|
| | | |
| | | returnValue.gameObject.SetActive(true);
|
| | | |
| | | // 自动设置父级UI(如果未指定且支持父子关系)
|
| | | if (returnValue.supportParentChildRelation && uiStack.Count > 0)
|
| | | {
|
| | | // 获取栈顶UI
|
| | | parentUI = GetLastSupportParentChildRelationUI();
|
| | | }
|
| | | |
| | | // 设置父级UI
|
| | | if (parentUI != null)
|
| | | {
|
| | | // 设置父子关系
|
| | | returnValue.parentUI = parentUI;
|
| | | if (parentUI.childrenUI == null)
|
| | | {
|
| | | // 初始化父级UI的子UI列表
|
| | | parentUI.childrenUI = new List<UIBase>();
|
| | | }
|
| | | // 添加到父级UI的子UI列表
|
| | | parentUI.childrenUI.Add(returnValue);
|
| | | }
|
| | | |
| | | // 更新回合数
|
| | | currentRound++;
|
| | | // 设置UI的最后使用回合数
|
| | | returnValue.lastUsedRound = currentRound;
|
| | | // 更新父级UI的回合数
|
| | | UpdateParentUIRounds(returnValue);
|
| | | |
| | | // 将UI添加到字典中
|
| | | if (!uiDict.ContainsKey(uiName))
|
| | | {
|
| | | // 如果字典中不存在该类型的UI,创建新列表
|
| | | uiDict[uiName] = new List<UIBase>();
|
| | | }
|
| | | // 添加到UI列表
|
| | | uiDict[uiName].Add(returnValue);
|
| | | |
| | | // 将UI添加到栈中
|
| | | uiStack.Push(returnValue);
|
| | | |
| | | // 更新UI排序顺序
|
| | | UpdateUISortingOrder();
|
| | | |
| | | // 打开UI
|
| | | returnValue.HandleOpen();
|
| | |
|
| | | OnOpenWindow?.Invoke(returnValue);
|
| | | |
| | | // 检查并关闭长时间未使用的UI
|
| | | CheckAndCloseIdleUI();
|
| | | |
| | | return returnValue;
|
| | | }
|
| | |
|
| | | /// <summary>
|
| | | /// 打开UI
|
| | | /// </summary>
|
| | | public T OpenWindow<T>() where T : UIBase
|
| | | {
|
| | | // 获取UI类型名称
|
| | | string uiName = typeof(T).Name;
|
| | | return OpenWindow(uiName) as T;
|
| | | }
|
| | | |
| | | /// <summary>
|
| | | /// 关闭UI
|
| | | /// </summary>
|
| | | public void CloseWindow<T>(bool destroy = false) where T : UIBase
|
| | | {
|
| | | // 获取UI类型名称
|
| | | string uiName = typeof(T).Name;
|
| | | |
| | | CloseWindow(uiName, destroy);
|
| | | |
| | | }
|
| | |
|
| | | public void CloseWindow(string uiName, bool destroy = false)
|
| | | {
|
| | | // 检查UI是否存在
|
| | | if (!uiDict.ContainsKey(uiName) || uiDict[uiName].Count == 0)
|
| | | {
|
| | | // 记录警告日志
|
| | | Debug.LogWarning($"尝试关闭不存在的UI: {uiName}");
|
| | | return;
|
| | | }
|
| | | |
| | | // 获取第一个UI实例
|
| | | UIBase ui = uiDict[uiName][0];
|
| | | |
| | | // 关闭UI
|
| | | CloseWindow(ui, destroy);
|
| | | }
|
| | | |
| | | /// <summary>
|
| | | /// 关闭指定的UI实例
|
| | | /// </summary>
|
| | | public void CloseWindow(UIBase ui, bool destroy = false)
|
| | | {
|
| | | // 检查UI是否为空
|
| | | if (ui == null)
|
| | | {
|
| | | // 记录警告日志
|
| | | Debug.LogWarning("尝试关闭空UI");
|
| | | return;
|
| | | }
|
| | | |
| | | // 获取UI类型名称
|
| | | string uiName = ui.uiName;
|
| | | |
| | | // 收集所有子UI
|
| | | List<UIBase> childrenUI = new List<UIBase>();
|
| | | if (ui.supportParentChildRelation)
|
| | | {
|
| | | CollectChildrenUI(ui, childrenUI);
|
| | | }
|
| | | |
| | | // 先关闭所有子UI
|
| | | foreach (var childUI in childrenUI)
|
| | | {
|
| | | // 关闭子UI
|
| | | CloseWindow(childUI, destroy);
|
| | | }
|
| | | |
| | | // 从栈中移除UI
|
| | | Stack<UIBase> tempStack = new Stack<UIBase>();
|
| | | while (uiStack.Count > 0)
|
| | | {
|
| | | // 弹出栈顶UI
|
| | | UIBase tempUI = uiStack.Pop();
|
| | | // 如果不是要关闭的UI,则保存到临时栈中
|
| | | if (tempUI != ui)
|
| | | {
|
| | | tempStack.Push(tempUI);
|
| | | }
|
| | | }
|
| | | |
| | | // 将临时栈中的UI重新压入栈中
|
| | | while (tempStack.Count > 0)
|
| | | {
|
| | | uiStack.Push(tempStack.Pop());
|
| | | }
|
| | | |
| | | // 从字典中移除UI
|
| | | if (uiDict.ContainsKey(uiName))
|
| | | {
|
| | | // 从UI列表中移除
|
| | | uiDict[uiName].Remove(ui);
|
| | | // 如果列表为空,从字典中移除该类型
|
| | | if (uiDict[uiName].Count == 0)
|
| | | {
|
| | | uiDict.Remove(uiName);
|
| | | }
|
| | | }
|
| | | |
| | | // 从父级UI的子UI列表中移除
|
| | | if (ui.supportParentChildRelation && ui.parentUI != null && ui.parentUI.childrenUI != null)
|
| | | {
|
| | | // 从父级UI的子UI列表中移除
|
| | | ui.parentUI.childrenUI.Remove(ui);
|
| | | }
|
| | | |
| | | // 关闭UI
|
| | | ui.HandleClose();
|
| | | OnCloseWindow?.Invoke(ui);
|
| | | |
| | | if (destroy)
|
| | | {
|
| | | // 销毁UI对象
|
| | | GameObject.Destroy(ui.gameObject);
|
| | | }
|
| | | else
|
| | | {
|
| | | // 添加到closedUIDict
|
| | | if (!closedUIDict.ContainsKey(uiName))
|
| | | {
|
| | | closedUIDict[uiName] = new List<UIBase>();
|
| | | }
|
| | | closedUIDict[uiName].Add(ui);
|
| | | |
| | | // 隐藏UI
|
| | | ui.gameObject.SetActive(false);
|
| | | }
|
| | | |
| | | // 更新UI排序顺序
|
| | | UpdateUISortingOrder();
|
| | | }
|
| | | |
| | | /// <summary>
|
| | | /// 关闭指定类型的所有UI实例
|
| | | /// </summary>
|
| | | public void CloseAllWindows<T>() where T : UIBase
|
| | | {
|
| | | // 获取UI类型名称
|
| | | string uiName = typeof(T).Name;
|
| | | |
| | | // 检查UI是否存在
|
| | | if (!uiDict.ContainsKey(uiName) || uiDict[uiName].Count == 0)
|
| | | {
|
| | | // 记录警告日志
|
| | | Debug.LogWarning($"尝试关闭不存在的UI: {uiName}");
|
| | | return;
|
| | | }
|
| | | |
| | | // 创建UI实例列表的副本,因为在关闭过程中会修改原列表
|
| | | List<UIBase> uiListCopy = new List<UIBase>(uiDict[uiName]);
|
| | | |
| | | // 关闭所有UI实例
|
| | | foreach (var ui in uiListCopy)
|
| | | {
|
| | | // 关闭UI实例
|
| | | CloseWindow(ui, false);
|
| | | }
|
| | | }
|
| | | |
| | | /// <summary>
|
| | | /// 关闭所有UI
|
| | | /// </summary>
|
| | | public void DestroyAllUI()
|
| | | {
|
| | | // 创建UI栈的副本,因为在关闭过程中会修改原栈
|
| | | UIBase[] uiArray = new UIBase[uiStack.Count];
|
| | | uiStack.CopyTo(uiArray, 0);
|
| | | |
| | | // 关闭所有UI
|
| | | foreach (var ui in uiArray)
|
| | | {
|
| | | // 关闭UI
|
| | | CloseWindow(ui, true);
|
| | | }
|
| | | |
| | | // 清空UI字典和栈
|
| | | uiDict.Clear();
|
| | | uiStack.Clear();
|
| | | closedUIDict.Clear();
|
| | | }
|
| | | |
| | | /// <summary>
|
| | | /// 刷新UI
|
| | | /// </summary>
|
| | | public void RefreshUI<T>() where T : UIBase
|
| | | {
|
| | | // 获取UI类型名称
|
| | | string uiName = typeof(T).Name;
|
| | | |
| | | // 检查UI是否存在
|
| | | if (!uiDict.ContainsKey(uiName) || uiDict[uiName].Count == 0)
|
| | | {
|
| | | // 记录警告日志
|
| | | Debug.LogWarning($"尝试刷新不存在的UI: {uiName}");
|
| | | return;
|
| | | }
|
| | | |
| | | // 刷新所有该类型的UI实例
|
| | | foreach (var ui in uiDict[uiName])
|
| | | {
|
| | | // 刷新UI
|
| | | ui.Refresh();
|
| | | }
|
| | | }
|
| | | |
| | | /// <summary>
|
| | | /// 刷新所有UI
|
| | | /// </summary>
|
| | | public void RefreshAllUI()
|
| | | {
|
| | | // 遍历所有UI类型
|
| | | foreach (var uiList in uiDict.Values)
|
| | | {
|
| | | // 遍历该类型的所有UI实例
|
| | | foreach (var ui in uiList)
|
| | | {
|
| | | // 刷新UI
|
| | | ui.Refresh();
|
| | | }
|
| | | }
|
| | | }
|
| | | |
| | | private void Update()
|
| | | {
|
| | | // 如果距离上次检查的时间超过了检查间隔
|
| | | if (Time.time - lastCheckTime > CHECK_INTERVAL)
|
| | | {
|
| | | // 更新上次检查时间
|
| | | lastCheckTime = Time.time;
|
| | | // 检查并关闭长时间未使用的UI
|
| | | CheckAndCloseIdleUI();
|
| | | }
|
| | | }
|
| | | |
| | | #endregion
|
| | |
|
| | | #region 释放资源
|
| | | |
| | | /// <summary>
|
| | | /// 释放资源
|
| | | /// </summary>
|
| | | public override void Release()
|
| | | {
|
| | | // 关闭所有UI
|
| | | DestroyAllUI();
|
| | | |
| | | // 清空缓存
|
| | | layerSortingOrderCache.Clear();
|
| | | layerTransformCache.Clear();
|
| | | uiInstanceCounter.Clear();
|
| | | |
| | | Debug.Log("UI管理器资源释放完成");
|
| | | }
|
| | | |
| | | #endregion
|
| | | }
|
| | | using System; |
| | | using System.Collections; |
| | | using System.Collections.Generic; |
| | | using UnityEngine; |
| | | using System.Linq; |
| | | |
| | | /// <summary> |
| | | /// UI管理器 - 负责管理所有UI界面的显示、隐藏和层级 |
| | | /// </summary> |
| | | public class UIManager : ManagerBase<UIManager> |
| | | { |
| | | #region 常量和枚举 |
| | | |
| | | // 基础排序顺序 |
| | | private const int BASE_SORTING_ORDER = 10; |
| | | |
| | | #endregion |
| | | |
| | | #region 字段和属性 |
| | | |
| | | // UI根节点 |
| | | private Transform uiRoot; |
| | | |
| | | // 各层级的Transform |
| | | private Transform staticTrans; |
| | | private Transform bottomTrans; |
| | | private Transform midTrans; |
| | | private Transform topTrans; |
| | | private Transform systemTrans; |
| | | |
| | | // UI字典,存储所有已加载的UI,键为UI名称,值为UI实例 |
| | | private Dictionary<string, List<UIBase>> uiDict = new Dictionary<string, List<UIBase>>(); |
| | | |
| | | // 存储关闭但未销毁的UI,键为UI名称,值为UI实例 |
| | | private Dictionary<string, List<UIBase>> closedUIDict = new Dictionary<string, List<UIBase>>(); |
| | | |
| | | // UI栈,用于管理UI的显示顺序 |
| | | private Stack<UIBase> uiStack = new Stack<UIBase>(); |
| | | |
| | | // 当前最高的排序顺序 |
| | | private int currentHighestSortingOrder = 0; |
| | | |
| | | // 当前回合数,用于记录UI的使用情况 |
| | | private int currentRound = 0; |
| | | |
| | | // 缓存层级对应的排序顺序 |
| | | private Dictionary<UILayer, int> layerSortingOrderCache = new Dictionary<UILayer, int>(); |
| | | |
| | | // 缓存层级对应的Transform |
| | | private Dictionary<UILayer, Transform> layerTransformCache = new Dictionary<UILayer, Transform>(); |
| | | |
| | | // UI实例计数器,用于为同类型UI生成唯一标识 |
| | | private Dictionary<string, int> uiInstanceCounter = new Dictionary<string, int>(); |
| | | |
| | | // 上次检查时间 |
| | | private float lastCheckTime = 0f; |
| | | // 检查间隔(秒) |
| | | private const float CHECK_INTERVAL = 5f; |
| | | |
| | | public Action<UIBase> OnOpenWindow; |
| | | |
| | | public Action<UIBase> OnCloseWindow; |
| | | |
| | | #endregion |
| | | |
| | | #region 初始化 |
| | | |
| | | /// <summary> |
| | | /// 初始化UI管理器 |
| | | /// </summary> |
| | | public override void Init() |
| | | { |
| | | base.Init(); |
| | | |
| | | // 初始化UI根节点 |
| | | InitUIRoot(); |
| | | |
| | | // 初始化缓存 |
| | | layerSortingOrderCache.Clear(); |
| | | layerTransformCache.Clear(); |
| | | uiInstanceCounter.Clear(); |
| | | |
| | | Debug.Log("UI管理器初始化完成"); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 初始化UI根节点 |
| | | /// </summary> |
| | | private void InitUIRoot() |
| | | { |
| | | // 查找UI根节点 |
| | | GameObject root = GameObject.Find("UIRoot"); |
| | | |
| | | // 如果场景中没有UI根节点,则创建一个 |
| | | if (root == null) |
| | | { |
| | | root = GameObject.Instantiate(BuiltInLoader.LoadPrefab("UIRoot")); |
| | | root.name = "UIRoot"; |
| | | if (root == null) |
| | | { |
| | | Debug.LogError("无法找到UI根节点"); |
| | | return; |
| | | } |
| | | |
| | | // 添加DontDestroyOnLoad组件,确保UI根节点在场景切换时不被销毁 |
| | | GameObject.DontDestroyOnLoad(root); |
| | | } |
| | | |
| | | uiRoot = root.transform; |
| | | uiRoot.position = Vector3.zero; |
| | | |
| | | staticTrans = uiRoot.Find("Static"); |
| | | bottomTrans = uiRoot.Find("Bottom"); |
| | | midTrans = uiRoot.Find("Middle"); |
| | | topTrans = uiRoot.Find("Top"); |
| | | systemTrans = uiRoot.Find("System"); |
| | | |
| | | layerTransformCache.Clear(); |
| | | layerTransformCache.Add(UILayer.Static, staticTrans); |
| | | layerTransformCache.Add(UILayer.Bottom, bottomTrans); |
| | | layerTransformCache.Add(UILayer.Mid, midTrans); |
| | | layerTransformCache.Add(UILayer.Top, topTrans); |
| | | layerTransformCache.Add(UILayer.System, systemTrans); |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | #region 辅助方法 |
| | | |
| | | // 获取UI层级对应的基础排序顺序 |
| | | private int GetBaseSortingOrderForLayer(UILayer layer) |
| | | { |
| | | // 尝试从缓存中获取排序顺序 |
| | | if (layerSortingOrderCache.TryGetValue(layer, out int order)) |
| | | return order; |
| | | |
| | | // 如果缓存中没有,使用原来的方法计算并缓存结果 |
| | | int result; |
| | | switch (layer) |
| | | { |
| | | case UILayer.Static: |
| | | result = BASE_SORTING_ORDER; |
| | | break; |
| | | case UILayer.Bottom: |
| | | result = BASE_SORTING_ORDER * 10; |
| | | break; |
| | | case UILayer.Mid: |
| | | result = BASE_SORTING_ORDER * 100; |
| | | break; |
| | | case UILayer.Top: |
| | | result = BASE_SORTING_ORDER * 1000; |
| | | break; |
| | | case UILayer.System: |
| | | result = BASE_SORTING_ORDER * 10000; |
| | | break; |
| | | default: |
| | | result = BASE_SORTING_ORDER * 10; |
| | | break; |
| | | } |
| | | |
| | | // 将结果存入缓存 |
| | | layerSortingOrderCache[layer] = result; |
| | | return result; |
| | | } |
| | | |
| | | // 获取层级对应的Transform |
| | | private Transform GetTransForLayer(UILayer layer) |
| | | { |
| | | // 尝试从缓存中获取Transform |
| | | if (layerTransformCache.TryGetValue(layer, out Transform trans)) |
| | | return trans; |
| | | |
| | | // 如果缓存中没有,使用原来的方法获取并缓存结果 |
| | | Transform result; |
| | | switch (layer) |
| | | { |
| | | case UILayer.Static: |
| | | result = staticTrans; |
| | | break; |
| | | case UILayer.Bottom: |
| | | result = bottomTrans; |
| | | break; |
| | | case UILayer.Mid: |
| | | result = midTrans; |
| | | break; |
| | | case UILayer.Top: |
| | | result = topTrans; |
| | | break; |
| | | case UILayer.System: |
| | | result = systemTrans; |
| | | break; |
| | | default: |
| | | result = bottomTrans; |
| | | break; |
| | | } |
| | | |
| | | // 将结果存入缓存 |
| | | layerTransformCache[layer] = result; |
| | | return result; |
| | | } |
| | | |
| | | // 获取UI实例,如果不存在则返回null |
| | | public T GetUI<T>() where T : UIBase |
| | | { |
| | | // 获取UI类型名称 |
| | | string uiName = typeof(T).Name; |
| | | if (string.IsNullOrEmpty(uiName)) |
| | | { |
| | | // 记录错误日志 |
| | | Debug.LogError("UI名称为空"); |
| | | return null; |
| | | } |
| | | |
| | | // 尝试从字典中获取UI实例列表 |
| | | if (uiDict.TryGetValue(uiName, out List<UIBase> uiList) && uiList.Count > 0) |
| | | { |
| | | // 返回第一个实例 |
| | | return uiList[0] as T; |
| | | } |
| | | |
| | | // 如果不存在,返回null |
| | | return null; |
| | | } |
| | | |
| | | public List<T> GetUIList<T>() where T : UIBase |
| | | { |
| | | List<T> uiList = new List<T>(); |
| | | |
| | | // 获取UI类型名称 |
| | | string uiName = typeof(T).Name; |
| | | if (string.IsNullOrEmpty(uiName)) |
| | | { |
| | | // 记录错误日志 |
| | | Debug.LogError("UI名称为空"); |
| | | return uiList; |
| | | } |
| | | |
| | | // 尝试从字典中获取UI实例列表 |
| | | List<UIBase> tempList = null; |
| | | uiDict.TryGetValue(uiName, out tempList); |
| | | |
| | | if (tempList != null) |
| | | { |
| | | for (int i = 0; i < tempList.Count; i++) |
| | | { |
| | | UIBase ui = tempList[i]; |
| | | if (null != ui) |
| | | { |
| | | uiList.Add(ui as T); |
| | | } |
| | | } |
| | | } |
| | | |
| | | return uiList; |
| | | } |
| | | |
| | | public bool IsOpenedInList<T>() where T : UIBase |
| | | { |
| | | List<T> uiList = GetUIList<T>(); |
| | | |
| | | foreach (T ui in uiList) |
| | | { |
| | | if (ui.IsActive()) |
| | | { |
| | | return true; |
| | | } |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | |
| | | public bool IsOpened<T>() where T : UIBase |
| | | { |
| | | T ui = GetUI<T>(); |
| | | |
| | | if (null != ui) |
| | | { |
| | | return ui.IsActive(); |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | |
| | | // 获取指定类型的所有UI实例 |
| | | public List<T> GetAllUI<T>() where T : UIBase |
| | | { |
| | | // 获取UI类型名称 |
| | | string uiName = typeof(T).Name; |
| | | if (string.IsNullOrEmpty(uiName)) |
| | | { |
| | | // 记录错误日志 |
| | | Debug.LogError("UI名称为空"); |
| | | return new List<T>(); |
| | | } |
| | | |
| | | // 尝试从字典中获取UI实例列表 |
| | | if (uiDict.TryGetValue(uiName, out List<UIBase> uiList) && uiList.Count > 0) |
| | | { |
| | | // 将列表中的所有实例转换为指定类型并返回 |
| | | return uiList.Cast<T>().ToList(); |
| | | } |
| | | |
| | | // 如果不存在,返回空列表 |
| | | return new List<T>(); |
| | | } |
| | | |
| | | // 更新父级UI的回合数 |
| | | private void UpdateParentUIRounds(UIBase ui) |
| | | { |
| | | // 如果UI为空或不支持父子关系,直接返回 |
| | | if (ui == null || !ui.supportParentChildRelation) |
| | | return; |
| | | |
| | | // 获取父级UI |
| | | UIBase parentUI = ui.parentUI; |
| | | // 遍历所有父级UI,更新回合数 |
| | | while (parentUI != null) |
| | | { |
| | | // 更新父级UI的最后使用回合数 |
| | | parentUI.lastUsedRound = currentRound; |
| | | // 继续向上查找父级UI |
| | | parentUI = parentUI.parentUI; |
| | | } |
| | | } |
| | | |
| | | // 递归收集所有子UI |
| | | private void CollectChildrenUI(UIBase ui, List<UIBase> result) |
| | | { |
| | | // 如果UI为空或没有子UI或不支持父子关系,直接返回 |
| | | if (ui == null || !ui.supportParentChildRelation || ui.childrenUI == null || ui.childrenUI.Count == 0) |
| | | return; |
| | | |
| | | // 遍历所有子UI |
| | | foreach (var childUI in ui.childrenUI) |
| | | { |
| | | // 如果结果列表中不包含当前子UI,则添加 |
| | | if (!result.Contains(childUI)) |
| | | { |
| | | // 添加到结果列表 |
| | | result.Add(childUI); |
| | | // 递归收集子UI的子UI |
| | | CollectChildrenUI(childUI, result); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 检查并关闭长时间未使用的UI |
| | | /// </summary> |
| | | public void CheckAndCloseIdleUI() |
| | | { |
| | | // 如果没有UI,直接返回 |
| | | if (uiDict.Count == 0 && closedUIDict.Count == 0) |
| | | return; |
| | | |
| | | // 创建需要关闭的UI列表 |
| | | List<UIBase> uiToClose = new List<UIBase>(); |
| | | |
| | | // 遍历所有活跃UI |
| | | foreach (var uiList in uiDict.Values) |
| | | { |
| | | foreach (var ui in uiList) |
| | | { |
| | | // 如果UI是持久化的,跳过 |
| | | if (ui.isPersistent) |
| | | continue; |
| | | |
| | | if (ui.IsActive()) |
| | | continue; |
| | | |
| | | // 计算UI空闲的回合数 |
| | | int idleRounds = currentRound - ui.lastUsedRound; |
| | | |
| | | // 如果空闲回合数超过最大空闲回合数,添加到关闭列表 |
| | | if (idleRounds > ui.maxIdleRounds) |
| | | { |
| | | uiToClose.Add(ui); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 遍历所有关闭的UI |
| | | List<string> emptyKeys = new List<string>(); |
| | | foreach (var kvp in closedUIDict) |
| | | { |
| | | string uiName = kvp.Key; |
| | | List<UIBase> uiList = kvp.Value; |
| | | List<UIBase> uiToRemove = new List<UIBase>(); |
| | | |
| | | foreach (var ui in uiList) |
| | | { |
| | | // 计算UI空闲的回合数 |
| | | int idleRounds = currentRound - ui.lastUsedRound; |
| | | |
| | | // 如果空闲回合数超过最大空闲回合数,添加到关闭列表 |
| | | if (idleRounds > ui.maxIdleRounds) |
| | | { |
| | | uiToClose.Add(ui); |
| | | uiToRemove.Add(ui); |
| | | } |
| | | } |
| | | |
| | | // 从关闭列表中移除需要销毁的UI |
| | | foreach (var ui in uiToRemove) |
| | | { |
| | | uiList.Remove(ui); |
| | | } |
| | | |
| | | // 如果列表为空,记录需要从字典中移除的键 |
| | | if (uiList.Count == 0) |
| | | { |
| | | emptyKeys.Add(uiName); |
| | | } |
| | | } |
| | | |
| | | // 从字典中移除空列表 |
| | | foreach (var key in emptyKeys) |
| | | { |
| | | closedUIDict.Remove(key); |
| | | } |
| | | |
| | | // 销毁所有需要关闭的UI |
| | | foreach (var ui in uiToClose) |
| | | { |
| | | // 记录日志 |
| | | Debug.Log($"销毁长时间未使用的UI: {ui.uiName}, 空闲回合数: {currentRound - ui.lastUsedRound}"); |
| | | // 销毁UI对象 |
| | | GameObject.Destroy(ui.gameObject); |
| | | } |
| | | } |
| | | |
| | | // // 生成UI实例的唯一标识 |
| | | // private string GenerateUIInstanceID(string uiName) |
| | | // { |
| | | // // 如果计数器中不存在该UI类型,初始化为0 |
| | | // if (!uiInstanceCounter.ContainsKey(uiName)) |
| | | // { |
| | | // uiInstanceCounter[uiName] = 0; |
| | | // } |
| | | |
| | | // // 递增计数器 |
| | | // uiInstanceCounter[uiName]++; |
| | | |
| | | // // 返回带有计数的唯一标识 |
| | | // return $"{uiName}_{uiInstanceCounter[uiName]}"; |
| | | // } |
| | | |
| | | #endregion |
| | | |
| | | #region UI资源管理 |
| | | |
| | | private UIBase LoadUIResource(string uiName) |
| | | { |
| | | // 从资源管理器加载UI预制体 |
| | | GameObject prefab = ResManager.Instance.LoadAsset<GameObject>("UI", uiName); |
| | | |
| | | // 检查预制体是否加载成功 |
| | | if (prefab == null) |
| | | { |
| | | // 记录错误日志 |
| | | Debug.LogError($"加载UI预制体失败: {uiName}"); |
| | | return null; |
| | | } |
| | | |
| | | // 实例化UI对象 |
| | | GameObject uiObject = GameObject.Instantiate(prefab); |
| | | // 设置对象名称 |
| | | uiObject.name = uiName; |
| | | |
| | | // 通过uiName映射Type |
| | | Type uiType = Type.GetType(uiName); |
| | | if (uiType == null) |
| | | { |
| | | Debug.LogError($"找不到UI类型: {uiName}"); |
| | | return null; |
| | | } |
| | | |
| | | // 获取UI基类组件 |
| | | UIBase uiBase = uiObject.GetComponent(uiType) as UIBase; |
| | | |
| | | // 检查UI基类组件是否存在 |
| | | if (uiBase == null) |
| | | { |
| | | // 记录错误日志 |
| | | Debug.LogError($"UI预制体 {uiName} 没有 UIBase 组件或类型不匹配"); |
| | | return null; |
| | | } |
| | | |
| | | // 设置UI名称 |
| | | uiBase.uiName = uiName; |
| | | |
| | | // 设置父节点为UI根节点 |
| | | Transform parentTrans = GetTransForLayer(uiBase.uiLayer); |
| | | |
| | | uiObject.transform.SetParent(parentTrans, false); |
| | | |
| | | // 设置排序顺序 |
| | | int baseSortingOrder = GetBaseSortingOrderForLayer(uiBase.uiLayer); |
| | | uiBase.SetSortingOrder(baseSortingOrder); |
| | | |
| | | return uiBase; |
| | | } |
| | | |
| | | // 加载UI预制体 |
| | | private T LoadUIResource<T>(string uiName) where T : UIBase |
| | | { |
| | | return LoadUIResource(uiName) as T; |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | #region UI排序管理 |
| | | |
| | | // 更新UI排序顺序 |
| | | private void UpdateUISortingOrder() |
| | | { |
| | | // 重置当前最高排序顺序 |
| | | currentHighestSortingOrder = 0; |
| | | |
| | | // 遍历UI栈,设置排序顺序 |
| | | UIBase[] uiArray = new UIBase[uiStack.Count]; |
| | | uiStack.CopyTo(uiArray, 0); |
| | | |
| | | // 先按照UILayer进行排序,然后再按照栈顺序排序 |
| | | Array.Sort(uiArray, (a, b) => { |
| | | // 比较UI层级 |
| | | int layerCompare = a.uiLayer.CompareTo(b.uiLayer); |
| | | if (layerCompare != 0) |
| | | return layerCompare; |
| | | |
| | | // 同层级内,按照栈中的顺序排序 |
| | | return Array.IndexOf(uiArray, a).CompareTo(Array.IndexOf(uiArray, b)); |
| | | }); |
| | | |
| | | // 遍历排序后的UI数组,设置排序顺序 |
| | | foreach (var ui in uiArray) |
| | | { |
| | | // 获取基础排序顺序 |
| | | int baseSortingOrder = GetBaseSortingOrderForLayer(ui.uiLayer); |
| | | // 计算当前UI的排序顺序 |
| | | int sortingOrder = baseSortingOrder + currentHighestSortingOrder; |
| | | // 设置UI的排序顺序 |
| | | ui.SetSortingOrder(sortingOrder); |
| | | // 更新当前最高排序顺序 |
| | | currentHighestSortingOrder += 10; |
| | | } |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | #region UI操作 |
| | | |
| | | |
| | | |
| | | private UIBase GetLastSupportParentChildRelationUI() |
| | | { |
| | | List<UIBase> tempList = new List<UIBase>(); |
| | | |
| | | UIBase target = null; |
| | | |
| | | while (target == null && uiStack.Count > 0) |
| | | { |
| | | UIBase uiBase = uiStack.Pop(); |
| | | |
| | | if (uiBase != null && uiBase.supportParentChildRelation && !uiBase.isMainUI) |
| | | { |
| | | target = uiBase; |
| | | } |
| | | |
| | | tempList.Add(uiBase); |
| | | } |
| | | |
| | | for (int i = tempList.Count - 1; i >= 0; i--) |
| | | { |
| | | uiStack.Push(tempList[i]); |
| | | } |
| | | |
| | | return target; |
| | | } |
| | | |
| | | public UIBase OpenWindow(string uiName) |
| | | { |
| | | // 优先从closedUIDict中获取 |
| | | UIBase parentUI = null; |
| | | |
| | | UIBase returnValue = null; |
| | | |
| | | if (closedUIDict.TryGetValue(uiName, out List<UIBase> closedUIList) && closedUIList.Count > 0) |
| | | { |
| | | returnValue = closedUIList[0] as UIBase; |
| | | closedUIList.RemoveAt(0); |
| | | |
| | | if (closedUIList.Count == 0) |
| | | { |
| | | closedUIDict.Remove(uiName); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | returnValue = LoadUIResource(uiName); |
| | | if (returnValue == null) |
| | | { |
| | | // 记录错误日志 |
| | | Debug.LogError($"打开UI失败: {uiName}"); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | returnValue.gameObject.SetActive(true); |
| | | |
| | | // 自动设置父级UI(如果未指定且支持父子关系) |
| | | if (returnValue.supportParentChildRelation && uiStack.Count > 0) |
| | | { |
| | | // 获取栈顶UI |
| | | parentUI = GetLastSupportParentChildRelationUI(); |
| | | } |
| | | |
| | | // 设置父级UI |
| | | if (parentUI != null) |
| | | { |
| | | // 设置父子关系 |
| | | returnValue.parentUI = parentUI; |
| | | if (parentUI.childrenUI == null) |
| | | { |
| | | // 初始化父级UI的子UI列表 |
| | | parentUI.childrenUI = new List<UIBase>(); |
| | | } |
| | | // 添加到父级UI的子UI列表 |
| | | parentUI.childrenUI.Add(returnValue); |
| | | } |
| | | |
| | | // 更新回合数 |
| | | currentRound++; |
| | | // 设置UI的最后使用回合数 |
| | | returnValue.lastUsedRound = currentRound; |
| | | // 更新父级UI的回合数 |
| | | UpdateParentUIRounds(returnValue); |
| | | |
| | | // 将UI添加到字典中 |
| | | if (!uiDict.ContainsKey(uiName)) |
| | | { |
| | | // 如果字典中不存在该类型的UI,创建新列表 |
| | | uiDict[uiName] = new List<UIBase>(); |
| | | } |
| | | // 添加到UI列表 |
| | | uiDict[uiName].Add(returnValue); |
| | | |
| | | // 将UI添加到栈中 |
| | | uiStack.Push(returnValue); |
| | | |
| | | // 更新UI排序顺序 |
| | | UpdateUISortingOrder(); |
| | | |
| | | // 打开UI |
| | | returnValue.HandleOpen(); |
| | | |
| | | OnOpenWindow?.Invoke(returnValue); |
| | | |
| | | // 检查并关闭长时间未使用的UI |
| | | CheckAndCloseIdleUI(); |
| | | |
| | | return returnValue; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 打开UI |
| | | /// </summary> |
| | | public T OpenWindow<T>() where T : UIBase |
| | | { |
| | | // 获取UI类型名称 |
| | | string uiName = typeof(T).Name; |
| | | return OpenWindow(uiName) as T; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 关闭UI |
| | | /// </summary> |
| | | public void CloseWindow<T>(bool destroy = false) where T : UIBase |
| | | { |
| | | // 获取UI类型名称 |
| | | string uiName = typeof(T).Name; |
| | | |
| | | CloseWindow(uiName, destroy); |
| | | |
| | | } |
| | | |
| | | public void CloseWindow(string uiName, bool destroy = false) |
| | | { |
| | | // 检查UI是否存在 |
| | | if (!uiDict.ContainsKey(uiName) || uiDict[uiName].Count == 0) |
| | | { |
| | | // 记录警告日志 |
| | | Debug.LogWarning($"尝试关闭不存在的UI: {uiName}"); |
| | | return; |
| | | } |
| | | |
| | | // 获取第一个UI实例 |
| | | UIBase ui = uiDict[uiName][0]; |
| | | |
| | | // 关闭UI |
| | | CloseWindow(ui, destroy); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 关闭指定的UI实例 |
| | | /// </summary> |
| | | public void CloseWindow(UIBase ui, bool destroy = false) |
| | | { |
| | | // 检查UI是否为空 |
| | | if (ui == null) |
| | | { |
| | | // 记录警告日志 |
| | | Debug.LogWarning("尝试关闭空UI"); |
| | | return; |
| | | } |
| | | |
| | | // 获取UI类型名称 |
| | | string uiName = ui.uiName; |
| | | |
| | | // 收集所有子UI |
| | | List<UIBase> childrenUI = new List<UIBase>(); |
| | | if (ui.supportParentChildRelation) |
| | | { |
| | | CollectChildrenUI(ui, childrenUI); |
| | | } |
| | | |
| | | // 先关闭所有子UI |
| | | foreach (var childUI in childrenUI) |
| | | { |
| | | // 关闭子UI |
| | | CloseWindow(childUI, destroy); |
| | | } |
| | | |
| | | // 从栈中移除UI |
| | | Stack<UIBase> tempStack = new Stack<UIBase>(); |
| | | while (uiStack.Count > 0) |
| | | { |
| | | // 弹出栈顶UI |
| | | UIBase tempUI = uiStack.Pop(); |
| | | // 如果不是要关闭的UI,则保存到临时栈中 |
| | | if (tempUI != ui) |
| | | { |
| | | tempStack.Push(tempUI); |
| | | } |
| | | } |
| | | |
| | | // 将临时栈中的UI重新压入栈中 |
| | | while (tempStack.Count > 0) |
| | | { |
| | | uiStack.Push(tempStack.Pop()); |
| | | } |
| | | |
| | | // 从字典中移除UI |
| | | if (uiDict.ContainsKey(uiName)) |
| | | { |
| | | // 从UI列表中移除 |
| | | uiDict[uiName].Remove(ui); |
| | | // 如果列表为空,从字典中移除该类型 |
| | | if (uiDict[uiName].Count == 0) |
| | | { |
| | | uiDict.Remove(uiName); |
| | | } |
| | | } |
| | | |
| | | // 从父级UI的子UI列表中移除 |
| | | if (ui.supportParentChildRelation && ui.parentUI != null && ui.parentUI.childrenUI != null) |
| | | { |
| | | // 从父级UI的子UI列表中移除 |
| | | ui.parentUI.childrenUI.Remove(ui); |
| | | } |
| | | |
| | | // 关闭UI |
| | | ui.HandleClose(); |
| | | OnCloseWindow?.Invoke(ui); |
| | | |
| | | if (destroy) |
| | | { |
| | | // 销毁UI对象 |
| | | GameObject.Destroy(ui.gameObject); |
| | | } |
| | | else |
| | | { |
| | | // 添加到closedUIDict |
| | | if (!closedUIDict.ContainsKey(uiName)) |
| | | { |
| | | closedUIDict[uiName] = new List<UIBase>(); |
| | | } |
| | | closedUIDict[uiName].Add(ui); |
| | | |
| | | // 隐藏UI |
| | | ui.gameObject.SetActive(false); |
| | | } |
| | | |
| | | // 更新UI排序顺序 |
| | | UpdateUISortingOrder(); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 关闭指定类型的所有UI实例 |
| | | /// </summary> |
| | | public void CloseAllWindows<T>() where T : UIBase |
| | | { |
| | | // 获取UI类型名称 |
| | | string uiName = typeof(T).Name; |
| | | |
| | | // 检查UI是否存在 |
| | | if (!uiDict.ContainsKey(uiName) || uiDict[uiName].Count == 0) |
| | | { |
| | | // 记录警告日志 |
| | | Debug.LogWarning($"尝试关闭不存在的UI: {uiName}"); |
| | | return; |
| | | } |
| | | |
| | | // 创建UI实例列表的副本,因为在关闭过程中会修改原列表 |
| | | List<UIBase> uiListCopy = new List<UIBase>(uiDict[uiName]); |
| | | |
| | | // 关闭所有UI实例 |
| | | foreach (var ui in uiListCopy) |
| | | { |
| | | // 关闭UI实例 |
| | | CloseWindow(ui, false); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 关闭所有UI |
| | | /// </summary> |
| | | public void DestroyAllUI() |
| | | { |
| | | // 创建UI栈的副本,因为在关闭过程中会修改原栈 |
| | | UIBase[] uiArray = new UIBase[uiStack.Count]; |
| | | uiStack.CopyTo(uiArray, 0); |
| | | |
| | | // 关闭所有UI |
| | | foreach (var ui in uiArray) |
| | | { |
| | | // 关闭UI |
| | | CloseWindow(ui, true); |
| | | } |
| | | |
| | | // 清空UI字典和栈 |
| | | uiDict.Clear(); |
| | | uiStack.Clear(); |
| | | closedUIDict.Clear(); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 刷新UI |
| | | /// </summary> |
| | | public void RefreshUI<T>() where T : UIBase |
| | | { |
| | | // 获取UI类型名称 |
| | | string uiName = typeof(T).Name; |
| | | |
| | | // 检查UI是否存在 |
| | | if (!uiDict.ContainsKey(uiName) || uiDict[uiName].Count == 0) |
| | | { |
| | | // 记录警告日志 |
| | | Debug.LogWarning($"尝试刷新不存在的UI: {uiName}"); |
| | | return; |
| | | } |
| | | |
| | | // 刷新所有该类型的UI实例 |
| | | foreach (var ui in uiDict[uiName]) |
| | | { |
| | | // 刷新UI |
| | | ui.Refresh(); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 刷新所有UI |
| | | /// </summary> |
| | | public void RefreshAllUI() |
| | | { |
| | | // 遍历所有UI类型 |
| | | foreach (var uiList in uiDict.Values) |
| | | { |
| | | // 遍历该类型的所有UI实例 |
| | | foreach (var ui in uiList) |
| | | { |
| | | // 刷新UI |
| | | ui.Refresh(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void Update() |
| | | { |
| | | // 如果距离上次检查的时间超过了检查间隔 |
| | | if (Time.time - lastCheckTime > CHECK_INTERVAL) |
| | | { |
| | | // 更新上次检查时间 |
| | | lastCheckTime = Time.time; |
| | | // 检查并关闭长时间未使用的UI |
| | | CheckAndCloseIdleUI(); |
| | | } |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | #region 释放资源 |
| | | |
| | | /// <summary> |
| | | /// 释放资源 |
| | | /// </summary> |
| | | public override void Release() |
| | | { |
| | | // 关闭所有UI |
| | | DestroyAllUI(); |
| | | |
| | | // 清空缓存 |
| | | layerSortingOrderCache.Clear(); |
| | | layerTransformCache.Clear(); |
| | | uiInstanceCounter.Clear(); |
| | | |
| | | Debug.Log("UI管理器资源释放完成"); |
| | | } |
| | | |
| | | #endregion |
| | | } |