版本: 3.8.0
语言: TypeScript
环境: Mac
Node事件派发
cocosCreator支持使用Node
节点进行事件派发(dispatchEvent),事件派发系统是按照 Web 的事件冒泡及捕获标准 实现的。
事件派发主要通过冒泡的方式逐渐向父节点传递。
在派发后,会经历如下阶段:
- 捕获:事件从场景根节点,逐级向子节点传递,直到到达目标节点或者在某个节点的响应函数中中断事件传递
- 目标:事件在目标节点上触发
- 冒泡:事件由目标节点,逐级向父节点冒泡传递,直到到达根节点或者在某个节点的响应函数中中断事件传递
实现的主要接口在Node
的基类BaseNode
中,主要有:
export class BaseNode extends CCObject implements ISchedulable {
// 在节点上注册指定类型的回调函数,也可以设置 target 用于绑定响应函数的 this 对象
on(type: string | __private.cocos_core_scene_graph_node_event_NodeEventType, callback: __private.AnyFunction, target?: unknown, useCapture?: any): void;
// 删除之前与同类型,回调,目标或 useCapture 注册的回调
off(type: string, callback?: __private.AnyFunction, target?: unknown, useCapture?: any): void;
// 注册节点的特定事件类型回调,回调会在第一时间被触发后删除自身
once(type: string, callback: __private.AnyFunction, target?: unknown, useCapture?: any): void;
// 通过事件名发送自定义事件, 支持最多5个参数的传递
emit(type: string, arg0?: any, arg1?: any, arg2?: any, arg3?: any, arg4?: any): void;
// 分发事件到事件流中
dispatchEvent(event: Event): void;
// 检查事件目标对象是否有为特定类型的事件注册的回调
hasEventListener(type: string, callback?: __private.AnyFunction, target?: unknown): any;
// 移除目标上的所有注册事件
targetOff(target: string | unknown): void;
}
使用dispatchEvent
进行事件分发,需要Event对象的支持,它是所有事件对象的基类,主要定义有:
export class Event {
static NO_TYPE: string; // 没有类型的事件
static TOUCH: string; // 触摸事件类型
static MOUSE: string; // 鼠标事件类型
static KEYBOARD: string; // 键盘事件类型
static ACCELERATION: string; // 加速器事件类型
static NONE: number; // 尚未派发事件阶段
static CAPTURING_PHASE: number; // 捕获阶段
static AT_TARGET: number; // 目标阶段
static BUBBLING_PHASE: number; // 冒泡阶段
bubbles: boolean; // 事件是否冒泡
target: any; // 最初事件触发的目标
currentTarget: any; // 当前目标
// 事件阶段,主要用于返回NONE,CAPTURING_PHASE,AT_TARGET,BUBBLING_PHASE等
eventPhase: number;
// 停止传递当前事件
propagationStopped: boolean;
// 立即停止当前事件的传递,事件甚至不会被分派到所连接的当前目标
propagationImmediateStopped: boolean;
// 检查该事件是否已经停止传递
isStopped(): boolean;
// 获取当前目标节点
getCurrentTarget(): any;
// 获取事件类型
getType(): __private.cocos_input_types_event_enum_SystemEventTypeUnion;
}
以上图为例,假设事件从节点c派发事件,节点a和b都收到事件的监听,可以这样编写示例:
// common.ts
class MyEvent extends Event {
constructor(name: string, bubbles?: boolean, detail?: any) {
super(name, bubbles);
this.detail = detail;
}
public detail: any = null; // 自定义的属性
}
// c.ts
import { Event } from 'cc';
public demo() {
this.node.dispatchEvent( new MyEvent('foobar', true, 'detail info') );
}
// b.ts
this.node.on('foobar', (event: MyEvent) => {
// 设置是否停止传递当前事件,如果为true,则a不再收到监听事件相关
event.propagationStopped = true;
});
// a.ts
this.node.on('foobar', (event: MyEvent) => {
//
});
如上内容,从官方文档: 节点事件系统 移植而来,主要用于对后面的自定义事件派发进行铺垫。
自定义事件派发
使用Node节点的冒泡派发,如果组件节点过多,可能会存在不够灵活和高效的问题。
事件分发的大概原理是:
- 通过
dispatchEvent
将事件相关注册到一个事件表中 - 通过
addEventListener
根据事件类型检测事件表中是否存在,如果存在则执行 - 通过
removeEventListener
根据事件类型将事件相关从表中移除,如果存在则移除
因此可封装一个简单的事件管理类: EventManager,大致实现:
import { error, _decorator } from "cc";
const { ccclass } = _decorator;
@ccclass("EventManager")
export class EventManager {
static handlers: { [name: string]: { handler: Function, target: any }[] };
// 添加监听(事件类型名,回调,目标节点)
public static addEventListener(name: string, handler: Function, target?: any) {
const objHandler = {handler: handler, target: target};
if (this.handlers === undefined) {
this.handlers = {};
}
let handlerList = this.handlers[name];
if (!handlerList) {
handlerList = [];
this.handlers[name] = handlerList;
}
for (var i = 0; i < handlerList.length; i++) {
if (!handlerList[i]) {
handlerList[i] = objHandler;
return i;
}
}
handlerList.push(objHandler);
return handlerList.length;
};
// 移除监听(事件类型名,回调,目标节点)
public static removeEventListener(name: string, handler: Function, target?: any) {
const handlerList = this.handlers[name];
if (!handlerList) {
return;
}
for (let i = 0; i < handlerList.length; i++) {
const oldObj = handlerList[i];
if (oldObj.handler === handler && (!target || target === oldObj.target)) {
handlerList.splice(i, 1);
break;
}
}
};
// 事件分发(事件类型名,自定义参数)
public static dispatchEvent(name: string, ...args: any) {
const handlerList = this.handlers[name];
const params = [];
let i;
for (i = 1; i < arguments.length; i++) {
params.push(arguments[i]);
}
if (!handlerList) {
return;
}
for (i = 0; i < handlerList.length; i++) {
const objHandler = handlerList[i];
if (objHandler.handler) {
objHandler.handler.apply(objHandler.target, args);
}
}
};
};
使用示例:
// demoLayer.ts
import { EventManager } from '../EventManager';
export class demoLayer extends Component {
protected onEnable(): void {
EventManager.on("DEBUG_CUSTOM", this.customEvent, this);
}
protected onDisable(): void {
EventManager.off("DEBUG_CUSTOM", this.customEvent, this);
}
private customEvent(param) {
console.log(param);
}
}
// gameManager.ts
EventManager.dispatchEvent("DEBUG_CUSTOM", 1);
理解可能有误,欢迎指出;最后祝大家学习生活愉快!