乐趣区

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

  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;

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

结语

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

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

退出移动版