使用UI管理器的目的

  1. 使用单场景与zindex结合的方式管理UI。
  2. 能够隐藏底层UI达到优化效果。
  3. 很好的组织和管理UI。
  4. 跨引擎使用。

管理器分类

根据以往经验我开发了三种类型的管理器,队列管理器,栈式管理器,单UI管理器。

  1. 单UI管理器:SingleManager负责管理如登录,loading,大厅,游戏这样的一级UI,同一时刻只有一个UI实例存在。UI之间是替换关系。
  2. 栈式管理器:StackManager用于管理先进后出的UI,弹出功能UI使用。
  3. 队列管理器:QueueManager用于管理先进先出的UI,用于第一次进入大厅弹出各种活动UI时候使用,关闭一个弹出另一个。
  4. 类图

将UI分为五层

  1. 第一层:使用单UI管理器用于管理,大厅,游戏等一级界面。
  2. 第二层:使用栈式管理器 管理二级界面
  3. 第三层:使用队列管理器用于管理进入游戏时弹出的各种活动面板。
  4. 第四层:使用栈式管理器用于管理toast,tip等提示框。
  5. 第五层:为最上层,使用栈式管理器,用于管理教学,对话界面和网络屏蔽层等。

特别说明:比如将一个战斗UI分为战斗层和按钮层,这个不属于管理器范畴。

  1. 结构图

代码

  1. 为了跨引擎使用,需要将各个引擎的组件抽象。
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;}
  1. 管理器的父类
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;    }}
  1. 单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;    }}
  1. 栈结构管理器
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;        }    }}
  1. 队列管理器
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;        }    }}
  1. 所有管理器的整合
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;    }}
  1. 所有组件的父类
import LayerInterface from "../cfw/ui/LayerInterface";import { UIIndex } from "../cfw/tools/Define";const { ccclass, property } = cc._decorator;@ccclassexport 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)    }}
  1. 使用方式

定义层级枚举

//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)

结语

欢迎扫码关注公众号《微笑游戏》,浏览更多内容。

欢迎扫码关注公众号《微笑游戏》,浏览更多内容。