Cocos2d-x 3.X 事件分发机制的一些案例
Cocos2d-X 3.X 引入了一种新的响应用户事件的机制,该机制主要涉及三个基本方面:
- Event listeners:用于封装事件处理代码。
- Event dispatcher:负责向 listener 分发用户事件。
- Event 对象:包含关于事件的信息。
事件响应步骤
1. 创建 EventListener
为了响应事件,首先需要创建一个 EventListener。Cocos2d-X 3.X 提供了五种不同类型的 EventListener:
EventListenerTouch:响应触控事件。EventListenerKeyboard:响应键盘事件。EventListenerAcceleration:响应加速器事件。EventListenerMouse:响应鼠标事件。EventListenerCustom:响应定制的事件。
2. 连接事件处理代码
将事件处理代码连接到适当的事件监听回调方法中,例如EventListenerTouch的onTouchBegan,或者EventListenerKeyboard的onKeyPressed。
3. 注册 EventListener
使用EventDispatcher注册创建好的EventListener。
4. 事件分发
当事件触发后(例如,用户触摸了屏幕,或者敲击了键盘),EventDispatcher会通过调用适当的EventListener的回调来分发Event对象(例如EventTouch,或者EventKeyboard),每个事件对象包含对应的事件信息(例如包含触控的坐标)。
示例代码
触控事件示例
以下代码在场景中添加三个按钮,每个按钮都可以响应触控事件:
auto sprite1 = Sprite::create("Images/CyanSquare.png");
sprite1->setPosition(origin + Point(size.width / 2, size.height / 2) + Point(-80, 80));
addChild(sprite1, 10);
auto sprite2 = Sprite::create("Images/MagentaSquare.png");
sprite2->setPosition(origin + Point(size.width / 2, size.height / 2));
addChild(sprite2, 20);
auto sprite3 = Sprite::create("Images/YellowSquare.png");
sprite3->setPosition(Point(0, 0));
sprite2->addChild(sprite3, 1);
创建一个触控的事件监听器和回调代码,这里使用 C++11 的 Lambda 表达式来实现回调:
// 创建一个排队的触控事件监听器 (同时仅仅处理一个触控事件)
auto listener = EventListenerTouchOneByOne::create();
// 当 "swallow touches" 设置为 true,在 onTouchBegan 方法返回 'true' 将会吃掉触控事件,防止其他监听器使用这个事件
listener->setSwallowTouches(true);
// 使用 lambda 表达式实现 onTouchBegan 事件的回调函数
listener->onTouchBegan = [](Touch* touch, Event* event) {
// event->getCurrentTarget() 返回 *listener's* sceneGraphPriority 节点
auto target = static_cast<Sprite*>(event->getCurrentTarget());
// 获取当前触控点相对与按钮的位置
Point locationInNode = target->convertToNodeSpace(touch->getLocation());
Size s = target->getContentSize();
Rect rect = Rect(0, 0, s.width, s.height);
// 检测点击区域
if (rect.containsPoint(locationInNode)) {
log("sprite began... x = %f, y = %f", locationInNode.x, locationInNode.y);
target->setOpacity(180);
return true;
}
return false;
};
// 当移动触控的时候
listener->onTouchMoved = [](Touch* touch, Event* event) {
auto target = static_cast<Sprite*>(event->getCurrentTarget());
// 移动当前的精灵
target->setPosition(target->getPosition() + touch->getDelta());
};
// 结束
listener->onTouchEnded = [=](Touch* touch, Event* event) {
auto target = static_cast<Sprite*>(event->getCurrentTarget());
log("sprite onTouchesEnded..");
target->setOpacity(255);
// 重新设置 zOrder,改变显示顺序
if (target == sprite1) {
sprite1->setZOrder(100);
} else if (target == sprite2) {
sprite2->setZOrder(0);
}
};
添加事件监听器到事件分发器:
// 注册监听器
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, sprite1);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener->clone(), sprite2);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener->clone(), sprite3);
_eventDispatcher是Node的属性,用于管理当前节点的所有事件分发(包括Scene、Layer、Sprite等)。需要注意的是,每个事件监听器只能被添加一次,因此在调用第二和第三个addEventListenerWithSceneGraphPriority中使用了clone()方法。addEventListenerWithSceneGraphPriority方法和addEventListenerWithFixedPriority会在事件监听器中设置一个注册标志,如果已经设置了标志,就不能再次添加。
另外,如果你添加了一个fixed priority监听器到节点,当节点被删除时,需要手动删除这个监听器;而绑定到节点的scene graph priority监听器,当节点被析构时,监听器将会被自动析构。
新的触控机制与旧版对比
与 2.X 版本相比,新的触控机制看起来更复杂。在旧版中,需要从delegate类派生,其中定义了onTouchBegan等方法,事件处理代码会放在这些委托方法中。而新的事件处理机制将事件处理逻辑从delegate中移到了监听器中,实现了以下功能:
- 通过使用事件监听器,精灵可以使用
SceneGraphPriority添加到事件分发器,即当点击精灵时,回调函数可以以显示的顺序来调用(显示在前面的精灵优先得到事件)。 - 在处理事件逻辑时,可以基于不同的状况来处理触控逻辑,例如显示点击的效果。
- 由于
listener->setSwallowTouches(true)的设置,以及onTouchBegan中的处理逻辑,不管何种显示顺序都可以被处理。
FixedPriority 与 SceneGraphPriority
EventDispatcher使用优先级来决定监听器对事件的分发:
FixedPriority:是一个整数,较低值的EventListeners优先于较高值的EventListeners。SceneGraphPriority:是一个节点的指针,较高Z Order的节点优先于较低Z Order的节点,这样可以确保前面的元素获取触控事件。
其它事件处理模式
键盘事件
可以使用CC_CALLBACK_N宏来实现键盘事件处理:
// 创建键盘监听器
auto listener = EventListenerKeyboard::create();
listener->onKeyPressed = CC_CALLBACK_2(KeyboardTest::onKeyPressed, this);
listener->onKeyReleased = CC_CALLBACK_2(KeyboardTest::onKeyReleased, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
// 实现键盘回调的宏
void KeyboardTest::onKeyPressed(EventKeyboard::KeyCode keyCode, Event* event) {
log("Key with keycode %d pressed", keyCode);
}
void KeyboardTest::onKeyReleased(EventKeyboard::KeyCode keyCode, Event* event) {
log("Key with keycode %d released", keyCode);
}
加速器事件
在使用加速器事件之前,需要在设备上启用加速器:
Device::setAccelerometerEnabled(true);
使用监听器:
auto listener = EventListenerAcceleration::create(CC_CALLBACK_2(AccelerometerTest::onAcceleration, this));
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
// 实现回调函数
void AccelerometerTest::onAcceleration(Acceleration* acc, Event* event) {
// Processing logic here
}
鼠标事件
在 V3.0 中添加了鼠标点击事件分发,支持多平台,丰富了用户的游戏体验。首先需要创建事件监听器:
_mouseListener = EventListenerMouse::create();
_mouseListener->onMouseMove = CC_CALLBACK_1(MouseTest::onMouseMove, this);
_mouseListener->onMouseUp = CC_CALLBACK_1(MouseTest::onMouseUp, this);
_mouseListener->onMouseDown = CC_CALLBACK_1(MouseTest::onMouseDown, this);
_mouseListener->onMouseScroll = CC_CALLBACK_1(MouseTest::onMouseScroll, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(_mouseListener, this);
然后实现监听器的回调函数:
void MouseTest::onMouseDown(Event *event) {
EventMouse* e = (EventMouse*)event;
std::string str = "Mouse Down detected, Key: ";
str += std::to_string(e->getMouseButton());
// ...
}
void MouseTest::onMouseUp(Event *event) {
EventMouse* e = (EventMouse*)event;
std::string str = "Mouse Up detected, Key: ";
str += std::to_string(e->getMouseButton());
// ...
}
void MouseTest::onMouseMove(Event *event) {
EventMouse* e = (EventMouse*)event;
std::string str = "MousePosition X: ";
str = str + std::to_string(e->getCursorX()) + " Y: " + std::to_string(e->getCursorY());
// ...
}
void MouseTest::onMouseScroll(Event *event) {
EventMouse* e = (EventMouse*)event;
std::string str = "Mouse Scroll detected, X: ";
str = str + std::to_string(e->getScrollX()) + " Y: " + std::to_string(e->getScrollY());
// ...
}
定制事件
除了系统定义的事件,还可以定制不是由系统自动触发的事件,通过自己的代码来实现:
_listener = EventListenerCustom::create("game_custom_event1", [=](EventCustom* event) {
std::string str("Custom event 1 received, ");
char* buf = static_cast<char*>(event->getUserData());
str += buf;
str += " times";
statusLabel->setString(str.c_str());
});
_eventDispatcher->addEventListenerWithFixedPriority(_listener, 1);
触发定制事件的代码如下:
static int count = 0;
++count;
char* buf = new char[10];
sprintf(buf, "%d", count);
EventCustom event("game_custom_event1");
event.setUserData(buf);
_eventDispatcher->dispatchEvent(&event);
CC_SAFE_DELETE_ARRAY(buf);
上述代码创建了一个EventCustom对象,设置了用户数据,通过手工调用_eventDispatcher的dispatchEvent方法触发,这会触发前面定义的处理器。
删除事件监听器
已经添加的事件监听器可以通过以下方式删除:
_eventDispatcher->removeEventListener(listener);
使用以下代码可以删除所有的事件监听器:
_eventDispatcher->removeAllEventListeners();
需要注意的是,当调用removeAllEventListeners时,该节点的所有监听器都会被删除,建议删除特定的监听器。另外,调用removeAll后,菜单会停止响应,因为它也需要接收触控事件。