using System; using System.Collections.Generic; using System.Threading; using Cysharp.Threading.Tasks; using GameLogic; using TEngine; using UnityEngine; using UnityEngine.UI; namespace GameLogic { /// /// UI管理模块。 /// public sealed partial class UIModule : Singleton, IUpdate { // 核心字段 private static Transform _instanceRoot = null; // UI根节点变换组件 private bool _enableErrorLog = true; // 是否启用错误日志 private Camera _uiCamera = null; // UI专用摄像机 private readonly List _uiStack = new List(128); // 窗口堆栈 private ErrorLogger _errorLogger; // 错误日志记录器 // 常量定义 public const int LAYER_DEEP = 2000; public const int WINDOW_DEEP = 100; public const int WINDOW_HIDE_LAYER = 2; // Ignore Raycast public const int WINDOW_SHOW_LAYER = 5; // UI // 资源加载接口 public static IUIResourceLoader Resource; /// /// UI根节点访问属性 /// public static Transform UIRoot => _instanceRoot; /// /// UI摄像机访问属性 /// public Camera UICamera => _uiCamera; /// /// 模块初始化(自动调用)。 /// 1. 查找场景中的UIRoot /// 2. 初始化资源加载器 /// 3. 配置错误日志系统 /// protected override void OnInit() { var uiRoot = GameObject.Find("UIRoot"); if (uiRoot != null) { _instanceRoot = uiRoot.GetComponentInChildren()?.transform; _uiCamera = uiRoot.GetComponentInChildren(); } else { Log.Fatal("UIRoot not found !"); return; } Resource = new UIResourceLoader(); UnityEngine.Object.DontDestroyOnLoad(_instanceRoot.parent != null ? _instanceRoot.parent : _instanceRoot); _instanceRoot.gameObject.layer = LayerMask.NameToLayer("UI"); if (Debugger.Instance != null) { switch (Debugger.Instance.ActiveWindowType) { case DebuggerActiveWindowType.AlwaysOpen: _enableErrorLog = true; break; case DebuggerActiveWindowType.OnlyOpenWhenDevelopment: _enableErrorLog = Debug.isDebugBuild; break; case DebuggerActiveWindowType.OnlyOpenInEditor: _enableErrorLog = Application.isEditor; break; default: _enableErrorLog = false; break; } if (_enableErrorLog) { _errorLogger = new ErrorLogger(this); } } } /// /// 模块释放(自动调用)。 /// 1. 清理错误日志系统 /// 2. 关闭所有窗口 /// 3. 销毁UI根节点 /// protected override void OnRelease() { if (_errorLogger != null) { _errorLogger.Dispose(); _errorLogger = null; } CloseAll(isShutDown:true); if (_instanceRoot != null && _instanceRoot.parent != null) { UnityEngine.Object.Destroy(_instanceRoot.parent.gameObject); } } #region 设置安全区域 /// /// 设置屏幕安全区域(异形屏支持)。 /// /// 安全区域矩形(基于屏幕像素坐标)。 public static void ApplyScreenSafeRect(Rect safeRect) { CanvasScaler scaler = UIRoot.GetComponentInParent(); if (scaler == null) { Log.Error($"Not found {nameof(CanvasScaler)} !"); return; } // Convert safe area rectangle from absolute pixels to UGUI coordinates float rateX = scaler.referenceResolution.x / Screen.width; float rateY = scaler.referenceResolution.y / Screen.height; float posX = (int)(safeRect.position.x * rateX); float posY = (int)(safeRect.position.y * rateY); float width = (int)(safeRect.size.x * rateX); float height = (int)(safeRect.size.y * rateY); float offsetMaxX = scaler.referenceResolution.x - width - posX; float offsetMaxY = scaler.referenceResolution.y - height - posY; // 注意:安全区坐标系的原点为左下角 var rectTrans = UIRoot.transform as RectTransform; if (rectTrans != null) { rectTrans.offsetMin = new Vector2(posX, posY); //锚框状态下的屏幕左下角偏移向量 rectTrans.offsetMax = new Vector2(-offsetMaxX, -offsetMaxY); //锚框状态下的屏幕右上角偏移向量 } } /// /// 模拟IPhoneX异形屏 /// public static void SimulateIPhoneXNotchScreen() { Rect rect; if (Screen.height > Screen.width) { // 竖屏Portrait float deviceWidth = 1125; float deviceHeight = 2436; rect = new Rect(0f / deviceWidth, 102f / deviceHeight, 1125f / deviceWidth, 2202f / deviceHeight); } else { // 横屏Landscape float deviceWidth = 2436; float deviceHeight = 1125; rect = new Rect(132f / deviceWidth, 63f / deviceHeight, 2172f / deviceWidth, 1062f / deviceHeight); } Rect safeArea = new Rect(Screen.width * rect.x, Screen.height * rect.y, Screen.width * rect.width, Screen.height * rect.height); ApplyScreenSafeRect(safeArea); } #endregion /// /// 获取所有层级下顶部的窗口名称。 /// public string GetTopWindow() { if (_uiStack.Count == 0) { return string.Empty; } UIWindow topWindow = _uiStack[^1]; return topWindow.WindowName; } /// /// 获取指定层级下顶部的窗口名称。 /// public string GetTopWindow(int layer) { UIWindow lastOne = null; for (int i = 0; i < _uiStack.Count; i++) { if (_uiStack[i].WindowLayer == layer) lastOne = _uiStack[i]; } if (lastOne == null) return string.Empty; return lastOne.WindowName; } /// /// 是否有任意窗口正在加载。 /// public bool IsAnyLoading() { for (int i = 0; i < _uiStack.Count; i++) { var window = _uiStack[i]; if (window.IsLoadDone == false) return true; } return false; } /// /// 查询窗口是否存在。 /// /// 界面类型。 /// 是否存在。 public bool HasWindow() { return HasWindow(typeof(T)); } /// /// 查询窗口是否存在。 /// /// 界面类型。 /// 是否存在。 public bool HasWindow(Type type) { return IsContains(type.FullName); } /// /// 异步打开窗口。 /// /// 用户自定义数据。 /// 打开窗口操作句柄。 public void ShowUIAsync(params System.Object[] userDatas) where T : UIWindow , new() { ShowUIImp(true, userDatas); } /// /// 异步打开窗口。 /// /// 界面类型。 /// 用户自定义数据。 /// 打开窗口操作句柄。 public void ShowUIAsync(Type type, params System.Object[] userDatas) { ShowUIImp(type, true, userDatas); } /// /// 同步打开窗口。 /// /// 窗口类。 /// 用户自定义数据。 /// 打开窗口操作句柄。 public void ShowUI(params System.Object[] userDatas) where T : UIWindow , new() { ShowUIImp(false, userDatas); } /// /// 异步打开窗口。 /// /// 用户自定义数据。 /// 打开窗口操作句柄。 public async UniTask ShowUIAsyncAwait(params System.Object[] userDatas) where T : UIWindow , new() { return await ShowUIAwaitImp(true, userDatas) as T; } /// /// 同步打开窗口。 /// /// /// /// 打开窗口操作句柄。 public void ShowUI(Type type, params System.Object[] userDatas) { ShowUIImp(type, false, userDatas); } private void ShowUIImp(Type type, bool isAsync, params System.Object[] userDatas) { string windowName = type.FullName; if (!TryGetWindow(windowName, out UIWindow window, userDatas)) { window = CreateInstance(type); Push(window); //首次压入 window.InternalLoad(window.AssetName, OnWindowPrepare, isAsync, userDatas).Forget(); } } private void ShowUIImp(bool isAsync, params System.Object[] userDatas) where T : UIWindow , new() { Type type = typeof(T); string windowName = type.FullName; if (!TryGetWindow(windowName, out UIWindow window, userDatas)) { window = CreateInstance(); Push(window); //首次压入 window.InternalLoad(window.AssetName, OnWindowPrepare, isAsync, userDatas).Forget(); } } private bool TryGetWindow(string windowName,out UIWindow window, params System.Object[] userDatas) { window = null; if (IsContains(windowName)) { window = GetWindow(windowName); Pop(window); //弹出窗口 Push(window); //重新压入 window.TryInvoke(OnWindowPrepare, userDatas); return true; } return false; } private async UniTask ShowUIAwaitImp(bool isAsync, params System.Object[] userDatas) where T : UIWindow , new() { Type type = typeof(T); string windowName = type.FullName; if (TryGetWindow(windowName, out UIWindow window, userDatas)) { return window as T; } else { window = CreateInstance(); Push(window); //首次压入 window.InternalLoad(window.AssetName, OnWindowPrepare, isAsync, userDatas).Forget(); float time = 0f; while (!window.IsLoadDone) { time += Time.deltaTime; if (time > 60f) { break; } await UniTask.Yield(); } return window as T; } } /// /// 关闭窗口。 /// /// 窗口类型 public void CloseUI() where T : UIWindow { CloseUI(typeof(T)); } public void CloseUI(Type type) { string windowName = type.FullName; UIWindow window = GetWindow(windowName); if (window == null) return; window.InternalDestroy(); Pop(window); OnSortWindowDepth(window.WindowLayer); OnSetWindowVisible(); } public void HideUI() where T : UIWindow { HideUI(typeof(T)); } public void HideUI(Type type) { string windowName = type.FullName; UIWindow window = GetWindow(windowName); if (window == null) { return; } if (window.HideTimeToClose <= 0) { CloseUI(type); return; } window.Visible = false; window.IsHide = true; window.HideTimerId = GameModule.Timer.AddTimer((arg) => { CloseUI(type); },window.HideTimeToClose); if (window.FullScreen) { OnSetWindowVisible(); } } /// /// 关闭所有窗口。 /// public void CloseAll(bool isShutDown = false) { for (int i = 0; i < _uiStack.Count; i++) { UIWindow window = _uiStack[i]; window.InternalDestroy(isShutDown); } _uiStack.Clear(); } /// /// 关闭所有窗口除了。 /// public void CloseAllWithOut(UIWindow withOut) { for (int i = _uiStack.Count - 1; i >= 0; i--) { UIWindow window = _uiStack[i]; if (window == withOut) { continue; } window.InternalDestroy(); _uiStack.RemoveAt(i); } } /// /// 关闭所有窗口除了。 /// public void CloseAllWithOut() where T : UIWindow { for (int i = _uiStack.Count - 1; i >= 0; i--) { UIWindow window = _uiStack[i]; if (window.GetType() == typeof(T)) { continue; } window.InternalDestroy(); _uiStack.RemoveAt(i); } } private void OnWindowPrepare(UIWindow window) { OnSortWindowDepth(window.WindowLayer); window.InternalCreate(); window.InternalRefresh(); OnSetWindowVisible(); } private void OnSortWindowDepth(int layer) { int depth = layer * LAYER_DEEP; for (int i = 0; i < _uiStack.Count; i++) { if (_uiStack[i].WindowLayer == layer) { _uiStack[i].Depth = depth; depth += WINDOW_DEEP; } } } private void OnSetWindowVisible() { bool isHideNext = false; for (int i = _uiStack.Count - 1; i >= 0; i--) { UIWindow window = _uiStack[i]; if (isHideNext == false) { if (window.IsHide) { continue; } window.Visible = true; if (window.IsPrepare && window.FullScreen) { isHideNext = true; } } else { window.Visible = false; } } } private UIWindow CreateInstance() where T : UIWindow , new() { Type type = typeof(T); UIWindow window = new T(); WindowAttribute attribute = Attribute.GetCustomAttribute(type, typeof(WindowAttribute)) as WindowAttribute; if (window == null) throw new GameFrameworkException($"Window {type.FullName} create instance failed."); if (attribute != null) { string assetName = string.IsNullOrEmpty(attribute.Location) ? type.Name : attribute.Location; window.Init(type.FullName, attribute.WindowLayer, attribute.FullScreen, assetName, attribute.FromResources, attribute.HideTimeToClose); } else { window.Init(type.FullName, (int)UILayer.UI, fullScreen: window.FullScreen, assetName: type.Name, fromResources: false, hideTimeToClose: 10); } return window; } private UIWindow CreateInstance(Type type) { UIWindow window = Activator.CreateInstance(type) as UIWindow; WindowAttribute attribute = Attribute.GetCustomAttribute(type, typeof(WindowAttribute)) as WindowAttribute; if (window == null) throw new GameFrameworkException($"Window {type.FullName} create instance failed."); if (attribute != null) { string assetName = string.IsNullOrEmpty(attribute.Location) ? type.Name : attribute.Location; window.Init(type.FullName, attribute.WindowLayer, attribute.FullScreen, assetName, attribute.FromResources, attribute.HideTimeToClose); } else { window.Init(type.FullName, (int)UILayer.UI, fullScreen: window.FullScreen, assetName: type.Name, fromResources: false, hideTimeToClose: 10); } return window; } /// /// 异步获取窗口。 /// /// 打开窗口操作句柄。 public async UniTask GetUIAsyncAwait(CancellationToken cancellationToken = default) where T : UIWindow { string windowName = typeof(T).FullName; var window = GetWindow(windowName); if (window == null) { return null; } var ret = window as T; if (ret == null) { return null; } if (ret.IsLoadDone) { return ret; } float time = 0f; while (!ret.IsLoadDone) { time += Time.deltaTime; if (time > 60f) { break; } await UniTask.Yield(cancellationToken: cancellationToken); } return ret; } /// /// 异步获取窗口。 /// /// 回调。 /// 打开窗口操作句柄。 public void GetUIAsync(Action callback) where T : UIWindow { string windowName = typeof(T).FullName; var window = GetWindow(windowName); if (window == null) { return; } var ret = window as T; if (ret == null) { return; } GetUIAsyncImp(callback).Forget(); async UniTaskVoid GetUIAsyncImp(Action ctx) { float time = 0f; while (!ret.IsLoadDone) { time += Time.deltaTime; if (time > 60f) { break; } await UniTask.Yield(); } ctx?.Invoke(ret); } } private UIWindow GetWindow(string windowName) { for (int i = 0; i < _uiStack.Count; i++) { UIWindow window = _uiStack[i]; if (window.WindowName == windowName) { return window; } } return null; } private bool IsContains(string windowName) { for (int i = 0; i < _uiStack.Count; i++) { UIWindow window = _uiStack[i]; if (window.WindowName == windowName) { return true; } } return false; } private void Push(UIWindow window) { // 如果已经存在 if (IsContains(window.WindowName)) { throw new GameFrameworkException($"Window {window.WindowName} is exist."); } // 获取插入到所属层级的位置 int insertIndex = -1; for (int i = 0; i < _uiStack.Count; i++) { if (window.WindowLayer == _uiStack[i].WindowLayer) { insertIndex = i + 1; } } // 如果没有所属层级,找到相邻层级 if (insertIndex == -1) { for (int i = 0; i < _uiStack.Count; i++) { if (window.WindowLayer > _uiStack[i].WindowLayer) { insertIndex = i + 1; } } } // 如果是空栈或没有找到插入位置 if (insertIndex == -1) { insertIndex = 0; } // 最后插入到堆栈 _uiStack.Insert(insertIndex, window); } private void Pop(UIWindow window) { // 从堆栈里移除 _uiStack.Remove(window); } public void OnUpdate() { if (_uiStack == null) { return; } int count = _uiStack.Count; for (int i = 0; i < _uiStack.Count; i++) { if (_uiStack.Count != count) { break; } var window = _uiStack[i]; window.InternalUpdate(); } } } }