using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using DG.Tweening;
using Cysharp.Threading.Tasks;
using UnityEngine.EventSystems;
///
/// UI管理器 - 负责管理所有UI界面的显示、隐藏和层级
///
public class UIManager : ManagerBase
{
#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 systemTrans;
private Transform loadingTrans;
// UI字典,存储所有已加载的UI,键为UI名称,值为UI实例
#if UNITY_EDITOR
public
#else
private
#endif
Dictionary> uiDict = new Dictionary>();
// 存储关闭但未销毁的UI,键为UI名称,值为UI实例
private Dictionary> closedUIDict = new Dictionary>();
// UI栈,用于管理UI的显示顺序
private Stack uiStack = new Stack();
// 当前最高的排序顺序
private int currentHighestSortingOrder = 0;
// 排序缓存,避免每次UpdateUISortingOrder分配
private List _sortTempList = new List(32);
private Dictionary _sortOrderDict = new Dictionary(32);
// 当前回合数,用于记录UI的使用情况
private int currentRound = 0;
// 缓存层级对应的排序顺序
private static Dictionary layerSortingOrderCache = new Dictionary();
// 缓存层级对应的Transform
private Dictionary layerTransformCache = new Dictionary();
// UI实例计数器,用于为同类型UI生成唯一标识
private Dictionary uiInstanceCounter = new Dictionary();
// 上次检查时间
private float lastCheckTime = 0f;
// 检查间隔(秒)
private const float CHECK_INTERVAL = 5f;
public Action OnOpenWindow;
public Action OnCloseWindow;
public Action OnAfterSortWinLayer;
#endregion
#region 初始化
///
/// 初始化UI管理器
///
public override async UniTask Init()
{
await InitUIRoot();
// 初始化缓存
layerSortingOrderCache.Clear();
layerTransformCache.Clear();
uiInstanceCounter.Clear();
DOTween.SetTweensCapacity(500, 50);
Debug.Log("UI管理器初始化完成");
}
///
/// 初始化UI根节点
///
private async UniTask InitUIRoot()
{
// 查找UI根节点
GameObject root = GameObject.Find("UIRoot");
// 如果场景中没有UI根节点,则创建一个
if (root == null)
{
var prefab = await BuiltInLoader.LoadPrefabAsync("UIRoot");
root = GameObject.Instantiate(prefab);
root.name = "UIRoot";
if (root == null)
{
Debug.LogError("无法找到UI根节点");
return;
}
}
// 无论是从场景找到还是新实例化的,都确保跨场景不被销毁
GameObject.DontDestroyOnLoad(root);
uiRoot = root.transform;
uiRoot.position = Vector3.zero;
staticTrans = uiRoot.Find("Static");
bottomTrans = uiRoot.Find("Bottom");
midTrans = uiRoot.Find("Middle");
loadingTrans = uiRoot.Find("Loading");
systemTrans = uiRoot.Find("System");
layerTransformCache.Clear();
layerTransformCache.Add(UILayer.Static, staticTrans);
layerTransformCache.Add(UILayer.Bottom, bottomTrans);
layerTransformCache.Add(UILayer.Mid, midTrans);
layerTransformCache.Add(UILayer.System, systemTrans);
layerTransformCache.Add(UILayer.Loading, loadingTrans);
LogUIRootEventSystemState("InitUIRoot");
}
public Transform GetUIRoot()
{
return uiRoot;
}
#endregion
#region 辅助方法
// 获取UI层级对应的基础排序顺序
public static 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 * 100;
break;
case UILayer.Mid:
result = BASE_SORTING_ORDER * 1000;
break;
case UILayer.System:
result = BASE_SORTING_ORDER * 2000;
break;
case UILayer.Loading:
result = BASE_SORTING_ORDER * 3000;
break;
default:
result = BASE_SORTING_ORDER * 10;
break;
}
// 将结果存入缓存
layerSortingOrderCache[layer] = result;
return result;
}
// 获取层级对应的Transform
public 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.System:
result = systemTrans;
break;
case UILayer.Loading:
result = loadingTrans;
break;
default:
result = bottomTrans;
break;
}
// 将结果存入缓存
layerTransformCache[layer] = result;
return result;
}
// 获取UI实例,如果不存在则返回null
public T GetUI() 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 uiList) && uiList.Count > 0)
{
// 返回第一个实例
return uiList[0] as T;
}
// 如果不存在,返回null
return null;
}
public UIBase GetUI(string uiName)
{
if (string.IsNullOrEmpty(uiName))
{
// 记录错误日志
Debug.LogError("UI名称为空");
return null;
}
// 尝试从字典中获取UI实例列表
if (uiDict.TryGetValue(uiName, out List uiList) && uiList.Count > 0)
{
// 返回第一个实例
return uiList[0];
}
// 如果不存在,返回null
return null;
}
public List GetUIList() where T : UIBase
{
List uiList = new List();
// 获取UI类型名称
string uiName = typeof(T).Name;
if (string.IsNullOrEmpty(uiName))
{
// 记录错误日志
Debug.LogError("UI名称为空");
return uiList;
}
// 尝试从字典中获取UI实例列表
List 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() where T : UIBase
{
List uiList = GetUIList();
foreach (T ui in uiList)
{
if (ui.IsActive())
{
return true;
}
}
return false;
}
public bool IsOpened() where T : UIBase
{
T ui = GetUI();
if (null != ui)
{
return ui.IsActive();
}
return false;
}
public bool IsOpened(string uiName)
{
UIBase ui = GetUI(uiName);
if (null != ui)
{
return ui.IsActive();
}
return false;
}
// 检查是否存在任何全屏或遮罩窗口, 有screenMask 即可代表全屏
public bool ExistAnyFullScreenOrMaskWin(string excludeUIName)
{
var exist = false;
foreach (var uiList in uiDict.Values)
{
// 遍历该类型的所有UI实例
foreach (var ui in uiList)
{
// 刷新UI
if (ui.IsActive() && ui.name != excludeUIName)
{
if (ui.screenMask != null)
{
exist = true;
break;
}
}
}
}
return exist;
}
//在此界面上有没任何全屏或遮罩窗口
public bool ExistAnyFullScreenOrMaskWinAbove(string uiName)
{
var ui = GetUI(uiName);
if (ui == null || !ui.IsActive())
{
// 如果UI不存在或未打开,默认为有被挡住
return true;
}
foreach (var uiBase in uiStack)
{
if (uiBase == null)
{
continue;
}
if (uiBase.name == uiName)
{
break;
}
if (uiBase.IsActive() && uiBase.screenMask != null)
{
return true;
}
}
return false;
}
// 检查是否存在任何同层级sortingOrde大于我的窗口
public bool ExistAnySameLevelWinHigherSortingOrder(UILayer uiLayer, string excludeUIName)
{
int mySortingOrder = 0;
if (uiDict.TryGetValue(excludeUIName, out List excludeList) && excludeList.Count > 0)
{
foreach (var ui in excludeList)
{
if (ui.IsActive())
{
mySortingOrder = ui.GetSortingOrder();
break;
}
}
}
if (mySortingOrder == 0)
return false;
foreach (var uiList in uiDict.Values)
{
foreach (var ui in uiList)
{
if (!ui.IsActive())
continue;
if (ui.uiName == excludeUIName)
continue;
if (ui.uiLayer != uiLayer)
continue;
if (ui.GetSortingOrder() > mySortingOrder)
return true;
}
}
return false;
}
// 获取指定类型的所有UI实例
public List GetAllUI() where T : UIBase
{
// 获取UI类型名称
string uiName = typeof(T).Name;
if (string.IsNullOrEmpty(uiName))
{
// 记录错误日志
Debug.LogError("UI名称为空");
return new List();
}
// 尝试从字典中获取UI实例列表
if (uiDict.TryGetValue(uiName, out List uiList) && uiList.Count > 0)
{
// 将列表中的所有实例转换为指定类型并返回
return uiList.Cast().ToList();
}
// 如果不存在,返回空列表
return new List();
}
// 更新父级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 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);
}
}
}
///
/// 检查并关闭长时间未使用的UI
///
public void CheckAndCloseIdleUI()
{
// 如果没有UI,直接返回
if (uiDict.Count == 0 && closedUIDict.Count == 0)
return;
// 创建需要关闭的UI列表
List uiToClose = new List();
// 遍历所有活跃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 emptyKeys = new List();
foreach (var kvp in closedUIDict)
{
string uiName = kvp.Key;
List uiList = kvp.Value;
List uiToRemove = new List();
foreach (var ui in uiList)
{
// 计算UI空闲的回合数
int idleRounds = currentRound - ui.lastUsedRound;
// 如果空闲回合数超过最大空闲回合数,添加到关闭列表
// 2025 11/13 修复 持久化UI被销毁的问题
if (idleRounds > ui.maxIdleRounds && !ui.isPersistent)
{
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)
{
// 记录日志
#if UNITY_EDITOR
Debug.Log($"销毁长时间未使用的UI: {ui.uiName}, 空闲回合数: {currentRound - ui.lastUsedRound}");
#endif
// 销毁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;
// if (uiName == "LaunchWin" || uiName == "DownLoadWin" || uiName == "RequestSecretWin" || uiName == "GameAgeWarnWin")
// {
// prefab = await BuiltInLoader.LoadPrefabAsync(uiName);
// }
// else
// {
// prefab = await ResManager.Instance.LoadAssetAsync("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 async UniTask LoadUIResource(string uiName) where T : UIBase
{
return await LoadUIResourceAsync(uiName) as T;
}
// ====================================================================
// US2: Async variants — InitUIRootAsync, LoadUIResourceAsync, OpenWindowAsync
// ====================================================================
///
/// US2: 异步初始化 UI 根节点。
///
public async UniTask InitUIRootAsync()
{
GameObject root = GameObject.Find("UIRoot");
if (root == null)
{
var prefab = await BuiltInLoader.LoadPrefabAsync("UIRoot"); // 已异步,无需修改
root = GameObject.Instantiate(prefab);
root.name = "UIRoot";
if (root == null)
{
Debug.LogError("无法加载UI根节点");
return;
}
}
// 无论是从场景找到还是新实例化的,都确保跨场景不被销毁
GameObject.DontDestroyOnLoad(root);
}
///
/// US2: 异步加载 UI 资源。
///
private async UniTask LoadUIResourceAsync(string uiName)
{
GameObject prefab;
if (uiName == "LaunchWin" || uiName == "DownLoadWin" || uiName == "RequestSecretWin" || uiName == "GameAgeWarnWin")
{
prefab = await BuiltInLoader.LoadPrefabAsync(uiName);
}
else
{
prefab = await ResManager.Instance.LoadAssetAsync("UI", uiName, true);
}
if (prefab == null)
{
Debug.LogError($"加载UI预制体失败: {uiName}");
return null;
}
GameObject uiObject = GameObject.Instantiate(prefab);
uiObject.name = uiName;
Type uiType = Type.GetType(uiName);
if (uiType == null)
{
Debug.LogError($"找不到UI类型: {uiName}");
return null;
}
UIBase uiBase = uiObject.GetComponent(uiType) as UIBase;
if (uiBase == null)
{
Debug.LogError($"UI预制体 {uiName} 没有 UIBase 组件或类型不匹配");
return null;
}
uiBase.uiName = uiName;
Transform parentTrans = GetTransForLayer(uiBase.uiLayer);
uiObject.transform.SetParent(parentTrans, false);
int baseSortingOrder = GetBaseSortingOrderForLayer(uiBase.uiLayer);
uiBase.SetSortingOrder(baseSortingOrder);
return uiBase;
}
///
/// US2: 异步打开窗口。
///
public async UniTask OpenWindowAsync(string uiName, int functionOrder = 0)
{
UIBase returnValue = null;
UIBase parentUI = null;
// Check closed cache
if (closedUIDict.TryGetValue(uiName, out var closedUIList) && closedUIList.Count > 0)
{
returnValue = closedUIList[0] as UIBase;
closedUIList.RemoveAt(0);
if (closedUIList.Count == 0)
{
closedUIDict.Remove(uiName);
}
}
else
{
// US3: Show loading indicator while loading UI prefab (auto-hide after load)
ShowLoadingIndicator();
try
{
returnValue = await LoadUIResourceAsync(uiName);
}
finally
{
HideLoadingIndicator();
}
if (returnValue == null)
{
Debug.LogError($"打开UI失败: {uiName}");
return null;
}
}
returnValue.gameObject.SetActive(true);
if (returnValue.supportParentChildRelation && uiStack.Count > 0 && !returnValue.isMainUI)
{
parentUI = GetLastSupportParentChildRelationUI();
}
if (parentUI != null)
{
returnValue.parentUI = parentUI;
if (parentUI.childrenUI == null)
{
parentUI.childrenUI = new List();
}
parentUI.childrenUI.Add(returnValue);
}
currentRound++;
returnValue.lastUsedRound = currentRound;
UpdateParentUIRounds(returnValue);
if (!uiDict.ContainsKey(uiName))
{
uiDict[uiName] = new List();
}
uiDict[uiName].Add(returnValue);
uiStack.Push(returnValue);
UpdateUISortingOrder();
returnValue.functionOrder = functionOrder;
returnValue.HandleOpen();
OnOpenWindow?.Invoke(returnValue);
CheckAndCloseIdleUI();
return returnValue;
}
///
/// US2: 泛型 异步打开窗口。
///
public async UniTask OpenWindowAsync(int functionOrder = 0) where T : UIBase
{
if (typeof(T).Name == "MainWin")
{
//MainWin 比较关键且唯一,做安全防范
var ui = GetUI();
if (ui != null)
{
ui.ClickFunc(0);
return ui as T;
}
}
string uiName = typeof(T).Name;
var result = await OpenWindowAsync(uiName, functionOrder);
return result as T;
}
// ====================================================================
// US3: Loading indicator for async UI loading
// ====================================================================
private GameObject _loadingIndicatorGO;
private int _loadingRefCount;
///
/// US3: 显示加载指示器(引用计数,支持重入)。
///
public void ShowLoadingIndicator()
{
_loadingRefCount++;
if (_loadingRefCount == 1)
{
EnsureLoadingIndicator();
if (_loadingIndicatorGO != null)
{
_loadingIndicatorGO.SetActive(true);
}
}
}
///
/// US3: 隐藏加载指示器。
///
public void HideLoadingIndicator()
{
_loadingRefCount = Mathf.Max(0, _loadingRefCount - 1);
if (_loadingRefCount == 0 && _loadingIndicatorGO != null)
{
_loadingIndicatorGO.SetActive(false);
}
}
public void DumpUIDiagnostics(string context)
{
var eventSystem = EventSystem.current;
Debug.Log($"[UIManager][Diag] {context} EventSystem={(eventSystem != null ? eventSystem.name : "")} selected={(eventSystem != null && eventSystem.currentSelectedGameObject != null ? eventSystem.currentSelectedGameObject.name : "")} loadingIndicatorActive={(_loadingIndicatorGO != null && _loadingIndicatorGO.activeSelf)} loadingRefCount={_loadingRefCount} uiStackCount={uiStack.Count}");
LogUIRootEventSystemState(context);
var uiArray = new UIBase[uiStack.Count];
uiStack.CopyTo(uiArray, 0);
foreach (var ui in uiArray)
{
if (ui == null)
{
continue;
}
var uiCanvas = ui.GetComponent