829 lines
27 KiB
C#
829 lines
27 KiB
C#
using System.Collections.Generic;
|
||
using System;
|
||
using UnityEngine;
|
||
using System.Collections;
|
||
using Unity.VisualScripting.FullSerializer;
|
||
using UnityEngine.AddressableAssets;
|
||
using System.Net;
|
||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||
using UnityEngine.UI;
|
||
using TMPro;
|
||
using UnityEngine.Video;
|
||
//ui面板枚举 这里要求分类 Main *00 *00-*99 整数为主界面尾数为子界面
|
||
public enum UIPanel
|
||
{
|
||
None=9999,
|
||
LoginPanel = 0,
|
||
#region 90-99loginPanel
|
||
LoginEnterPanel=90,
|
||
ResetPasswordPanel=91,
|
||
RegisterPanel = 92,
|
||
#endregion
|
||
|
||
HomeMainPanel = 1,
|
||
MainGamePanelDefault = 2,
|
||
MainGamePanelFLZY = 3,
|
||
MainGamePanelYYSC = 4,
|
||
MainGamePanelZGXQ = 5,
|
||
MainGamePanelXJMH = 6,
|
||
MainGamePanelHHWK = 7,
|
||
MainGamePanelJQQD = 8,
|
||
#region 100-199HomePanel
|
||
//100-140center界面
|
||
|
||
SelectRoomLeavePenel = 101,
|
||
MySelectRoomPanel = 102,//这个界面没有加载到子界面中直接在选择房间的界面中使用
|
||
|
||
|
||
//140-199 Margin界面
|
||
UserInfo = 141,
|
||
|
||
VIPPenel = 151,
|
||
JJJPenel = 152,
|
||
ActivityPenel = 153,
|
||
JiGuHuiLi=154,
|
||
QianDaoPanel = 155,
|
||
|
||
MonthCardPanel = 161,
|
||
BackPackPanel = 162,
|
||
AnnouncementPanel = 163,
|
||
RankPanel = 164,
|
||
EmailPanel = 165,
|
||
SafeBoxPanel = 166,
|
||
ServicePanel = 167,
|
||
ShopPanel = 168,
|
||
ChouJiangPanel=169,
|
||
|
||
TurretPanel = 181,
|
||
ZSPanel = 182,
|
||
WingPanel = 183,
|
||
|
||
SettingPanel = 199,
|
||
#endregion
|
||
|
||
#region 200-299 Game界面
|
||
|
||
|
||
|
||
ZGXQDistrict = 251,
|
||
ZGXQCreateRoom=252,
|
||
ZGXQSelectCoin=2521,
|
||
ZGXQGameExplain=2522,
|
||
ZGXQJoinGame=253,
|
||
ZGXQWin = 254,
|
||
ZGXQDefeated= 255,
|
||
ZGXQHe= 259,
|
||
ZGXQSetting=256,
|
||
ZGXQHint=257,
|
||
ZGXQIntroduce = 258,
|
||
|
||
|
||
#endregion
|
||
|
||
#region 500 DefaultGame界面
|
||
GameTuJian=501,
|
||
GameSuoDing=502,
|
||
|
||
#endregion
|
||
#region
|
||
GuaGuaKa=900,
|
||
#endregion
|
||
}
|
||
|
||
public class UIManager : MonoSingleton<UIManager>
|
||
{
|
||
// 已加载的面板实例 主面板列表
|
||
private readonly Dictionary<UIPanel, UIBase> panels = new Dictionary<UIPanel, UIBase>();
|
||
[SerializeField] private UIPanelAddressConfig config;
|
||
|
||
|
||
// 父节点(可指定到 Canvas 下某个容器)
|
||
[SerializeField] public Transform uiRoot;
|
||
public GameObject obj_JiaZaiZhong; //加载中界面
|
||
private Dictionary<UIPanel, string> addressMap = new Dictionary<UIPanel, string>(); //第二种加载
|
||
|
||
// ⭐ prefab 缓存:避免重复 LoadAssetAsync,第二次打开同一面板秒开
|
||
private readonly Dictionary<UIPanel, GameObject> prefabCache = new Dictionary<UIPanel, GameObject>();
|
||
// ⭐ handle 缓存:用于 Addressables.Release 释放内存
|
||
private readonly Dictionary<UIPanel, AsyncOperationHandle<GameObject>> handleCache = new Dictionary<UIPanel, AsyncOperationHandle<GameObject>>();
|
||
public GameObject loadPanel;
|
||
public LoadingPanel ShowLoadPanel(VideoClip videoClip, Action action=null)
|
||
{
|
||
ShowAsync(UIPanel.LoginPanel);
|
||
|
||
GameObject load = Instantiate(loadPanel, uiRoot);
|
||
var loadScript = load.GetComponent<LoadingPanel>();
|
||
load.transform.GetChild(0).gameObject.SetActive(true);
|
||
loadScript.player.clip = videoClip;
|
||
loadScript.player.Play();
|
||
loadScript.actionEnd = action;
|
||
return loadScript;
|
||
}
|
||
public LoadingPanel ShowLoadPanel(Sprite sprite, Action action = null)
|
||
{
|
||
GameObject load = Instantiate(loadPanel, uiRoot);
|
||
var loadScript = load.GetComponent<LoadingPanel>();
|
||
load.transform.GetChild(1).gameObject.SetActive(true);
|
||
loadScript.image_Bg.sprite = sprite;
|
||
loadScript.actionEnd = action;
|
||
return loadScript;
|
||
|
||
}
|
||
protected override void Awake()
|
||
{
|
||
base.Awake();
|
||
foreach (var entry in config.panels)
|
||
{
|
||
if (!addressMap.ContainsKey(entry.type))
|
||
addressMap.Add(entry.type, entry.address);
|
||
}
|
||
|
||
if (uiRoot == null)
|
||
{
|
||
var canvas = FindObjectOfType<Canvas>();
|
||
uiRoot = canvas != null ? canvas.transform : transform;
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 同步显示(若未实例化则立即实例化)
|
||
/// </summary>
|
||
public T Show<T>(UIPanel type, object args = null) where T : UIBase
|
||
{
|
||
if (addressMap.ContainsKey(type))
|
||
{
|
||
// ⭐ 优先用已缓存的 prefab,避免重复 LoadAsset
|
||
GameObject prefab = null;
|
||
if (!prefabCache.TryGetValue(type, out prefab))
|
||
{
|
||
// 使用 LoadAssetAsync + WaitForCompletion 替代已过时的 LoadAsset
|
||
var handle = Addressables.LoadAssetAsync<GameObject>(addressMap[type]);
|
||
handle.WaitForCompletion();
|
||
if (handle.Status == AsyncOperationStatus.Succeeded)
|
||
{
|
||
prefab = handle.Result;
|
||
prefabCache[type] = prefab;
|
||
handleCache[type] = handle;
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError($"Failed to load UI prefab for {type}");
|
||
return null;
|
||
}
|
||
}
|
||
var panel = GetOrCreatePanel(type, prefab);
|
||
panel?.Show(args);
|
||
return (T)panel;
|
||
}
|
||
else
|
||
{
|
||
Debug.Log("加载失败" + type.ToString() + "不存在");
|
||
return null;
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 同步显示(若未实例化则立即实例化)
|
||
/// </summary>
|
||
public UIBase Show(UIPanel type, object args = null)
|
||
{
|
||
if (addressMap.ContainsKey(type))
|
||
{
|
||
// ⭐ 优先用已缓存的 prefab,避免重复 LoadAsset
|
||
GameObject prefab = null;
|
||
if (!prefabCache.TryGetValue(type, out prefab))
|
||
{
|
||
// 使用 LoadAssetAsync + WaitForCompletion 替代已过时的 LoadAsset
|
||
var handle = Addressables.LoadAssetAsync<GameObject>(addressMap[type]);
|
||
handle.WaitForCompletion();
|
||
if (handle.Status == AsyncOperationStatus.Succeeded)
|
||
{
|
||
prefab = handle.Result;
|
||
prefabCache[type] = prefab;
|
||
handleCache[type] = handle;
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError($"Failed to load UI prefab for {type}");
|
||
return null;
|
||
}
|
||
}
|
||
var panel = GetOrCreatePanel(type, prefab);
|
||
panel?.Show(args);
|
||
return panel;
|
||
}
|
||
else
|
||
{
|
||
Debug.Log("加载失败" + type.ToString() + "不存在");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Addressables/异步加载 关闭另外一个面板
|
||
/// </summary>
|
||
public Coroutine ShowAsync(UIPanel type, UIPanel isClosePanel, object args = null, Action<UIBase> onShown = null)
|
||
{
|
||
|
||
return StartCoroutine(CoShow(type, args, onShown, isClosePanel));
|
||
|
||
}
|
||
/// <summary>
|
||
/// Addressables/异步加载
|
||
/// </summary>
|
||
public Coroutine ShowAsync(UIPanel type, object args = null, Action<UIBase> onShown = null,float YCTime=0)
|
||
{
|
||
return StartCoroutine(CoShow(type, args, onShown, YCTime));
|
||
}
|
||
public GameObject BoardPanelParefab;
|
||
public GameObject BoardPanelParefabGongGao;
|
||
public GameObject HuoDeParefab;
|
||
public GameObject TiShiParefab;
|
||
public void ShowBoardError(string str)
|
||
{
|
||
|
||
StartCoroutine(IEShowBoard(str));
|
||
|
||
}
|
||
/// <summary>
|
||
/// 显示"恭喜获得"奖励面板(随时访问,类似公告)。10秒未关闭则自动关闭。
|
||
/// </summary>
|
||
/// <param name="backPackPropItemDatas">奖励道具数据列表</param>
|
||
/// <param name="onCloseAction">关闭面板时回调(处理特殊情况)</param>
|
||
public void ShowHuoDe(List<BackPackPropItemData> backPackPropItemDatas, Action onCloseAction = null)
|
||
{
|
||
StartCoroutine(IEShowHuoDe(backPackPropItemDatas, onCloseAction));
|
||
}
|
||
/// <summary>
|
||
/// 显示提示窗口(随时访问)。不自动关闭,需手动点确认/关闭按钮。
|
||
/// </summary>
|
||
/// <param name="text">提示内容</param>
|
||
/// <param name="onConfirmAction">点击确认按钮时的回调(可选),关闭按钮不触发</param>
|
||
/// <param name="showClose">是否显示关闭按钮(默认 true,部分提示只能点确认)</param>
|
||
public void ShowTiShi(string text, Action onConfirmAction = null, bool showClose = true)
|
||
{
|
||
if (TiShiParefab == null)
|
||
{
|
||
Debug.LogWarning("TiShiParefab 未赋值");
|
||
return;
|
||
}
|
||
var obj = Instantiate(TiShiParefab, uiRoot);
|
||
obj.transform.SetAsLastSibling();
|
||
var tishi = obj.GetComponent<TiShi>();
|
||
if (tishi != null)
|
||
tishi.Init(text, onConfirmAction, showClose);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 显示提示窗口(随时访问),并注册关闭事件回调。
|
||
/// 关闭事件在点确认或点X时都会触发,常用于恢复外部可弹窗状态。
|
||
/// </summary>
|
||
/// <param name="text">提示内容</param>
|
||
/// <param name="onConfirmAction">点击确认按钮时的回调(可选)</param>
|
||
/// <param name="onClose">关闭事件回调(点确认或点X都触发)</param>
|
||
/// <param name="showClose">是否显示关闭按钮(默认 true)</param>
|
||
public void ShowTiShi(string text, Action onConfirmAction, Action onClose, bool showClose = true)
|
||
{
|
||
if (TiShiParefab == null)
|
||
{
|
||
Debug.LogWarning("TiShiParefab 未赋值");
|
||
return;
|
||
}
|
||
var obj = Instantiate(TiShiParefab, uiRoot);
|
||
obj.transform.SetAsLastSibling();
|
||
var tishi = obj.GetComponent<TiShi>();
|
||
if (tishi != null)
|
||
tishi.Init(text, onConfirmAction, onClose, showClose);
|
||
}
|
||
IEnumerator IEShowHuoDe(List<BackPackPropItemData> backPackPropItemDatas, Action onCloseAction)
|
||
{
|
||
if (HuoDeParefab == null)
|
||
{
|
||
Debug.LogWarning("HuoDeParefab 未赋值");
|
||
yield break;
|
||
}
|
||
var obj = Instantiate(HuoDeParefab, uiRoot);
|
||
obj.transform.SetAsLastSibling();
|
||
var huode = obj.GetComponent<HuoDe>();
|
||
if (huode != null)
|
||
huode.Init(backPackPropItemDatas, onCloseAction);
|
||
|
||
// 10秒后未手动关闭则自动关闭
|
||
yield return new WaitForSeconds(10f);
|
||
if (obj != null)
|
||
huode?.Close();
|
||
}
|
||
public void ShowBoardGongGao(string str)
|
||
{
|
||
_gongGaoQueue.Enqueue(str);
|
||
if (!_isGongGaoRunning)
|
||
StartCoroutine(ProcessGongGaoQueue());
|
||
}
|
||
|
||
private Queue<string> _gongGaoQueue = new Queue<string>();
|
||
private bool _isGongGaoRunning = false;
|
||
|
||
private IEnumerator ProcessGongGaoQueue()
|
||
{
|
||
_isGongGaoRunning = true;
|
||
while (_gongGaoQueue.Count > 0)
|
||
{
|
||
string str = _gongGaoQueue.Dequeue();
|
||
yield return IEShowBoardGongGao(str);
|
||
}
|
||
_isGongGaoRunning = false;
|
||
}
|
||
|
||
public float speedGongGao;
|
||
IEnumerator IEShowBoardGongGao(string str)
|
||
{
|
||
var obj = Instantiate(BoardPanelParefabGongGao, uiRoot);
|
||
var gg = obj.transform.GetChild(0).GetChild(0).GetComponent<RectTransform>();
|
||
gg.GetComponent<TextMeshProUGUI>().text = str;
|
||
TextMeshProUGUI t = obj.transform.GetChild(0).GetComponent<TextMeshProUGUI>();
|
||
Image i = obj.GetComponent<Image>();
|
||
while (true)
|
||
{
|
||
gg.Translate(Vector3.left*speedGongGao);
|
||
if (gg.anchoredPosition3D.x<=-gg.sizeDelta.x)
|
||
{
|
||
break;
|
||
}
|
||
yield return null;
|
||
}
|
||
Destroy(obj);
|
||
|
||
}
|
||
IEnumerator IEShowBoard(string str)
|
||
{
|
||
var obj = Instantiate(BoardPanelParefab, uiRoot);
|
||
obj.transform.SetAsLastSibling();
|
||
obj.transform.GetChild(0).GetComponent<TextMeshProUGUI>().text = str;
|
||
yield return new WaitForSeconds(1);
|
||
float a = 1;
|
||
Color c;
|
||
TextMeshProUGUI t = obj.transform.GetChild(0).GetComponent<TextMeshProUGUI>();
|
||
Image i = obj.GetComponent<Image>();
|
||
while (true)
|
||
{
|
||
a-=Time.deltaTime;
|
||
yield return null;
|
||
c = i.color;
|
||
c.a=a;
|
||
i.color = c;
|
||
|
||
c = t.color;
|
||
c.a = a;
|
||
t.color = c;
|
||
if (a<0)
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
Destroy(obj);
|
||
|
||
}
|
||
public void StopShowPanel()
|
||
{
|
||
StopAllCoroutines();
|
||
}
|
||
private IEnumerator CoShow(UIPanel type, object args, Action<UIBase> onShown, float YCtime)
|
||
{
|
||
yield return new WaitForSeconds(YCtime);
|
||
if (addressMap.ContainsKey(type))
|
||
{
|
||
// ⭐ 优先用已缓存的 prefab,避免重复 LoadAssetAsync
|
||
GameObject prefab = null;
|
||
if (prefabCache.TryGetValue(type, out prefab))
|
||
{
|
||
var panel = GetOrCreatePanel(type, prefab);
|
||
panel?.Show(args);
|
||
onShown?.Invoke(panel);
|
||
yield break;
|
||
}
|
||
var handle = Addressables.LoadAssetAsync<GameObject>(addressMap[type]);
|
||
yield return handle;
|
||
if (handle.Status == AsyncOperationStatus.Succeeded)
|
||
{
|
||
prefabCache[type] = handle.Result;
|
||
handleCache[type] = handle;
|
||
var panel = GetOrCreatePanel(type, handle.Result);
|
||
panel?.Show(args);
|
||
onShown?.Invoke(panel);
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError($"Failed to load UI prefab for {type}");
|
||
}
|
||
|
||
}
|
||
else
|
||
{
|
||
Debug.Log("加载失败" + type.ToString() + "不存在");
|
||
yield break;
|
||
}
|
||
|
||
}
|
||
private IEnumerator CoShow(UIPanel type, object args, Action<UIBase> onShown, UIPanel ClosePanel)
|
||
{
|
||
|
||
if (addressMap.ContainsKey(type))
|
||
{
|
||
// ⭐ 优先用已缓存的 prefab
|
||
GameObject prefab = null;
|
||
if (prefabCache.TryGetValue(type, out prefab))
|
||
{
|
||
UIManager.Instance.Close(ClosePanel);
|
||
var panel = GetOrCreatePanel(type, prefab);
|
||
panel?.Show(args);
|
||
onShown?.Invoke(panel);
|
||
yield break;
|
||
}
|
||
var handle = Addressables.LoadAssetAsync<GameObject>(addressMap[type]);
|
||
yield return handle;
|
||
if (handle.Status == AsyncOperationStatus.Succeeded)
|
||
{
|
||
prefabCache[type] = handle.Result;
|
||
handleCache[type] = handle;
|
||
UIManager.Instance.Close(ClosePanel);
|
||
var panel = GetOrCreatePanel(type, handle.Result);
|
||
panel?.Show(args);
|
||
onShown?.Invoke(panel);
|
||
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError($"Failed to load UI prefab for {type}");
|
||
UIManager.Instance.Get<UIBase>(ClosePanel)?.Show();
|
||
}
|
||
|
||
}
|
||
else
|
||
{
|
||
Debug.Log("加载失败" + type.ToString() + "不存在");
|
||
UIManager.Instance.Get<UIBase>(ClosePanel)?.Show();
|
||
yield break;
|
||
}
|
||
|
||
|
||
}
|
||
/// <summary>
|
||
/// 隐藏
|
||
/// </summary>
|
||
/// <param name="type"></param>
|
||
public void Hide(UIPanel type)
|
||
{
|
||
if (panels.TryGetValue(type, out var panel) && panel != null)
|
||
{
|
||
panel.Hide();
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"Hide failed: panel {type} not found.");
|
||
}
|
||
}
|
||
|
||
public bool IsShown(UIPanel type)
|
||
{
|
||
return panels.TryGetValue(type, out var panel) && panel.gameObject.activeSelf;
|
||
}
|
||
/// <summary>
|
||
/// 获取uiPanel 有就返回没有就返回空
|
||
/// </summary>
|
||
/// <typeparam name="T"></typeparam>
|
||
/// <param name="type"></param>
|
||
/// <returns></returns>
|
||
public T Get<T>(UIPanel type) where T : UIBase
|
||
{
|
||
return panels.TryGetValue(type, out var panel) ? panel as T : null;
|
||
}
|
||
/// <summary>
|
||
/// 获取或者创建panel
|
||
/// </summary>
|
||
/// <param name="type"></param>
|
||
/// <returns></returns>
|
||
private UIBase GetOrCreatePanel(UIPanel type, GameObject obj)
|
||
{
|
||
if (panels.TryGetValue(type, out var existing) && existing != null)
|
||
return existing;
|
||
|
||
if (!addressMap.TryGetValue(type, out var prefabPath) || prefabPath == null)
|
||
{
|
||
Debug.LogError($"No prefab registered for {type}");
|
||
return null;
|
||
}
|
||
|
||
var instance = Instantiate(obj, uiRoot).GetComponent<UIBase>();
|
||
instance.name = $"{type}_Panel";
|
||
instance.Init(type);
|
||
instance.transform.SetAsLastSibling(); // 新面板放到最上层
|
||
|
||
panels[type] = instance;
|
||
return instance;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 关闭并销毁某个面板实例(内存回收)
|
||
/// </summary>
|
||
public void Close(UIPanel type)
|
||
{
|
||
if (panels.TryGetValue(type, out var panel) && panel != null)
|
||
{
|
||
panel.Hide();
|
||
Destroy(panel.gameObject);
|
||
}
|
||
panels.Remove(type);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 关闭并销毁所有面板
|
||
/// </summary>
|
||
public void CloseAll()
|
||
{
|
||
foreach (var kv in panels)
|
||
{
|
||
if (kv.Value != null)
|
||
{
|
||
kv.Value.Hide();
|
||
Destroy(kv.Value.gameObject);
|
||
}
|
||
}
|
||
panels.Clear();
|
||
}
|
||
|
||
// ==================== 预加载 / 释放(优化加载速度)==================
|
||
|
||
/// <summary>
|
||
/// ⭐ 卸载大厅 UI 资源(进游戏房间时调用,配合大厅资源散包使用)。
|
||
/// 按面板枚举值域识别大厅面板(HomeMainPanel=1、子面板 100~199),不依赖 config 的 group 配置。
|
||
/// 会:销毁还开着的大厅面板实例 → 释放 prefab handle → 清缓存。
|
||
/// 大厅子面板(经 SubUIManager 加载的 VIP/背包/邮件等)的 handle 由
|
||
/// GameMainPanel_Base.OnDestroy 里的 subUIManager.ReleaseAll() 释放。
|
||
/// 卸载后回大厅:ShowAsync 会重新加载(配合离房时的 PreloadPanel 预载可秒开)。
|
||
/// </summary>
|
||
public void ReleaseHomeUI()
|
||
{
|
||
// 收集要释放的面板(不直接遍历中修改字典)
|
||
var toRelease = new List<UIPanel>();
|
||
foreach (var kv in panels)
|
||
if (IsHomePanel(kv.Key)) toRelease.Add(kv.Key);
|
||
foreach (var t in toRelease)
|
||
Close(t); // Hide + Destroy 实例 + Remove
|
||
|
||
var handlesToRelease = new List<UIPanel>();
|
||
foreach (var kv in handleCache)
|
||
if (IsHomePanel(kv.Key)) handlesToRelease.Add(kv.Key);
|
||
foreach (var t in handlesToRelease)
|
||
{
|
||
Addressables.Release(handleCache[t]);
|
||
handleCache.Remove(t);
|
||
prefabCache.Remove(t);
|
||
}
|
||
Debug.Log($"[UIManager] ReleaseHomeUI 完成:销毁面板 {toRelease.Count} 个,释放 prefab 缓存 {handlesToRelease.Count} 个");
|
||
}
|
||
|
||
/// <summary>是否大厅相关面板(主面板 + 100~199 子面板区间)</summary>
|
||
private static bool IsHomePanel(UIPanel type)
|
||
{
|
||
int v = (int)type;
|
||
return v == (int)UIPanel.HomeMainPanel || (v >= 100 && v <= 199);
|
||
}
|
||
|
||
/// <summary>
|
||
/// ⭐ 卸载登录 UI 资源(登录成功进入大厅后调用)。
|
||
/// 按值域识别(LoginPanel=0、登录子面板 90~92),销毁实例 + 释放 prefab 句柄 →
|
||
/// LoginUIRes bundle 真正卸载。登出回登录页时 ShowAsync 会重新加载。
|
||
/// </summary>
|
||
public void ReleaseLoginUI()
|
||
{
|
||
var toRelease = new List<UIPanel>();
|
||
foreach (var kv in panels)
|
||
if (IsLoginPanel(kv.Key)) toRelease.Add(kv.Key);
|
||
foreach (var t in toRelease)
|
||
Close(t);
|
||
|
||
var handlesToRelease = new List<UIPanel>();
|
||
foreach (var kv in handleCache)
|
||
if (IsLoginPanel(kv.Key)) handlesToRelease.Add(kv.Key);
|
||
foreach (var t in handlesToRelease)
|
||
{
|
||
Addressables.Release(handleCache[t]);
|
||
handleCache.Remove(t);
|
||
prefabCache.Remove(t);
|
||
}
|
||
Debug.Log($"[UIManager] ReleaseLoginUI 完成:销毁面板 {toRelease.Count} 个,释放 prefab 缓存 {handlesToRelease.Count} 个");
|
||
}
|
||
|
||
/// <summary>是否登录相关面板(LoginPanel + 90~92 子面板区间)</summary>
|
||
private static bool IsLoginPanel(UIPanel type)
|
||
{
|
||
int v = (int)type;
|
||
return v == (int)UIPanel.LoginPanel || (v >= 90 && v <= 92);
|
||
}
|
||
|
||
/// <summary>
|
||
/// ⭐ 预加载单个面板 prefab 到缓存(离房回大厅前调用,让 ShowAsync 秒开)。
|
||
/// 已缓存则直接返回。
|
||
/// </summary>
|
||
public Coroutine PreloadPanel(UIPanel type, Action onDone = null)
|
||
{
|
||
return StartCoroutine(IEPreloadPanel(type, onDone));
|
||
}
|
||
|
||
private IEnumerator IEPreloadPanel(UIPanel type, Action onDone)
|
||
{
|
||
if (prefabCache.ContainsKey(type) || !addressMap.ContainsKey(type))
|
||
{
|
||
onDone?.Invoke();
|
||
yield break;
|
||
}
|
||
var handle = Addressables.LoadAssetAsync<GameObject>(addressMap[type]);
|
||
yield return handle;
|
||
if (handle.Status == AsyncOperationStatus.Succeeded)
|
||
{
|
||
prefabCache[type] = handle.Result;
|
||
handleCache[type] = handle;
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"[UIManager] PreloadPanel 失败: {type}");
|
||
}
|
||
onDone?.Invoke();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 按 config 中的 group 字段预加载一组面板的 prefab 到缓存。
|
||
/// 调用时机:进入某个场景时(如登录成功后 PreloadGroup(Home),进游戏前 PreloadGroup(GameFish))
|
||
/// </summary>
|
||
public Coroutine PreloadGroup(UIPanelGroup group, Action onDone = null)
|
||
{
|
||
return StartCoroutine(IEPreloadGroup(group, onDone));
|
||
}
|
||
|
||
private IEnumerator IEPreloadGroup(UIPanelGroup group, Action onDone)
|
||
{
|
||
if (config == null)
|
||
{
|
||
onDone?.Invoke();
|
||
yield break;
|
||
}
|
||
|
||
// 收集需要加载的条目(跳过已缓存的)
|
||
var todo = new System.Collections.Generic.List<UIPanelAddressConfig.PanelEntry>();
|
||
foreach (var entry in config.panels)
|
||
{
|
||
if (entry.group == group && !prefabCache.ContainsKey(entry.type) && !string.IsNullOrEmpty(entry.address))
|
||
todo.Add(entry);
|
||
}
|
||
|
||
if (todo.Count == 0)
|
||
{
|
||
onDone?.Invoke();
|
||
yield break;
|
||
}
|
||
|
||
// 并行加载
|
||
var handles = new System.Collections.Generic.List<AsyncOperationHandle<GameObject>>();
|
||
foreach (var entry in todo)
|
||
{
|
||
var h = Addressables.LoadAssetAsync<GameObject>(entry.address);
|
||
handles.Add(h);
|
||
}
|
||
|
||
// 等待全部完成
|
||
bool allDone = false;
|
||
while (!allDone)
|
||
{
|
||
allDone = true;
|
||
foreach (var h in handles)
|
||
{
|
||
if (!h.IsDone)
|
||
{
|
||
allDone = false;
|
||
break;
|
||
}
|
||
}
|
||
if (!allDone) yield return null;
|
||
}
|
||
|
||
// 存入缓存
|
||
for (int i = 0; i < todo.Count; i++)
|
||
{
|
||
if (handles[i].Status == AsyncOperationStatus.Succeeded)
|
||
{
|
||
prefabCache[todo[i].type] = handles[i].Result;
|
||
handleCache[todo[i].type] = handles[i];
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"[PreloadGroup] 加载失败: {todo[i].type}");
|
||
}
|
||
}
|
||
|
||
onDone?.Invoke();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 按 Addressables Label 预加载(用 Addressables.LoadAssetsAsync 批量加载带该 Label 的资源)。
|
||
/// 需要你在 Addressables Groups 窗口给 prefab 打上对应 Label。
|
||
/// </summary>
|
||
public Coroutine PreloadByLabel(string label, Action onDone = null)
|
||
{
|
||
return StartCoroutine(IEPreloadByLabel(label, onDone));
|
||
}
|
||
|
||
private IEnumerator IEPreloadByLabel(string label, Action onDone)
|
||
{
|
||
if (string.IsNullOrEmpty(label))
|
||
{
|
||
onDone?.Invoke();
|
||
yield break;
|
||
}
|
||
|
||
var handle = Addressables.LoadAssetsAsync<GameObject>(label, null);
|
||
yield return handle;
|
||
|
||
if (handle.Status == AsyncOperationStatus.Succeeded)
|
||
{
|
||
// 按 prefab 名匹配回 UIPanel(prefab 名需与 address 或 type 名对应)
|
||
// 这里只缓存到 labelHandleCache,Show 时如果 address 匹配则复用
|
||
_labelHandles[label] = handle;
|
||
foreach (var prefab in handle.Result)
|
||
{
|
||
// 用 prefab.name 作为 key 缓存(可选:在 config 里 address == prefab.name 时才能命中)
|
||
// 这里不做强制匹配,仅释放用
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"[PreloadByLabel] Label '{label}' 加载失败");
|
||
}
|
||
|
||
onDone?.Invoke();
|
||
}
|
||
|
||
/// <summary>Label 预加载的 handle 缓存(用于释放)</summary>
|
||
private readonly Dictionary<string, AsyncOperationHandle<IList<GameObject>>> _labelHandles = new Dictionary<string, AsyncOperationHandle<IList<GameObject>>>();
|
||
|
||
/// <summary>
|
||
/// 释放指定分组的 prefab 缓存(退出场景时调用,释放内存)。
|
||
/// 会先销毁该组已实例化的面板,再释放 handle。
|
||
/// </summary>
|
||
public void ReleaseGroup(UIPanelGroup group)
|
||
{
|
||
if (config == null) return;
|
||
|
||
foreach (var entry in config.panels)
|
||
{
|
||
if (entry.group != group) continue;
|
||
|
||
// 先销毁实例
|
||
if (panels.TryGetValue(entry.type, out var panel) && panel != null)
|
||
{
|
||
panel.Hide();
|
||
Destroy(panel.gameObject);
|
||
panels.Remove(entry.type);
|
||
}
|
||
|
||
// 再释放 handle
|
||
if (handleCache.TryGetValue(entry.type, out var handle))
|
||
{
|
||
Addressables.Release(handle);
|
||
handleCache.Remove(entry.type);
|
||
}
|
||
prefabCache.Remove(entry.type);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 释放指定 Label 的预加载资源
|
||
/// </summary>
|
||
public void ReleaseLabel(string label)
|
||
{
|
||
if (_labelHandles.TryGetValue(label, out var handle))
|
||
{
|
||
Addressables.Release(handle);
|
||
_labelHandles.Remove(label);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 释放所有 prefab 缓存(如登出时调用)
|
||
/// </summary>
|
||
public void ReleaseAll()
|
||
{
|
||
CloseAll();
|
||
|
||
foreach (var kv in handleCache)
|
||
{
|
||
Addressables.Release(kv.Value);
|
||
}
|
||
handleCache.Clear();
|
||
prefabCache.Clear();
|
||
|
||
foreach (var kv in _labelHandles)
|
||
{
|
||
Addressables.Release(kv.Value);
|
||
}
|
||
_labelHandles.Clear();
|
||
}
|
||
}
|