設(shè)計(jì)緣由:
紅點(diǎn)系統(tǒng)作為手游中最普遍使用的基礎(chǔ)系統(tǒng),承擔(dān)著部分指引玩家的職責(zé)栋荸,其地位十分重要佣赖,如果相關(guān)代碼不成系統(tǒng)煤墙,沒有統(tǒng)一的管理堕扶,就很容易導(dǎo)致大量的代碼冗余碍彭,不易于維護(hù)和改動(dòng)。
設(shè)計(jì)思路:
- 紅點(diǎn)只是一種表現(xiàn)的形式摸袁,它是依賴于數(shù)據(jù)的表現(xiàn)钥顽,所以需要將其與界面代碼進(jìn)行分離處理,提煉成單獨(dú)的一套管理類靠汁。
- 界面不應(yīng)該關(guān)心檢測(cè)的具體方法蜂大,只需要調(diào)用統(tǒng)一的接口,就能得到是否顯示紅點(diǎn)的結(jié)果蝶怔。
- 紅點(diǎn)對(duì)應(yīng)于界面奶浦,應(yīng)該是分層級(jí)的,子界面上有紅點(diǎn)顯示踢星,那么其對(duì)應(yīng)的父界面也應(yīng)該有相應(yīng)的紅點(diǎn)顯示澳叉,所以需要將其設(shè)計(jì)為多叉樹的結(jié)構(gòu)形式,每個(gè)父節(jié)點(diǎn)只需要關(guān)注其子節(jié)點(diǎn)的狀態(tài)變化,逐級(jí)向下進(jìn)行檢測(cè)成洗,而每個(gè)子節(jié)點(diǎn)發(fā)生變化后五督,只需要將狀態(tài)變化層層傳遞給其父節(jié)點(diǎn)即可。
- 每次檢測(cè)不應(yīng)該直接就對(duì)所有的界面紅點(diǎn)模塊進(jìn)行檢測(cè)瓶殃,而是僅僅只需要檢測(cè)數(shù)據(jù)發(fā)生變動(dòng)的對(duì)應(yīng)模塊充包,這樣就能減少性能損耗,提高運(yùn)行效率遥椿。與此同時(shí)基矮,各模塊之間相互獨(dú)立,減少了耦合冠场,也便于后續(xù)的修改家浇、擴(kuò)展與優(yōu)化。
結(jié)構(gòu)框架:
- RedPointDefine: 紅點(diǎn)系統(tǒng)中所有模塊的ID定義類碴裙,在該紅點(diǎn)系統(tǒng)中钢悲,所有的紅點(diǎn)模塊通過(guò)ID進(jìn)行識(shí)別控制調(diào)用。新增紅點(diǎn)模塊都需要在此進(jìn)行ID的定義青团。
- RedModBase: 所有紅點(diǎn)模塊的基類譬巫,新增紅點(diǎn)模塊都需要繼承該類咖楣,在類中定義需要檢測(cè)該模塊的數(shù)據(jù)變動(dòng)事件督笆,以及相關(guān)的判斷紅點(diǎn)方法。
- RedModMgr: 所有紅點(diǎn)模塊的總管理類诱贿,逐幀對(duì)狀態(tài)變動(dòng)的紅點(diǎn)模塊進(jìn)行檢測(cè)娃肿,而忽略未改變的模塊。
- RedModRoot: 所有紅點(diǎn)模塊的根父類珠十,通過(guò)檢測(cè)該父類料扰,就能逐級(jí)向下檢測(cè)到所有子模塊。利用了該根節(jié)點(diǎn)就實(shí)現(xiàn)了普適性焙蹭,即可將所有模塊的設(shè)計(jì)做同樣的處理晒杈,而不需要考慮特殊例。
代碼示例(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) {
//無(wú)檢測(cè)孔厉,依賴子節(jié)點(diǎn)
this.isActive = false;
for (const id of this.children) {
const mod = RedModMgr.GetInstance().getModule(id);
if (mod.checkActive()) {
this.isActive = true;
break;
}
}
} else {
//葉子節(jié)點(diǎn)
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;
}
//葉子節(jié)點(diǎn)實(shí)現(xiàn)該方法
protected onCheckActive?(): boolean;
//葉子節(jié)點(diǎn)實(shí)現(xiàn)該方法
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拯钻,否則死循環(huán)
}
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(`模塊重復(fù)添加,請(qǐng)檢查撰豺。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;
}
//只檢測(cè)有數(shù)據(jù)變動(dòng)的模塊
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;
}
//在游戲開始后逐幀進(jìn)行調(diào)用檢測(cè)紅點(diǎn)的方法
public lateUpdate(): void {
if (this.isEnabled && this.isDirty) {
this.isDirty = false;
this.checkChange();
}
}
private regCommonEvent(): void {
//這里放常用的數(shù)據(jù)變更粪般,例如等級(jí)和vip等級(jí)的變化,對(duì)應(yīng)大量功能變動(dòng)的事件污桦,就進(jìn)行全局檢測(cè)亩歹。
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 {
//在此注冊(cè)會(huì)引起該紅點(diǎn)變化的數(shù)據(jù)變動(dòng)事件,其調(diào)用方法(this.onChange)
}
override onCheckActive(): boolean {
//在此填充檢測(cè)對(duì)應(yīng)紅點(diǎn)的方法,返回true則通知注冊(cè)該紅點(diǎn)模塊的界面顯示紅點(diǎn)小作,反之不顯示亭姥。
}
}