NLDClient-yudde/ProjectNLD/Assets/Code/Scripts/Framework/UI/UICanvasLayerManager.cs

411 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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
}