游戏开发之UI管理器(跨引擎)

610 阅读5分钟

使用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. 类图 image.png

将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;
    

}

2. 管理器的父类

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;
}

}

3. 单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;
}

}

4. 栈结构管理器

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;
    }
}

}

5. 队列管理器

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;
    }
}

}


6. 所有管理器的整合

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;
}

}

7.  所有组件的父类

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

}

8. 使用方式
定义层级枚举

//ui分层 export enum UIIndex { ROOT,//最底层 STACK,//堆栈管理 QUEUE,//队列管理 TOAST,// TOP,//永远不会清除的ui层 }

初始化:
![image.png](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/6/16/172bd0156cf5d9ac~tplv-t2oaga2asx-image.image)

封装函数:

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

```

结语

欢迎扫码关注公众号《微笑游戏》,浏览更多内容。 微信图片_20190904220029.jpg

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