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.
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
The EventDispatcher uses priorities to decide which listeners get delivered an event first.
FixedPriority
is an integer value. Event listeners with lower Priority values
get to process events before event listeners with higher Priority values.
SceneGraphPriority
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?
XXX: Check order... I goes first, then H
Well, when use SceneGraphPriority you are actually walking this above tree backwards... H, I, 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 thought to G.. and so on until the event either swallowed or it does not get answered.
XXX: Add reverse Scene Graph screenshot
XXX: Keep using the scene graph in the sample... assume that each node is a consumer. how the events are propagated.
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
};
// 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.
When you have a listener and you want an object to accept the event it was given you must swallow it. In other words you comsume 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;
};
For dekstop 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);
}
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
}
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)
{
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());
// ...
}
XXX: REview the sample code
The event types above are defined by the system, and the events are triggered by
the system automatically. In addition, you can make your own custom events which
are triggered by your code and not the system. These are called Custom Events
.
It is easy to use a lambda
to house the functionality of these events.
_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);
// create the custom event
EventCustom event("game_custom_event1");
// pass the needed data to it
event.setUserData(buf);
// dispatch the event!
_eventDispatcher->dispatchEvent(&event);
CC_SAFE_DELETE_ARRAY(buf);
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 for your custom event.
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);
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 senese 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.