层级管理的核心实现比较简单,代码如下:
switch (uiLevel) { case UILevel.Bg: ui.Transform.SetParent(mBgTrans); break; case UILevel.AnimationUnderPage: ui.Transform.SetParent(mAnimationUnderTrans); break; case UILevel.Common: ui.Transform.SetParent(mCommonTrans); break; case UILevel.AnimationOnPage: ui.Transform.SetParent(mAnimationOnTrans); break; case UILevel.PopUI: ui.Transform.SetParent(mPopUITrans); break; case UILevel.Const: ui.Transform.SetParent(mConstTrans); break; case UILevel.Toast: ui.Transform.SetParent(mToastTrans); break; case UILevel.Forward: ui.Transform.SetParent(mForwardTrans); break; }这种是最通用最常见的层级管理方式。只要将 UIPanel 放到对应层级的 GameObject 下面就好了。
不过这种层级管理会有一点问题。当这个 UIRoot 只有一个 Canvas 的时候,页面的打开或关闭都会进行 Canvas 网格的重新排序,也就是网格重建,所以很多方案都建议,每个 UIPanel 挂一个 Canvas。然后根据 Canvas Sorting Order 进行层级管理。
使用 Canvas 的 Sorting Order 可以更精细地进行层级管理,而 UI Kit 目前的方式相对粗糙一些。
使用 Canvas 的方式是 UI Kit 的一个方向,等 QFramework 团队找到比较简洁的实现之前,UI Kit 还是默认使用当前方式。
UI Kit 最佳实践 (一) 界面跳转及数据展示接下来,我们来制作两个界面。一个是 UILevelPanel,一个是 UIGamePanel。
UILevelPanel 功能定义:
有若干个关卡,这里暂时定义为九个关卡。
点击任意关卡则跳转到 UIGamePanel,并在 UIGamePanel 中显示当前关卡信息。比如,在 UILevelPanel 点击第一关,则打开的 UIGamePanel 则展示 第一关,同理第二关。
UIGamePanel 功能定义:
游戏暂停按钮。
关卡信息展示,这里只展示第几关就够了。
这里 UILevelPanel 和 UIGamePanel 的制作的详细过程和 UI Kit 快速入门类似。简单展示下两个页面的布局,和相关的逻辑代码。
UIGamePanel.prefab:
BtnPause 是一个 Button。
Level 是一个 Text,主要是用来表示第几关的。
其代码如下:
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) { throw new System.NotImplementedException (); } protected override void RegisterUIEvent() { BtnPause.onClick.AddListener(() => { Log.E("BtnPause Clicked"); }); } } }在 InitUI 方法中,会接收到一个 UIData,然后转为 UIGamePanelData 后赋值给 MData。
那么这个 UIData 从哪里传过来呢?
答案是 UIMgr.Open 这个 API,这个 API 提供传入相应界面的 UIData。这部分在下一小结详细说。
现在重点是,在 UIMgr.Open 不传入 UIData 时,UIGamePanel, UIData 有可能是空的,那么当 UIData 为空时,则会默认创建一个 UIGamePanelData()。
默认创建一个 UIGamePanelData 这样做有什么好处呢?
答案是 方便测试,可以在 UIGamePanelData 里给数据设置一些默认值,这里 LevelIndex 默认是 1。当然这也不是本小节的重点,这个也下一小节详细说。
由于可以设置默认值,所以 TestUIGamePanel.scene 运行之后如下:
默认就是第一关。这样负责编写 UIGamePanel 和游戏战斗模块的同学们,主要改自己部分的 UIGamePanelData 就可以进行一些测试了,而不是从头开始运行游戏手点直到打开 UIGamePanel 才看到测试结果,重点是 UIGamePanel 不依赖任何其他数据,只是作为数据的一个展示而已。反正这不是本节重点。。。不好意思实在忍不住:)
接下来 UILevelPanel
UILevelPanel.prefab:
LevelProtoype 是一个 Button,代表关卡 item 的原型。
LevelsContainer 则是一个 普通的 GameObject,不过挂上了 GridLayoutGroup 了。
BtnBack 则是左上角的的返回按钮。
UILevelPanel 可能会复杂一点了,代码如下:
/* 2018.7 凉鞋的MacBook Pro (2) */ using UnityEngine.UI; namespace QFramework.Example { public class UILevelPanelData : UIPanelData { // TODO: Query Mgr's Data } public partial class UILevelPanel : UIPanel { protected override void InitUI(IUIData uiData = null) { mData = uiData as UILevelPanelData ?? new UILevelPanelData(); //please add init code here for (var i = 0; i < 10; i++) { LevelPrototype.Instantiate() .Parent(LevelsContainer) .LocalScaleIdentity() .LocalPositionIdentity() .Show() .ApplySelfTo(btnLevel=> { var levelIndex = i + 1; btnLevel.onClick.AddListener(() => { CloseSelf(); UIMgr.OpenPanel<UIGamePanel>(uiData: new UIGamePanelData() { LevelIndex = levelIndex }); }); }) .transform.Find("Text").GetComponent<Text>().text = "第" + (i + 1) + "关"; } } protected override void ProcessMsg (int eventId,QMsg msg) { throw new System.NotImplementedException (); } protected override void RegisterUIEvent() { BtnBack.onClick.AddListener(() => { CloseSelf(); UIMgr.OpenPanel<UIMainPanel>(); }); } } }