发射和监听事件:https://docs.cocos.com/creator/manual/zh/scripting/events.html
鼠标、触摸事件:https://docs.cocos.com/creator/manual/zh/scripting/internal-events.html
键盘、重力感应事件:https://docs.cocos.com/creator/manual/zh/scripting/player-controls.html
注意:目前已经不建议直接使用 cc.eventManager 来注册任何事件,cc.eventManager 的用法也不保证持续性,有可能随时被修改
事件处理是在节点(cc.Node)中完成的。对于组件,可以通过访问节点 this.node 来注册和监听事件。监听事件可以 通过 this.node.on() 函数来注册,方法如下:
cc.Class({
extends: cc.Component,
properties: {
},
onLoad: function () {
this.node.on('mousedown', function ( event ) {
console.log('Hello!');
});
},
});
值得一提的是,事件监听函数 on 可以传第三个参数 target,用于绑定响应函数的调用者。以下两种调用方式, 效果上是相同的:
// 使用函数绑定
this.node.on('mousedown', function ( event ) {
this.enabled = false;
}.bind(this));
// 使用第三个参数
this.node.on('mousedown', function (event) {
this.enabled = false;
}, this);
除了使用 on 监听,我们还可以使用 once 方法。once 监听在监听函数响应后就会关闭监听事件。
关闭监听
当我们不再关心某个事件时,我们可以使用 off 方法关闭对应的监听事件。需要注意的是,off 方法的 参数必须和 on 方法的参数一一对应,才能完成关闭。
我们推荐的书写方法如下:
cc.Class({
extends: cc.Component,
_sayHello: function () {
console.log('Hello World');
},
onEnable: function () {
this.node.on('foobar', this._sayHello, this);
},
onDisable: function () {
this.node.off('foobar', this._sayHello, this);
},
});
我们可以通过两种方式发射事件:emit 和 dispatchEvent。两者的区别在于,后者可以做事件传递。 我们先通过一个简单的例子来了解 emit 事件:
cc.Class({
extends: cc.Component,
onLoad () {
// args are optional param.
this.node.on('say-hello', function (msg) {
console.log(msg);
});
},
start () {
// At most 5 args could be emit.
this.node.emit('say-hello', 'Hello, this is Cocos Creator');
},
});
上文提到了 dispatchEvent 方法,通过该方法发射的事件,会进入事件派送阶段。在 Cocos Creator 的事件派送系统中,我们采用冒泡派送的方式。冒泡派送会将事件从事件发起节点,不断地向上传递给他的父级节点,直到到达根节点或者在某个节点的响应函数中做了中断处理 event.stopPropagation()。
如上图所示,当我们从节点 c 发送事件 “foobar”,倘若节点 a,b 均做了 “foobar” 事件的监听,则 事件会经由 c 依次传递给 b,a 节点。如:
// 节点 c 的组件脚本中
this.node.dispatchEvent( new cc.Event.EventCustom('foobar', true) );
如果我们希望在 b 节点截获事件后就不再将事件传递,我们可以通过调用 event.stopPropagation() 函数来完成。具体方法如下:
// 节点 b 的组件脚本中
this.node.on('foobar', function (event) {
event.stopPropagation();
});
请注意,在发送用户自定义事件的时候,请不要直接创建 cc.Event 对象,因为它是一个抽象类,请创建 cc.Event.EventCustom 对象来进行派发。
在事件监听回调中,开发者会接收到一个 cc.Event 类型的事件对象 event,stopPropagation 就是 cc.Event 的标准 API,其它重要的 API 包含:
API 名 | 类型 | 意义 |
type | String | 事件的类型(事件名) |
target | cc.Node | 接收到事件的原始对象 |
currentTarget | cc.Node | 接收到事件的当前对象,事件在冒泡阶段当前对象可能与原始对象不同 |
getType | Funciton | 获取事件的类型 |
stopPropagation | Function | 停止冒泡阶段,事件将不会继续向父节点传递,当前节点的剩余监听器仍然会接收到事件 |
stopPropagationImmediate | Function | 立即停止事件的传递,事件将不会传给父节点以及当前节点的剩余监听器 |
getCurrentTarget | Function | 获取当前接收到事件的目标节点 |
detail | Function | 自定义事件的信息(属于 cc.Event.EventCustom) |
setUserData | Function | 设置自定义事件的信息(属于 cc.Event.EventCustom) |
getUserData | Function | 获取自定义事件的信息(属于 cc.Event.EventCustom) |
完整的 API 列表可以参考 cc.Event 及其子类的 API 文档。
如上一篇文档所述,cc.Node 有一套完整的事件监听和分发机制。在这套机制之上,我们提供了一些基础的节点相关的系统事件,这篇文档将介绍这些事件的使用方式。
Cocos Creator 支持的系统事件包含鼠标、触摸、键盘、重力传感四种,其中本章节重点介绍与节点树相关联的鼠标和触摸事件,这些事件是被直接触发在相关节点上的,所以被称为节点系统事件。与之对应的,键盘和重力传感事件被称为全局系统事件,细节可以参考全局系统事件文档。
系统事件遵守通用的注册方式,开发者既可以使用枚举类型也可以直接使用事件名来注册事件的监听器,事件名的定义遵循 DOM 事件标准。
// 使用枚举类型来注册
node.on(cc.Node.EventType.MOUSE_DOWN, function (event) {
console.log('Mouse down');
}, this);
// 使用事件名来注册
node.on('mousedown', function (event) {
console.log('Mouse down');
}, this);
鼠标事件在桌面平台才会触发,系统提供的事件类型如下:
枚举对象定义 | 对应的事件名 | 事件触发的时机 |
cc.Node.EventType.MOUSE_DOWN | ‘mousedown’ | 当鼠标在目标节点区域按下时触发一次 |
cc.Node.EventType.MOUSE_ENTER | ‘mouseenter’ | 当鼠标移入目标节点区域时,不论是否按下 |
cc.Node.EventType.MOUSE_MOVE | ‘mousemove’ | 当鼠标在目标节点在目标节点区域中移动时,不论是否按下 |
cc.Node.EventType.MOUSE_LEAVE | ‘mouseleave’ | 当鼠标移出目标节点区域时,不论是否按下 |
cc.Node.EventType.MOUSE_UP | ‘mouseup’ | 当鼠标从按下状态松开时触发一次 |
cc.Node.EventType.MOUSE_WHEEL | ‘mousewheel’ | 当鼠标滚轮滚动时 |
鼠标事件(cc.Event.EventMouse)的重要 API 如下(cc.Event 标准事件 API 之外):
函数名 | 返回值类型 | 意义 |
getScrollY | Number | 获取滚轮滚动的 Y 轴距离,只有滚动时才有效 |
getLocation | Object | 获取鼠标位置对象,对象包含 x 和 y 属性 |
getLocationX | Number | 获取鼠标的 X 轴位置 |
getLocationY | Number | 获取鼠标的 Y 轴位置 |
getPreviousLocation | Object | 获取鼠标事件上次触发时的位置对象,对象包含 x 和 y 属性 |
getDelta | Object | 获取鼠标距离上一次事件移动的距离对象,对象包含 x 和 y 属性 |
getButton | Number | cc.Event.EventMouse.BUTTON_LEFT 或 cc.Event.EventMouse.BUTTON_RIGHT 或 cc.Event.EventMouse.BUTTON_MIDDLE |
触摸事件在移动平台和桌面平台都会触发,这样做的目的是为了更好得服务开发者在桌面平台调试,只需要监听触摸事件即可同时响应移动平台的触摸事件和桌面端的鼠标事件。系统提供的触摸事件类型如下:
枚举对象定义 | 对应的事件名 | 事件触发的时机 |
cc.Node.EventType.TOUCH_START | ‘touchstart’ | 当手指触点落在目标节点区域内时 |
cc.Node.EventType.TOUCH_MOVE | ‘touchmove’ | 当手指在屏幕上目标节点区域内移动时 |
cc.Node.EventType.TOUCH_END | ‘touchend’ | 当手指在目标节点区域内离开屏幕时 |
cc.Node.EventType.TOUCH_CANCEL | ‘touchcancel’ | 当手指在目标节点区域外离开屏幕时 |
触摸事件(cc.Event.EventTouch)的重要 API 如下(cc.Event 标准事件 API 之外):
API 名 | 类型 | 意义 |
touch | cc.Touch | 与当前事件关联的触点对象 |
getID | Number | 获取触点的 ID,用于多点触摸的逻辑判断 |
getLocation | Object | 获取触点位置对象,对象包含 x 和 y 属性 |
getLocationX | Number | 获取触点的 X 轴位置 |
getLocationY | Number | 获取触点的 Y 轴位置 |
getPreviousLocation | Object | 获取触点上一次触发事件时的位置对象,对象包含 x 和 y 属性 |
getStartLocation | Object | 获取触点初始时的位置对象,对象包含 x 和 y 属性 |
getDelta | Object | 获取触点距离上一次事件移动的距离对象,对象包含 x 和 y 属性 |
需要注意的是,触摸事件支持多点触摸,每个触点都会发送一次事件给事件监听器。
触摸事件冒泡
触摸事件支持节点树的事件冒泡,以下图为例:
在图中的场景里,A节点拥有一个子节点B,B拥有一个子节点C。假设开发者对A、B、C都监听了触摸事件。当鼠标或手指在B节点区域内按下时,事件将首先在B节点触发,B节点监听器接收到事件。接着B节点会将事件向其父节点传递这个事件,A节点的监听器将会接收到事件。这就是最基本的事件冒泡过程。
当鼠标或手指在C节点区域内按下时,事件将首先在C节点触发并通知C节点上注册的事件监听器。C节点会通知B节点这个事件,B节点内逻辑会负责检查触点是否发生在自身区域内,如果是则通知自己的监听器,否则什么都不做。紧接着A节点会收到事件,由于C节点完整处在A节点中,所以注册在A节点上的事件监听器都将收到触摸按下事件。以上的过程解释了事件冒泡的过程和根据节点区域来判断是否分发事件的逻辑。
除了根据节点区域来判断是否分发事件外,触摸事件的冒泡过程与普通事件的冒泡过程并没有区别。所以,调用 event 的 stopPropagation 函数可以主动停止冒泡过程。
cc.Node 的其它事件
枚举对象定义 | 对应的事件名 | 事件触发的时机 |
无 | ‘position-changed’ | 当位置属性修改时 |
无 | ‘rotation-changed’ | 当旋转属性修改时 |
无 | ‘scale-changed’ | 当缩放属性修改时 |
无 | ‘size-changed’ | 当宽高属性修改时 |
无 | ‘anchor-changed’ | 当锚点属性修改时 |
本篇教程,我们将介绍 Cocos Creator 的全局系统事件。
全局系统事件是指与节点树不相关的各种全局事件,由 cc.systemEvent 来统一派发,目前支持了以下几种事件:
除此之外,鼠标事件与触摸事件请参考节点系统事件
注意:目前已经不建议直接使用 cc.eventManager 来注册任何事件,cc.eventManager 的用法也不保证持续性,有可能随时被修改
键盘、设备重力传感器此类全局事件是通过函数 cc.systemEvent.on(type, callback, target) 注册的。
可选的 type 类型有:
事件监听器类型:cc.SystemEvent.EventType.KEY_DOWN 和 cc.SystemEvent.EventType.KEY_UP
事件触发后的回调函数:
自定义回调函数:callback(event);
回调参数:
KeyCode: API 传送门
Event:API 传送门
cc.Class({
extends: cc.Component,
onLoad: function () {
// add key down and key up event
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
},
onDestroy () {
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
},
onKeyDown: function (event) {
switch(event.keyCode) {
case cc.macro.KEY.a:
console.log('Press a key');
break;
}
},
onKeyUp: function (event) {
switch(event.keyCode) {
case cc.macro.KEY.a:
console.log('release a key');
break;
}
}
});
cc.Class({
extends: cc.Component,
onLoad () {
// open Accelerometer
cc.systemEvent.setAccelerometerEnabled(true);
cc.systemEvent.on(cc.SystemEvent.EventType.DEVICEMOTION, this.onDeviceMotionEvent, this);
},
onDestroy () {
cc.systemEvent.off(cc.SystemEvent.EventType.DEVICEMOTION, this.onDeviceMotionEvent, this);
},
onDeviceMotionEvent (event) {
cc.log(event.acc.x + " " + event.acc.y);
},
});
大家可以也去看 官方范例 cases03_gameplay/01_player_control 目录下的完整范例(这里包含了,键盘,重力感应,单点触摸,多点触摸的范例)。