前言
在前面的代码中,我们有看到 GameEntry.UI.OpenUIForm(UIFormId.MenuForm, this);
这篇文章就从这行代码开始追踪,探索GF框架的UI组件
protected override void OnEnter(ProcedureOwner procedureOwner)
{
base.OnEnter(procedureOwner);
GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId, OnOpenUIFormSuccess);
m_StartGame = false;
GameEntry.UI.OpenUIForm(UIFormId.MenuForm, this);
}
代码追踪
//在UIExtension.cs中的静态扩展方法
public static int? OpenUIForm(this UIComponent uiComponent, UIFormId uiFormId, object userData = null)
{
return uiComponent.OpenUIForm((int)uiFormId, userData);
}
public static int? OpenUIForm(this UIComponent uiComponent, int uiFormId, object userData = null)
{
//从配置表中获取UI的配置信息
IDataTable<DRUIForm> dtUIForm = GameEntry.DataTable.GetDataTable<DRUIForm>();
DRUIForm drUIForm = dtUIForm.GetDataRow(uiFormId);
if (drUIForm == null)
{
Log.Warning("Can not load UI form '{0}' from data table.", uiFormId.ToString());
return null;
}
//从配置信息中读取AssetName, 这个接口获取的是Ui的预设体路径
string assetName = AssetUtility.GetUIFormAsset(drUIForm.AssetName);
if (!drUIForm.AllowMultiInstance)
{
if (uiComponent.IsLoadingUIForm(assetName))
{
return null;
}
if (uiComponent.HasUIForm(assetName))
{
return null;
}
}
//继续追,注意这里的传参
return uiComponent.OpenUIForm(assetName, drUIForm.UIGroupName, Constant.AssetPriority.UIFormAsset, drUIForm.PauseCoveredUIForm, userData);
}
//UIComponent.cs中调用UIManager.OpenUIForm
public int OpenUIForm(string uiFormAssetName, string uiGroupName, int priority, bool pauseCoveredUIForm, object userData)
{
return m_UIManager.OpenUIForm(uiFormAssetName, uiGroupName, priority, pauseCoveredUIForm, userData);
}
//UIManager.cs, 下面的代码中,我删除了一些判空操作,方便阅读
public int OpenUIForm(string uiFormAssetName, string uiGroupName, int priority, bool pauseCoveredUIForm, object userData)
{
UIGroup uiGroup = (UIGroup)GetUIGroup(uiGroupName);
// 对于UIManager来说,Serial一直是递增的,所有每一个UI实例的serialId都是不一样的
int serialId = ++m_Serial;
//首先从池中获取,如果没获取到,使用资源管理器加载UI资源
UIFormInstanceObject uiFormInstanceObject = m_InstancePool.Spawn(uiFormAssetName);
if (uiFormInstanceObject == null)
{
//加载资源,资源加载模块以后另开文章讨论,
m_UIFormsBeingLoaded.Add(serialId, uiFormAssetName);
m_ResourceManager.LoadAsset(uiFormAssetName, priority, m_LoadAssetCallbacks, OpenUIFormInfo.Create(serialId, uiGroup, pauseCoveredUIForm, userData));
}
else
{
//打开资源
InternalOpenUIForm(serialId, uiFormAssetName, uiGroup, uiFormInstanceObject.Target, pauseCoveredUIForm, false, 0f, userData);
}
return serialId;
}
资源加载模块以后另开文章讨论,假设现在获取了实例,走到 InternalOpenUIForm中
private void InternalOpenUIForm(int serialId, string uiFormAssetName, UIGroup uiGroup, object uiFormInstance, bool pauseCoveredUIForm, bool isNewInstance, float duration, object userData)
{
try
{
//实例化UI
IUIForm uiForm = m_UIFormHelper.CreateUIForm(uiFormInstance, uiGroup, userData);
if (uiForm == null)
{
throw new GameFrameworkException("Can not create UI form in UI form helper.");
}
//调用OnInit, OnOpen等方法
uiForm.OnInit(serialId, uiFormAssetName, uiGroup, pauseCoveredUIForm, isNewInstance, userData);
uiGroup.AddUIForm(uiForm);
uiForm.OnOpen(userData);
uiGroup.Refresh();
//打开成功了,触发事件,还记得上一节Event组件吧
if (m_OpenUIFormSuccessEventHandler != null)
{
OpenUIFormSuccessEventArgs openUIFormSuccessEventArgs = OpenUIFormSuccessEventArgs.Create(uiForm, duration, userData);
m_OpenUIFormSuccessEventHandler(this, openUIFormSuccessEventArgs);
ReferencePool.Release(openUIFormSuccessEventArgs);
}
}
catch (Exception exception)
{
if (m_OpenUIFormFailureEventHandler != null)
{
OpenUIFormFailureEventArgs openUIFormFailureEventArgs = OpenUIFormFailureEventArgs.Create(serialId, uiFormAssetName, uiGroup.Name, pauseCoveredUIForm, exception.ToString(), userData);
m_OpenUIFormFailureEventHandler(this, openUIFormFailureEventArgs);
ReferencePool.Release(openUIFormFailureEventArgs);
return;
}
throw;
}
}
GameEntry.UI.OpenUIForm这个接口的执行总结,
首先读取配置,获取UIForm的相关信息,如资源名称,分组
首先到对象池重查找UI实例,
如果没有查找到使用资源加载模块,加载UI实例,
在获取UI实例后,创建UIForm组件,执行UIForm的 OnInit ,添加分组, OnOpen方法,并且触发OpenUIFormSuccessEventArgs 事件
IUIForm
IUIForm是封装的UI生命周期的接口类,主要的接口有 SerialId, UIFormAssetName,Handle,
UIGroup, DepthInUIGroup,OnInit,OnOpen,OnClose等,请自行查看
UIForm是对IUIForm的实现,其中 //生命周期的实现封装到了UIFormLogic中
public sealed class UIForm : MonoBehaviour, IUIForm
{
private int m_SerialId;
private string m_UIFormAssetName;
private IUIGroup m_UIGroup;
private int m_DepthInUIGroup;
private bool m_PauseCoveredUIForm;
//生命周期的实现封装到了UIFormLogic中
private UIFormLogic m_UIFormLogic;
/// <summary>
/// 获取界面序列编号。
/// </summary>
public int SerialId
{
get
{
return m_SerialId;
}
}
public UIFormLogic Logic
{
get
{
return m_UIFormLogic;
}
}
public void OnInit(int serialId, string uiFormAssetName, IUIGroup uiGroup, bool pauseCoveredUIForm, bool isNewInstance, object userData)
{
m_SerialId = serialId;
m_UIFormAssetName = uiFormAssetName;
m_UIGroup = uiGroup;
m_DepthInUIGroup = 0;
m_PauseCoveredUIForm = pauseCoveredUIForm;
if (!isNewInstance)
{
return;
}
m_UIFormLogic = GetComponent<UIFormLogic>();
if (m_UIFormLogic == null)
{
Log.Error("UI form '{0}' can not get UI form logic.", uiFormAssetName);
return;
}
try
{
m_UIFormLogic.OnInit(userData);
}
catch (Exception exception)
{
Log.Error("UI form '[{0}]{1}' OnInit with exception '{2}'.", m_SerialId, m_UIFormAssetName, exception);
}
}
}
UGUIForm继承子UIFormLogic,实现了生命周期中通用的一些功能
public abstract class UGuiForm : UIFormLogic
{
public const int DepthFactor = 100;
private const float FadeTime = 0.3f;
private static Font s_MainFont = null;
private Canvas m_CachedCanvas = null;
private CanvasGroup m_CanvasGroup = null;
private List<Canvas> m_CachedCanvasContainer = new List<Canvas>();
public int OriginalDepth
{
get;
private set;
}
......
#if UNITY_2017_3_OR_NEWER
protected override void OnInit(object userData)
#else
protected internal override void OnInit(object userData)
#endif
{
base.OnInit(userData);
//添加Canvas,
m_CachedCanvas = gameObject.GetOrAddComponent<Canvas>();
m_CachedCanvas.overrideSorting = true;
OriginalDepth = m_CachedCanvas.sortingOrder;
m_CanvasGroup = gameObject.GetOrAddComponent<CanvasGroup>();
RectTransform transform = GetComponent<RectTransform>();
//设置Transform
transform.anchorMin = Vector2.zero;
transform.anchorMax = Vector2.one;
transform.anchoredPosition = Vector2.zero;
transform.sizeDelta = Vector2.zero;
gameObject.GetOrAddComponent<GraphicRaycaster>();
//获取组件
Text[] texts = GetComponentsInChildren<Text>(true);
for (int i = 0; i < texts.Length; i++)
{
texts[i].font = s_MainFont;
if (!string.IsNullOrEmpty(texts[i].text))
{
texts[i].text = GameEntry.Localization.GetString(texts[i].text);
}
}
}
}
平时开发UI界面,只需要继承UGUIForm,实现我们自定义的功能即可,参考MenuForm
public class MenuForm : UGuiForm
{
[SerializeField]
private GameObject m_QuitButton = null;
private ProcedureMenu m_ProcedureMenu = null;
public void OnStartButtonClick()
{
m_ProcedureMenu.StartGame();
}
public void OnSettingButtonClick()
{
GameEntry.UI.OpenUIForm(UIFormId.SettingForm);
}
public void OnAboutButtonClick()
{
GameEntry.UI.OpenUIForm(UIFormId.AboutForm);
}
public void OnQuitButtonClick()
{
GameEntry.UI.OpenDialog(new DialogParams()
{
Mode = 2,
Title = GameEntry.Localization.GetString("AskQuitGame.Title"),
Message = GameEntry.Localization.GetString("AskQuitGame.Message"),
OnClickConfirm = delegate (object userData) { UnityGameFramework.Runtime.GameEntry.Shutdown(ShutdownType.Quit); },
});
}
#if UNITY_2017_3_OR_NEWER
protected override void OnOpen(object userData)
#else
protected internal override void OnOpen(object userData)
#endif
{
base.OnOpen(userData);
m_ProcedureMenu = (ProcedureMenu)userData;
if (m_ProcedureMenu == null)
{
Log.Warning("ProcedureMenu is invalid when open MenuForm.");
return;
}
m_QuitButton.SetActive(Application.platform != RuntimePlatform.IPhonePlayer);
}
#if UNITY_2017_3_OR_NEWER
protected override void OnClose(bool isShutdown, object userData)
#else
protected internal override void OnClose(bool isShutdown, object userData)
#endif
{
m_ProcedureMenu = null;
base.OnClose(isShutdown, userData);
}
UI代码分析到此结束,下面看一下日常开发
UI面板开发流程:
1. 制作UI面板预设,添加脚本,脚本需要继承自 UGUIForm, 在脚本中实现相应的功能,见上文
2. 编写配置文件
注意UI预设体的路径和配置的AssetName,必须正确映射, 默认的路径如下图:
这里是映射关系,如果要改默认路径,记得改一下AssetUtility中的路径映射
//这是UIExtension.cs文件中,OPenUIForm中的代码
//通过配置的AssetName获取的其实是预设体的路径
string assetName = AssetUtility.GetUIFormAsset(drUIForm.AssetName);
//AssetUtility.cs
public static string GetUIFormAsset(string assetName)
{
return Utility.Text.Format("Assets/GameMain/UI/UIForms/{0}.prefab", assetName);
}
3. 调用 GameEntry.UI.OpenUIForm(UIFormId.MenuForm, this); 就可以加载UI了,
UIComponent
UIComponent封装了UI操作的一些常用接口,可以使用GameEntry.UI.xxx调用,举几个粒子
关闭自身
点击按钮切换子页签
2022年11月18日,再读源码
问题1:OpenUI中都干了什么
这种写法其实是有楼栋的,当返回值为null时,可能会覆盖之前的有效值
正确的写法应该是:
配置表参数:
# 界面配置表
# Id AssetName UIGroupName AllowMultiInstance PauseCoveredUIForm
# int string string bool bool
# 界面编号 策划备注 资源名称 界面组名称 是否允许多个界面实例 是否暂停被其覆盖的界面
1 弹出框 DialogForm Default TRUE TRUE
100 主菜单 MenuForm Default FALSE TRUE
101 设置 SettingForm Default FALSE TRUE
102 关于 AboutForm Default FALSE TRUE
//
return uiComponent.OpenUIForm(assetName, drUIForm.UIGroupName, Constant.AssetPriority.UIFormAsset, drUIForm.PauseCoveredUIForm, userData);
public static OpenUIFormInfo Create(int serialId, UIGroup uiGroup, bool pauseCoveredUIForm, object userData)
{
OpenUIFormInfo openUIFormInfo = ReferencePool.Acquire<OpenUIFormInfo>();
openUIFormInfo.m_SerialId = serialId;
openUIFormInfo.m_UIGroup = uiGroup;
openUIFormInfo.m_PauseCoveredUIForm = pauseCoveredUIForm;
openUIFormInfo.m_UserData = userData;
return openUIFormInfo;
}
//加载回调
m_LoadAssetCallbacks = new LoadAssetCallbacks(LoadAssetSuccessCallback, LoadAssetFailureCallback, LoadAssetUpdateCallback, LoadAssetDependencyAssetCallback);
private void LoadAssetSuccessCallback(string uiFormAssetName, object uiFormAsset, float duration, object userData)
{
OpenUIFormInfo openUIFormInfo = (OpenUIFormInfo)userData;
if (openUIFormInfo == null)
{
throw new GameFrameworkException("Open UI form info is invalid.");
}
if (m_UIFormsToReleaseOnLoad.Contains(openUIFormInfo.SerialId))
{
m_UIFormsToReleaseOnLoad.Remove(openUIFormInfo.SerialId);
ReferencePool.Release(openUIFormInfo);
m_UIFormHelper.ReleaseUIForm(uiFormAsset, null);
return;
}
m_UIFormsBeingLoaded.Remove(openUIFormInfo.SerialId);
UIFormInstanceObject uiFormInstanceObject = UIFormInstanceObject.Create(uiFormAssetName, uiFormAsset, m_UIFormHelper.InstantiateUIForm(uiFormAsset), m_UIFormHelper);
m_InstancePool.Register(uiFormInstanceObject, true);
InternalOpenUIForm(openUIFormInfo.SerialId, uiFormAssetName, openUIFormInfo.UIGroup, uiFormInstanceObject.Target, openUIFormInfo.PauseCoveredUIForm, true, duration, openUIFormInfo.UserData);
ReferencePool.Release(openUIFormInfo);
}
在加载完成资源之后,
生成实例化对象UIFormInstanceObject ,
把实例化对象注册到对象池
public static UIFormInfo Create(IUIForm uiForm)
{
if (uiForm == null)
{
throw new GameFrameworkException("UI form is invalid.");
}
UIFormInfo uiFormInfo = ReferencePool.Acquire<UIFormInfo>();
uiFormInfo.m_UIForm = uiForm;
uiFormInfo.m_Paused = true;
uiFormInfo.m_Covered = true;
return uiFormInfo;
}
public void AddUIForm(IUIForm uiForm)
{
m_UIFormInfos.AddFirst(UIFormInfo.Create(uiForm));
}
public void OnOpen(object userData)
{
try
{
//转发
m_UIFormLogic.OnOpen(userData);
}
catch (Exception exception)
{
Log.Error("UI form '[{0}]{1}' OnOpen with exception '{2}'.", m_SerialId, m_UIFormAssetName, exception);
}
}
//实际调用
protected internal virtual void OnOpen(object userData)
{
m_Available = true;
Visible = true;
}
层级逻辑刷新
public void Refresh()
{
//新打开的 是 AddFirst
LinkedListNode<UIFormInfo> current = m_UIFormInfos.First;
bool pause = m_Pause;
bool cover = false;
int depth = UIFormCount;
while (current != null && current.Value != null)
{
LinkedListNode<UIFormInfo> next = current.Next;
current.Value.UIForm.OnDepthChanged(Depth, depth--);
if (current.Value == null)
{
return;
}
//当前组暂停,调用form的pause和cover
if (pause)
{
if (!current.Value.Covered)
{
current.Value.Covered = true;
current.Value.UIForm.OnCover();
if (current.Value == null)
{
return;
}
}
if (!current.Value.Paused)
{
current.Value.Paused = true;
current.Value.UIForm.OnPause();
if (current.Value == null)
{
return;
}
}
}
else //当前组没有暂停
{
//当前Form暂停,则恢复
if (current.Value.Paused)
{
current.Value.Paused = false;
current.Value.UIForm.OnResume();
if (current.Value == null)
{
return;
}
}
if (current.Value.UIForm.PauseCoveredUIForm)
{
pause = true;
}
if (cover) //这里默认是false
{
if (!current.Value.Covered)
{
current.Value.Covered = true;
current.Value.UIForm.OnCover();
if (current.Value == null)
{
return;
}
}
}
else
{
//当前Form被覆盖,则恢复
if (current.Value.Covered)
{
current.Value.Covered = false;
current.Value.UIForm.OnReveal();
if (current.Value == null)
{
return;
}
}
cover = true;
}
}
current = next;
}
}
问题2:如果需要在CloseUIForm中执行一些延迟操作该如何操作?
可以利用 userData参数,执行一起操作之后,比如说延迟1秒等,
再执行Close操作
public void CloseUIForm(IUIForm uiForm, object userData)
{
if (uiForm == null)
{
throw new GameFrameworkException("UI form is invalid.");
}
UIGroup uiGroup = (UIGroup)uiForm.UIGroup;
if (uiGroup == null)
{
throw new GameFrameworkException("UI group is invalid.");
}
//这里改变了 uiform的cover 和 pause属性
uiGroup.RemoveUIForm(uiForm);
uiForm.OnClose(m_IsShutdown, userData); //这里可能有异步操作
//这里改变了深度值
uiGroup.Refresh();
if (m_CloseUIFormCompleteEventHandler != null)
{
CloseUIFormCompleteEventArgs closeUIFormCompleteEventArgs = CloseUIFormCompleteEventArgs.Create(uiForm.SerialId, uiForm.UIFormAssetName, uiGroup, userData);
m_CloseUIFormCompleteEventHandler(this, closeUIFormCompleteEventArgs);
ReferencePool.Release(closeUIFormCompleteEventArgs);
}
m_RecycleQueue.Enqueue(uiForm);
}