411 lines
13 KiB
C#
411 lines
13 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
|
||
/// <summary>
|
||
/// UI Canvas 层级管理器
|
||
/// 负责管理 UI 窗口的层级关系、排序和可见性控制
|
||
/// </summary>
|
||
public class UICanvasLayerManager
|
||
{
|
||
#region Singleton
|
||
|
||
private static UICanvasLayerManager _instance;
|
||
public static UICanvasLayerManager Instance
|
||
{
|
||
get
|
||
{
|
||
if (_instance == null)
|
||
{
|
||
_instance = new UICanvasLayerManager();
|
||
}
|
||
return _instance;
|
||
}
|
||
}
|
||
|
||
private UICanvasLayerManager() { }
|
||
|
||
#endregion
|
||
|
||
#region Fields
|
||
|
||
/// <summary>
|
||
/// UICanvasLayer 到 sortingOrder 的映射
|
||
/// </summary>
|
||
private static readonly Dictionary<UICanvasLayer, int> LayerToSortingOrderMap = new Dictionary<UICanvasLayer, int>
|
||
{
|
||
{ UICanvasLayer.BackGround, 100 },
|
||
{ UICanvasLayer.Normal, 200 },
|
||
{ UICanvasLayer.ForeGround, 300 },
|
||
{ UICanvasLayer.Tips, 400 },
|
||
{ UICanvasLayer.Guide, 500 },
|
||
{ UICanvasLayer.System, 600 },
|
||
};
|
||
|
||
/// <summary>
|
||
/// UI层级根节点字典
|
||
/// </summary>
|
||
private Dictionary<UICanvasLayer, GameObject> _rootDic = new Dictionary<UICanvasLayer, GameObject>(6);
|
||
|
||
/// <summary>
|
||
/// 每个层级的窗口列表
|
||
/// </summary>
|
||
private Dictionary<int, LinkedList<UIWindow>> _layerWindowLists = new Dictionary<int, LinkedList<UIWindow>>(16);
|
||
|
||
#endregion
|
||
|
||
#region Initialization & Lifecycle
|
||
|
||
/// <summary>
|
||
/// 从Transform自动收集层级根节点并初始化
|
||
/// </summary>
|
||
/// <param name="uiRootTransform">UIRoot的Transform,包含各层级子节点</param>
|
||
public void InitializeFromTransform(Transform uiRootTransform)
|
||
{
|
||
if (uiRootTransform == null)
|
||
{
|
||
DebugUtil.LogError("[UICanvasLayerManager] InitializeFromTransform failed: uiRootTransform is null");
|
||
return;
|
||
}
|
||
|
||
_rootDic.Clear();
|
||
|
||
// 查找并注册各层级根节点
|
||
TryAddLayerRoot(uiRootTransform, "UIBackGround", UICanvasLayer.BackGround);
|
||
TryAddLayerRoot(uiRootTransform, "UINormal", UICanvasLayer.Normal);
|
||
TryAddLayerRoot(uiRootTransform, "UIForeGround", UICanvasLayer.ForeGround);
|
||
TryAddLayerRoot(uiRootTransform, "UITips", UICanvasLayer.Tips);
|
||
TryAddLayerRoot(uiRootTransform, "UIGuide", UICanvasLayer.Guide);
|
||
TryAddLayerRoot(uiRootTransform, "UISystem", UICanvasLayer.System);
|
||
|
||
// 初始化Canvas的sortingOrder
|
||
InitializeCanvasSorting();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清理所有数据
|
||
/// </summary>
|
||
public void Clear()
|
||
{
|
||
_rootDic.Clear();
|
||
_layerWindowLists.Clear();
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Window Registration
|
||
|
||
/// <summary>
|
||
/// 注册窗口到层级管理(统一入口)
|
||
///
|
||
/// 使用场景:
|
||
/// - 窗口创建后注册:RegisterWindow(window) 或 RegisterWindow(window, layer)
|
||
/// - 运行时改变层级:RegisterWindow(window, newLayer)
|
||
///
|
||
/// 特性:
|
||
/// - 幂等性:可重复调用,自动处理层级变更
|
||
/// - 自动去重:同一窗口在同一层级只会存在一次
|
||
/// - 数据同步:自动更新 window.uiWindowLayer 和 Transform.parent
|
||
/// </summary>
|
||
/// <param name="window">窗口实例</param>
|
||
/// <param name="layer">目标层级(null则使用window.uiWindowLayer)</param>
|
||
public void RegisterWindow(UIWindow window, UICanvasLayer? layer = null)
|
||
{
|
||
if (window == null) return;
|
||
|
||
var targetLayer = layer ?? window.uiWindowLayer;
|
||
var oldLayer = window.uiWindowLayer;
|
||
|
||
// 如果层级改变,先从旧层级移除
|
||
if (oldLayer != targetLayer)
|
||
{
|
||
RemoveFromLayerList(oldLayer, window);
|
||
|
||
// 更新窗口数据
|
||
window.uiWindowLayer = targetLayer;
|
||
SetObjectLayer(window.gameObject, targetLayer);
|
||
|
||
#if UNITY_EDITOR || DEBUG || DEVELOPMENT_BUILD
|
||
DebugUtil.Log($"[UICanvasLayerManager] RegisterWindow: Moved '{window.WindowName}' from {oldLayer} to {targetLayer}");
|
||
#endif
|
||
}
|
||
else
|
||
{
|
||
// 层级未变,验证现有设置是否正确
|
||
ValidateWindowPlacement(window);
|
||
}
|
||
|
||
// 添加到目标层级列表
|
||
AddToLayerList(targetLayer, window);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从层级管理中注销窗口(窗口销毁前调用)
|
||
/// 带容错机制:如果在预期层级找不到,会搜索所有层级
|
||
/// </summary>
|
||
public void UnregisterWindow(UIWindow window)
|
||
{
|
||
if (window == null) return;
|
||
|
||
// 尝试从预期层级移除
|
||
if (!RemoveFromLayerList(window.uiWindowLayer, window))
|
||
{
|
||
// 容错:搜索所有层级
|
||
FallbackRemoveFromAllLayers(window);
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Query Methods
|
||
|
||
/// <summary>
|
||
/// 获取指定层级的所有窗口
|
||
/// </summary>
|
||
public LinkedList<UIWindow> GetWindowsInLayer(UICanvasLayer layer)
|
||
{
|
||
if (_layerWindowLists.TryGetValue((int)layer, out var windowList))
|
||
{
|
||
return windowList;
|
||
}
|
||
return new LinkedList<UIWindow>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取指定层级的最上层窗口
|
||
/// </summary>
|
||
public UIWindow GetTopWindowInLayer(UICanvasLayer layer)
|
||
{
|
||
if (_layerWindowLists.TryGetValue((int)layer, out var windowList) && windowList.Count > 0)
|
||
{
|
||
return windowList.Last.Value;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取所有层级的顶部窗口(从高到低遍历)
|
||
/// </summary>
|
||
/// <param name="window">输出:顶部窗口</param>
|
||
/// <param name="layer">输出:窗口所在层级</param>
|
||
/// <param name="banLayerMask">排除的层级掩码</param>
|
||
/// <returns>是否找到窗口</returns>
|
||
public bool GetTopWindow(out UIWindow window, out UICanvasLayer layer, int banLayerMask = 0)
|
||
{
|
||
for (int i = (int)UICanvasLayer.Max - 1; i >= 0; i--)
|
||
{
|
||
if (((1 << i) & banLayerMask) == 0)
|
||
{
|
||
if (_layerWindowLists.TryGetValue(i, out var windowList) && windowList.Count > 0)
|
||
{
|
||
window = windowList.Last.Value;
|
||
if (window != null)
|
||
{
|
||
layer = (UICanvasLayer)i;
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
window = null;
|
||
layer = UICanvasLayer.Max;
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取所有可见窗口列表(从最上层到最下层)
|
||
/// </summary>
|
||
public List<UIWindow> GetAllVisibleWindows()
|
||
{
|
||
var visibleWindows = new List<UIWindow>();
|
||
|
||
// 从最高层级到最低层级遍历
|
||
for (int i = (int)UICanvasLayer.Max - 1; i >= 0; i--)
|
||
{
|
||
if (_layerWindowLists.TryGetValue(i, out var windowList))
|
||
{
|
||
// 从链表尾到头(最上层到最下层)遍历该层级的窗口
|
||
var node = windowList.Last;
|
||
while (node != null)
|
||
{
|
||
var window = node.Value;
|
||
if (window != null &&
|
||
!window.IsClosing &&
|
||
window.gameObject != null &&
|
||
window.IsShow())
|
||
{
|
||
visibleWindows.Add(window);
|
||
}
|
||
node = node.Previous;
|
||
}
|
||
}
|
||
}
|
||
|
||
return visibleWindows;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Layer Operations
|
||
|
||
/// <summary>
|
||
/// 获取指定层级的根节点
|
||
/// </summary>
|
||
public GameObject GetLayerRoot(UICanvasLayer layer)
|
||
{
|
||
return _rootDic.TryGetValue(layer, out var root) ? root : null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置UI对象的层级(用于非窗口UI元素,如血条、行动条等动态UI元素)
|
||
/// </summary>
|
||
public void SetObjectLayer(GameObject go, UICanvasLayer layer)
|
||
{
|
||
if (go == null)
|
||
{
|
||
DebugUtil.LogWarning("[UICanvasLayerManager] SetObjectLayer: GameObject is null");
|
||
return;
|
||
}
|
||
|
||
if (!_rootDic.TryGetValue(layer, out var layerRoot))
|
||
{
|
||
DebugUtil.LogWarning($"[UICanvasLayerManager] SetObjectLayer: Cannot find root for layer {layer}");
|
||
return;
|
||
}
|
||
|
||
go.transform.SetParent(layerRoot.transform, false);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Utilities
|
||
|
||
/// <summary>
|
||
/// 获取层级掩码
|
||
/// </summary>
|
||
public int GetLayerMask(UICanvasLayer layer)
|
||
{
|
||
return 1 << ((int)layer);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Internal Helpers
|
||
|
||
/// <summary>
|
||
/// 尝试添加层级根节点
|
||
/// </summary>
|
||
private void TryAddLayerRoot(Transform parent, string childName, UICanvasLayer layer)
|
||
{
|
||
var child = parent.Find(childName);
|
||
if (child != null)
|
||
{
|
||
_rootDic.Add(layer, child.gameObject);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化Canvas的sortingOrder
|
||
/// </summary>
|
||
private void InitializeCanvasSorting()
|
||
{
|
||
foreach (var kv in LayerToSortingOrderMap)
|
||
{
|
||
if (_rootDic.TryGetValue(kv.Key, out var rootGo))
|
||
{
|
||
var canvas = rootGo.GetComponent<Canvas>();
|
||
if (canvas != null)
|
||
{
|
||
canvas.overrideSorting = true;
|
||
canvas.sortingOrder = kv.Value;
|
||
|
||
DebugUtil.Log($"[UICanvasLayerManager] Set {rootGo.name} sortingOrder = {canvas.sortingOrder}");
|
||
}
|
||
else
|
||
{
|
||
DebugUtil.LogWarning($"[UICanvasLayerManager] {rootGo.name} has no Canvas component");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
DebugUtil.LogWarning($"[UICanvasLayerManager] Cannot find root for layer {kv.Key}");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加窗口到指定层级列表
|
||
/// </summary>
|
||
private void AddToLayerList(UICanvasLayer layer, UIWindow window)
|
||
{
|
||
var layerInt = (int)layer;
|
||
|
||
// 获取或创建窗口列表
|
||
if (!_layerWindowLists.TryGetValue(layerInt, out var windowList))
|
||
{
|
||
windowList = new LinkedList<UIWindow>();
|
||
_layerWindowLists.Add(layerInt, windowList);
|
||
}
|
||
|
||
// 防御性去重(避免重复添加)
|
||
windowList.Remove(window);
|
||
|
||
// 添加到列表末尾(最上层)
|
||
windowList.AddLast(window);
|
||
|
||
// 设置渲染顺序(在同层级内最上层)
|
||
window.transform.SetAsLastSibling();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从指定层级列表移除窗口
|
||
/// </summary>
|
||
/// <returns>是否成功移除</returns>
|
||
private bool RemoveFromLayerList(UICanvasLayer layer, UIWindow window)
|
||
{
|
||
if (_layerWindowLists.TryGetValue((int)layer, out var windowList))
|
||
{
|
||
return windowList.Remove(window);
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证窗口位置是否正确(调试模式)
|
||
/// </summary>
|
||
private void ValidateWindowPlacement(UIWindow window)
|
||
{
|
||
#if UNITY_EDITOR || DEBUG || DEVELOPMENT_BUILD
|
||
var expectedRoot = GetLayerRoot(window.uiWindowLayer);
|
||
if (expectedRoot != null && window.transform.parent != expectedRoot.transform)
|
||
{
|
||
DebugUtil.LogWarning($"[UICanvasLayerManager] RegisterWindow: '{window.WindowName}' parent mismatch! " +
|
||
$"Expected {window.uiWindowLayer} ({expectedRoot.name}) but is {window.transform.parent?.name}");
|
||
}
|
||
#endif
|
||
}
|
||
|
||
/// <summary>
|
||
/// 容错:从所有层级搜索并移除窗口
|
||
/// </summary>
|
||
private void FallbackRemoveFromAllLayers(UIWindow window)
|
||
{
|
||
#if UNITY_EDITOR || DEBUG || DEVELOPMENT_BUILD
|
||
DebugUtil.LogWarning($"[UICanvasLayerManager] UnregisterWindow: '{window.WindowName}' not found in layer {window.uiWindowLayer}, searching all layers...");
|
||
#endif
|
||
|
||
foreach (var kv in _layerWindowLists)
|
||
{
|
||
if (kv.Value.Remove(window))
|
||
{
|
||
#if UNITY_EDITOR || DEBUG || DEVELOPMENT_BUILD
|
||
DebugUtil.LogWarning($"[UICanvasLayerManager] Found and removed '{window.WindowName}' from layer {(UICanvasLayer)kv.Key}");
|
||
#endif
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
}
|