五邑隱俠,本名關健昌,10年游戲生涯,現隱居五邑。本系列文章以TypeScript為介紹語言。
我們已經知道怎樣制作、加載、顯示界面。但cocos沒有提供一個彈窗管理模塊,對於一個多人合作的項目,沒有統一的管理,界面層級容易混亂。作為主程,在項目開始就應該處理好這些問題,將彈窗划分為不同的層次,不同類型的信息顯示在不同的層中。下面將講解怎樣設計彈窗堆棧。
一般地,從下向上,我會將彈窗划分為以下層:
1)內容層,展示游戲相關的信息界面。
2)tips層,顯示提示性信息界面,例如獲得物品的浮窗、網絡異常的提示。
3)新手引導層,主要顯示新手引導的手指、新手提示文本框等。
4)alert層,主要顯示系統級的信息、錯誤,例如斷網、被踢下線。
5)loading層,顯示加載動畫。

具體怎樣實現?彈窗通常有統一的動畫,所以定義一個界面邏輯的基類ViewCtrl,定義一些通用的屬性和方法。每個層中可能同時存在多個界面,我把每個層做成組ViewGroup。再寫一個類PopupCtrl,對界面分層,並對外提供編程接口。
先來說說ViewCtrl這個類,繼承自cc.Component,這樣我們寫的ViewCtrl子類就可以掛載到prefab的節點上。定義兩個boolean屬性:hasMask、touchOutClose,用於控制是否有灰色半透明遮罩,和是否點擊彈窗外關閉。后面在介紹ViewGroup時會介紹怎樣實現這兩個功能。
cc.Component本身有onEnable,onDisable兩個生命周期方法,但我們希望同屏不出現太多彈窗,彈出新彈窗時隱藏上一個彈窗,這會導致這兩個方法頻繁調用。對於打開界面時的刷新,在界面放到堆棧時回調更合適。同理對重用的界面的重置,在界面移出堆棧時回調重置。可以在ViewCtrl定義onAddToStack和onRemoveFromStack兩個生命周期方法,在ViewGroup里在合適的時機觸發,后面會介紹。再添加兩個方法,onPlayShowAni,onPlayHideAni,在這里可以實現統一的彈窗動畫。子類的特殊顯示和關閉動畫由子類重寫這兩個方法實現。這兩個方法也由ViewGroup在合適的時機觸發。
/** * auth: 關健昌 * date: 2018-11-17 * desc: 界面基類 * modify: */ const {ccclass, property} = cc._decorator; @ccclass export default class ViewCtrl extends cc.Component { @property hasMask: boolean = true; @property touchOutClose: boolean = false; public onAddToStack(): void { } public onRemoveFromStack(): void { } public onPlayShowAni(): void { } public onPlayHideAni(): void { } }
接下來是ViewGroup這個類,它代表一個彈窗層,也繼承自cc.Component,它的node可以添加到PopupCtrl的node上從而展示這個彈窗層。它應該有自己的maskLayer、blockLayer,用於顯示灰色半透明遮罩,和屏蔽下層界面的觸屏事件。當然,像tips層,我們不希望屏蔽下層界面的觸屏事件,可以提供接口來控制這兩個layer是否生效。
ViewGroup最重要的作用是管理一層的彈窗。所以它應該有一個Array<ViewCtrl>類型的viewArr屬性。基本的操作包括:
/** * auth: 關健昌 * date: 2018-11-17 * desc: 界面組 * modify: */ export default class ViewGroup extends cc.Component { private maskLayer: cc.Node = null; private blockLayer: cc.Node = null; private viewArr: Array<ViewCtrl> = []; public pushView(ctrl: ViewCtrl, hideOld: boolean): void { } public popView(cleanup: boolean): void { } public insertView(ctrl: ViewCtrl): void { } public removeView(ctrl: ViewCtrl): void { } public pushViews(ctrls: Array<ViewCtrl>, hideOld: boolean): void { } public removeAllViews(cleanup: boolean): void { } public lastView(): ViewCtrl { } public getViewByName(name: string): ViewCtrl { } public getViewCount(): number { } public isEmpty(): boolean { } }
其中pushView、insertView、pushViews會將對應ViewCtrl放入viewArr,並觸發ViewCtrl.onAddToStack。通過ViewGroup.node.addChild將ViewCtrl.node添加為子節點將對應界面顯示出來。具體是否要添加到界面要看是否在hideOld的界面下面。pushViews主要是為自動彈窗設計的,例如登錄后自動顯示公告、簽到等,關閉一個自動打開下一個。
popView、removeView、removeAllViews通過ViewCtrl.node.removeFromParent將ViewCtrl.node移出界面,同時將ViewCtrl移出viewArr,並觸發ViewCtrl.onRemoveFromStack。pushView、popView還會觸發onPlayShowAni,onPlayHideAni這兩個生命周期方法。
現在來說說PopupCtrl這個類,它也繼承自cc.Component,可以直接掛載到prefab的節點上,在介紹游戲入口時我們給Canvas添加了popupLayer子節點,通過添加組件將PopupCtrl掛上去。PopupCtrl要實現分層,它有Array<ViewGroup>類型的groupArr屬性。在onLoaded生命周期方法里按彈窗層順序,通過PopupCtrl.addChild添加對應的彈窗層。PopupCtrl提供跟ViewGroup類似的接口,不同的是PopupCtrl的pushView、insertView、pushViews提供layer參數,用於指定彈窗添加的層。
enum PopupLayer { CONTENT = 0, TIPS, GUIDE, ALERT, LOADING }
彈窗管理先說到這里,下一篇我們將介紹下怎樣做資源加載管理。
