使用 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)
结语
欢迎扫码关注公众号《微笑游戏》,浏览更多内容。
欢迎扫码关注公众号《微笑游戏》,浏览更多内容。