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

Posted cgw0827

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了游戏开发之UI管理器(跨引擎)相关的知识,希望对你有一定的参考价值。

使用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分为战斗层和按钮层,这个不属于管理器范畴。

  6. 结构图
    技术图片

代码

  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;

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

}

  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)

结语

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

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








以上是关于游戏开发之UI管理器(跨引擎)的主要内容,如果未能解决你的问题,请参考以下文章

小游戏开发之资源管理(跨引擎)

「游戏引擎 浅入浅出」4.3 片段着色器

「游戏引擎 浅入浅出」4.3 片段着色器

「游戏引擎 浅入浅出」4.3 片段着色器

「游戏引擎 浅入浅出」4.3 片段着色器

如何在片段着色器中平铺部分纹理