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

199 lines
8.1 KiB
C#
Raw Normal View History

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