UIPanelStack 也就是堆栈,一般只是用来完成返回上一页这个功能的。在没用堆栈管理页面信息之前,都是打开一个页面,然后在打开的页面上记录上一页面的信息,然后当页面点击返回时,再根据记录的上一个页面的信息打开上一个页面。这种实现在页面数量不多、跳转逻辑不复杂的情况下可以勉强应付。但是跳转逻辑比较复杂的情况下还是要实现一个,所以就添加了 UIPanelStack 这个功能。
起初的实现是所有的页面,每当关闭时页面信息全部压到 Stack 里。这里的页面信息定义如下:
public class UIPanelInfo { public IUIData UIData; public UILevel Level; public string AssetBundleName; public string PanelName; }简单介绍下:
UIData 是 在关闭时页面的数据快照
Level 则是关闭时所在的层级
AssetBundleName 则是所加载的 AB 名,如果没有传入则为空。
PanelName 则是打开页面时传入的页面名字。
后来发现笔者参与的项目不需要每个页面都压入栈中,只是少数的几个页面需要。
所以提供了两个手动的 API。
UIMgr.Push<T>/UIMgr.Push(string panelName); UIMgr.Back(IUIPanel panel)/UIMgr.Back(string panelName);Push 很容易理解,就是 UIPanel 的压栈操作。Back 则是,返回到最近 Push 进栈中的页面。
实现原理也很简单。
Push 时将传入的 panelName 对应的 Panel 关闭掉,生成 UIPanelInfo 后,将 UIPanelInfo 压入到栈中。
Back 则是将传入的 panelName 对应的 Panel 关闭掉,从 栈中弹出一个 UIPanelInfo,之后根据信息打开页面并传入 Info 中的数据。
核心代码:
UIManager.cs
public void Push(IUIPanel view) { if (view != null) { mUIStack.Push(view.PanelInfo); view.Close(); mAllUI.Remove(view.Transform.name); } } public void Back(string currentPanelName) { var previousPanelInfo = mUIStack.Pop(); CloseUI(currentPanelName); OpenUI(previousPanelInfo.PanelName, previousPanelInfo.Level, previousPanelInfo.UIData, previousPanelInfo.AssetBundleName); }关于 UIPanelStack 介绍到这里。
这个功能目前还比较简陋,在 QFramework 文档中还没有进行介绍。不过相比把所有的页面信息都进行压栈操作,按需手动的这种方式更合适一些。
小结 (一)事实上,独立测试的界面完全替代了 QFramework 初期所提供的 QApp 模块化的方式。这个不理解没关系,每个界面的开发已经是模块化了,不过只是从业务进行横向的模块化。已经够用了,毕竟大部分的项目都是进行 UI 界面的制作和修改。其他一些战斗系统等等,比较大的模块,也多少会依赖于 UI 部分,那么这种的推荐用一个 UI 界面进行一个模块的入口。总之在业务逻辑以及界面角度来看,已经支持了模块化的架构。
UI Kit 最佳实践 (二) 暂停界面与通信接下来我们接着进行 UI Kit 最佳实践,之前我们完成了 UILevelPanel 和 UIGamePanel。
而 UIGamePanel 中的暂停功能还没有完成。点击暂停功能则应该打开暂停界面。
所以我们先完成暂停界面的制作:
UIGamePausePanel.prefab:
Image: 是对话框的白色背景
BtnContinue: 是继续按钮
BtnReplay: 是重玩按钮
BtnMain: 是主页按钮
其代码如下:
/* 2018.7 凉鞋的MacBook Pro (2) */ namespace QFramework.Example { public class UIGamePausePanelData : UIPanelData { // TODO: Query Mgr's Data } public partial class UIGamePausePanel : UIPanel { protected override void InitUI(IUIData uiData = null) { mData = uiData as UIGamePausePanelData ?? new UIGamePausePanelData(); //please add init code here } protected override void ProcessMsg (int eventId,QMsg msg) { throw new System.NotImplementedException (); } protected override void RegisterUIEvent() { BtnMain.onClick.AddListener(() => { CloseSelf(); UIMgr.ClosePanel<UIGamePanel>(); UIMgr.OpenPanel<UIMainPanel>(); }); BtnReplay.onClick.AddListener(() => { Log.E("BtnPlay Clicked"); }); BtnContinue.onClick.AddListener(() => { CloseSelf(); }); } } }代码比较简单,主要是三个按钮的事件注册。
而 UIGamePanel.cs 进行暂停按钮的注册:
/* 2018.7 凉鞋的MacBook Pro (2) */ namespace QFramework.Example { public class UIGamePanelData : UIPanelData { public int LevelIndex = 1; } public partial class UIGamePanel : UIPanel { protected override void InitUI(IUIData uiData = null) { mData = uiData as UIGamePanelData ?? new UIGamePanelData(); //please add init code here Level.text = "第" + mData.LevelIndex + "关"; } protected override void ProcessMsg (int eventId,QMsg msg) { } protected override void RegisterUIEvent() { BtnPause.onClick.AddListener(() => { UIMgr.OpenPanel<UIGamePausePanel>(UILevel.PopUI); }); } } }原来的 BtnPause 点击之后进行一些日志的输出,现在则改为了打开 UIGamePausePanel 页面。
这里层级为 UILevel.PopUI。
这样一个打开暂停页面的功能就完成了。
结果如下:
但是一般的暂停页面没有这么简单。
当打开暂停页面的时候,正在进行的游戏应该全部暂停。这里最容易的实现就是 Time.timeScale = 0.0f;
不过这不是重点,当继续游戏时候,还要通知 UIGamePanel 或者对应的 GameManager 进行暂停的恢复。
这就涉及到了对象之间通信的问题。
最简单的方式就是,在 UIGamePausePanel 中获取 UIGamePanel ,然后进行相应恢复操作。