using System.Collections.Generic; /// /// UI窗口可见性与优化管理器 /// /// 核心功能: /// 1. 遮挡判断:基于层级和 SiblingIndex 自动判断遮挡关系 /// 2. 渲染优化:被遮挡窗口 SetActive(false),减少 Overdraw /// 3. 逻辑优化:被遮挡窗口 Pause(),停止音乐/动画/Update /// /// 设计理念: /// - 被遮挡 = 不渲染 + 逻辑暂停(通过 PauseWindow 实现) /// - 显示 = 渲染 + 逻辑恢复(通过 ResumeWindow 实现) /// - 不触发 UIWindow 生命周期(不调用 CloseWindow/ShowWindow) /// - 不改变窗口逻辑状态(_isShow 保持不变) /// /// 优化效果: /// - GPU 优化:减少 Overdraw,节省绘制开销 /// - CPU 优化:暂停窗口不执行 Update,节省 CPU 开销 /// - 音效优化:自动停止被遮挡窗口的音乐播放 /// /// 注意: /// - 名称中的 "Visibility" 是历史遗留,实际包含渲染和逻辑管理 /// 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 /// /// 处理窗口打开时的优化(遮挡优化 + 逻辑暂停) /// Occlusion 策略:暂停所有被遮挡的下层窗口 /// /// 优化效果: /// - 渲染优化:SetActive(false) 停止绘制 /// - 逻辑优化:Pause() 停止音乐/动画/Update /// /// 新打开的窗口 /// 可见性策略 public void HandleVisibilityOnOpen(UIWindow newWindow, UIVisibilityStrategy visibilityStrategy) { if (newWindow == null || visibilityStrategy == UIVisibilityStrategy.None) return; // Occlusion 策略:基于渲染层级暂停被遮挡窗口 if (visibilityStrategy == UIVisibilityStrategy.Occlusion) { var newWindowLayer = newWindow.uiWindowLayer; var newWindowSiblingIndex = newWindow.transform.GetSiblingIndex(); var layerManager = UICanvasLayerManager.Instance; // 从底层到当前层,暂停所有应该被遮挡的窗口 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 shouldPause = false; if (layer < (int)newWindowLayer) { // 更低层:全部暂停 shouldPause = true; } else // layer == (int)newWindowLayer { // 同层:只暂停 SiblingIndex 更小(在下面)的窗口 var windowSiblingIndex = window.transform.GetSiblingIndex(); shouldPause = windowSiblingIndex < newWindowSiblingIndex; } if (shouldPause) { // 暂停窗口:停止逻辑 + 隐藏渲染 window.PauseWindow(hideRendering: true); DebugUtil.Log($"[VisibilityMgr] Paused window: {window.WindowName} (covered by {newWindow.WindowName})"); } } } } } } /// /// 处理窗口关闭时的恢复(显示渲染 + 恢复逻辑) /// Occlusion 策略:恢复被遮挡窗口的渲染和逻辑,直到遇到另一个遮挡窗口 /// /// 恢复效果: /// - 渲染恢复:SetActive(true) 开始绘制 /// - 逻辑恢复:Resume() 恢复音乐/动画/Update /// /// 正在关闭的窗口 /// 可见性策略 public void HandleVisibilityOnClose(UIWindow closingWindow, UIVisibilityStrategy visibilityStrategy) { if (closingWindow == null || visibilityStrategy == UIVisibilityStrategy.None) return; // Occlusion 策略:基于渲染层级恢复被暂停的窗口 if (visibilityStrategy == UIVisibilityStrategy.Occlusion) { var closingWindowLayer = closingWindow.uiWindowLayer; var closingWindowSiblingIndex = closingWindow.transform.GetSiblingIndex(); var layerManager = UICanvasLayerManager.Instance; // 从当前层开始,向下遍历所有层级,直到遇到另一个遮挡窗口 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); // 恢复被暂停的窗口 if (!window.gameObject.activeSelf) { // 恢复窗口:恢复逻辑 + 显示渲染 window.ResumeWindow(showRendering: true); DebugUtil.Log($"[VisibilityMgr] Resumed window: {window.WindowName}"); } // 如果遇到另一个遮挡窗口,停止继续向下恢复 // (因为这个窗口会负责管理它下面的窗口) if (config.VisibilityStrategy == UIVisibilityStrategy.Occlusion) { foundBlocker = true; break; } } if (foundBlocker) { break; } } } } #endregion }