Files
5.24BuYu/Assets/Framework/Tool/UITool/UIManager.cs
T
2026-10-12 15:32:36 +08:00

898 lines
30 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
// ==================== 加载中界面(obj_JiaZaiZhong) ====================
// ⭐ 资源加载超过 jiaZaiZhongDelay(默认 0.1 秒)还没结束才显示"加载中"界面,避免快速加载闪一下。
// 支持多段加载并发(引用计数):所有 BeginLoading 都配对 EndLoading 后才隐藏。
// 用法:UIManager.BeginLoading(); try { await 加载过程(); } finally { UIManager.EndLoading(); }
[Tooltip("资源加载超过该秒数还没结束才显示\"加载中\"界面(避免快速加载闪一下)")]
[SerializeField] private float jiaZaiZhongDelay = 0.1f;
private int _loadingRefCount; // 进行中的资源加载段数
private bool _loadingShown; // "加载中"界面是否已显示
private Coroutine _loadingDelayCoroutine; // 延迟显示协程(0.1 秒内加载结束就取消,不显示)
private bool _jiaZaiZhongWarned; // obj_JiaZaiZhong 未挂引用时只提醒一次
/// <summary>开始一段资源加载(与 EndLoading 成对):超过 0.1 秒还没结束才显示"加载中"界面</summary>
public static void BeginLoading() { Instance.BeginLoadingInternal(); }
/// <summary>结束一段资源加载(与 BeginLoading 成对)</summary>
public static void EndLoading() { Instance.EndLoadingInternal(); }
private void BeginLoadingInternal()
{
_loadingRefCount++;
if (obj_JiaZaiZhong == null)
{
if (!_jiaZaiZhongWarned)
{
_jiaZaiZhongWarned = true;
Debug.LogWarning("[UIManager] 加载中界面 obj_JiaZaiZhong 未挂引用,资源加载提示不会显示");
}
return;
}
if (_loadingShown || _loadingDelayCoroutine != null) return; // 已显示 / 已在等待显示
_loadingDelayCoroutine = StartCoroutine(CoShowJiaZaiZhongAfterDelay());
}
private void EndLoadingInternal()
{
if (_loadingRefCount > 0) _loadingRefCount--;
if (_loadingRefCount > 0) return; // 还有其它加载在进行,继续显示
if (_loadingDelayCoroutine != null)
{
StopCoroutine(_loadingDelayCoroutine);
_loadingDelayCoroutine = null;
}
if (_loadingShown)
{
_loadingShown = false;
if (obj_JiaZaiZhong != null) obj_JiaZaiZhong.SetActive(false);
}
}
private IEnumerator CoShowJiaZaiZhongAfterDelay()
{
yield return new WaitForSecondsRealtime(jiaZaiZhongDelay);
_loadingDelayCoroutine = null;
if (_loadingRefCount <= 0 || obj_JiaZaiZhong == null) yield break;
obj_JiaZaiZhong.SetActive(true);
obj_JiaZaiZhong.transform.SetAsLastSibling(); // 盖到最上层,避免被别的面板挡住
_loadingShown = true;
}
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;
}
// ⭐ 面板资源未缓存:异步加载超过 0.1 秒才显示"加载中"界面
BeginLoadingInternal();
var handle = Addressables.LoadAssetAsync<GameObject>(addressMap[type]);
yield return handle;
EndLoadingInternal();
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;
}
// ⭐ 面板资源未缓存:异步加载超过 0.1 秒才显示"加载中"界面
BeginLoadingInternal();
var handle = Addressables.LoadAssetAsync<GameObject>(addressMap[type]);
yield return handle;
EndLoadingInternal();
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();
}
}