为什么要用消息系统
最重要的功能就是解耦,减少功能和功能之间的关联来提升代码的健壮性.比如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');
}
}

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


