Files
5.24BuYu/Assets/Framework/Tool/UITool/UIManager.cs
T
2026-09-22 03:59:20 +08:00

731 lines
23 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;
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>
/// 按 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();
}
}