using System.Collections;
|
using System.Collections.Generic;
|
using UnityEngine;
|
using System;
|
|
public class UIManager : Singleton<UIManager>
|
{
|
// 单例实例
|
|
#region 变量定义
|
|
// UI根节点
|
private Transform uiRoot;
|
private Transform staticTrans;
|
private Transform bottomTrans;
|
private Transform midTrans;
|
private Transform topTrans;
|
private Transform systemTrans;
|
|
// 基础排序顺序
|
private const int BASE_SORTING_ORDER = 10;
|
private const int SORTING_ORDER_STEP = 10;
|
|
// 已加载的UI字典
|
private Dictionary<string, UIBase> uiDict = new Dictionary<string, UIBase>();
|
|
// 当前打开的UI栈
|
private Stack<UIBase> uiStack = new Stack<UIBase>();
|
|
// 当前最高的排序顺序
|
private int currentHighestSortingOrder = 0;
|
|
// 当前回合数
|
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>();
|
|
#endregion
|
|
// 初始化缓存
|
private void InitCache()
|
{
|
// 初始化层级Transform缓存
|
layerTransformCache.Clear();
|
layerTransformCache[UILayer.Static] = staticTrans;
|
layerTransformCache[UILayer.Bottom] = bottomTrans;
|
layerTransformCache[UILayer.Mid] = midTrans;
|
layerTransformCache[UILayer.Top] = topTrans;
|
layerTransformCache[UILayer.System] = systemTrans;
|
|
// 初始化层级排序顺序缓存
|
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()
|
{
|
InitUIRoot();
|
}
|
|
// 创建UI根节点
|
private void InitUIRoot()
|
{
|
GameObject root = GameObject.Find("UIRoot");
|
if (root == null)
|
{
|
root = GameObject.Instantiate(Resources.Load<GameObject>("UI/UIRoot"));
|
|
if (root == null)
|
{
|
Debug.LogError("无法找到UI根节点");
|
return;
|
}
|
}
|
|
uiRoot = root.transform;
|
uiRoot.position = Vector3.zero;
|
|
// 初始化各层级的Transform
|
staticTrans = uiRoot.Find("Static");
|
bottomTrans = uiRoot.Find("Bottom");
|
midTrans = uiRoot.Find("Mid");
|
topTrans = uiRoot.Find("Top");
|
systemTrans = uiRoot.Find("System");
|
|
// // 添加基础Canvas
|
// Canvas rootCanvas = root.AddComponent<Canvas>();
|
// rootCanvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
// rootCanvas.sortingOrder = 0;
|
|
// // 添加CanvasScaler
|
// UnityEngine.UI.CanvasScaler scaler = root.AddComponent<UnityEngine.UI.CanvasScaler>();
|
// scaler.uiScaleMode = UnityEngine.UI.CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
// scaler.referenceResolution = new Vector2(1920, 1080);
|
// scaler.screenMatchMode = UnityEngine.UI.CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
|
// scaler.matchWidthOrHeight = 1.0f;
|
|
// // 添加GraphicRaycaster
|
// root.AddComponent<UnityEngine.UI.GraphicRaycaster>();
|
}
|
|
#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)
|
{
|
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
|
{
|
string uiName = typeof(T).Name;
|
if (string.IsNullOrEmpty(uiName))
|
{
|
Debug.LogError("UI名称为空");
|
return null;
|
}
|
|
UIBase ui;
|
if (uiDict.TryGetValue(uiName, out ui))
|
{
|
return ui as T;
|
}
|
|
return null;
|
}
|
|
// 更新父级UI的回合数
|
private void UpdateParentUIRounds(UIBase ui)
|
{
|
if (ui == null)
|
return;
|
|
UIBase parentUI = ui.parentUI;
|
while (parentUI != null)
|
{
|
parentUI.lastUsedRound = currentRound;
|
parentUI = parentUI.parentUI;
|
}
|
}
|
|
// 递归收集所有子UI
|
private void CollectChildrenUI(UIBase ui, List<UIBase> result)
|
{
|
if (ui == null || ui.childrenUI == null || ui.childrenUI.Count == 0)
|
return;
|
|
foreach (var childUI in ui.childrenUI)
|
{
|
if (!result.Contains(childUI))
|
{
|
result.Add(childUI);
|
// 递归收集子UI的子UI
|
CollectChildrenUI(childUI, result);
|
}
|
}
|
}
|
|
#endregion
|
|
#region UI资源管理
|
|
// 加载UI预制体
|
private T LoadUIResource<T>(string uiName) where T : UIBase
|
|
{
|
GameObject prefab = ResManager.Instance.LoadUI(uiName);
|
|
if (prefab == null)
|
{
|
Debug.LogError($"加载UI预制体失败: {uiName}");
|
return null;
|
}
|
|
GameObject uiObject = GameObject.Instantiate(prefab);
|
uiObject.name = uiName;
|
T uiBase = uiObject.GetComponent<T>();
|
|
if (uiBase == null)
|
{
|
Debug.LogError($"UI预制体 {uiName} 没有 UIBase 组件");
|
return null;
|
}
|
|
uiBase.uiName = uiName;
|
|
// 设置父节点为UI根节点
|
uiObject.transform.SetParent(GetTransForLayer(uiBase.uiLayer), false);
|
|
|
// 设置排序顺序
|
int baseSortingOrder = GetBaseSortingOrderForLayer(uiBase.uiLayer);
|
uiBase.SetSortingOrder(baseSortingOrder);
|
|
return uiBase;
|
}
|
|
#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) => {
|
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);
|
});
|
|
for (int i = 0; i < uiArray.Length; i++)
|
{
|
UIBase ui = uiArray[i];
|
int baseSortingOrder = GetBaseSortingOrderForLayer(ui.uiLayer);
|
int sortingOrder = baseSortingOrder + i * SORTING_ORDER_STEP;
|
|
ui.SetSortingOrder(sortingOrder);
|
|
if (sortingOrder > currentHighestSortingOrder)
|
{
|
currentHighestSortingOrder = sortingOrder;
|
}
|
}
|
}
|
|
#endregion
|
|
#region UI栈管理
|
|
// 从UI栈中移除指定UI
|
private void RemoveFromUIStack(UIBase ui)
|
{
|
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)
|
{
|
UIBase topUI = uiStack.Pop();
|
if (!uisToRemove.Contains(topUI))
|
{
|
tempStack.Push(topUI);
|
}
|
}
|
|
// 恢复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))
|
{
|
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))
|
{
|
// 在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);
|
}
|
}
|
}
|
|
// 添加到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++)
|
{
|
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
|
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栈中移除UI
|
RemoveFromUIStack(ui);
|
}
|
|
// 销毁指定名称的UI
|
public void DestroyUI(string uiName)
|
{
|
if (string.IsNullOrEmpty(uiName))
|
return;
|
|
UIBase ui;
|
if (uiDict.TryGetValue(uiName, out ui))
|
{
|
DestroyUI(ui);
|
}
|
}
|
|
// 销毁所有UI
|
public void DestroyAllUI()
|
{
|
List<string> uiNames = new List<string>(uiDict.Keys);
|
|
foreach (var ui in uiNames)
|
{
|
if (ui != null)
|
{
|
DestroyUI(ui);
|
}
|
}
|
|
uiDict.Clear();
|
uiStack.Clear();
|
}
|
|
#endregion
|
|
#region 生命周期管理
|
|
// 检查UI生命周期
|
private void CheckUILifecycle()
|
{
|
// 使用临时列表存储需要销毁的UI名称,避免在迭代过程中修改字典
|
List<string> uiToDestroy = new List<string>();
|
|
foreach (var pair in uiDict)
|
{
|
if (!pair.Value.isPersistent && !uiStack.Contains(pair.Value) &&
|
(currentRound - pair.Value.lastUsedRound) > pair.Value.maxIdleRounds)
|
{
|
uiToDestroy.Add(pair.Key);
|
}
|
}
|
|
foreach (var uiName in uiToDestroy)
|
{
|
DestroyUI(uiName);
|
}
|
}
|
|
#endregion
|
}
|