Cocos2d-x 开发者指南08:事件分发机制

2015年03月20日 14:23 0 点赞 0 评论 更新于 2025-11-21 17:52

在这一章课程中,我们将深入了解事件分发机制以及事件监听的类型。

事件分发机制

基本概念

EventDispatch 是一种响应用户事件的机制,它主要包含以下几个核心概念:

  • 事件监听器:封装了事件处理的代码,负责对特定事件进行响应和处理。
  • 事件调度器:其作用是通知用户事件的监听器,当有事件发生时,调度器会根据相应规则找到合适的监听器并触发其处理逻辑。
  • 事件对象:包含了关于事件的详细信息,例如事件的类型、发生的位置等,这些信息可供事件监听器在处理事件时使用。

事件监听器的 5 种类型

Cocos2d-x 提供了 5 种不同类型的事件监听器,分别用于响应不同类型的事件:

  • EventListenerTouch:用于响应触摸事件,在移动游戏开发中十分常用。
  • EventListenerKeyboard:响应键盘事件,适用于桌面游戏开发。
  • EventListenerAcceleration:响应加速度计的事件,可用于检测设备的倾斜、晃动等动作。
  • EventListenerMouse:响应鼠标事件,方便在支持鼠标操作的环境中使用。
  • EventListenerCustom:用于响应自定义事件,开发者可以根据自己的需求定义和触发特定的事件。

FixedPriority vs SceneGraphPriority

EventDispatcher 事件分发机制使用优先级来决定在事件开始时触发哪一个监听器,主要有两种优先级类型:

  • FixedPriority:是一个整数值。优先级值较低的事件监听器会在优先级值较高的事件监听器之前处理。例如,优先级为 1 的监听器会比优先级为 2 的监听器先处理事件。
  • SceneGraphPriority:是一个指向 Node 的指针。节点 z - order 值较高(在顶端绘制)的事件监听器在节点 z - order 值较低(在底端绘制)的事件监听器之前接收到事件。这将确保触摸事件能像我们所期望的那样,从前往后依次传递。

回顾第二章中关于场景图谱的讨论,当我们使用 SceneGraphPriority 时,事件处理顺序其实是按倒序执行的。例如,对于场景中的节点 I, H, G, F, E, D, C, B, A,如果一个事件被触发,H 将会首先检查这个事件,它可以选择吞并这个事件或者将事件传递给 I。同理,I 将吞并这个事件或者将事件传递给 G,依次执行,直到没有可吞并的事件或者没有事件响应为止。

触摸事件

在手游开发中,触摸事件是最重要的事件类型之一。它们很容易被创建来提供通用的功能。当你触摸移动设备的屏幕时,屏幕会接收到这个触摸行为,并检查触摸的位置以及决定触摸到了什么对象。然后,相应的触摸行为就会被响应。需要注意的是,你所触摸的或许不是直接的响应对象,但很有可能是它下面的东西。通常会给触摸事件分配优先级,优先级最高的对象会被先响应。

以下代码展示了如何创建一个基本的触摸事件监听器:

// Create a "one by one" touch event listener
// (processes one touch at a time)
auto listener1 = EventListenerTouchOneByOne::create();

// trigger when you push down
listener1->onTouchBegan = [](Touch* touch, Event* event) {
// your code
return true; // if you are consuming it
};

// trigger when moving touch
listener1->onTouchMoved = [](Touch* touch, Event* event) {
// your code
};

// trigger when you let up
listener1->onTouchEnded = [=](Touch* touch, Event* event) {
// your code
};

// Add listener
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1, this);

当使用触摸事件侦听器时,有三个不同的事件供我们操作,它们的调用时间段各不相同:

  • onTouchBegan:按下屏幕时会触发该函数。
  • onTouchMoved:按下屏幕并移动对象时会触发该函数。
  • onTouchEnded:停止触摸屏幕时会触发该函数。

吞并事件(Swallowing Events)

如果你有一个监听器并且希望某个对象能接收到事件,那么你必须吞并它。吞并事件可以避免将高优先级的事件传递给其他低优先级对象。实现方法如下:

// When "swallow touches" is true, then returning 'true' from the
// onTouchBegan method will "swallow" the touch event, preventing
// other listeners from using it.
listener1->setSwallowTouches(true);

// you should also return true in onTouchBegan()
listener1->onTouchBegan = [](Touch* touch, Event* event) {
// your code
return true;
};

创建键盘事件

对于桌面游戏开发,键盘机制非常有用。Cocos2d-x 支持键盘事件,并且创建键盘事件和触摸事件一样简单:

// creating a keyboard event listener
auto listener = EventListenerKeyboard::create();
listener->onKeyPressed = CC_CALLBACK_2(KeyboardTest::onKeyPressed, this);
listener->onKeyReleased = CC_CALLBACK_2(KeyboardTest::onKeyReleased, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);

// Implementation of the keyboard event callback function prototype
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);
}

创建加速计事件

许多移动设备都配备了加速度计,它是一个可以测量重力和方向上变化的传感器。例如,我们可以通过来回移动电话来模拟平衡。Cocos2d-x 支持这些事件,并且创建起来很简单。在使用加速计事件之前,你需要在设备上激活这个事件:

Device::setAccelerometerEnabled(true);

// creating an accelerometer event
auto listener = EventListenerAcceleration::create(CC_CALLBACK_2(AccelerometerTest::onAcceleration, this));
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);

// Implementation of the accelerometer callback function prototype
void AccelerometerTest::onAcceleration(Acceleration* acc, Event* event) {
// Processing logic here
}

创建鼠标事件

Cocos2d-x 一直支持鼠标事件,以下是创建鼠标事件监听器的代码示例:

_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;
string str = "Mouse Down detected, Key: ";
str += tostr(e->getMouseButton());
// ...
}

void MouseTest::onMouseUp(Event *event) {
EventMouse* e = (EventMouse*)event;
string str = "Mouse Up detected, Key: ";
str += tostr(e->getMouseButton());
// ...
}

void MouseTest::onMouseMove(Event *event) {
EventMouse* e = (EventMouse*)event;
string str = "MousePosition X:";
str = str + tostr(e->getCursorX()) + " Y:" + tostr(e->getCursorY());
// ...
}

void MouseTest::onMouseScroll(Event *event) {
EventMouse* e = (EventMouse*)event;
string str = "Mouse Scroll detected, X: ";
str = str + tostr(e->getScrollX()) + " Y: " + tostr(e->getScrollY());
// ...
}

使用分配器注册事件

使用事件分配器可以很容易地注册事件。以上文的触摸事件监视器为例:

// Add listener
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1, sprite1);

需要注意的是,每个对象都只能注册一个触摸事件。如果多个对象需要使用相同的监听器,你需要使用 clone() 方法。示例代码如下:

// Add listener
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1, sprite1);

// Add the same listener to multiple objects.
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(), sprite2);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(), sprite3);

从分配器中移除事件

使用如下方法可以移除一个已有的监听器:

_eventDispatcher->removeEventListener(listener);

尽管这看起来很特殊,但是内置的 Node 对象使用事件分发机制与我们所讲的方式是相同的。以 Menu 为例,当点击带有 MenuItems 属性的 Menu 时,你就已经分配到了一个事件。同样也可对内置的 Node 对象使用 removeEventListener 方法。

作者信息

boke

boke

共发布了 3994 篇文章