从0撸一个消息发送系统

为什么要用消息系统

最重要的功能就是解耦,减少功能和功能之间的关联来提升代码的健壮性.比如cocos里本来是一个常驻节点A被B申明引用,此时想把A做成预制体就会导致B中的申明A丢失.如果A和B一开始就是通过发消息传递数据的话,就不会出现这样的问题,让功能和功能之间只关注自身的逻辑,从而达到解耦的目的.

消息系统的目标

  • 在两个没有任何依赖的文件之间可以发送和接受消息

  • 发送的消息要能接受可变数量的参数

    我们先把目标转换成具体的任务,场景中有两个节点node_main,node_sub,分别对应同名的脚本,我们要实现的功能是点击node_main中的按钮用传递消息的方式调用refresh_label方法

@ccclass
export class NodeMain extends cc.Component {
    public click_send() {

    }
}
@ccclass
export class NodeSub extends cc.Component {
    @property(cc.Label)
    public label: cc.Label = undefined;

    public refresh_label(content: string) {
        this.label.string = content;
    }
}

设计

要想发送消息就得有个全局的方法,要想接收消息我们就得想办法注册接收者并存起来,当发送消息时候得去找到相应的接受者,到这里我们也就知道如何去做了,理清楚了需求,事情也就成功了一半.

新建message_uitl,有个存放接收者的数组

   export class MessageUtil {
       private static node_arr = [];
   
   }

有个注册方法存放node即可

    public static register(node) {
        this.node_arr.push(node);
    }

有个移除方法,遍历移除

    public static remove(node) {
        for (let i = 0; i < this.node_arr.length; i++) {
            if (this.node_arr[i] === node) {
                this.node_arr.splice(i, 1);
                break;
            }
        }
    }

有个发送方法,好在js是个动态语言不需要通过反射,只需判断是否有个这个方法即可,参数通过es6语法…args实现

    public static send_message(func, ...args) {
        for (const node of this.node_arr) {
            if (node[func]) {
                node[func](...args);
            }
        }
    }

我们还需要找个合适的实际去调用register和remove,好在cocos脚本的生命周期中提供了onLoad和onDestroy方法,我们在NodeSub脚本中加上

@ccclass
export class NodeSub extends cc.Component {
    @property(cc.Label)
    public label: cc.Label = undefined;

    public refresh_label(content: string) {
        this.label.string = content;
    }

    protected onLoad() {
        MessageUtil.register(this);
    }

    protected onDestroy() {
        MessageUtil.remove(this);
    }
}

到此,一个简易的消息系统已经好了,在NodeMain中调用发送代码,我们来试一下

@ccclass
export class NodeMain extends cc.Component {
    public click_send() {
        MessageUtil.send_message('refresh_label', 'hello world');
    }
}

msg3
你以为到这里就完了?嘿嘿,并没有,还有可以优化的地方.接收者每次都需要自己调用注册和销毁很容易遗忘,我们封装成父类让需要接收消息的类继承MsgComponent

import {MessageUtil} from "./message_util";

export class MsgComponent extends cc.Component{
    protected onLoad() {
        MessageUtil.register(this);
    }

    protected onDestroy() {
        MessageUtil.remove(this);
    }
}
export class NodeSub extends MsgComponent

这种方式看起来挺好,在长期的使用过程中还是发现了不少缺点

  • ts是单继承,这会导致我无法继承其他组件
  • 如果再有子类继承这个组件的话会经常忘了调用super.onLoad导致消息注册不成功

这里我们用装饰器来自动注册移除组件,装饰器分为类装饰器,方法装饰器.属性装饰器,参数装饰器四种,我们只需要用类装饰器就行了,关于装饰器网上有很多文章这里就不赘述了.按照装饰器规范我们在message_util中写好方法得到了接收者的target

export function register_message(): ClassDecorator {
    return (target: any) => {
        
    };
}
@ccclass
@register_message()
export class NodeSub extends MsgComponent {
    @property(cc.Label)
    public label: cc.Label = undefined;

    public refresh_label(content: string) {
        this.label.string = content;
    }
}

打印target我们知道这个是js原型链的一套东西,我们把onLoad,onDestroy单独提出来并重写

export function register_message(): ClassDecorator {
    return (target: any) => {
        const on_load = target.prototype.onLoad;
        const on_destroy = target.prototype.onDestroy;
        target.prototype.onLoad = function () {
            MessageUtil.register(this);
            this.on_load = on_load;
            this.on_load && this.on_load();
        }
        target.prototype.onDestroy = function () {
            MessageUtil.remove(this);
            this.on_destroy = on_destroy;
            this.on_destroy && this.on_destroy();
        }
    };
}

这样一来就大功告成了,最后留个小问题,为啥要用

this.on_load = on_load;
this.on_load && this.on_load();

而不直接调用呢?动动脑瓜思考下吧

on_load && on_load();

最后

工程代码放在附件了,需要的自取哈
message_test.rar (771.9 KB)

1赞

是onEnable和onDisable比较健壮点。

这个看个人需求,如果隐藏了想做一些事onEnable,onDisable就做不到了

创建一个全局的cc.EventTarget中转消息是不是就解决了?

3赞

还是需要自己注册事件的吧

class EventMsg extends cc.EventTarget{
        private static _instance: EventMsg = null;
        public static get instance(): EventMsg {
            if (this._instance == null) {
                this._instance = new EventMsg();
            }
            return this._instance;
        }

    private constructor() { 
        super();
    }
}
export default EventMsg.instance;

简单粗暴 :crazy_face:

4赞