遊戲開發之UI管理器(跨引擎)
- 2020 年 6 月 7 日
- 筆記
使用UI管理器的目的
- 使用單場景與zindex結合的方式管理UI。
- 能夠隱藏底層UI達到優化效果。
- 很好的組織和管理UI。
- 跨引擎使用。
管理器分類
根據以往經驗我開發了三種類型的管理器,隊列管理器,棧式管理器,單UI管理器。
- 單UI管理器:SingleManager負責管理如登錄,loading,大廳,遊戲這樣的一級UI,同一時刻只有一個UI實例存在。UI之間是替換關係。
- 棧式管理器:StackManager用於管理先進後出的UI,彈出功能UI使用。
- 隊列管理器:QueueManager用於管理先進先出的UI,用於第一次進入大廳彈出各種活動UI時候使用,關閉一個彈出另一個。
- 類圖
將UI分為五層
-
第一層:使用單UI管理器用於管理,大廳,遊戲等一級介面。
-
第二層:使用棧式管理器 管理二級介面
-
第三層:使用隊列管理器用於管理進入遊戲時彈出的各種活動面板。
-
第四層:使用棧式管理器用於管理toast,tip等提示框。
-
第五層:為最上層,使用棧式管理器,用於管理教學,對話介面和網路屏蔽層等。
特別說明:比如將一個戰鬥UI分為戰鬥層和按鈕層,這個不屬於管理器範疇。 -
結構圖
程式碼
- 為了跨引擎使用,需要將各個引擎的組件抽象。
export default interface LayerInterface {
exit(): void;
/**
* 設置組件是否可見
* @param f
*/
setVisible(f: boolean): void;
/**
* 設置組件節點的zroder
* @param order
*/
setOrder(order: number): void;
/**
*
* @param t 管理器層級
*/
setUIIndex(t: number): void;
getUIIndex(): number;
/**
* 獲得組件的node
*/
getNode(): any;
isLoad(): boolean;
}
- 管理器的父類
import LayerInterface from "./LayerInterface";
export default abstract class LayerManager {
//根節點
protected root: any;
protected list: LayerInterface[]
//管理器中的內容是否可以被刪除
protected popFlag: boolean = false;
protected zOrder: number = 1;
constructor(zOrder: number = 1, canPop: boolean = true) {
this.list = []
this.zOrder = zOrder;
this.popFlag = canPop;
}
init(node: any) {
this.root = node;
}
setZOrder(order: number) {
this.zOrder = order;
}
getZOrder(): number {
return this.zOrder;
}
canPop() {
return this.popFlag;
}
//ui數量
count() {
return this.list.length;
}
setVisible(flag: boolean) {
for (let index = 0; index < this.list.length; index++) {
const element = this.list[index];
element.setVisible(flag)
}
}
//判斷某個ui是否存在
has(layer: LayerInterface) {
for (let index = 0; index < this.list.length; index++) {
const element = this.list[index];
if (layer === element) {
return true;
}
}
return false;
}
//添加layer
abstract pushView(layer: LayerInterface): void;
// 移除layer
abstract popView(view: LayerInterface): boolean;
//刪除指定ui
removeView(layer: LayerInterface): boolean {
// logInfo(' LayerManger removeView ')
for (let index = 0; index < this.list.length; index++) {
const element: LayerInterface = this.list[index];
if (layer === element) {
element.exit();
this.list.splice(index, 1);
return true;
}
}
// console.warn(' removeView is not have ', layer, ' list ', this.list)
return false;
}
//清空所有ui
clear() {
// logInfo(' LayerManger clear ')
for (let index = 0; index < this.list.length; index++) {
const element: LayerInterface = this.list[index];
element.exit();
}
this.list.length = 0;
}
}
- 單UI管理器
import LayerManager from "./LayerManager";
import LayerInterface from "./LayerInterface";
export default class SingleManager extends LayerManager {
pushView(view: LayerInterface) {
if (this.list.length > 0) {
this.removeView(this.list.shift())
}
this.list.push(view);
view.setOrder(this.zOrder);
this.root.addChild(view.getNode())
}
//不支援主動移除
popView(view: LayerInterface) {
return false;
}
}
- 棧結構管理器
import LayerManager from "./LayerManager"
import LayerInterface from "./LayerInterface"
export default class StackLayerManager extends LayerManager {
//添加layer
pushView(view: LayerInterface) {
this.list.push(view);
view.setOrder(this.zOrder)
this.root.addChild(view.getNode())
}
// 移除layer
popView(view: LayerInterface): boolean {
if (this.list.length > 0) {
let layerInfo = this.list.pop();
layerInfo.exit();
return true;
} else {
return false;
}
}
}
- 隊列管理器
import LayerManager from "./LayerManager"
import LayerInterface from "./LayerInterface";
export default class QueueLayerManager extends LayerManager {
//添加layer
pushView(view: LayerInterface) {
this.list.push(view);
if (this.list.length == 1) {
this.show(view);
}
}
show(view: LayerInterface) {
view.setOrder(this.zOrder);
this.root.addChild(view.getNode())
}
// 移除layer
popView(view: any): boolean {
if (this.list.length > 0) {
let layerInfo = this.list.shift();
layerInfo.exit();
if (this.list.length > 0) {
this.show(this.list[0]);
}
return true;
} else {
return false;
}
}
}
- 所有管理器的整合
import LayerManager from "./LayerManager"
import EventDispatcher from "../event/EventDispatcher";
import GlobalEvent from "../event/GlobalEvent";
import LayerInterface from "./LayerInterface";
export default class UIManager extends EventDispatcher {
private managers: LayerManager[] = [];
private root: any;
private static ins: UIManager;
static instance(): UIManager {
if (!UIManager.ins) {
UIManager.ins = new UIManager();
}
return UIManager.ins;
}
constructor() {
super();
this.managers = [];
}
/**
*
* @param uiIndex
* @param flag
*/
setVisible(uiIndex: number, flag: boolean) {
// logInfo('setVisible ', uiIndex, flag)
this.managers[uiIndex].setVisible(flag)
}
init(node: any) {
this.root = node
}
setManager(index: number, manager: LayerManager) {
this.managers[index] = manager;
this.managers[index].init(this.root)
}
/**
* 清除UI
*/
clear() {
for (let index = this.managers.length - 1; index >= 0; index--) {
const manager = this.managers[index];
if (manager.canPop() && manager.count() > 0) {
manager.clear()
}
}
}
//添加UI
pushView(layer: LayerInterface) {
if (layer) {
let uiIndex = layer.getUIIndex()
let manager = this.managers[uiIndex];
if (!manager) {
console.log(' manager is null ', layer)
return;
}
layer.setUIIndex(uiIndex)
manager.pushView(layer);
this.getOpenUICount();
}
}
/**
* 此方法用於關閉介面,為了兼容Android的back鍵 所以layer有為null的情況。
* 如果 返回false 表明已經沒有介面可以彈出,此時就可以彈出是否退出遊戲提示了。
* @param layer
*/
popView(layer?: LayerInterface) {
// console.log('popView layer is ', layer)
let flag = false;
if (layer) {
let index: number = layer.getUIIndex()
// console.log(' popView index is ', index, ' layer is ', layer)
let manger = this.managers[index];
if (!manger) {
// console.log(' popView layer is not found type is ', type)
flag = false;
} else {
flag = manger.popView(layer);
}
} else {
for (let index = this.managers.length - 1; index >= 0; index--) {
const manager = this.managers[index];
if (manager.canPop() && manager.count() > 0) {
if (manager.popView(null)) {
flag = true;
break;
}
}
}
}
return flag;
}
//獲得當前存在的ui的數量
getOpenUICount() {
let count: number = 0;
for (let index = this.managers.length - 1; index >= 0; index--) {
const manager = this.managers[index];
if (manager.canPop()) {
count += manager.count()
}
}
return count;
}
}
- 所有組件的父類
import LayerInterface from "../cfw/ui/LayerInterface";
import { UIIndex } from "../cfw/tools/Define";
const { ccclass, property } = cc._decorator;
@ccclass
export default class EngineView extends cc.Component implements LayerInterface {
private uiIndex: number = 0;
protected loadFlag: boolean = false;
isLoad() {
return this.loadFlag
}
start() {
this.loadFlag = true;
}
exit() {
this.node.destroy()
}
setVisible(f: boolean): void {
this.node.active = f;
}
setOrder(order: number): void {
this.node.zIndex = order;
}
setUIIndex(t: UIIndex): void {
this.uiIndex = t;
}
getUIIndex(): UIIndex {
return this.uiIndex
}
getNode() {
return this.node;
}
show() {
UIManager.instance().pushView(this)
}
hide(){
UIManager.instance().popView(this)
}
}
- 使用方式
定義層級枚舉
//ui分層
export enum UIIndex {
ROOT,//最底層
STACK,//堆棧管理
QUEUE,//隊列管理
TOAST,//
TOP,//永遠不會清除的ui層
}
初始化:
封裝函數:
/**
* 將ui添加到管理器中。
* @param prefab 預製體麓景
* @param className 組件類型
* @param model 模型
* @param uiIndex ui管理器層級
* @param loader 載入器
* @param func 載入成功回調
*/
pushView(prefab: string, className: string, c: BaseController, model: any, uiIndex: UIIndex = UIIndex.STACK, loader: ResLoader, func?: Function) {
if (this.viewMap.has(prefab)) {
console.warn(' pushVIew ', this.viewMap.has(prefab))
return;
}
this.viewMap.set(prefab, 1)
this.pushToast(prefab, className, c, model, uiIndex, loader, (comp) => {
// console.log(' delete viewMap ', prefab)
this.viewMap.delete(prefab)
if (func) {
func(comp)
}
})
}
/**
*
* @param prefabName 預製體的名稱
* @param callback 載入後的回調。
*/
getPrefab(prefabName: string, loader: ResLoader, callback: (err: string, node?: cc.Node) => void) {
let resName = "";
if (prefabName.indexOf("/") >= 0) {
resName = prefabName;
let list = prefabName.split("/");
prefabName = list[list.length - 1];
} else {
resName = "prefabs/" + prefabName;
}
loader.loadRes(resName, ResType.Prefab,
(err, item: ResItem) => {
if (err) {
callback(" UIManager getComponent err " + err);
return;
}
//這裡可以配合對象池使用。
let node = cc.instantiate(item.getRes())
if (node) {
callback(null, node);
} else {
callback("node is null");
}
});
}
這裡邊有個一小的處理,就是當一個UI成功載入後才會彈出另一個UI,避免的按鈕被連續點擊彈出多個UI的情況。
使用:
this.pushView('LoginView', 'LoginView', null, ModuleManager.getLoader(), UIIndex.ROOT)
結語
歡迎掃碼關注公眾號《微笑遊戲》,瀏覽更多內容。
歡迎掃碼關注公眾號《微笑遊戲》,瀏覽更多內容。