Version: 3.8.0
Language: TypeScript
Environment: Mac
Node event dispatch
cocosCreator supports using Node
nodes for event dispatch (dispatchEvent). The event dispatch system is implemented in accordance with the Web’s event bubbling and capturing standards.
Event dispatch is mainly passed to the parent node gradually through bubbling.
After distribution, it will go through the following stages:
- Capture: The event is passed from the scene root node to the child nodes step by step until it reaches the target node or the event delivery is interrupted in the response function of a node
- Target: The event is triggered on the target node
- Bubbling: The event is bubbled from the target node to the parent node step by step until it reaches the root node or the event delivery is interrupted in the response function of a node
The main interfaces implemented are in the base class BaseNode
of Node
, mainly including:
export class BaseNode extends CCObject implements ISchedulable {<!-- --> //Register a callback function of the specified type on the node. You can also set the target to the this object used to bind the response function. on(type: string | __private.cocos_core_scene_graph_node_event_NodeEventType, callback: __private.AnyFunction, target?: unknown, useCapture?: any): void; // Delete previously registered callbacks with the same type, callback, target or useCapture off(type: string, callback?: __private.AnyFunction, target?: unknown, useCapture?: any): void; // Register a specific event type callback of the node. The callback will delete itself after being triggered for the first time. once(type: string, callback: __private.AnyFunction, target?: unknown, useCapture?: any): void; //Send custom events through event names, supporting the passing of up to 5 parameters emit(type: string, arg0?: any, arg1?: any, arg2?: any, arg3?: any, arg4?: any): void; // Distribute events to the event stream dispatchEvent(event: Event): void; // Check if the event target object has a callback registered for a specific type of event hasEventListener(type: string, callback?: __private.AnyFunction, target?: unknown): any; // Remove all registered events on the target targetOff(target: string | unknown): void; }
Using dispatchEvent
for event distribution requires the support of Event object, which is the base class of all event objects. Its main definitions are:
export class Event {<!-- --> static NO_TYPE: string; // Event without type static TOUCH: string; // Touch event type static MOUSE: string; //Mouse event type static KEYBOARD: string; //Keyboard event type static ACCELERATION: string; // Accelerator event type static NONE: number; // The event phase has not been dispatched yet static CAPTURING_PHASE: number; // Capturing phase static AT_TARGET: number; // Target stage static BUBBLING_PHASE: number; // bubbling phase bubbles: boolean; // Whether the event bubbles target: any; // The target of the initial event trigger currentTarget: any; // current target // Event stage, mainly used to return NONE, CAPTURING_PHASE, AT_TARGET, BUBBLING_PHASE, etc. eventPhase: number; // Stop delivering the current event propagationStopped: boolean; // Immediately stop delivery of the current event, the event will not even be dispatched to the current connected target propagationImmediateStopped: boolean; // Check whether the event has stopped being delivered isStopped(): boolean; // Get the current target node getCurrentTarget(): any; // Get event type getType(): __private.cocos_input_types_event_enum_SystemEventTypeUnion; }
Taking the above figure as an example, assuming that the event is dispatched from node c, and both nodes a and b receive event monitoring, the example can be written like this:
// common.ts class MyEvent extends Event {<!-- --> constructor(name: string, bubbles?: boolean, detail?: any) {<!-- --> super(name, bubbles); this.detail = detail; } public detail: any = null; // Custom attributes } //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) => {<!-- --> //Set whether to stop delivering the current event. If it is true, a will no longer receive listening events. event.propagationStopped = true; }); // a.ts this.node.on('foobar', (event: MyEvent) => {<!-- --> // });
The above content is transplanted from the official document: Node Event System, and is mainly used to pave the way for subsequent custom event distribution.
Custom event dispatch
Using bubbling dispatch of Node nodes, if there are too many component nodes, there may be problems of inflexibility and inefficiency.
The general principle of event distribution is:
- Register events into an event table through
dispatchEvent
- Use
addEventListener
to detect whether the event exists in the event table according to the event type, and execute if it exists - Use
removeEventListener
to remove the event from the table according to the event type, if it exists, remove it
Therefore, a simple event management class can be encapsulated: EventManager, which roughly implements:
import {<!-- --> error, _decorator } from "cc"; const {<!-- --> ccclass } = _decorator; @ccclass("EventManager") export class EventManager {<!-- --> static handlers: {<!-- --> [name: string]: {<!-- --> handler: Function, target: any }[] }; //Add listener (event type name, callback, target node) 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; }; // Remove the listener (event type name, callback, target node) 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 & amp; & amp; (!target || target === oldObj.target)) {<!-- --> handlerList.splice(i, 1); break; } } }; //Event distribution (event type name, custom parameters) 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); } } }; };
Usage example:
// 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);
The understanding may be wrong, please point it out; finally, I wish everyone a happy study and life!