Event Dispatcher
What is the EventDispatch mechanism?
EventDispatch is a mechanism for responding to user events.
The basics:
- Event listeners encapsulate your event processing code.
- Event dispatcher notifies listeners of user events.
- Event objects contain information about the event.
5 types of event listeners.
EventListenerTouch
- responds to touch events
EventListenerKeyboard
- responds to keyboard events
EventListenerAcceleration
- responds to accelerometer events
EventListenMouse
- responds to mouse events
EventListenerCustom
- responds to custom events
FixedPriority vs SceneGraphPriority
The EventDispatcher uses priorities to decide which listeners get delivered an event first.
Fixed Priority is an integer value. Event listeners with lower Priority values get to process events before event listeners with higher Priority values.
Scene Graph Priority is a pointer to a Node
. Event listeners whose Nodes have
higher z-order values (that is, are drawn on top) receive events before event
listeners whose Nodes have lower z-order values (that is, are drawn below).
This ensures that touch events, for example, get delivered front-to-back, as you
would expect.
Remember Chapter 2? Where we talked about the scene graph and we talked about this diagram?
Well, when use Scene Graph Priority you are actually walking this above tree backwards... I, H, G, F, E, D, C, B, A. If an event is triggered, H would take a look and either swallow it (more on this below) or let is pass through to I. Same thing, I will either consume it or let is pass through to G and so on until the event either swallowed_ it or does not get answered.
Touch Events
Touch events are the most important event in mobile gaming. They are easy to create and provide versatile functionality. Let's make sure we know what a touch event is. When you touch the screen of your mobile device, it accepts the touch, looks at where you touched and decides what you touched. Your touch is then answered. It is possible that what you touched might not be the responding object but perhaps something underneath it. Touch events are usually assigned a priority and the event with the highest priority is the one that answers. Here is how you create a basic touch event listener:
// 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);
As you can see there are 3 distinct events that you can act upon when using a touch event listener. They each have a distinct time in which they are called.
onTouchBegan is triggered when you press down.
onTouchMoved is triggered if you move the object around while still pressing down.
onTouchEnded is triggered when you let up on the touch.
Swallowing Events
When you have a listener and you want an object to accept the event it was given you must swallow it. To say it another way, you consume it so that it doesn't get passed to other objects in highest to lowest priority. This is easy to do.
// 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;
};
Creating a keyboard event
For desktop games, you might want find using keyboard mechanics useful. Cocos2d-x supports keyboard events. Just like with touch events above, keyboard events are easy to create.
// 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);
}
Creating an accelerometer event
Some mobile devices come equipped with an accelerometer. An accelerometer is a sensor that measures g-force as well as changes in direction. A use case would be needing to move your phone back and forth, perhaps to simulate a balancing act. Cocos2d-x also supports these events and creating them is simple. Before using accelerometer events, you need to enable them on the device:
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
}
Creating a mouse event
As it always has, Cocos2d-x supports mouse events.
_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)
{
// to illustrate the event....
EventMouse* e = (EventMouse*)event;
string str = "Mouse Down detected, Key: ";
str += tostr(e->getMouseButton());
}
void MouseTest::onMouseUp(Event *event)
{
// to illustrate the event....
EventMouse* e = (EventMouse*)event;
string str = "Mouse Up detected, Key: ";
str += tostr(e->getMouseButton());
}
void MouseTest::onMouseMove(Event *event)
{
// to illustrate the event....
EventMouse* e = (EventMouse*)event;
string str = "MousePosition X:";
str = str + tostr(e->getCursorX()) + " Y:" + tostr(e->getCursorY());
}
void MouseTest::onMouseScroll(Event *event)
{
// to illustrate the event....
EventMouse* e = (EventMouse*)event;
string str = "Mouse Scroll detected, X: ";
str = str + tostr(e->getScrollX()) + " Y: " + tostr(e->getScrollY());
}
Creating a Custom Event
The event types above are defined by the system, and the events (such as touch screen, keyboard response etc) are triggered by the system automatically. In addition, you can make your own custom events which are not triggered by the system, but by your code, as follows:
_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);
A custom event listener has been defined above, with a response method, and added to the event dispatcher. So how is the event handler triggered? Check it out:
static int count = 0;
++count;
char* buf[10];
sprintf(buf, "%d", count);
EventCustom event("game_custom_event1");
event.setUserData(buf);
_eventDispatcher->dispatchEvent(&event);
The above example creates an EventCustom object and sets its UserData. It is then
dispatched manually with _eventDispatcher->dispatchEvent(&event)
. This triggers
the event handler defined previously. The handler is called immediately so a local
stack variable can be used as the UserData.
Registering event with the dispatcher
It is easy to register an event with the Event Dispatcher. Taking the sample touch event listener from above:
// Add listener
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1,
sprite1);
It is important to note that a touch event can only be registered once per object. If you need to use the same listener for multiple objects you should use clone().
// Add listener
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1,
sprite1);
// Add the same listener to multiple objects.
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(),
sprite2);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(),
sprite3);
Removing events from the dispatcher
An added listener can be removed with following method:
_eventDispatcher->removeEventListener(listener);
Although they may seem special, built-in Node
objects use the event dispatcher
in the same way we have talked out. Makes sense, right? Take Menu
for an example.
When you have a Menu
with MenuItems
when you click them you are dispatching a
event. You can also removeEventListener() on built-in Node
objects.