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

652 lines
19 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;
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);
}
}