• cocosCreator 之 dispatchEvent事件分发


    版本: 3.8.0

    语言: TypeScript

    环境: Mac


    Node事件派发


    cocosCreator支持使用Node节点进行事件派发(dispatchEvent),事件派发系统是按照 Web 的事件冒泡及捕获标准 实现的。

    事件派发主要通过冒泡的方式逐渐向父节点传递。

    bubble-event

    在派发后,会经历如下阶段:

    • 捕获:事件从场景根节点,逐级向子节点传递,直到到达目标节点或者在某个节点的响应函数中中断事件传递
    • 目标:事件在目标节点上触发
    • 冒泡:事件由目标节点,逐级向父节点冒泡传递,直到到达根节点或者在某个节点的响应函数中中断事件传递

    实现的主要接口在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;   
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    使用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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    以上图为例,假设事件从节点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) => {
      //
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    如上内容,从官方文档: 节点事件系统 移植而来,主要用于对后面的自定义事件派发进行铺垫。


    自定义事件派发


    使用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);
          }
        }
      };
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    使用示例:

    // 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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    理解可能有误,欢迎指出;最后祝大家学习生活愉快!

  • 相关阅读:
    红细胞膜包裹仿生型六价铬还原去除剂/磁性纳米马达/PFC高负载的聚合物仿生纳米颗粒的研究
    Java中八种基本数据类型及其区别、字符编码
    爬虫-获取数据xpath
    学术论文写作
    深入理解 python 虚拟机:字节码教程(3)——深入剖析循环实现原理
    什么变量能够影响苦艾酒的味道?
    MAE
    HC32_HC32F072FAUA_DAC的使用
    贴片电阻具有哪些特性?
    电脑技巧:原版Windows系统与Ghost系统的区别
  • 原文地址:https://blog.csdn.net/qq_24726043/article/details/134025394