设计缘由:
红点系统作为手游中最普遍使用的基础系统,承担着部分指引玩家的职责,其地位十分重要,如果相关代码不成系统,没有统一的管理,就很容易导致大量的代码冗余,不易于维护和改动。
设计思路:
- 红点只是一种表现的形式,它是依赖于数据的表现,所以需要将其与界面代码进行分离处理,提炼成单独的一套管理类。
- 界面不应该关心检测的具体方法,只需要调用统一的接口,就能得到是否显示红点的结果。
- 红点对应于界面,应该是分层级的,子界面上有红点显示,那么其对应的父界面也应该有相应的红点显示,所以需要将其设计为多叉树的结构形式,每个父节点只需要关注其子节点的状态变化,逐级向下进行检测,而每个子节点发生变化后,只需要将状态变化层层传递给其父节点即可。
- 每次检测不应该直接就对所有的界面红点模块进行检测,而是仅仅只需要检测数据发生变动的对应模块,这样就能减少性能损耗,提高运行效率。与此同时,各模块之间相互独立,减少了耦合,也便于后续的修改、扩展与优化。
结构框架:
- RedPointDefine: 红点系统中所有模块的ID定义类,在该红点系统中,所有的红点模块通过ID进行识别控制调用。新增红点模块都需要在此进行ID的定义。
- RedModBase: 所有红点模块的基类,新增红点模块都需要继承该类,在类中定义需要检测该模块的数据变动事件,以及相关的判断红点方法。
- RedModMgr: 所有红点模块的总管理类,逐帧对状态变动的红点模块进行检测,而忽略未改变的模块。
- RedModRoot: 所有红点模块的根父类,通过检测该父类,就能逐级向下检测到所有子模块。利用了该根节点就实现了普适性,即可将所有模块的设计做同样的处理,而不需要考虑特殊例。
代码示例(typescript + Laya):
RedPointDefine:
export const ROOT = "ROOT";
export const MAINMENU = "MAINMENU";
...
RedModBase:
export enum RedModEvent {
Notify = "RED_MOD_NOTIFY",
Change = "RED_MOD_CHANGE",
}
export interface RedModConstructor {
readonly ID: string;
readonly CHILD_MOD_LIST: RedModConstructor[];
readonly OPEN_ID?: number;
new (): RedModBase;
}
export class RedModBase extends Laya.EventDispather {
public static readonly ID: string = "";
public static readonly CHILD_MOD_LIST: RedModConstructor[] = [];
public static readonly OPEN_ID?: number = null;
private isActive = null;
private isDirty = true;
private parent: string;
private children: string[];
public get ID(): string {
return (this.constructor as RedModConstructor).ID;
}
public get CHILD_MOD_LIST(): RedModConstructor[] {
return (this.constructor as RedModConstructor).CHILD_MOD_LIST;
}
public get OPEN_ID(): number {
return (this.constructor as RedModConstructor).OPEN_ID;
}
constructor() {
super();
this.isActive = null;
this.isDirty = false;
this.parent = null;
this.children = [];
if (this.register !== undefined) {
this.register();
}
this.initChildren();
}
private initChildren(): void {
this.children = [];
for (const childCtor of this.CHILD_MOD_LIST) {
const child = new childCtor();
child.parent = this.ID;
this.children.push(childCtor.ID);
RedPointMgr.GetInstance().addModule(childCtor.ID, child);
}
}
protected onChange(): void {
this.notifyChange();
}
public notifyChange(): void {
this.isDirty = true;
this.event(RedModEvent.Notify, this.ID);
if (this.parent !== null) {
const mod: RedModBase = RedPointMgr.GetInstance().getModule(this.parent);
if (mod !== undefined) {
mod.notifyChange();
}
}
}
public checkActive(): boolean {
if (this.isDirty || this.isActive === null) {
this.isDirty = false;
const bLastActive = this.isActive;
let isOpen = true;
if (this.OPEN_ID !== null) {
isOpen = OpenData.isOpen(this.OPEN_ID);
}
if (!isOpen) {
this.isActive = false;
} else {
if (this.children.length > 0) {
//无检测,依赖子节点
this.isActive = false;
for (const id of this.children) {
const mod = RedModMgr.GetInstance().getModule(id);
if (mod.checkActive()) {
this.isActive = true;
break;
}
}
} else {
//叶子节点
this.isActive = false;
if (this.onCheckActive !== undefined) {
this.isActive = this.onCheckActive();
}
}
}
if (bLastActive !== this.isActive) {
this.event(RedModEvent.Change, [this.ID, this.isActive]);
}
}
return this.isActive;
}
//叶子节点实现该方法
protected onCheckActive?(): boolean;
//叶子节点实现该方法
protected register?(): void;
}
RedPointMgr:
type Listener = {
(...args: unknown[]): void;
}
export default class RedPointMgr extends Laya.EventDispatcher {
private static instance: RedPointMgr = null;
public static GetInstance(): RedPointMgr {
if (RedPointMgr.instance === null) {
const mgr: RedPointMgr = new RedPointMgr();
RedPointMgr.instance = mgr;
mgr.addModule(RedModRoot.ID, new RedModRoot()); //在赋值之后add,否则死循环
}
return RedPointMgr.instance;
}
private modules: RedModBase[];
private notifyList: string[];
private isDirty: boolean;
private isEnabled: boolean;
constructor() {
super();
this.modules = [];
this.notifyList = [];
this.isDirty = false;
this.isEnabled = true;
this.regCommonEvent();
}
public resetMgr(): void {
this.notifyList = [];
this.isDirty = false;
}
public addModule(id: string, mod: RedModBase): void {
if (this.modules[id] !== undefined) {
console.warn(`模块重复添加,请检查。ID=${id}`);
return;
}
this.modules[id] = mod;
mod.on(RedModEvent.Notify, this, this.onModuleNotify);
mod.on(RedModEvent.Change, this, this.onModuleChange);
}
public getModule(id: string): RedModBase {
return this.modules[id];
}
public override on(type: string, caller: unknown, listener: Listener, args?: unknown[]): Laya.EventDispatcher {
const mod = this.modules[type];
if (mod !== undefined) {
super.on(type, caller, listener, args);
}
return this;
}
public override off(type: string, caller: unknown, listener: Listener, onceOnly?: boolean): Laya.EventDispatcher {
const mod = this.modules[type];
if (mod !== undefined) {
super.off(type, caller, listener, onceOnly);
}
return this;
}
//只检测有数据变动的模块
public checkActive(id: string): boolean {
for (const id of this.notifyList) {
this.modules[id].checkActive();
}
this.notifyList = [];
this.isDirty = false;
}
public setEnabled(enabled: boolean): void {
this.isEnabled = enabled;
}
//在游戏开始后逐帧进行调用检测红点的方法
public lateUpdate(): void {
if (this.isEnabled && this.isDirty) {
this.isDirty = false;
this.checkChange();
}
}
private regCommonEvent(): void {
//这里放常用的数据变更,例如等级和vip等级的变化,对应大量功能变动的事件,就进行全局检测。
PropMgr.GetInstance().on(PropEvent.UPDATE_SINGLE_PROP, this, this.onPropChange);
}
private onPropChange(prop: string): void {
if (prop === "iGrade" || prop === "iVIP") {
for (const mod of this.modules) {
if (mod.OPEN_ID !== undefined) {
mod.notifyChange();
}
}
}
}
private onModuleNotify(id: string): void {
if (this.notifyList.indexOf(id) === -1) {
this.notifyList.push(id);
}
this.isDirty = true;
}
private onModuleChange(id: string, active: boolean): void {
this.regCommonEvent(id, [id, active]);
}
}
RedModRoot:
export default class RedModRoot extends RedModBase {
public static override ID: string = RedPointDefine.ROOT;
public static override CHILD_MOD_LIST: RedModConstructor[] = [
RedModMain,
RedModShop,
......
];
override register(): void {
//在此注册会引起该红点变化的数据变动事件,其调用方法(this.onChange)
}
override onCheckActive(): boolean {
//在此填充检测对应红点的方法,返回true则通知注册该红点模块的界面显示红点,反之不显示。
}
}