Files
2026-09-10 17:29:24 +08:00

367 lines
12 KiB
C#

using System.Collections.Generic;
using UnityEngine;
namespace MFrame
{
/// <summary>
/// 面板创建委托,用于动态创建UI面板实例
/// </summary>
/// <returns>返回创建的面板实例</returns>
public delegate IPanelBase PanelCreate(PanelBaseData data, System.Action<IPanelBase> completeHandler);
/// <summary>
/// UI面板基类,提供基础UI功能和资源加载管理
/// 继承自UIBase,用于实现具体的UI面板
/// </summary>
public class PanelBase<V> : UIBase, IPanelBase where V : UI_PanelBase
{
/// <summary>
/// UI层级索引,控制面板在UI层级中的显示顺序
/// </summary>
protected UILayerIdx _layerIdx;
/// <summary>
/// 面板数据,用于存储和传递面板所需的数据
/// </summary>
protected PanelBaseData _data;
/// <summary>
/// 面板视图组件,负责处理UI的具体显示和交互逻辑
/// </summary>
protected V _view;
/// <summary>
/// 预制体资源路径
/// </summary>
protected string _url;
protected string _registerModule;
// 全屏遮挡以组件形式配置:在面板预制体根节点挂 FullScreenCover 组件即生效(策划可自助添加/移除)
/// <summary>
/// 面板预制体根节点上的 FullScreenCover 组件(未挂则为 null,即非全屏面板)
/// </summary>
private FullScreenCover _fullScreenCover;
/// <summary>
/// 是否正被上层全屏面板隐藏
/// </summary>
private bool _hiddenByCover;
/// <summary>
/// 被本面板(全屏遮挡)隐藏的下层面板列表
/// </summary>
private readonly List<IPanelBase> _panelsHiddenByMe = new List<IPanelBase>();
/// <summary>
/// 业务是否已关闭(防止被遮挡隐藏的已关闭面板被“复活”)
/// </summary>
private bool _closed;
/// <summary>
/// 构造函数,初始化面板基本属性
/// </summary>
/// <param name="autoDestroy">是否在关闭时自动销毁</param>
public PanelBase(bool autoDestroy) : base(null)
{
_autoDestroy = autoDestroy;
}
/// <summary>
/// 在关闭界面的时候是否进行销毁
/// </summary>
/// <returns></returns>
public bool IsAutoDestroy()
{
return _autoDestroy;
}
// —— 全屏遮挡:IPanelBase 接口实现 ——
UILayerIdx IPanelBase.panelLayerIdx => _layerIdx;
bool IPanelBase.isKeepShowWhenCovered => _fullScreenCover != null && _fullScreenCover.enabled && _fullScreenCover.isKeepShowWhenCovered;
bool IPanelBase.isHiddenByCover => _hiddenByCover;
bool IPanelBase.isClosed => _closed;
/// <summary>
/// 全屏遮挡优化:由上层全屏面板调用,隐藏本面板(省去被盖住的 DrawCall)
/// </summary>
public void SetCoverHidden()
{
_hiddenByCover = true;
if (_view != null)
{
_view.gameObject.SetActive(false);
}
}
/// <summary>
/// 全屏遮挡优化:恢复本面板显示(幂等;面板视图已销毁时自动跳过)
/// </summary>
public void RestoreFromCover()
{
if (!_hiddenByCover)
{
return;
}
_hiddenByCover = false;
if (_view != null)
{
_view.gameObject.SetActive(true);
}
}
/// <summary>
/// 尝试异步加载UI预制体
/// </summary>
/// <param name="url">预制体资源路径</param>
/// <param name="layerIdx">目标UI层级</param>
/// <param name="data">面板初始化数据</param>
protected virtual void _TryInit(string registerModule, string url, UILayerIdx layerIdx, PanelBaseData data, System.Action<IPanelBase> completeHandler = null)
{
_registerModule = registerModule;
_url = url;
// 初始化并启用资源加载器
_layerIdx = layerIdx;
ResLoaderMgr.Instance.TryLoadAsync(_registerModule, _url, (float progress) =>
{
_OnProgress(progress);
}, (Object prefab) =>
{
if (prefab != null)
{
// 加载成功后实例化预制体并设置到指定层级
_TryInitGameObject(prefab as GameObject, layerIdx, data, completeHandler);
}
else
{
// 处理加载失败的情况
_OnLoadFail();
}
});
}
/// <summary>
/// 初始化 UI预制体
/// </summary>
/// <param name="layerIdx">目标UI层级</param>
/// <param name="data">面板初始化数据</param>
protected virtual void _TryInitGameObject(GameObject prefab, UILayerIdx layerIdx, PanelBaseData data, System.Action<IPanelBase> completeHandler = null)
{
LayerItem litem = GameGlobalData.GetLayerItem(_layerIdx);
GameObject go = UnityUtils.InstantiatePrefab(prefab, litem.transform);
transform = go.transform;
// 获取视图组件并完成初始化
_view = transform.GetComponent<V>();
// 缓存全屏遮挡组件(策划在预制体根节点上挂载,未挂则为 null)
_fullScreenCover = transform.GetComponent<FullScreenCover>();
_view.AddUpdateHandler(this.Update);
_OnLoadComplete();
Show(data, completeHandler);
}
public V view
{
get
{
return _view;
}
}
protected virtual void _OnProgress(float progress)
{
}
/// <summary>
/// 资源加载完成回调,子类可重写此方法实现自定义加载完成逻辑
/// </summary>
protected virtual void _OnLoadComplete()
{
foreach (var btn in this._view.btnsClose)
{
if (btn != null)
this.AddBtnOnClick(btn, this.Close);
}
}
/// <summary>
/// 资源加载失败回调,子类可重写此方法实现自定义失败处理逻辑
/// </summary>
protected virtual void _OnLoadFail()
{
}
/// <summary>
/// 打开面板,可传入自定义数据
/// </summary>
/// <param name="data">面板初始化数据</param>
public void Show(PanelBaseData data, System.Action<IPanelBase> completeHandler)
{
_data = data;
_closed = false;
if (this._view != null)
{
// 若此前被上层全屏面板隐藏,主动 Show 视为恢复显示
//(隐藏者列表中的遗留记录由 RestorePanelsHiddenByMe 的 isHiddenByCover 守卫失效,无需清理)
if (_hiddenByCover)
{
RestoreFromCover();
}
this._view.gameObject.SetActive(true);
}
_OnShow();
// 全屏遮挡面板(预制体根节点挂 FullScreenCover 组件且处于启用状态):
// 打开时隐藏下层正在显示的面板,省去被盖住的 DrawCall
if (_fullScreenCover != null && _fullScreenCover.enabled)
{
HidePanelsUnderThis();
}
_view.Show(() =>
{
completeHandler?.Invoke(this);
});
}
/// <summary>
/// 判断面板是否正在显示
/// </summary>
/// <returns></returns>
public bool isShowing()
{
if (_view == null)
{
return false;
}
// 被上层全屏面板隐藏时仍视为“打开中”,保持业务语义不变;已关闭的面板不算
return !_closed && (_view.gameObject.activeSelf || _hiddenByCover);
}
public bool isLoading()
{
return _view == null;
}
protected virtual void _OnShow()
{
}
/// <summary>
/// 关闭面板
/// </summary>
public void Close()
{
PanelMgr.Instance.Close(_registerModule);
}
/// <summary>
/// 关闭面板(内部使用)
/// </summary>
public void ___CloseForPanelMgr(System.Action complete)
{
// 标记业务已关闭,防止关闭过程中被遮挡机制“复活”
_closed = true;
// 若正被上层全屏面板隐藏(inactive 会导致关闭动画不更新而卡死),先恢复再播关闭动画
if (_hiddenByCover)
{
RestoreFromCover();
}
_view.Close(() =>
{
// 关闭完成后恢复被本面板隐藏的下层面板
RestorePanelsHiddenByMe();
_OnClose();
complete?.Invoke();
});
}
protected virtual void _OnClose()
{
}
/// <summary>
/// 全屏遮挡优化:隐藏本面板下方所有层级中正在显示的面板,
/// 被完全遮住的 UI 不再提交 DrawCall(关闭/销毁时由 RestorePanelsHiddenByMe 恢复)。
/// </summary>
private void HidePanelsUnderThis()
{
Dictionary<string, PanelMsgItem> items = PanelMgr.Instance.items;
if (items == null)
{
return;
}
foreach (KeyValuePair<string, PanelMsgItem> kvp in items)
{
PanelMsgItem item = kvp.Value;
if (item.panel == null || ReferenceEquals(item.panel, this))
{
continue;
}
IPanelBase panel = item.panel;
if (panel.panelLayerIdx < _layerIdx
&& !panel.isKeepShowWhenCovered
&& !panel.isHiddenByCover
&& panel.isShowing())
{
_panelsHiddenByMe.Add(panel);
panel.SetCoverHidden();
}
}
}
/// <summary>
/// 恢复被本面板隐藏的下层面板(幂等,可重复调用)
/// </summary>
private void RestorePanelsHiddenByMe()
{
int len = _panelsHiddenByMe.Count;
for (int i = 0; i < len; i++)
{
IPanelBase panel = _panelsHiddenByMe[i];
// 已关闭的面板不恢复,防止“复活”已关闭的界面
if (panel != null && panel.isHiddenByCover && !panel.isClosed)
{
panel.RestoreFromCover();
}
}
_panelsHiddenByMe.Clear();
}
/// <summary>
/// 销毁面板实例,清理资源防止内存泄漏
/// </summary>
protected override void _Destroy()
{
// 兼容未走 Close 直接销毁的路径,确保下层面板一定被恢复
RestorePanelsHiddenByMe();
base._Destroy();
// 清理资源加载器
ResLoaderMgr.Instance.TryRemoveLoads(_registerModule);
// 清除面板数据
_data = null;
_fullScreenCover = null;
if (_view != null)
{
_view.RemoveUpdateHandler(this.Update);
GameObject.Destroy(_view.gameObject);
_view = null;
}
}
}
}