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

199 lines
8.1 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;
/// <summary>
/// UI窗口可见性与优化管理器
///
/// 核心功能:
/// 1. 遮挡判断:基于层级和 SiblingIndex 自动判断遮挡关系
/// 2. 渲染优化:被遮挡窗口 SetActive(false),减少 Overdraw
/// 3. 逻辑优化:被遮挡窗口 Pause(),停止音乐/动画/Update
///
/// 设计理念:
/// - 被遮挡 = 不渲染 + 逻辑暂停(通过 PauseWindow 实现)
/// - 显示 = 渲染 + 逻辑恢复(通过 ResumeWindow 实现)
/// - 不触发 UIWindow 生命周期(不调用 CloseWindow/ShowWindow
/// - 不改变窗口逻辑状态_isShow 保持不变)
///
/// 优化效果:
/// - 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>
/// 处理窗口打开时的优化(遮挡优化 + 逻辑暂停)
/// Occlusion 策略:暂停所有被遮挡的下层窗口
///
/// 优化效果:
/// - 渲染优化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;
// 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})");
}
}
}
}
}
}
/// <summary>
/// 处理窗口关闭时的恢复(显示渲染 + 恢复逻辑)
/// Occlusion 策略:恢复被遮挡窗口的渲染和逻辑,直到遇到另一个遮挡窗口
///
/// 恢复效果:
/// - 渲染恢复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;
// 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
}