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