| | |
| | | using System;
|
| | | using System.Collections;
|
| | | using System.Collections.Generic;
|
| | | using UnityEngine;
|
| | | using System;
|
| | | using System.Linq;
|
| | |
|
| | | public class UIManager : Singleton<UIManager>
|
| | | /// <summary>
|
| | | /// UI管理器 - 负责管理所有UI界面的显示、隐藏和层级
|
| | | /// </summary>
|
| | | public class UIManager : ManagerBase<UIManager>
|
| | | {
|
| | | // 单例实例
|
| | | #region 常量和枚举
|
| | | |
| | | // 基础排序顺序
|
| | | private const int BASE_SORTING_ORDER = 10;
|
| | | |
| | | #endregion
|
| | |
|
| | | #region 变量定义
|
| | | #region 字段和属性
|
| | |
|
| | | // UI根节点
|
| | | private Transform uiRoot;
|
| | | |
| | | // 各层级的Transform
|
| | | private Transform staticTrans;
|
| | | private Transform bottomTrans;
|
| | | private Transform midTrans;
|
| | | private Transform topTrans;
|
| | | private Transform systemTrans; |
| | | private Transform systemTrans;
|
| | |
|
| | | // 基础排序顺序
|
| | | private const int BASE_SORTING_ORDER = 10;
|
| | | private const int SORTING_ORDER_STEP = 10;
|
| | | // UI字典,存储所有已加载的UI,键为UI名称,值为UI实例
|
| | | private Dictionary<string, List<UIBase>> uiDict = new Dictionary<string, List<UIBase>>();
|
| | |
|
| | | // 已加载的UI字典
|
| | | private Dictionary<string, UIBase> uiDict = new Dictionary<string, UIBase>();
|
| | | |
| | | // 当前打开的UI栈
|
| | | // UI栈,用于管理UI的显示顺序
|
| | | private Stack<UIBase> uiStack = new Stack<UIBase>();
|
| | |
|
| | | // 当前最高的排序顺序
|
| | | private int currentHighestSortingOrder = 0;
|
| | |
|
| | | // 当前回合数
|
| | | // 当前回合数,用于记录UI的使用情况
|
| | | private int currentRound = 0;
|
| | |
|
| | | #endregion
|
| | |
|
| | | #region 初始化方法
|
| | | |
| | |
|
| | | #region 缓存变量
|
| | | |
| | | // 缓存Transform查找结果
|
| | | private Dictionary<UILayer, Transform> layerTransformCache = new Dictionary<UILayer, Transform>();
|
| | | |
| | | // 缓存层级对应的基础排序顺序
|
| | | // 缓存层级对应的排序顺序
|
| | | 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;
|
| | | |
| | | #endregion
|
| | |
|
| | | #region 初始化
|
| | |
|
| | | // 初始化缓存
|
| | | private void InitCache()
|
| | | /// <summary>
|
| | | /// 初始化UI管理器
|
| | | /// </summary>
|
| | | public override void Init()
|
| | | {
|
| | | // 初始化层级Transform缓存
|
| | | layerTransformCache.Clear();
|
| | | layerTransformCache[UILayer.Static] = staticTrans;
|
| | | layerTransformCache[UILayer.Bottom] = bottomTrans;
|
| | | layerTransformCache[UILayer.Mid] = midTrans;
|
| | | layerTransformCache[UILayer.Top] = topTrans;
|
| | | layerTransformCache[UILayer.System] = systemTrans;
|
| | | base.Init();
|
| | |
|
| | | // 初始化层级排序顺序缓存
|
| | | layerSortingOrderCache.Clear();
|
| | | layerSortingOrderCache[UILayer.Static] = BASE_SORTING_ORDER;
|
| | | layerSortingOrderCache[UILayer.Bottom] = BASE_SORTING_ORDER * 10;
|
| | | layerSortingOrderCache[UILayer.Mid] = BASE_SORTING_ORDER * 100;
|
| | | layerSortingOrderCache[UILayer.Top] = BASE_SORTING_ORDER * 1000;
|
| | | layerSortingOrderCache[UILayer.System] = BASE_SORTING_ORDER * 10000;
|
| | | }
|
| | |
|
| | |
|
| | | // 初始化
|
| | | public void Init()
|
| | | {
|
| | | // 初始化UI根节点
|
| | | InitUIRoot();
|
| | | |
| | | // 初始化缓存
|
| | | layerSortingOrderCache.Clear();
|
| | | layerTransformCache.Clear();
|
| | | uiInstanceCounter.Clear();
|
| | | |
| | | Debug.Log("UI管理器初始化完成");
|
| | | }
|
| | |
|
| | | // 创建UI根节点
|
| | | /// <summary>
|
| | | /// 初始化UI根节点
|
| | | /// </summary>
|
| | | private void InitUIRoot()
|
| | | {
|
| | | // 查找UI根节点
|
| | | GameObject root = GameObject.Find("UIRoot");
|
| | | |
| | | // 如果场景中没有UI根节点,则创建一个
|
| | | if (root == null)
|
| | | {
|
| | | root = GameObject.Instantiate(Resources.Load<GameObject>("UI/UIRoot"));
|
| | |
|
| | | root = new GameObject("UIRoot");
|
| | | |
| | | // 添加DontDestroyOnLoad组件,确保UI根节点在场景切换时不被销毁
|
| | | GameObject.DontDestroyOnLoad(root);
|
| | | |
| | | // 创建各层级节点
|
| | | GameObject staticNode = new GameObject("Static");
|
| | | GameObject bottomNode = new GameObject("Bottom");
|
| | | GameObject midNode = new GameObject("Mid");
|
| | | GameObject topNode = new GameObject("Top");
|
| | | GameObject systemNode = new GameObject("System");
|
| | | |
| | | // 设置父节点
|
| | | staticNode.transform.SetParent(root.transform, false);
|
| | | bottomNode.transform.SetParent(root.transform, false);
|
| | | midNode.transform.SetParent(root.transform, false);
|
| | | topNode.transform.SetParent(root.transform, false);
|
| | | systemNode.transform.SetParent(root.transform, false);
|
| | | |
| | | if (root == null)
|
| | | {
|
| | | Debug.LogError("无法找到UI根节点");
|
| | |
| | | // 获取UI层级对应的基础排序顺序
|
| | | private int GetBaseSortingOrderForLayer(UILayer layer)
|
| | | {
|
| | | // 尝试从缓存中获取排序顺序
|
| | | if (layerSortingOrderCache.TryGetValue(layer, out int order))
|
| | | return order;
|
| | |
|
| | |
| | | break;
|
| | | }
|
| | |
|
| | | // 将结果存入缓存
|
| | | layerSortingOrderCache[layer] = result;
|
| | | return result;
|
| | | }
|
| | |
| | | // 获取层级对应的Transform
|
| | | private Transform GetTransForLayer(UILayer layer)
|
| | | {
|
| | | // 尝试从缓存中获取Transform
|
| | | if (layerTransformCache.TryGetValue(layer, out Transform trans))
|
| | | return trans;
|
| | |
|
| | |
| | | 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;
|
| | | }
|
| | |
|
| | | UIBase ui;
|
| | | if (uiDict.TryGetValue(uiName, out ui))
|
| | | // 尝试从字典中获取UI实例列表
|
| | | if (uiDict.TryGetValue(uiName, out List<UIBase> uiList) && uiList.Count > 0)
|
| | | {
|
| | | return ui as T;
|
| | | // 返回第一个实例
|
| | | return uiList[0] as T;
|
| | | }
|
| | |
|
| | | // 如果不存在,返回null
|
| | | return null;
|
| | | }
|
| | | |
| | | // 获取指定类型的所有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)
|
| | | {
|
| | | if (ui == null)
|
| | | // 如果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)
|
| | | {
|
| | | if (ui == null || ui.childrenUI == null || ui.childrenUI.Count == 0)
|
| | | // 如果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)
|
| | | 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;
|
| | | |
| | | // 计算UI空闲的回合数
|
| | | int idleRounds = currentRound - ui.lastUsedRound;
|
| | | |
| | | // 如果空闲回合数超过最大空闲回合数,添加到关闭列表
|
| | | if (idleRounds > ui.maxIdleRounds)
|
| | | {
|
| | | uiToClose.Add(ui);
|
| | | }
|
| | | }
|
| | | }
|
| | | |
| | | // 关闭所有需要关闭的UI
|
| | | foreach (var ui in uiToClose)
|
| | | {
|
| | | // 记录日志
|
| | | Debug.Log($"关闭长时间未使用的UI: {ui.uiName}, 空闲回合数: {currentRound - ui.lastUsedRound}");
|
| | | // 关闭UI
|
| | | CloseWindow(ui);
|
| | | }
|
| | | }
|
| | | |
| | | // // 生成UI实例的唯一标识
|
| | | // private string GenerateUIInstanceID(string uiName)
|
| | | // {
|
| | | // // 如果计数器中不存在该UI类型,初始化为0
|
| | | // if (!uiInstanceCounter.ContainsKey(uiName))
|
| | | // {
|
| | | // uiInstanceCounter[uiName] = 0;
|
| | | // }
|
| | | |
| | | // // 递增计数器
|
| | | // uiInstanceCounter[uiName]++;
|
| | | |
| | | // // 返回带有计数的唯一标识
|
| | | // return $"{uiName}_{uiInstanceCounter[uiName]}";
|
| | | // }
|
| | | |
| | | #endregion
|
| | |
|
| | | #region UI资源管理
|
| | |
|
| | | // 加载UI预制体
|
| | | private T LoadUIResource<T>(string uiName) where T : UIBase
|
| | |
|
| | | {
|
| | | // 从资源管理器加载UI预制体
|
| | | GameObject prefab = ResManager.Instance.LoadUI(uiName);
|
| | |
|
| | | // 检查预制体是否加载成功
|
| | | if (prefab == null)
|
| | | {
|
| | | // 记录错误日志
|
| | | Debug.LogError($"加载UI预制体失败: {uiName}");
|
| | | return null;
|
| | | }
|
| | |
|
| | | // 实例化UI对象
|
| | | GameObject uiObject = GameObject.Instantiate(prefab);
|
| | | // 设置对象名称
|
| | | uiObject.name = uiName;
|
| | | // 获取UI基类组件
|
| | | T uiBase = uiObject.GetComponent<T>();
|
| | |
|
| | | // 检查UI基类组件是否存在
|
| | | if (uiBase == null)
|
| | | {
|
| | | // 记录错误日志
|
| | | Debug.LogError($"UI预制体 {uiName} 没有 UIBase 组件");
|
| | | return null;
|
| | | }
|
| | |
|
| | | // 设置UI名称
|
| | | uiBase.uiName = uiName;
|
| | | // // 设置UI实例ID
|
| | | // uiBase.instanceID = GenerateUIInstanceID(uiName);
|
| | |
|
| | | // 设置父节点为UI根节点
|
| | | uiObject.transform.SetParent(GetTransForLayer(uiBase.uiLayer), false);
|
| | |
|
| | |
|
| | | // 设置排序顺序
|
| | | int baseSortingOrder = GetBaseSortingOrderForLayer(uiBase.uiLayer);
|
| | |
| | | // 更新UI排序顺序
|
| | | private void UpdateUISortingOrder()
|
| | | {
|
| | | // 重置当前最高排序顺序
|
| | | currentHighestSortingOrder = 0;
|
| | |
|
| | | // 遍历UI栈,设置排序顺序
|
| | |
| | |
|
| | | // 先按照UILayer进行排序,然后再按照栈顺序排序
|
| | | Array.Sort(uiArray, (a, b) => {
|
| | | // 比较UI层级
|
| | | int layerCompare = a.uiLayer.CompareTo(b.uiLayer);
|
| | | if (layerCompare != 0)
|
| | | return layerCompare;
|
| | |
|
| | | // 同层级内,按照栈中的顺序排序
|
| | | int aIndex = Array.IndexOf(uiArray, a);
|
| | | int bIndex = Array.IndexOf(uiArray, b);
|
| | | return aIndex.CompareTo(bIndex);
|
| | | return Array.IndexOf(uiArray, a).CompareTo(Array.IndexOf(uiArray, b));
|
| | | });
|
| | |
|
| | | for (int i = 0; i < uiArray.Length; i++)
|
| | | // 遍历排序后的UI数组,设置排序顺序
|
| | | foreach (var ui in uiArray)
|
| | | {
|
| | | UIBase ui = uiArray[i];
|
| | | // 获取基础排序顺序
|
| | | int baseSortingOrder = GetBaseSortingOrderForLayer(ui.uiLayer);
|
| | | int sortingOrder = baseSortingOrder + i * SORTING_ORDER_STEP;
|
| | | |
| | | // 计算当前UI的排序顺序
|
| | | int sortingOrder = baseSortingOrder + currentHighestSortingOrder;
|
| | | // 设置UI的排序顺序
|
| | | ui.SetSortingOrder(sortingOrder);
|
| | | |
| | | if (sortingOrder > currentHighestSortingOrder)
|
| | | {
|
| | | currentHighestSortingOrder = sortingOrder;
|
| | | }
|
| | | // 更新当前最高排序顺序
|
| | | currentHighestSortingOrder += 10;
|
| | | }
|
| | | }
|
| | |
|
| | | #endregion
|
| | |
|
| | | #region UI栈管理
|
| | | #region UI操作
|
| | |
|
| | | // 从UI栈中移除指定UI
|
| | | private void RemoveFromUIStack(UIBase ui)
|
| | | /// <summary>
|
| | | /// 打开UI
|
| | | /// </summary>
|
| | | public T OpenWindow<T>(UIBase parentUI = null) where T : UIBase
|
| | | {
|
| | | if (ui == null || !uiStack.Contains(ui))
|
| | | return;
|
| | | |
| | | RemoveFromUIStack(new List<UIBase> { ui });
|
| | | }
|
| | |
|
| | | // 从UI栈中移除多个UI
|
| | | private void RemoveFromUIStack(List<UIBase> uisToRemove)
|
| | | {
|
| | | if (uisToRemove == null || uisToRemove.Count == 0)
|
| | | return;
|
| | | |
| | | Stack<UIBase> tempStack = new Stack<UIBase>();
|
| | | while (uiStack.Count > 0)
|
| | | // 获取UI类型名称
|
| | | string uiName = typeof(T).Name;
|
| | | |
| | | // 加载UI资源
|
| | | T ui = LoadUIResource<T>(uiName);
|
| | | if (ui == null)
|
| | | {
|
| | | UIBase topUI = uiStack.Pop();
|
| | | if (!uisToRemove.Contains(topUI))
|
| | | // 记录错误日志
|
| | | Debug.LogError($"打开UI失败: {uiName}");
|
| | | return null;
|
| | | }
|
| | | |
| | | // 自动设置父级UI(如果未指定且支持父子关系)
|
| | | if (parentUI == null && ui.supportParentChildRelation && uiStack.Count > 0)
|
| | | {
|
| | | // 获取栈顶UI
|
| | | UIBase topUI = uiStack.Peek();
|
| | | // 如果栈顶UI也支持父子关系且不是主UI,则将其设为父级
|
| | | if (topUI != null && topUI.supportParentChildRelation && !topUI.isMainUI)
|
| | | {
|
| | | tempStack.Push(topUI);
|
| | | parentUI = topUI;
|
| | | }
|
| | | }
|
| | |
|
| | | // 恢复UI栈
|
| | | // 设置父级UI
|
| | | if (parentUI != null && ui.supportParentChildRelation && !parentUI.isMainUI)
|
| | | {
|
| | | // 设置父子关系
|
| | | ui.parentUI = parentUI;
|
| | | if (parentUI.childrenUI == null)
|
| | | {
|
| | | // 初始化父级UI的子UI列表
|
| | | parentUI.childrenUI = new List<UIBase>();
|
| | | }
|
| | | // 添加到父级UI的子UI列表
|
| | | parentUI.childrenUI.Add(ui);
|
| | | }
|
| | | |
| | | // 更新回合数
|
| | | currentRound++;
|
| | | // 设置UI的最后使用回合数
|
| | | ui.lastUsedRound = currentRound;
|
| | | // 更新父级UI的回合数
|
| | | UpdateParentUIRounds(ui);
|
| | | |
| | | // 将UI添加到字典中
|
| | | if (!uiDict.ContainsKey(uiName))
|
| | | {
|
| | | // 如果字典中不存在该类型的UI,创建新列表
|
| | | uiDict[uiName] = new List<UIBase>();
|
| | | }
|
| | | // 添加到UI列表
|
| | | uiDict[uiName].Add(ui);
|
| | | |
| | | // 将UI添加到栈中
|
| | | uiStack.Push(ui);
|
| | | |
| | | // 更新UI排序顺序
|
| | | UpdateUISortingOrder();
|
| | | |
| | | // 打开UI
|
| | | ui.HandleOpen();
|
| | | |
| | | // 检查并关闭长时间未使用的UI
|
| | | CheckAndCloseIdleUI();
|
| | | |
| | | return ui;
|
| | | }
|
| | | |
| | | /// <summary>
|
| | | /// 关闭UI
|
| | | /// </summary>
|
| | | public void CloseWindow<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实例
|
| | | UIBase ui = uiDict[uiName][0];
|
| | | |
| | | // 关闭UI
|
| | | CloseWindow(ui);
|
| | | }
|
| | | |
| | | /// <summary>
|
| | | /// 关闭指定的UI实例
|
| | | /// </summary>
|
| | | public void CloseWindow(UIBase ui)
|
| | | {
|
| | | // 检查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);
|
| | | }
|
| | | |
| | | // 从栈中移除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());
|
| | | }
|
| | |
|
| | | // 更新排序顺序
|
| | | UpdateUISortingOrder();
|
| | | }
|
| | | |
| | | #endregion
|
| | |
|
| | | #region 公共接口方法
|
| | | |
| | | // 打开单个UI窗口
|
| | | public T OpenWindow<T>() where T : UIBase
|
| | | {
|
| | | string uiName = typeof(T).Name;
|
| | | if (string.IsNullOrEmpty(uiName))
|
| | | // 从字典中移除UI
|
| | | if (uiDict.ContainsKey(uiName))
|
| | | {
|
| | | Debug.LogError("尝试打开空名称的UI窗口");
|
| | | return null;
|
| | | }
|
| | | |
| | | // 增加回合数
|
| | | currentRound++;
|
| | | |
| | | // 先尝试获取已加载的UI
|
| | | T ui = GetUI<T>();
|
| | | |
| | | // 如果UI不存在,则加载
|
| | | if (ui == null)
|
| | | {
|
| | | ui = LoadUIResource<T>(uiName);
|
| | | if (ui == null)
|
| | | return null;
|
| | | |
| | | // 将新加载的UI添加到字典中
|
| | | uiDict[uiName] = ui;
|
| | | }
|
| | | |
| | | // 更新UI的最后使用回合数
|
| | | ui.lastUsedRound = currentRound;
|
| | | |
| | | // 更新所有父级UI的回合数
|
| | | UpdateParentUIRounds(ui);
|
| | | |
| | | if (ui.uiLayer != UILayer.System && !ui.isMainUI)
|
| | | {
|
| | | if (uiStack.Contains(ui))
|
| | | // 从UI列表中移除
|
| | | uiDict[uiName].Remove(ui);
|
| | | // 如果列表为空,从字典中移除该类型
|
| | | if (uiDict[uiName].Count == 0)
|
| | | {
|
| | | // 在OpenWindow方法中修改关闭UI的逻辑
|
| | | // 找到UI在栈中的位置
|
| | | List<UIBase> uiList = new List<UIBase>(uiStack);
|
| | | int index = uiList.IndexOf(ui);
|
| | |
|
| | | // 关闭该UI之后的相关UI,但保留System层级的UI
|
| | | Stack<UIBase> tempStack = new Stack<UIBase>();
|
| | | while (uiStack.Count > index)
|
| | | {
|
| | | UIBase topUI = uiStack.Pop();
|
| | |
|
| | | // 如果是System层级的UI,保留它
|
| | | if (topUI.uiLayer == UILayer.System || topUI.isMainUI)
|
| | | {
|
| | | tempStack.Push(topUI);
|
| | | continue;
|
| | | }
|
| | |
|
| | | // 如果不是当前UI链上的UI,也保留它
|
| | | // 判断是否属于当前UI链的逻辑:检查是否有父子关系
|
| | | bool isInCurrentChain = false;
|
| | | UIBase parent = ui;
|
| | | while (parent != null)
|
| | | {
|
| | | if (parent == topUI || parent.childrenUI.Contains(topUI))
|
| | | {
|
| | | isInCurrentChain = true;
|
| | | break;
|
| | | }
|
| | | parent = parent.parentUI;
|
| | | }
|
| | |
|
| | | if (!isInCurrentChain)
|
| | | {
|
| | | tempStack.Push(topUI);
|
| | | continue;
|
| | | }
|
| | |
|
| | | // 关闭当前UI链上的非System层UI
|
| | | topUI.HandleClose();
|
| | |
|
| | | // 清理父子关系
|
| | | if (topUI.parentUI != null)
|
| | | {
|
| | | topUI.parentUI.RemoveChildUI(topUI);
|
| | | topUI.parentUI = null; // 清理自己对父UI的引用
|
| | | }
|
| | | topUI.ClearChildrenUI();
|
| | | }
|
| | |
|
| | | // 恢复保留的UI到栈中
|
| | | while (tempStack.Count > 0)
|
| | | {
|
| | | uiStack.Push(tempStack.Pop());
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | ui.HandleOpen();
|
| | | |
| | | // 如果栈中有其他UI,则将栈顶UI设为父UI
|
| | | if (!ui.isMainUI && ui.uiLayer != UILayer.System && uiStack.Count > 0)
|
| | | {
|
| | | UIBase topUI = uiStack.Peek();
|
| | | |
| | | if (topUI != null)
|
| | | {
|
| | | // System层级UI不应该成为其他UI的父UI,也不应该成为其他UI的子UI
|
| | | if (topUI.uiLayer != UILayer.System)
|
| | | {
|
| | | // 清理旧的父子关系
|
| | | if (ui.parentUI != null && ui.parentUI != topUI)
|
| | | {
|
| | | ui.parentUI.RemoveChildUI(ui);
|
| | | }
|
| | | |
| | | // 设置新的父子关系
|
| | | topUI.AddChildUI(ui);
|
| | | }
|
| | | uiDict.Remove(uiName);
|
| | | }
|
| | | }
|
| | |
|
| | | // 添加到UI栈
|
| | | uiStack.Push(ui);
|
| | | |
| | | // 更新排序顺序
|
| | | UpdateUISortingOrder();
|
| | | |
| | | // 在打开界面后检查UI生命周期
|
| | | CheckUILifecycle();
|
| | | |
| | | return ui;
|
| | | }
|
| | |
|
| | | // 按顺序打开多级UI窗口
|
| | | public UIBase OpenWindow(params string[] uiNames)
|
| | | {
|
| | | if (uiNames == null || uiNames.Length == 0)
|
| | | return null;
|
| | |
|
| | | UIBase ui = null;
|
| | |
|
| | | for (int i = 0; i < uiNames.Length; i++)
|
| | | // 从父级UI的子UI列表中移除
|
| | | if (ui.supportParentChildRelation && ui.parentUI != null && ui.parentUI.childrenUI != null)
|
| | | {
|
| | | string uiName = uiNames[i];
|
| | | ui = OpenWindow(uiName);
|
| | | }
|
| | |
|
| | | return ui;
|
| | | }
|
| | | |
| | | // 关闭指定UI窗口
|
| | | public void CloseWindow(UIBase ui)
|
| | | {
|
| | | if (ui == null)
|
| | | return;
|
| | | |
| | | // 创建一个列表来存储需要关闭的UI
|
| | | List<UIBase> uisToClose = new List<UIBase>
|
| | | {
|
| | | ui
|
| | | };
|
| | | |
| | | // 递归收集所有子UI
|
| | | CollectChildrenUI(ui, uisToClose);
|
| | | |
| | | // 更新父级UI的回合数
|
| | | if (ui.parentUI != null)
|
| | | {
|
| | | UpdateParentUIRounds(ui.parentUI);
|
| | | // 从父级UI的子UI列表中移除
|
| | | ui.parentUI.childrenUI.Remove(ui);
|
| | | }
|
| | |
|
| | | // 关闭所有收集到的UI
|
| | | foreach (var uiToClose in uisToClose)
|
| | | {
|
| | | // 关闭UI
|
| | | uiToClose.HandleClose();
|
| | | |
| | | // 清理父子关系
|
| | | if (uiToClose.parentUI != null)
|
| | | {
|
| | | uiToClose.parentUI.RemoveChildUI(uiToClose);
|
| | | uiToClose.parentUI = null;
|
| | | }
|
| | | |
| | | // 检查是否需要立即销毁
|
| | | // 如果是非持久化UI且超过了最大空闲回合数,立即销毁
|
| | | if (!uiToClose.isPersistent && (currentRound - uiToClose.lastUsedRound) > uiToClose.maxIdleRounds)
|
| | | {
|
| | | DestroyUI(uiToClose);
|
| | | }
|
| | | }
|
| | | |
| | | // 使用公共方法从UI栈中移除所有关闭的UI
|
| | | RemoveFromUIStack(uisToClose);
|
| | | }
|
| | | |
| | |
|
| | | // 返回上一级UI
|
| | | public void GoBack()
|
| | | {
|
| | | if (uiStack.Count <= 1)
|
| | | return;
|
| | | |
| | | // 关闭当前UI
|
| | | UIBase currentUI = uiStack.Pop();
|
| | |
|
| | | CloseWindow(currentUI);
|
| | | |
| | | // 更新排序顺序
|
| | | UpdateUISortingOrder();
|
| | | }
|
| | | |
| | | // 销毁指定UI对象
|
| | | public void DestroyUI(UIBase ui)
|
| | | {
|
| | | if (ui == null)
|
| | | return;
|
| | | |
| | | // 关闭UI
|
| | | ui.Destroy();
|
| | | |
| | | uiDict.Remove(ui.uiName);
|
| | | ui.HandleClose();
|
| | |
|
| | | // 使用公共方法从UI栈中移除UI
|
| | | RemoveFromUIStack(ui);
|
| | | // 销毁UI对象
|
| | | GameObject.Destroy(ui.gameObject);
|
| | | |
| | | // 更新UI排序顺序
|
| | | UpdateUISortingOrder();
|
| | | }
|
| | |
|
| | | // 销毁指定名称的UI
|
| | | public void DestroyUI(string uiName)
|
| | | |
| | | /// <summary>
|
| | | /// 关闭指定类型的所有UI实例
|
| | | /// </summary>
|
| | | public void CloseAllWindows<T>() where T : UIBase
|
| | | {
|
| | | if (string.IsNullOrEmpty(uiName))
|
| | | return;
|
| | |
|
| | | UIBase ui;
|
| | | if (uiDict.TryGetValue(uiName, out ui))
|
| | | // 获取UI类型名称
|
| | | string uiName = typeof(T).Name;
|
| | | |
| | | // 检查UI是否存在
|
| | | if (!uiDict.ContainsKey(uiName) || uiDict[uiName].Count == 0)
|
| | | {
|
| | | DestroyUI(ui);
|
| | | // 记录警告日志
|
| | | Debug.LogWarning($"尝试关闭不存在的UI: {uiName}");
|
| | | return;
|
| | | }
|
| | | |
| | | // 创建UI实例列表的副本,因为在关闭过程中会修改原列表
|
| | | List<UIBase> uiListCopy = new List<UIBase>(uiDict[uiName]);
|
| | | |
| | | // 关闭所有UI实例
|
| | | foreach (var ui in uiListCopy)
|
| | | {
|
| | | // 关闭UI实例
|
| | | CloseWindow(ui);
|
| | | }
|
| | | }
|
| | |
|
| | | // 销毁所有UI
|
| | | |
| | | /// <summary>
|
| | | /// 关闭所有UI
|
| | | /// </summary>
|
| | | public void DestroyAllUI()
|
| | | {
|
| | | List<string> uiNames = new List<string>(uiDict.Keys);
|
| | |
|
| | | foreach (var ui in uiNames)
|
| | | // 创建UI栈的副本,因为在关闭过程中会修改原栈
|
| | | UIBase[] uiArray = new UIBase[uiStack.Count];
|
| | | uiStack.CopyTo(uiArray, 0);
|
| | | |
| | | // 关闭所有UI
|
| | | foreach (var ui in uiArray)
|
| | | {
|
| | | if (ui != null)
|
| | | {
|
| | | DestroyUI(ui);
|
| | | }
|
| | | // 关闭UI
|
| | | CloseWindow(ui);
|
| | | }
|
| | |
|
| | | // 清空UI字典和栈
|
| | | uiDict.Clear();
|
| | | uiStack.Clear();
|
| | | }
|
| | |
|
| | | #endregion
|
| | |
|
| | | #region 生命周期管理
|
| | | |
| | | // 检查UI生命周期
|
| | | private void CheckUILifecycle()
|
| | | /// <summary>
|
| | | /// 刷新UI
|
| | | /// </summary>
|
| | | public void RefreshUI<T>() where T : UIBase
|
| | | {
|
| | | // 使用临时列表存储需要销毁的UI名称,避免在迭代过程中修改字典
|
| | | List<string> uiToDestroy = new List<string>();
|
| | | // 获取UI类型名称
|
| | | string uiName = typeof(T).Name;
|
| | |
|
| | | foreach (var pair in uiDict)
|
| | | // 检查UI是否存在
|
| | | if (!uiDict.ContainsKey(uiName) || uiDict[uiName].Count == 0)
|
| | | {
|
| | | if (!pair.Value.isPersistent && !uiStack.Contains(pair.Value) && |
| | | (currentRound - pair.Value.lastUsedRound) > pair.Value.maxIdleRounds)
|
| | | // 记录警告日志
|
| | | 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)
|
| | | {
|
| | | uiToDestroy.Add(pair.Key);
|
| | | // 刷新UI
|
| | | ui.Refresh();
|
| | | }
|
| | | }
|
| | | |
| | | foreach (var uiName in uiToDestroy)
|
| | | }
|
| | | |
| | | private void Update()
|
| | | {
|
| | | // 如果距离上次检查的时间超过了检查间隔
|
| | | if (Time.time - lastCheckTime > CHECK_INTERVAL)
|
| | | {
|
| | | DestroyUI(uiName);
|
| | | // 更新上次检查时间
|
| | | 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
|
| | | }
|