欢迎喜欢或者从事CocosCreator开发的小伙伴请加入我的大家庭CocosCreator游戏开发Q群:26855530
首先,小伙伴应该知道CocosCreator本身已经实现了自己一套事件传播机制的,例如:
this.node.on('foobar', this._sayHello, this);
cc.game.on("foobar", this._sayHello, this);
cc.director.on("foobar", this._sayHello, this);
以上三种都是可以提供给开发者自行使用的事件传播,其中第一种是使用上是比较平凡的,而且有一定的局限性,只能在同一树节点,由下往上传播,后两者是基于全局的cc.game或者cc.director,基本可以做到无死角传播,但是官方并不提倡使用.
综上所诉,就没有一个能吊打全部的事件管理器吗?答案还真没有,哈哈~既然没有,那我们就自己撸一个,造个轮子!所以以下的代码就诞生了......
import Singleton from "../base/Singleton";
import SysLog from "../utils/SysLog";
import GamePublicUtil from "../utils/GamePublicUtil";
interface IEvent {
func: Function,
ctx: unknown
}
/**
* 事件(订阅/发布)管理器
*/
export default class EventManager extends Singleton {
private _eventMap: Map<string, Array<IEvent>> = new Map<string, Array<IEvent>>();
role: cc.Node;
private _timer;
private _duration: number = 2000;
private _delayMap: Map<string, any> = new Map();
constructor() {
super();
this._delayMap.clear();
}
static get instance() {
return super.getInstance<EventManager>();
}
on(eventName: string, func: Function, ctx: unknown) {
if (this._eventMap.has(eventName)) {
this._eventMap.get(eventName).push({func, ctx});
} else {
this._eventMap.set(eventName, [{func, ctx}]);
}
}
off(eventName: string, func: Function, ctx: unknown) {
if (this._eventMap.has(eventName)) {
let events = this._eventMap.get(eventName);
let index = events.findIndex(i => i.func === func && i.ctx === ctx);
index > -1 && events.splice(index, 1);
if (events.length == 0) {
this._eventMap.delete(eventName);
}
} else {
SysLog.warn(`事件解绑失败:事件名(${eventName})不存在`);
}
}
emit(eventName: string, detail?: any) {
if (this._eventMap.has(eventName)) {
this._eventMap.get(eventName).forEach(({func, ctx}) => {
typeof detail === "undefined" ? func.call(ctx) : func.call(ctx, detail);
})
}
}
/**
* 延迟事件通知
* @param eventName
* @param detail
*/
delayEmit(eventName: string, detail?: any) {
if (this._delayMap) {
this._delayMap.set(eventName, detail);
if (!this._timer) {
this._timer = setInterval(this.delayEvent.bind(this), this._duration);
}
}
}
private delayEvent() {
if (this.role && !GamePublicUtil.getIsTalkingNpc() && this._delayMap.size > 0) {
let next = this._delayMap.entries().next();
if (next) {
let key: string = next.value[0];
let val = next.value[1];
this.emit(key, val);
this._delayMap.delete(key);
}
if (this._delayMap.size === 0 && this._timer) {
//没有延迟事件停止轮询
clearInterval(this._timer);
this._timer = null;
}
}
}
clear() {
this._delayMap.clear();
this._eventMap.clear();
}
log() {
if (this._eventMap.size > 0) {
this._eventMap.forEach((events, eventName) => {
SysLog.info(`事件名:${eventName}`);
SysLog.info(` 订阅者信息:`);
events.forEach((event) => {
// @ts-ignore
SysLog.info(` ${event.ctx.node.name}`);
})
})
} else {
SysLog.info(`事件管理器暂无任何信息`);
}
}
}
代码很简单,我就不多阐述,一些类也是我自己封装的,大佬们需要的话可以看我往期的文章,或者直接删除吧,其中有一个还是我自己根据我项目需求做的一个延时事件处理,根据自身需求修改或删除.下面看使用:
监听(订阅):
import EventManager from "../manager/EventManager";
const {ccclass, property} = cc._decorator;
@ccclass
export default class NewClass extends cc.Component {
onEnable() {
EventManager.instance.on("sayHI", this.sayHI, this);
}
private sayHI(mes) {
console.log(mes);
}
onDisable() {
EventManager.instance.off("sayHI", this.sayHI, this);
}
}
触发(发布):
EventManager.instance.emit("sayHI", "hi~");
需要注意的是,这个也是个全局变量,到处都能使用,但是订阅方应该注意在适当时机,比如以上代码在onDisable生命周期取消订阅,避免造成不必要的内存泄露问题等~好的,期待下期的更新吧~