紅點(diǎn)系統(tǒng)

設(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ì)思路:
  1. 紅點(diǎn)只是一種表現(xiàn)的形式摸袁,它是依賴于數(shù)據(jù)的表現(xiàn)钥顽,所以需要將其與界面代碼進(jìn)行分離處理,提煉成單獨(dú)的一套管理類靠汁。
  2. 界面不應(yīng)該關(guān)心檢測(cè)的具體方法蜂大,只需要調(diào)用統(tǒng)一的接口,就能得到是否顯示紅點(diǎn)的結(jié)果蝶怔。
  3. 紅點(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)即可。
  4. 每次檢測(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)框架:
  1. RedPointDefine: 紅點(diǎn)系統(tǒng)中所有模塊的ID定義類碴裙,在該紅點(diǎn)系統(tǒng)中钢悲,所有的紅點(diǎn)模塊通過(guò)ID進(jìn)行識(shí)別控制調(diào)用。新增紅點(diǎn)模塊都需要在此進(jìn)行ID的定義青团。
  2. RedModBase: 所有紅點(diǎn)模塊的基類譬巫,新增紅點(diǎn)模塊都需要繼承該類咖楣,在類中定義需要檢測(cè)該模塊的數(shù)據(jù)變動(dòng)事件督笆,以及相關(guān)的判斷紅點(diǎn)方法。
  3. RedModMgr: 所有紅點(diǎn)模塊的總管理類诱贿,逐幀對(duì)狀態(tài)變動(dòng)的紅點(diǎn)模塊進(jìn)行檢測(cè)娃肿,而忽略未改變的模塊。
  4. 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)小作,反之不顯示亭姥。
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市顾稀,隨后出現(xiàn)的幾起案子致份,更是在濱河造成了極大的恐慌,老刑警劉巖础拨,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件氮块,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡诡宗,警方通過(guò)查閱死者的電腦和手機(jī)滔蝉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)塔沃,“玉大人蝠引,你說(shuō)我怎么就攤上這事≈瘢” “怎么了螃概?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)鸽疾。 經(jīng)常有香客問我吊洼,道長(zhǎng),這世上最難降的妖魔是什么制肮? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任冒窍,我火速辦了婚禮,結(jié)果婚禮上豺鼻,老公的妹妹穿的比我還像新娘综液。我一直安慰自己,他們只是感情好儒飒,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布谬莹。 她就那樣靜靜地躺著,像睡著了一般桩了。 火紅的嫁衣襯著肌膚如雪附帽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天圣猎,我揣著相機(jī)與錄音士葫,去河邊找鬼。 笑死送悔,一個(gè)胖子當(dāng)著我的面吹牛慢显,可吹牛的內(nèi)容都是我干的爪模。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼荚藻,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼屋灌!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起应狱,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤共郭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后疾呻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體除嘹,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年岸蜗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了尉咕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡璃岳,死狀恐怖年缎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情铃慷,我是刑警寧澤单芜,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站犁柜,受9級(jí)特大地震影響洲鸠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜赁温,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一坛怪、第九天 我趴在偏房一處隱蔽的房頂上張望淤齐。 院中可真熱鬧股囊,春花似錦、人聲如沸更啄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)祭务。三九已至内狗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間义锥,已是汗流浹背柳沙。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拌倍,地道東北人赂鲤。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓噪径,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親数初。 傳聞我的和親對(duì)象是個(gè)殘疾皇子找爱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 前言 小紅點(diǎn)提示,可以說(shuō)是現(xiàn)在app都會(huì)有的一個(gè)功能泡孩,比如微信的消息界面车摄,微博的我的界面。一般來(lái)說(shuō)仑鸥,一個(gè)小紅點(diǎn)不會(huì)...
    程序員WW閱讀 498評(píng)論 0 2
  • 寫了一個(gè)demo可以自動(dòng)顯示紅點(diǎn)吮播,在其他界面紅點(diǎn)數(shù)變化,對(duì)應(yīng)的父界面的紅點(diǎn)也會(huì)發(fā)生變化眼俊。 如下圖: 設(shè)計(jì)思路: 每...
    colinWong閱讀 1,592評(píng)論 2 8
  • 紅點(diǎn)大家一定不陌生薄料,不管是游戲還是軟件,甚至是手機(jī)系統(tǒng)都通過(guò)紅點(diǎn)的直觀方式來(lái)告知用戶或者玩家泵琳,你有新的消息摄职,請(qǐng)注意...
    小鯨魚的U3d之旅閱讀 1,400評(píng)論 0 0
  • 最近在寫一個(gè)社交APP的時(shí)候,在控制消息計(jì)數(shù)获列,及界面紅點(diǎn)顯示時(shí)總會(huì)或多或少有延遲或計(jì)數(shù)偏差谷市,網(wǎng)上大多是對(duì)界面繪制的...
    此屁天下之絕響閱讀 2,745評(píng)論 6 122
  • 小紅點(diǎn)(消息推送提醒)在現(xiàn)今的各個(gè)App中幾乎無(wú)處不在,特別是內(nèi)容的更新日漸頻繁击孩,大量的小紅點(diǎn)被投放在各個(gè)業(yè)務(wù)入口...
    金小俊閱讀 9,383評(píng)論 15 131