红点系统

设计缘由:

红点系统作为手游中最普遍使用的基础系统,承担着部分指引玩家的职责,其地位十分重要,如果相关代码不成系统,没有统一的管理,就很容易导致大量的代码冗余,不易于维护和改动。

设计思路:
  1. 红点只是一种表现的形式,它是依赖于数据的表现,所以需要将其与界面代码进行分离处理,提炼成单独的一套管理类。
  2. 界面不应该关心检测的具体方法,只需要调用统一的接口,就能得到是否显示红点的结果。
  3. 红点对应于界面,应该是分层级的,子界面上有红点显示,那么其对应的父界面也应该有相应的红点显示,所以需要将其设计为多叉树的结构形式,每个父节点只需要关注其子节点的状态变化,逐级向下进行检测,而每个子节点发生变化后,只需要将状态变化层层传递给其父节点即可。
  4. 每次检测不应该直接就对所有的界面红点模块进行检测,而是仅仅只需要检测数据发生变动的对应模块,这样就能减少性能损耗,提高运行效率。与此同时,各模块之间相互独立,减少了耦合,也便于后续的修改、扩展与优化。
结构框架:
  1. RedPointDefine: 红点系统中所有模块的ID定义类,在该红点系统中,所有的红点模块通过ID进行识别控制调用。新增红点模块都需要在此进行ID的定义。
  2. RedModBase: 所有红点模块的基类,新增红点模块都需要继承该类,在类中定义需要检测该模块的数据变动事件,以及相关的判断红点方法。
  3. RedModMgr: 所有红点模块的总管理类,逐帧对状态变动的红点模块进行检测,而忽略未改变的模块。
  4. 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则通知注册该红点模块的界面显示红点,反之不显示。
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 前言 小红点提示,可以说是现在app都会有的一个功能,比如微信的消息界面,微博的我的界面。一般来说,一个小红点不会...
    程序员WW阅读 578评论 0 2
  • 写了一个demo可以自动显示红点,在其他界面红点数变化,对应的父界面的红点也会发生变化。 如下图: 设计思路: 每...
    colinWong阅读 1,630评论 2 8
  • 红点大家一定不陌生,不管是游戏还是软件,甚至是手机系统都通过红点的直观方式来告知用户或者玩家,你有新的消息,请注意...
    小鲸鱼的U3d之旅阅读 1,438评论 0 0
  • 最近在写一个社交APP的时候,在控制消息计数,及界面红点显示时总会或多或少有延迟或计数偏差,网上大多是对界面绘制的...
    此屁天下之绝响阅读 2,796评论 6 122
  • 小红点(消息推送提醒)在现今的各个App中几乎无处不在,特别是内容的更新日渐频繁,大量的小红点被投放在各个业务入口...
    金小俊阅读 9,465评论 15 131