写在前面
开个头
依旧是面试官系列。
面试官:怎么实现节点的点击事件监听?
我:用this.node.on()实现
面试官:那他是怎么产生的呢?
我:…
本文以触摸事件(cc.Node.EventType.TOUCH_START)作为主线,简单分析一下Cocos引擎中的事件是如何从浏览器传递到Node的事件中的。
你可能还想要问,那事件是怎么从浏览器产生的呢?我:…
一些话
我是个即将毕业的应届生,受水平所限,难免有些地方写的不是很好,但不动手的话,也不会进步,就指望大家多多包涵啦,有错误的地方也劳烦大家伙们指正。
代码中,注释以 //_
开头的是引用了官方注释。
为提高可读性,代码中会省略部分类似或与主题不是特别相关的代码。
环境
Cocos Creator 2.4
Chrome 88
概要
模块作用
事件监听机制应该是所有游戏都必不可少的内容。不管是按钮的点击还是物体的拖动,都少不了事件的监听与分发。
主要的功能还是通过节点的on/once函数,对系统事件(如触摸、点击)进行监听,随后触发对应的游戏逻辑。同时,也支持用户发射/监听自定义的事件,这方面可以看一下官方文档监听和发射事件。
涉及文件
本次涉及文件较多。有点难截图,直接文字描述吧。
类名 | 路径 | 作用 |
---|---|---|
CCNode | cocos2d\core\CCNode.js | 注册、接收事件 |
CCGame | cocos2d\core\CCGame.js | 调用初始化、注册浏览器事件、兼容处理 |
CCInputManager | cocos2d\core\platform\CCInputManager.js | 初始化、注册浏览器事件 |
CCEventManager | cocos2d\core\event-manager\CCEventManager.js | 分发系统事件 |
其中,CCGame和CCInputManager都有涉及注册事件,但他们负责的是不同的部分。
源码解析
事件是怎么(从浏览器)到达引擎的?
想知道这个问题,必须要了解引擎和浏览器的交互是从何而起。
上代码。
CCGame.js
// 初始化事件系统
_initEvents: function () {
var win = window, hiddenPropName;
//_ register system events
// 注册系统事件,这里调用了CCInputManager的方法
if (this.config.registerSystemEvent)
_cc.inputManager.registerSystemEvent(this.canvas);
// document.hidden表示页面隐藏,后面的if用于处理浏览器兼容
if (typeof document.hidden !== 'undefined') {
hiddenPropName = "hidden";
} else if (typeof document.mozHidden !== 'undefined') {
hiddenPropName = "mozHidden";
} else if (typeof document.msHidden !== 'undefined') {
hiddenPropName = "msHidden";
} else if (typeof document.webkitHidden !== 'undefined') {
hiddenPropName = "webkitHidden";
}
// 当前页面是否隐藏
var hidden = false;
// 页面隐藏时的回调,并发射game.EVENT_HIDE事件
function onHidden () {
if (!hidden) {
hidden = true;
game.emit(game.EVENT_HIDE);
}
}
//_ In order to adapt the most of platforms the onshow API.
// 为了适配大部分平台的onshow API。应该是指传参的部分...
// 页面可视时的回调,并发射game.EVENT_SHOW事件
function onShown (arg0, arg1, arg2, arg3, arg4) {
if (hidden) {
hidden = false;
game.emit(game.EVENT_SHOW, arg0, arg1, arg2, arg3, arg4);
}
}
// 如果浏览器支持隐藏属性,则注册页面可视状态变更事件
if (hiddenPropName) {
var changeList = [
"visibilitychange",
"mozvisibilitychange",
"msvisibilitychange",
"webkitvisibilitychange",
"qbrowserVisibilityChange"
];
// 循环注册上面的列表里的事件,同样是是为了兼容
// 隐藏状态变更后,根据可视状态调用onHidden/onShown回调函数
for (var i = 0; i < changeList.length; i++) {
document.addEventListener(changeList[i], function (event) {
var visible = document[hiddenPropName];
//_ QQ App
visible = visible || event["hidden"];
if (visible)
onHidden();
else
onShown();
});
}
}
// 此处省略部分关于 页面可视状态改变 的兼容性代码
// 注册隐藏和显示事件,暂停或重新开始游戏主逻辑。
this.on(game.EVENT_HIDE, function () {
game.pause();
});
this.on(game.EVENT_SHOW, function () {
game.resume();
});
}
其实核心代码只有一点点…为了保持对各个平台的兼容性,
重要的地方有两个:
- 调用CCInputManager的方法
- 注册页面可视状态改变事件,并派发game.EVENT_HIDE和game.EVENT_SHOW事件。
来看看CCInputManager。
CCInputManager.js
// 注册系统事件 element是canvas
registerSystemEvent (element) {
if(this._isRegisterEvent) return;
// 注册过了,直接return
this._glView = cc.view;
let selfPointer = this;
let canvasBoundingRect = this._canvasBoundingRect;
// 监听resize事件,修改this._canvasBoundingRect
window.addEventListener('resize', this._updateCanvasBoundingRect.bind(this));
let prohibition = sys.isMobile;
let supportMouse = ('mouse' in sys.capabilities);
// 是否支持触摸
let supportTouches = ('touches' in sys.capabilities);
// 省略了鼠标事件的注册代码
//_register touch event
// 注册触摸事件
if (supportTouches) {
// 事件map
let _touchEventsMap = {
"touchstart": function (touchesToHandle) {
selfPointer.handleTouchesBegin(touchesToHandle);
element.focus();
},
"touchmove": function (touchesToHandle) {
selfPointer.handleTouchesMove(touchesToHandle);
},
"touchend": function (touchesToHandle) {
selfPointer.handleTouchesEnd(touchesToHandle);
},
"touchcancel": function (touchesToHandle) {
selfPointer.handleTouchesCancel(touchesToHandle);
}
};
// 遍历map注册事件
let registerTouchEvent = function (eventName) {
let handler = _touchEventsMap[eventName];
// 注册事件到canvas上
element.addEventListener(eventName, (function(event) {
if (!event.changedTouches) return;
let body = document.body;
// 计算偏移量
canvasBoundingRect.adjustedLeft = canvasBoundingRect.left - (body.scrollLeft || window.scrollX || 0);
canvasBoundingRect.adjustedTop = canvasBoundingRect.top - (body.scrollTop || window.scrollY || 0);
// 从事件中获得触摸点,并调用回调函数
handler(selfPointer.getTouchesByEvent(event, canvasBoundingRect));
// 停止事件冒泡
event.stopPropagation(