using System.Collections.Generic; /// /// UI窗口可见性管理器(纯性能优化模块) /// /// 核心理念: /// - 只控制 GameObject.SetActive(),仅影响渲染层面 /// - 不触发 UIWindow 生命周期(不调用 CloseWindow/ShowWindow) /// - 不改变窗口逻辑状态(_isShow 保持不变) /// /// 优化目标: /// - 当全屏窗口打开时,自动隐藏被遮挡的下层窗口,减少 Overdraw /// - 当全屏窗口关闭时,自动恢复被遮挡的窗口渲染 /// - 节省 GPU 绘制开销和 CPU UI 遍历开销 /// public class UIWindowVisibilityManager { #region Singleton private static UIWindowVisibilityManager _instance; public static UIWindowVisibilityManager Instance { get { if (_instance == null) { _instance = new UIWindowVisibilityManager(); } return _instance; } } private UIWindowVisibilityManager() { } #endregion #region Fields /// /// 复用的窗口列表,避免 GC /// private readonly List _tempWindowList = new List(32); #endregion #region Visibility Operations /// /// 处理窗口打开时的渲染优化 /// HideBelow 策略:隐藏所有被遮挡的下层窗口 /// /// 性能优化:只控制 GameObject.SetActive(false),不影响窗口逻辑状态 /// /// 新打开的窗口 /// 可见性策略 public void HandleVisibilityOnOpen(UIWindow newWindow, UIVisibilityStrategy visibilityStrategy) { if (newWindow == null || visibilityStrategy == UIVisibilityStrategy.None) return; // HideBelow 策略:基于渲染层级隐藏 if (visibilityStrategy == UIVisibilityStrategy.Occlusion) { var newWindowLayer = newWindow.uiWindowLayer; var newWindowSiblingIndex = newWindow.transform.GetSiblingIndex(); var layerManager = UICanvasLayerManager.Instance; // 从底层到当前层,隐藏所有应该被遮挡的窗口(只控制 GameObject.active) for (int layer = 0; layer <= (int)newWindowLayer; layer++) { var windowList = layerManager.GetWindowsInLayer((UICanvasLayer)layer); foreach (var window in windowList) { // 只隐藏逻辑上显示的窗口(预加载窗口不参与遮挡管理) if (window != null && window != newWindow && window.IsShow() && window.gameObject.activeSelf) { bool shouldHide = false; if (layer < (int)newWindowLayer) { // 更低层:全部隐藏 shouldHide = true; } else // layer == (int)newWindowLayer { // 同层:只隐藏 SiblingIndex 更小(在下面)的窗口 var windowSiblingIndex = window.transform.GetSiblingIndex(); shouldHide = windowSiblingIndex < newWindowSiblingIndex; } if (shouldHide) { window.gameObject.SetActive(false); } } } } } } /// /// 处理窗口关闭时的渲染恢复 /// HideBelow 策略:恢复被遮挡窗口的渲染,直到遇到另一个 HideBelow 窗口 /// /// 性能优化:只控制 GameObject.SetActive(true),不影响窗口逻辑状态 /// /// 正在关闭的窗口 /// 可见性策略 public void HandleVisibilityOnClose(UIWindow closingWindow, UIVisibilityStrategy visibilityStrategy) { if (closingWindow == null || visibilityStrategy == UIVisibilityStrategy.None) return; // HideBelow 策略:基于渲染层级显示 if (visibilityStrategy == UIVisibilityStrategy.Occlusion) { var closingWindowLayer = closingWindow.uiWindowLayer; var closingWindowSiblingIndex = closingWindow.transform.GetSiblingIndex(); var layerManager = UICanvasLayerManager.Instance; // 从当前层开始,向下遍历所有层级,直到遇到 HideBelow 窗口 bool foundBlocker = false; for (int layer = (int)closingWindowLayer; layer >= 0; layer--) { var windowList = layerManager.GetWindowsInLayer((UICanvasLayer)layer); // 清空并复用列表,避免 GC _tempWindowList.Clear(); // 收集需要检查的窗口(只收集逻辑上显示的窗口) foreach (var window in windowList) { // ✅ 预加载的窗口(IsShow=false)不参与遮挡管理 if (window != null && window != closingWindow && window.IsShow()) { // 同层:只检查在关闭窗口下面的窗口 if (layer == (int)closingWindowLayer) { var windowSiblingIndex = window.transform.GetSiblingIndex(); if (windowSiblingIndex < closingWindowSiblingIndex) { _tempWindowList.Add(window); } } // 其他层:收集所有逻辑上显示的窗口 else { _tempWindowList.Add(window); } } } // 按 SiblingIndex 倒序排序(从上往下检查) _tempWindowList.Sort((a, b) => b.transform.GetSiblingIndex().CompareTo(a.transform.GetSiblingIndex())); // 遍历并处理窗口 foreach (var window in _tempWindowList) { var config = UIManager.Instance.GetWindowInfo(window.WindowName); // 显示被隐藏的窗口(只控制 GameObject.active) if (!window.gameObject.activeSelf) { // // 🎯 如果窗口需要截图,在恢复前重新截图 // if (config.NeedScreenshot) // { // ScreenshotManager.Instance.CaptureScreenshot(); // // // 🎯 刷新模糊背景 texture // RefreshBlurBackground(window); // } // else // { // ScreenshotManager.Instance.ResetScreenshot(); // } window.gameObject.SetActive(true); } // 如果遇到另一个 HideBelow 窗口,停止继续向下恢复 // (因为这个窗口会负责隐藏它下面的窗口) if (config.VisibilityStrategy == UIVisibilityStrategy.Occlusion) { foundBlocker = true; break; } } if (foundBlocker) { break; } } } } #endregion }