367 lines
12 KiB
C#
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;
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
}
|