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

652 lines
19 KiB
C#
Raw Normal View History

2026-01-09 18:20:13 +08:00
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Cysharp.Threading.Tasks;
using Framework;
using Obfuz;
using PhxhSDK;
using PhxhSDK.AOT.Aspect;
using UnityEngine;
using Object = UnityEngine.Object;
[ObfuzIgnore]
public partial class UIManagerBase: IInitable, IUpdatable
{
public const string UI_PREFAB_PATH = "Assets/Art_Out/UI/Prefab/{0}.prefab";
protected Dictionary<string, UIWindow> mMemoryWindows = new Dictionary<string, UIWindow>();
// ✅ 使用独立的层级管理器
protected UICanvasLayerManager LayerManager => UICanvasLayerManager.Instance;
/// <summary>
/// 窗口工厂(负责创建窗口控制器实例)
/// </summary>
protected IUIWindowFactory _windowFactory;
protected ScreenOrientation _currentOrientation;
protected Dictionary<string, string> _openingWindows = new Dictionary<string, string>();
protected UIWindow _curFullScreen;
protected UIWindowGroup _tempHideGroup;
protected Dictionary<GameObject, UIWindow> _uiWindowObjDic = new Dictionary<GameObject, UIWindow>();
public Dictionary<GameObject, UIWindow> UIWindowObjDic => _uiWindowObjDic;
public Vector2 Size;
/// <summary>
/// UI和主相机屏幕缩放
/// </summary>
public Vector2 UIAndMainScreenScale;
/// <summary>
/// UI和UI屏幕缩放
/// </summary>
public Vector2 UIAndUIScreenScale;
/// <summary>
/// 禁用返回键计数
/// </summary>
protected int disableBackCount;
/// <summary>
/// 是否响应返回键
/// </summary>
public bool IsEnableBack
{
get { return disableBackCount <= 0; }
set
{
if (value)
{
if (disableBackCount > 0)
--disableBackCount;
}
else
++disableBackCount;
}
}
public void Init()
{
// ✅ 设置默认工厂(项目特定实现)
_windowFactory = new UIWindowReflectionFactory();
// ✅ 初始化截图管理器
ScreenshotManager.Instance.Initialize(new ScreenshotConfig
{
BlurMaterialPath = Framework.Constants.BLUR_MAT_PATH,
EnableBlur = true,
ResolutionScale = 0.5f
});
// 初始化层级管理器(从当前transform收集层级根节点)
LayerManager.InitializeFromTransform(UIRoot.Instance.transform);
IsEnableBack = true;
_AllWindowMeta();
if (DeviceHelper.IsFullScreenIOS())
{
SetFullScreenIOS();
}
EventManager.Instance.Register(EventManager.EventName.Android_Click_Back, _OnAndroidClickBack);
OnScreenSizeChange();
AspectManager.Instance.ScreenSizeChange += OnScreenSizeChange;
}
/// <summary>
/// 设置窗口工厂(可选,用于测试或替换工厂)
/// </summary>
public void SetWindowFactory(IUIWindowFactory factory)
{
_windowFactory = factory ?? throw new ArgumentNullException(nameof(factory));
}
public void Release()
{
LayerManager.Clear();
AspectManager.Instance.ScreenSizeChange -= OnScreenSizeChange;
EventManager.Instance.Unregister(EventManager.EventName.Android_Click_Back, _OnAndroidClickBack);
}
public bool disableUILogic = false;
public void Update(float dt)
{
if (disableUILogic) return;
foreach (var winKv in mMemoryWindows)
{
var window = winKv.Value;
if (window != null
&& window.gameObject
&& window.IsCreated()
&& window.IsActive() // 改为 IsActive():只更新活跃窗口(显示且未暂停)
&& !window.IsClosing)
{
window.Update(dt);
}
}
_UpdateInputBlockTimer(dt);
}
#region UIWindow Manager
/// <summary>
/// 创建窗口实例(从UIRoot迁移过来,职责更清晰)
/// </summary>
/// <param name="windowName">窗口名称(路径)</param>
/// <param name="layer">目标层级</param>
/// <returns>创建的UIWindow实例,失败返回null</returns>
private async UniTask<UIWindow> CreateWindow(string windowName, UICanvasLayer layer)
{
// 1. 加载预制体
GameObject uiPrefab = await LoadPrefab(windowName);
if (uiPrefab is null)
{
DebugUtil.LogError("[UIManager] CreateWindow: cannot find window resource: {0}", windowName);
return null;
}
// 2. 获取目标层级根节点
var layerRoot = LayerManager.GetLayerRoot(layer);
if (layerRoot == null)
{
DebugUtil.LogError("[UIManager] CreateWindow: cannot find layer root for layer: {0}", layer);
return null;
}
// 3. 实例化UI对象到目标层级
GameObject obj = Object.Instantiate(uiPrefab, layerRoot.transform, false);
// 4. 使用工厂创建Controller实例
UIWindow window = _windowFactory.CreateWindow(windowName);
if (window == null)
{
DebugUtil.LogError("[UIManager] CreateWindow: factory failed to create controller for: {0}", windowName);
Object.Destroy(obj); // 创建失败,销毁GameObject
return null;
}
// 5. 绑定GameObject和UIWindow
window.gameObject = obj.gameObject;
window.transform = obj.transform;
window.name = obj.name;
return window;
}
/// <summary>
/// 加载UI预制体
/// </summary>
private async UniTask<GameObject> LoadPrefab(string windowName)
{
var path = string.Format(UI_PREFAB_PATH, windowName);
GameObject uiPrefab = await AssetManager.Instance.LoadAssetAsync<GameObject>(path);
// 如果AssetBundle加载失败,尝试从Resources加载(兼容方案)
if (uiPrefab is null)
{
uiPrefab = Resources.Load<GameObject>(Path.Combine("Loading", windowName));
}
return uiPrefab;
}
#endregion
#region OpenWindowAPI
/// <summary>
/// 打开一个UI
/// UIManager.Instance.OpenWindow("Common/GameUI")
/// </summary>
/// <summary>
/// 创建并打开窗口
/// </summary>
/// <param name="path">窗口路径</param>
/// <param name="data">传递的数据</param>
public async UniTask<UIWindow> CreateAndOpenWindow(string path, object data = null)
{
DebugUtil.LogP($"打开窗口:{path}");
if (mMemoryWindows.ContainsKey(path))
{
DebugUtil.LogG($"窗口已打开!!!{path}");
//return null;
}
var window = await _LoadWindow(path, false);
if (window != null)
{
window.ShowWindow(data);
}
return window;
}
public bool Back()
{
return false;
}
public bool RemoveWindow(string wName)
{
string windowName = wName;
bool result = false;
UIWindow window;
if (mMemoryWindows.TryGetValue(windowName, out window))
{
LayerManager.UnregisterWindow(window);
mMemoryWindows.Remove(windowName);
result = true;
}
return result;
}
/// <summary>
/// 关闭窗口
/// </summary>
/// <param name="wName">窗口名称</param>
/// <param name="destroy">是否销毁窗口</param>
public void CloseWindow(string wName, bool destroy)
{
var window = GetLoadedWindowByPath<UIWindow>(wName);
if (window != null)
{
window.CloseWindow(destroy);
}
}
/// <summary>
/// 打开已加载的窗口(通过路径)
/// </summary>
public void OpenLoadedWindow(string path, object data = null)
{
var window = GetLoadedWindowByPath(path);
if (window != null)
{
window.ShowWindow(data);
}
else
{
DebugUtil.LogError($"OpenLoadedWindow: window not found: {path}");
}
}
public T GetLoadedWindowByPath<T>(string uipath) where T : UIWindow
{
var window = GetLoadedWindowByPath(uipath) as T;
//return window.gameObject.activeSelf ? window : null;
return window;
}
public UIWindow GetLoadedWindowByPath(string uipath)
{
if (string.IsNullOrEmpty(uipath))
{
DebugUtil.LogError("UIManager.GetOpenedWindowByPath, uipath is null or empty");
return null;
}
if (mMemoryWindows.ContainsKey(uipath))
{
//return window.gameObject.activeSelf ? window : null;
return mMemoryWindows[uipath];
}
return null;
}
public bool GetTopWindow(out UIWindow window, out UICanvasLayer layer, int banLayerMask)
{
return LayerManager.GetTopWindow(out window, out layer, banLayerMask);
}
public int LayerMask(UICanvasLayer layer)
{
return LayerManager.GetLayerMask(layer);
}
async void SetFullScreenIOS()
{
await UniTask.Delay(670);
}
/// <summary>
/// 加载窗口(不显示)
/// 用于预加载场景
/// </summary>
public async UniTask<UIWindow> LoadWindow(string path)
{
return await _LoadWindow(path, true);
}
private async UniTask<UIWindow> _LoadWindow(string path, bool forceHide)
{
string uipath = path;
UIWindow window = null;
WindowInfo wInfo;
if (!_windowsInfo.ContainsKey(uipath))
{
//if (_windowsInfo.TryGetValue(uipath, out wInfo))
{
DebugUtil.LogError("Create ui fail: {0}, no windows info inited", uipath);
return window;
}
}
wInfo = _windowsInfo[uipath];
var uiWindowLayer = wInfo.windowLayer;
if (!mMemoryWindows.TryGetValue(uipath, out window))
{
if (_openingWindows.ContainsKey(uipath))
{
return window;
}
_openingWindows.Add(uipath, uipath);
window = await CreateWindow(uipath, uiWindowLayer);
_openingWindows.Remove(uipath);
if (window != null)
{
mMemoryWindows[uipath] = window;
window.WindowName = uipath;
window.uiWindowLayer = uiWindowLayer;
window.Init();
}
else
{
DebugUtil.LogError("Create ui fail: {0}", uipath);
return window;
}
}
LayerManager.RegisterWindow(window);
if (window == null)
{
DebugUtil.LogError("Create ui fail: {0}", uipath);
return null;
}
if (forceHide)
{
window.gameObject.SetActive(false);
}
_uiWindowObjDic.TryAdd(window.gameObject, window);
return window;
}
public void ForeachUI(Action<UIWindow> action)
{
var windowList = mMemoryWindows.ToList();
foreach (var kv in windowList)
{
var window = kv.Value;
if (window != null)
{
action?.Invoke(kv.Value);
}
}
}
/// <summary>
/// 获取所有已创建的窗口(包括显示和隐藏的)
/// </summary>
/// <returns>所有窗口集合</returns>
public IEnumerable<UIWindow> GetAllWindows()
{
return mMemoryWindows.Values;
}
public void CloseAllUIExcept(string path)
{
var windowList = mMemoryWindows.ToList();
foreach (var kv in windowList)
{
var window = kv.Value;
if(window.WindowName != path)
{
window.CloseWindow(destroy: true);
mMemoryWindows.Remove(window.WindowName);
LayerManager.UnregisterWindow(window);
}
}
}
#endregion
#region Show And Hide
/// <summary>
/// 隐藏指定层级及以下的所有UI
/// 使用 UIWindowGroup 实现
///
/// 使用场景:
/// - 进入全屏场景(如战斗、过场动画)时隐藏UI
/// - 显示模态对话框时隐藏背景UI
///
/// 注意:必须与 ShowAllUI 成对调用以恢复UI
/// </summary>
/// <param name="maxLayer">最大层级(包含该层级及以下的所有UI都会被隐藏)</param>
/// <param name="exceptions">不隐藏的UI窗口名称列表</param>
public void HideAllUI(UICanvasLayer maxLayer, params string[] exceptions)
{
// 收集需要隐藏的窗口名称
var windowNames = new List<string>();
for (int i = 0; i <= (int)maxLayer; i++)
{
var layer = (UICanvasLayer)i;
var windowsInLayer = LayerManager.GetWindowsInLayer(layer);
foreach (var window in windowsInLayer)
{
if (window != null &&
window.gameObject.activeInHierarchy &&
!IsException(window.WindowName, exceptions))
{
windowNames.Add(window.WindowName);
}
}
}
// 创建窗口组并隐藏
if (_tempHideGroup == null)
{
_tempHideGroup = new UIWindowGroup(windowNames);
}
_tempHideGroup.Close(false);
}
/// <summary>
/// 显示所有被 HideAllUI 隐藏的UI
/// </summary>
public void ShowAllUI()
{
if (_tempHideGroup != null)
{
_tempHideGroup.Show();
_tempHideGroup = null;
}
}
/// <summary>
/// 检查窗口是否在例外列表中
/// </summary>
private bool IsException(string windowName, string[] exceptions)
{
if (exceptions == null || exceptions.Length == 0)
return false;
foreach (var exception in exceptions)
{
if (exception == windowName)
return true;
}
return false;
}
#endregion
//根据路径判断UI界面是否打开
public bool IsWindowOpening(string uiPath)
{
string windowName;
if (_openingWindows.TryGetValue(uiPath, out windowName))
{
if (windowName != null)
{
return true;
}
}
return false;
}
/// <summary>
/// 关闭指定层级的所有窗口(支持单个或多个层级)
/// </summary>
/// <param name="layers">要关闭的层级列表</param>
public void CloseAllInLayer(params UICanvasLayer[] layers)
{
CloseAllInLayer(layers, null);
}
/// <summary>
/// 关闭指定层级的所有窗口,可排除指定窗口(支持单个或多个层级)
/// </summary>
/// <param name="layers">要关闭的层级列表</param>
/// <param name="exceptions">不关闭的窗口名称列表</param>
public void CloseAllInLayer(UICanvasLayer[] layers, params string[] exceptions)
{
if (layers == null || layers.Length == 0)
{
DebugUtil.LogWarning("[CloseAllInLayer] No layers specified");
return;
}
int totalClosed = 0;
foreach (var layer in layers)
{
var windowsInLayer = LayerManager.GetWindowsInLayer(layer);
if (windowsInLayer.Count == 0)
{
DebugUtil.Log($"[CloseAllInLayer] No windows in layer: {layer}");
continue;
}
List<UIWindow> toBeRemoved = new List<UIWindow>();
var node = windowsInLayer.First;
while (node != null)
{
var window = node.Value;
bool shouldClose = true;
// 检查是否在例外列表中
if (exceptions != null && exceptions.Length > 0)
{
foreach (var exception in exceptions)
{
if (window.WindowName == exception)
{
shouldClose = false;
DebugUtil.Log($"[CloseAllInLayer] Skipping window: {window.WindowName} (in exception list)");
break;
}
}
}
if (shouldClose)
{
toBeRemoved.Add(window);
}
if (node == windowsInLayer.Last)
{
break;
}
node = node.Next;
}
// 关闭窗口
foreach (var window in toBeRemoved)
{
DebugUtil.Log($"[CloseAllInLayer] Closing window: {window.WindowName} in layer {layer}");
window.CloseWindow(destroy: true);
}
totalClosed += toBeRemoved.Count;
}
if (layers.Length == 1)
DebugUtil.Log($"[CloseAllInLayer] Closed {totalClosed} windows in layer {layers[0]}");
else
DebugUtil.Log($"[CloseAllInLayer] Closed {totalClosed} windows in {layers.Length} layers");
}
private async void _OnAndroidClickBack()
{
if (!IsEnableBack)
return;
// 获取所有可见窗口(从上到下)
var visibleWindows = LayerManager.GetAllVisibleWindows();
DebugUtil.Log($"[AndroidBack] Found {visibleWindows.Count} visible windows");
// 从上到下遍历,找到第一个处理返回键的窗口
foreach (var window in visibleWindows)
{
DebugUtil.Log($"[AndroidBack] Trying window: {window.WindowName} (Layer: {window.uiWindowLayer})");
if (await window.Back())
{
// 窗口处理了返回键,结束遍历
DebugUtil.Log($"[AndroidBack] Handled by: {window.WindowName}");
return;
}
}
// 没有窗口处理返回键,显示退出确认
DebugUtil.Log("[AndroidBack] No window handled, showing exit confirmation");
if (!CheckWindowCreated(UINameConst.UIBackReturn))
{
await CreateAndOpenWindow(UINameConst.UIBackReturn);
}
}
public bool CheckWindowCreated(string uiPath)
{
if (mMemoryWindows.TryGetValue(uiPath, out var window))
{
return window.IsCreated();
}
return false;
}
private async void OnScreenSizeChange()
{
await UniTask.NextFrame(); // 必须等待一帧,等RectTransform大小刷新后获取
RectTransform rectTrans = UIRoot.Instance.UINormalRoot.GetComponent<RectTransform>();
Size = new(rectTrans.sizeDelta.x, rectTrans.sizeDelta.y);
UIAndMainScreenScale = new Vector3(Size.x / Screen.width, Size.y / Screen.height);
UIAndUIScreenScale = new Vector3(Size.x / AspectManager.Instance.ScreenSize.x, Size.y / AspectManager.Instance.ScreenSize.y);
}
}