乐趣区

cocos-creator-事件

cocos creator 事件

在做一个消除类游戏时,需要对点击的方块做出响应。代码很简单,可背后的原理还多着呢。

1. 普通节点注册 click 事件

在 cc 中如果需要相应 click 事件,需要为该节点添加一个 Button 组件。或使用类似效果的事件比如

  1. cc.Node.EventType.MOUSE_DOWN
  2. cc.Node.EventType.TOUCH_END
//author herbert qq:464884492
// 注册按钮 click 事件
btn.node.on("click", event=>{cc.log("button click")});
// 注册 MOUSE_DOWN 
btn.node.on(cc.Node.EventType.MOUSE_DOWN,event=>{cc.log("button MOUSE_DOWN")});
// 注册 TOUCH_END
btn.node.on(cc.Node.EventType.TOUCH_END,event=>{cc.log("button TOUCH_END")})

2. 应该减少事件注册量

是否没有问题了?在写 juqery 时,有事件委托(delegate)的概念。啥意思呢,就是在节点的父级注册事件,来响应子节点的事件源。为啥可以实现,主要归功于 js 事件的两大机制

  1. 事件冒泡,事件响应从子节点往上冒泡到顶层节点
  2. 事件捕获,事件响应冲顶层节点依次传递到最末级节点

所以问题来了,消除类游戏都是通过预制资源生成不同样式的方块。若在每一方块上都注册事件,势必导致内存上涨(虽然现在内存很大了)。看看 cc 文档,事件机制完全是一样的(最终都是 JS),然而我想在我的 Canvas 上注册一个 click 事件,问题出现了。

3. 问题来了

问题就是我在 Canvas 上注册了 click 事件,点击 button 时,Canvas 上居然没有收到我的 click 事件。由此我查看 cc 源码,写了一堆测试代码,最终得出以下结

  1. click事件确实 Button 组件特殊存
  2. click事件不会向上或向下传递
  3. node.emit触发事件不会向上或向下传递
  4. node.dispatchEvent支持事件向上或向下传递
  5. 使用 node.dispatchEvent 参数必须是 cc.Event.EventCustom对象

4.click 事件特殊在哪里

cc.Button 组件中的 click 事件,其实是 cc 的自定义事件,源码为证

//author:herbert wx:464884492
...
 this.node.on(cc.Node.EventType.TOUCH_END, this._onTouchEnded, this);
...
_onTouchEnded (event) {if (!this.interactable || !this.enabledInHierarchy) return;
 if (this._pressed) {cc.Component.EventHandler.emitEvents(this.clickEvents, event);
 this.node.emit('click', this);// 触发事件
 }
 this._pressed = false;
 this._updateState();
 event.stopPropagation(); // 停止冒泡},
...

所以,之所 Button 能响应 click 事件,是因为组件注册了 TOUCH_END 事件,并在响应该事件函数中发射一个 click 事件。

5. javascript 自定义事件

参考 mdn 文档,js 自定事件方式如下

// author:herbert wx:464884492
<script text="javascript">
let cusEvent = new Event("custom", {bubbles: true // 允许冒泡});
document.body.addEventListener("custom", e => {console.log("自定义事件");
    console.log("Body event by custom");
});

let btn = document.querySelector("#btn");
btn.addEventListener("custom", e => {console.log("自定义事件");
    console.log("Button event by custom");
})
btn.dispatchEvent(cusEvent);
</script>

6. 了解下 cc.node.emit

cc.node.emit 最终调用的是 CallbacksInvoker.prototype.invoke 方法,从源码来看,是从对应的缓存对象中找到注册的回调方法,依次调用回调函数。

//author herbert wx:464884492
CallbacksInvoker.prototype.invoke = function (key, p1, p2, p3, p4, p5) {var list = this._callbackTable[key];
if (list) {
var rootInvoker = !list.isInvoking;
list.isInvoking = true;
var callbacks = list.callbacks;
var targets = list.targets;
for (var i = 0, len = callbacks.length; i < len; ++i) {var callbmhtack = callbacks[i];
    if (callback) {var target = targets[i];
        if (target) {callback.call(target, p1, p2, p3, p4, p5);
        }
        else {callback(p1, p2, p3, p4, p5);
        }
    }
}
if (rootInvoker) {
    list.isInvoking = false;
    if (list.containCanceled) {list.purgeCanceled();
    }
}}};

所以 click 自然不会往上或往下传递。

7.dispatchEvent,事件冒泡

dispatchEvent 利用自定义事件的 bubbles 属性,实现冒泡。至于为啥使用 btn.node.dispatchEvent(new cc.Event.EventMouse(cc.Node.EventType.MOUSE_DOWN, true))没有触发事件是因为 cc 在底层,将事件类型统一改成了 cc.Event.MOUSE,源码为证

author:herbert wx:464884492
 ...
 var EventMouse = function (eventType, bubbles) {cc.Event.call(this, cc.Event.MOUSE, bubbles);
 ...
};

场景

运行效果

8. 总结

做开发,不管是开发游戏还是其他应用程序。思路基本是一样的。再简单的事,多想想,再发散一下,你会收获更多。
需要进 cocos 游戏开发群的朋友,请添加我微信回复 cocos

欢迎感兴趣的朋友关注我的订阅号“小院不小”,或点击下方二维码关注。我将多年开发中遇到的难点,以及一些有意思的功能,体会都会一一发布到我的订阅号中。如需本文 demo 请在订阅号中回复ccevent

退出移动版