| | |
| | | using UnityEngine; |
| | | using System.Linq; |
| | | using DG.Tweening; |
| | | using Cysharp.Threading.Tasks; |
| | | |
| | | /// <summary> |
| | | /// UI管理器 - 负责管理所有UI界面的显示、隐藏和层级 |
| | |
| | | } |
| | | else |
| | | { |
| | | #pragma warning disable CS0618 // Obsolete — sync legacy fallback, use LoadUIResourceAsync |
| | | prefab = ResManager.Instance.LoadAsset<GameObject>("UI", uiName); |
| | | #pragma warning restore CS0618 |
| | | } |
| | | |
| | | // 检查预制体是否加载成功 |
| | |
| | | { |
| | | return LoadUIResource(uiName) as T; |
| | | } |
| | | |
| | | // ==================================================================== |
| | | // US2: Async variants — InitUIRootAsync, LoadUIResourceAsync, OpenWindowAsync |
| | | // ==================================================================== |
| | | |
| | | /// <summary> |
| | | /// US2: 异步初始化 UI 根节点。 |
| | | /// </summary> |
| | | public async UniTask InitUIRootAsync() |
| | | { |
| | | GameObject root = GameObject.Find("UIRoot"); |
| | | if (root == null) |
| | | { |
| | | var prefab = await BuiltInLoader.LoadPrefabAsync("UIRoot"); |
| | | root = GameObject.Instantiate(prefab); |
| | | root.name = "UIRoot"; |
| | | if (root == null) |
| | | { |
| | | Debug.LogError("无法加载UI根节点"); |
| | | return; |
| | | } |
| | | GameObject.DontDestroyOnLoad(root); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// US2: 异步加载 UI 资源。 |
| | | /// </summary> |
| | | private async UniTask<UIBase> LoadUIResourceAsync(string uiName) |
| | | { |
| | | GameObject prefab; |
| | | if (uiName == "LaunchWin" || uiName == "DownLoadWin" || uiName == "RequestSecretWin" || uiName == "GameAgeWarnWin") |
| | | { |
| | | prefab = await BuiltInLoader.LoadPrefabAsync(uiName); |
| | | } |
| | | else |
| | | { |
| | | prefab = await ResManager.Instance.LoadAssetAsync<GameObject>("UI", uiName); |
| | | } |
| | | |
| | | if (prefab == null) |
| | | { |
| | | Debug.LogError($"加载UI预制体失败: {uiName}"); |
| | | return null; |
| | | } |
| | | |
| | | GameObject uiObject = GameObject.Instantiate(prefab); |
| | | uiObject.name = uiName; |
| | | |
| | | Type uiType = Type.GetType(uiName); |
| | | if (uiType == null) |
| | | { |
| | | Debug.LogError($"找不到UI类型: {uiName}"); |
| | | return null; |
| | | } |
| | | |
| | | UIBase uiBase = uiObject.GetComponent(uiType) as UIBase; |
| | | if (uiBase == null) |
| | | { |
| | | Debug.LogError($"UI预制体 {uiName} 没有 UIBase 组件或类型不匹配"); |
| | | return null; |
| | | } |
| | | |
| | | uiBase.uiName = uiName; |
| | | |
| | | Transform parentTrans = GetTransForLayer(uiBase.uiLayer); |
| | | uiObject.transform.SetParent(parentTrans, false); |
| | | |
| | | int baseSortingOrder = GetBaseSortingOrderForLayer(uiBase.uiLayer); |
| | | uiBase.SetSortingOrder(baseSortingOrder); |
| | | |
| | | return uiBase; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// US2: 异步打开窗口。 |
| | | /// </summary> |
| | | public async UniTask<UIBase> OpenWindowAsync(string uiName, int functionOrder = 0) |
| | | { |
| | | UIBase returnValue = null; |
| | | UIBase parentUI = null; |
| | | |
| | | // Check closed cache |
| | | if (closedUIDict.TryGetValue(uiName, out var closedUIList) && closedUIList.Count > 0) |
| | | { |
| | | returnValue = closedUIList[0] as UIBase; |
| | | closedUIList.RemoveAt(0); |
| | | if (closedUIList.Count == 0) |
| | | { |
| | | closedUIDict.Remove(uiName); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | // US3: Show loading indicator while loading UI prefab (auto-hide after load) |
| | | ShowLoadingIndicator(); |
| | | try |
| | | { |
| | | returnValue = await LoadUIResourceAsync(uiName); |
| | | } |
| | | finally |
| | | { |
| | | HideLoadingIndicator(); |
| | | } |
| | | |
| | | if (returnValue == null) |
| | | { |
| | | Debug.LogError($"打开UI失败: {uiName}"); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | returnValue.gameObject.SetActive(true); |
| | | |
| | | if (returnValue.supportParentChildRelation && uiStack.Count > 0 && !returnValue.isMainUI) |
| | | { |
| | | parentUI = GetLastSupportParentChildRelationUI(); |
| | | } |
| | | |
| | | if (parentUI != null) |
| | | { |
| | | returnValue.parentUI = parentUI; |
| | | if (parentUI.childrenUI == null) |
| | | { |
| | | parentUI.childrenUI = new List<UIBase>(); |
| | | } |
| | | parentUI.childrenUI.Add(returnValue); |
| | | } |
| | | |
| | | currentRound++; |
| | | returnValue.lastUsedRound = currentRound; |
| | | UpdateParentUIRounds(returnValue); |
| | | |
| | | if (!uiDict.ContainsKey(uiName)) |
| | | { |
| | | uiDict[uiName] = new List<UIBase>(); |
| | | } |
| | | uiDict[uiName].Add(returnValue); |
| | | |
| | | uiStack.Push(returnValue); |
| | | UpdateUISortingOrder(); |
| | | |
| | | returnValue.functionOrder = functionOrder; |
| | | returnValue.HandleOpen(); |
| | | OnOpenWindow?.Invoke(returnValue); |
| | | CheckAndCloseIdleUI(); |
| | | |
| | | return returnValue; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// US2: 泛型 异步打开窗口。 |
| | | /// </summary> |
| | | public async UniTask<T> OpenWindowAsync<T>(int functionOrder = 0) where T : UIBase |
| | | { |
| | | string uiName = typeof(T).Name; |
| | | var result = await OpenWindowAsync(uiName, functionOrder); |
| | | return result as T; |
| | | } |
| | | |
| | | // ==================================================================== |
| | | // US3: Loading indicator for async UI loading |
| | | // ==================================================================== |
| | | |
| | | private GameObject _loadingIndicatorGO; |
| | | private int _loadingRefCount; |
| | | |
| | | /// <summary> |
| | | /// US3: 显示加载指示器(引用计数,支持重入)。 |
| | | /// </summary> |
| | | public void ShowLoadingIndicator() |
| | | { |
| | | _loadingRefCount++; |
| | | if (_loadingRefCount == 1) |
| | | { |
| | | EnsureLoadingIndicator(); |
| | | if (_loadingIndicatorGO != null) |
| | | { |
| | | _loadingIndicatorGO.SetActive(true); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// US3: 隐藏加载指示器。 |
| | | /// </summary> |
| | | public void HideLoadingIndicator() |
| | | { |
| | | _loadingRefCount = Mathf.Max(0, _loadingRefCount - 1); |
| | | if (_loadingRefCount == 0 && _loadingIndicatorGO != null) |
| | | { |
| | | _loadingIndicatorGO.SetActive(false); |
| | | } |
| | | } |
| | | |
| | | private void EnsureLoadingIndicator() |
| | | { |
| | | if (_loadingIndicatorGO != null) return; |
| | | |
| | | // 创建简易加载指示器: 半透明遮罩 + 旋转图标 |
| | | var canvas = uiRoot != null ? uiRoot.GetComponentInChildren<Canvas>() : null; |
| | | if (canvas == null) return; |
| | | |
| | | _loadingIndicatorGO = new GameObject("UILoadingIndicator"); |
| | | _loadingIndicatorGO.transform.SetParent(canvas.transform, false); |
| | | |
| | | // 全屏半透明遮罩 |
| | | var maskRT = _loadingIndicatorGO.AddComponent<RectTransform>(); |
| | | maskRT.anchorMin = Vector2.zero; |
| | | maskRT.anchorMax = Vector2.one; |
| | | maskRT.offsetMin = Vector2.zero; |
| | | maskRT.offsetMax = Vector2.zero; |
| | | |
| | | var maskImage = _loadingIndicatorGO.AddComponent<UnityEngine.UI.Image>(); |
| | | maskImage.color = new Color(0, 0, 0, 0.3f); |
| | | maskImage.raycastTarget = true; // 拦截点击 |
| | | |
| | | // 加载提示文字 |
| | | var textGO = new GameObject("LoadingText"); |
| | | textGO.transform.SetParent(_loadingIndicatorGO.transform, false); |
| | | var textRT = textGO.AddComponent<RectTransform>(); |
| | | textRT.anchorMin = new Vector2(0.5f, 0.5f); |
| | | textRT.anchorMax = new Vector2(0.5f, 0.5f); |
| | | textRT.sizeDelta = new Vector2(300, 60); |
| | | |
| | | var text = textGO.AddComponent<UnityEngine.UI.Text>(); |
| | | text.text = "Loading..."; |
| | | text.alignment = TextAnchor.MiddleCenter; |
| | | text.fontSize = 28; |
| | | text.color = Color.white; |
| | | text.font = UnityEngine.Font.CreateDynamicFontFromOSFont("Arial", 28); |
| | | |
| | | // 确保在最上层 |
| | | var sortCanvas = _loadingIndicatorGO.AddComponent<Canvas>(); |
| | | sortCanvas.overrideSorting = true; |
| | | sortCanvas.sortingOrder = 30000; |
| | | _loadingIndicatorGO.AddComponent<UnityEngine.UI.GraphicRaycaster>(); |
| | | |
| | | _loadingIndicatorGO.SetActive(false); |
| | | } |
| | | |
| | | #endregion |
| | | |