using System; using System.Collections.Generic; using Cysharp.Threading.Tasks; using UnityEngine; using UnityEngine.UI; using YooAsset; namespace TEngine { /// /// UI模块。 /// [DisallowMultipleComponent] public sealed partial class UIModule : Module { [SerializeField] private Transform m_InstanceRoot = null; [SerializeField] private bool m_dontDestroyUIRoot = true; [SerializeField] private bool m_enableErrorLog = true; [SerializeField] private Camera m_UICamera = null; private readonly List _stack = new List(100); 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 /// /// UI根节点。 /// public Transform UIRoot => m_InstanceRoot; public static Transform UIRootStatic; /// /// UI根节点。 /// public Camera UICamera => m_UICamera; private UIModuleImpl _uiModuleImpl; private ErrorLogger _errorLogger; private void Start() { RootModule rootModule = ModuleSystem.GetModule(); if (rootModule == null) { Log.Fatal("Base component is invalid."); return; } _uiModuleImpl = ModuleImpSystem.GetModule(); _uiModuleImpl.Initialize(_stack); if (m_InstanceRoot == null) { m_InstanceRoot = new GameObject("UI Form Instances").transform; m_InstanceRoot.SetParent(gameObject.transform); m_InstanceRoot.localScale = Vector3.one; } else if (m_dontDestroyUIRoot) { DontDestroyOnLoad(m_InstanceRoot.parent != null ? m_InstanceRoot.parent : m_InstanceRoot); } m_InstanceRoot.gameObject.layer = LayerMask.NameToLayer("UI"); UIRootStatic = m_InstanceRoot; switch (GameModule.Debugger.ActiveWindowType) { case DebuggerActiveWindowType.AlwaysOpen: m_enableErrorLog = true; break; case DebuggerActiveWindowType.OnlyOpenWhenDevelopment: m_enableErrorLog = Debug.isDebugBuild; break; case DebuggerActiveWindowType.OnlyOpenInEditor: m_enableErrorLog = Application.isEditor; break; default: m_enableErrorLog = false; break; } if (m_enableErrorLog) { _errorLogger = new ErrorLogger(); } } private void OnDestroy() { if (_errorLogger != null) { _errorLogger.Dispose(); _errorLogger = null; } CloseAll(isShutDown:true); if (m_InstanceRoot != null && m_InstanceRoot.parent != null) { Destroy(m_InstanceRoot.parent.gameObject); } } #region 设置安全区域 /// /// 设置屏幕安全区域(异形屏支持)。 /// /// 安全区域 public static void ApplyScreenSafeRect(Rect safeRect) { CanvasScaler scaler = UIRootStatic.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 = UIRootStatic.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 (_stack.Count == 0) { return string.Empty; } UIWindow topWindow = _stack[^1]; return topWindow.WindowName; } /// /// 获取指定层级下顶部的窗口名称。 /// public string GetTopWindow(int layer) { UIWindow lastOne = null; for (int i = 0; i < _stack.Count; i++) { if (_stack[i].WindowLayer == layer) lastOne = _stack[i]; } if (lastOne == null) return string.Empty; return lastOne.WindowName; } /// /// 是否有任意窗口正在加载。 /// public bool IsAnyLoading() { for (int i = 0; i < _stack.Count; i++) { var window = _stack[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 { ShowUIImp(typeof(T), 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 { ShowUIImp(typeof(T), false, userDatas); } /// /// 异步打开窗口。 /// /// 用户自定义数据。 /// 打开窗口操作句柄。 public async UniTask ShowUIAsyncAwait(params System.Object[] userDatas) where T : UIWindow { return await ShowUIAwaitImp(typeof(T), true, userDatas); } /// /// 同步打开窗口。 /// /// /// /// 打开窗口操作句柄。 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 (IsContains(windowName)) { UIWindow window = GetWindow(windowName); Pop(window); //弹出窗口 Push(window); //重新压入 window.TryInvoke(OnWindowPrepare, userDatas); } else { UIWindow window = CreateInstance(type); Push(window); //首次压入 window.InternalLoad(window.AssetName, OnWindowPrepare, isAsync, userDatas).Forget(); } } private async UniTask ShowUIAwaitImp(Type type, bool isAsync, params System.Object[] userDatas) { string windowName = type.FullName; // 如果窗口已经存在 if (IsContains(windowName)) { UIWindow window = GetWindow(windowName); Pop(window); //弹出窗口 Push(window); //重新压入 window.TryInvoke(OnWindowPrepare, userDatas); return window; } else { UIWindow window = CreateInstance(type); 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; } } /// /// 关闭窗口 /// 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 < _stack.Count; i++) { UIWindow window = _stack[i]; window.InternalDestroy(isShutDown); } _stack.Clear(); } /// /// 关闭所有窗口除了。 /// public void CloseAllWithOut(UIWindow withOut) { for (int i = _stack.Count - 1; i >= 0; i--) { UIWindow window = _stack[i]; if (window == withOut) { continue; } window.InternalDestroy(); _stack.RemoveAt(i); } } /// /// 关闭所有窗口除了。 /// public void CloseAllWithOut() where T : UIWindow { for (int i = _stack.Count - 1; i >= 0; i--) { UIWindow window = _stack[i]; if (window.GetType() == typeof(T)) { continue; } window.InternalDestroy(); _stack.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 < _stack.Count; i++) { if (_stack[i].WindowLayer == layer) { _stack[i].Depth = depth; depth += WINDOW_DEEP; } } } private void OnSetWindowVisible() { bool isHideNext = false; for (int i = _stack.Count - 1; i >= 0; i--) { UIWindow window = _stack[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(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() 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(); } 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 < _stack.Count; i++) { UIWindow window = _stack[i]; if (window.WindowName == windowName) { return window; } } return null; } private bool IsContains(string windowName) { for (int i = 0; i < _stack.Count; i++) { UIWindow window = _stack[i]; if (window.WindowName == windowName) { return true; } } return false; } private void Push(UIWindow window) { // 如果已经存在 if (IsContains(window.WindowName)) throw new System.Exception($"Window {window.WindowName} is exist."); // 获取插入到所属层级的位置 int insertIndex = -1; for (int i = 0; i < _stack.Count; i++) { if (window.WindowLayer == _stack[i].WindowLayer) { insertIndex = i + 1; } } // 如果没有所属层级,找到相邻层级 if (insertIndex == -1) { for (int i = 0; i < _stack.Count; i++) { if (window.WindowLayer > _stack[i].WindowLayer) { insertIndex = i + 1; } } } // 如果是空栈或没有找到插入位置 if (insertIndex == -1) { insertIndex = 0; } // 最后插入到堆栈 _stack.Insert(insertIndex, window); } private void Pop(UIWindow window) { // 从堆栈里移除 _stack.Remove(window); } } [UpdateModule] internal sealed partial class UIModuleImpl : ModuleImp { private List _stack; internal void Initialize(List stack) { _stack = stack; } internal override void Shutdown() { } internal override void Update(float elapseSeconds, float realElapseSeconds) { if (_stack == null) { return; } int count = _stack.Count; for (int i = 0; i < _stack.Count; i++) { if (_stack.Count != count) { break; } var window = _stack[i]; window.InternalUpdate(); } } } }