From the main post, we saw that there are three steps to setup an input device.
- Create a listener
- Assign callback functions to events
- Add the listener to _eventDispatcher.
To setup the keyboard listener, just create an EventListenerKeyboard instance.
auto listener = EventListenerKeyboard::create();
This EventListenerKeyboard has two event callbacks that we can use.
- void onKeyPressed (EventKeyboard::KeyCode, Event*)
- void onKeyReleased (EventKeyboard::KeyCode, Event*)
They are defined in the cocos2d-x header as:
std::function<void(EventKeyboard::KeyCode, Event*)> onKeyPressed; std::function<void(EventKeyboard::KeyCode, Event*)> onKeyReleased;
They are of type std::function which means we can point them to functions, methods or lambdas. Whenever a key is pressed or released, the functions that we specify here will be called.
At last, you add your listener to the _eventDispatcher:
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
The KeyCode (in EventKeyboard) is an enum that defines a constant for each keyboard’s key code (For most of them anyway). Have a look at the key list:
enum class KeyCode { KEY_NONE, KEY_PAUSE, KEY_SCROLL_LOCK, KEY_PRINT, KEY_SYSREQ, KEY_BREAK, KEY_ESCAPE, KEY_BACK = KEY_ESCAPE, KEY_BACKSPACE, KEY_TAB, KEY_BACK_TAB, KEY_RETURN, KEY_CAPS_LOCK, KEY_SHIFT, KEY_LEFT_SHIFT = KEY_SHIFT, KEY_RIGHT_SHIFT, KEY_CTRL, KEY_LEFT_CTRL = KEY_CTRL, KEY_RIGHT_CTRL, KEY_ALT, KEY_LEFT_ALT = KEY_ALT, KEY_RIGHT_ALT, KEY_MENU, KEY_HYPER, KEY_INSERT, KEY_HOME, KEY_PG_UP, KEY_DELETE, KEY_END, KEY_PG_DOWN, KEY_LEFT_ARROW, KEY_RIGHT_ARROW, KEY_UP_ARROW, KEY_DOWN_ARROW, KEY_NUM_LOCK, KEY_KP_PLUS, KEY_KP_MINUS, KEY_KP_MULTIPLY, KEY_KP_DIVIDE, KEY_KP_ENTER, KEY_KP_HOME, KEY_KP_UP, KEY_KP_PG_UP, KEY_KP_LEFT, KEY_KP_FIVE, KEY_KP_RIGHT, KEY_KP_END, KEY_KP_DOWN, KEY_KP_PG_DOWN, KEY_KP_INSERT, KEY_KP_DELETE, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_SPACE, KEY_EXCLAM, KEY_QUOTE, KEY_NUMBER, KEY_DOLLAR, KEY_PERCENT, KEY_CIRCUMFLEX, KEY_AMPERSAND, KEY_APOSTROPHE, KEY_LEFT_PARENTHESIS, KEY_RIGHT_PARENTHESIS, KEY_ASTERISK, KEY_PLUS, KEY_COMMA, KEY_MINUS, KEY_PERIOD, KEY_SLASH, KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_COLON, KEY_SEMICOLON, KEY_LESS_THAN, KEY_EQUAL, KEY_GREATER_THAN, KEY_QUESTION, KEY_AT, KEY_CAPITAL_A, KEY_CAPITAL_B, KEY_CAPITAL_C, KEY_CAPITAL_D, KEY_CAPITAL_E, KEY_CAPITAL_F, KEY_CAPITAL_G, KEY_CAPITAL_H, KEY_CAPITAL_I, KEY_CAPITAL_J, KEY_CAPITAL_K, KEY_CAPITAL_L, KEY_CAPITAL_M, KEY_CAPITAL_N, KEY_CAPITAL_O, KEY_CAPITAL_P, KEY_CAPITAL_Q, KEY_CAPITAL_R, KEY_CAPITAL_S, KEY_CAPITAL_T, KEY_CAPITAL_U, KEY_CAPITAL_V, KEY_CAPITAL_W, KEY_CAPITAL_X, KEY_CAPITAL_Y, KEY_CAPITAL_Z, KEY_LEFT_BRACKET, KEY_BACK_SLASH, KEY_RIGHT_BRACKET, KEY_UNDERSCORE, KEY_GRAVE, KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z, KEY_LEFT_BRACE, KEY_BAR, KEY_RIGHT_BRACE, KEY_TILDE, KEY_EURO, KEY_POUND, KEY_YEN, KEY_MIDDLE_DOT, KEY_SEARCH, KEY_DPAD_LEFT, KEY_DPAD_RIGHT, KEY_DPAD_UP, KEY_DPAD_DOWN, KEY_DPAD_CENTER, KEY_ENTER, KEY_PLAY };
Now, Lets have a look at a couple of examples…
Simple Keyboard Example
In this test I will create a sprite in my Layer class and move it around with the arrow keys.
Lets jump right into the code…
KeyboardScene.h
#ifndef KeyboardScene_hpp #define KeyboardScene_hpp #include "cocos2d.h" USING_NS_CC; class KeyboardScene : public Layer { public: static Scene* createScene(); virtual bool init(); CREATE_FUNC(KeyboardScene); float x, y; virtual void onKeyPressed(EventKeyboard::KeyCode keyCode, Event* event); }; #endif /* KeyboardScene_hpp */
KeyboardScene.cpp
#include "KeyboardScene.h" #include <iostream> USING_NS_CC; Scene* KeyboardScene::createScene() { auto scene = Scene::create(); auto layer = KeyboardScene::create(); scene->addChild(layer); return scene; } bool KeyboardScene::init() { auto visibleSize = Director::getInstance()->getVisibleSize(); Vec2 origin = Director::getInstance()->getVisibleOrigin(); x = origin.x + visibleSize.width / 2; y = origin.y + visibleSize.height / 2; auto sprite = Sprite::create("Box.png"); sprite->setPosition(x, y); this->addChild(sprite, 0, 1); // Add the sprite as a child with tag = 1 auto listener = EventListenerKeyboard::create(); // Create a keyboard listener listener->onKeyPressed = CC_CALLBACK_2(KeyboardScene::onKeyPressed, this); // Assign a callback to onKeyPressed event _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this); // Add the listener to the event dispatcher return true; } void KeyboardScene::onKeyPressed(EventKeyboard::KeyCode keyCode, Event* event) { float translate = 2.0; switch(keyCode){ case EventKeyboard::KeyCode::KEY_LEFT_ARROW: x -= translate; break; case EventKeyboard::KeyCode::KEY_RIGHT_ARROW: x += translate; break; case EventKeyboard::KeyCode::KEY_UP_ARROW: y += translate; break; case EventKeyboard::KeyCode::KEY_DOWN_ARROW: y -= translate; break; default: break; } auto sprite = this->getChildByTag(1); // Retrieve the sprite using the tag sprite->setPosition(x, y); CCLOG("Code: %d, x= %f, y= %f", keyCode, x, y); }
As you can see in the code, we have our own onKeyPressed method in the class that checks the keyboard code and if it is an arrow key, adjusts the position of the sprite.
The problem with this code is that if you want to move the sprite to a direction constantly, you will have to keep pressing and releasing the arrow keys! If you hold down an arrow key, nothing will happen!
To fix that we should change our code to store the keys in a std::vector or std::list (some kind of array) when they are pressed down and remove them from the list when they are released. This way we can know what key is still pressed down so that we can make a continuous animation for our sprite.
You can even store the time that a key has been held down if you use a std::map instead of a vector or list.
In the next example, I will create a map container to store the key code that is down and the time that it was pressed to fix our sprite animation.
Enhanced Keyboard Example
In this example, I will save the keys when they are pressed and remove them when they are released. This way we will know which keys are down and can act accordingly.
KeyboardScene.h
#ifndef KeyboardScene_hpp #define KeyboardScene_hpp #include <chrono> #include "cocos2d.h" USING_NS_CC; class KeyboardScene : public Layer { public: static Scene* createScene(); virtual bool init(); CREATE_FUNC(KeyboardScene); void update(float delta); virtual void onKeyPressed (EventKeyboard::KeyCode keyCode, Event* event); virtual void onKeyReleased(EventKeyboard::KeyCode keyCode, Event* event); std::map<EventKeyboard::KeyCode, std::chrono::system_clock::time_point> keys; float x, y; }; #endif
KeyboardScene.cpp
#include "KeyboardScene.h" #include <iostream> USING_NS_CC; Scene* KeyboardScene::createScene() { auto scene = Scene::create(); auto layer = KeyboardScene::create(); scene->addChild(layer); return scene; } bool KeyboardScene::init() { auto visibleSize = Director::getInstance()->getVisibleSize(); Vec2 origin = Director::getInstance()->getVisibleOrigin(); x = origin.x + visibleSize.width / 2; y = origin.y + visibleSize.height / 2; auto sprite = Sprite::create("Box.png"); sprite->setPosition(x, y); this->addChild(sprite, 0, 1); auto listener = EventListenerKeyboard::create(); listener->onKeyPressed = CC_CALLBACK_2(KeyboardScene::onKeyPressed, this); listener->onKeyReleased = CC_CALLBACK_2(KeyboardScene::onKeyReleased, this); _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this); this->scheduleUpdate(); return true; } void KeyboardScene::onKeyPressed(EventKeyboard::KeyCode keyCode, Event* event) { if (keys.find(keyCode) == keys.end()) keys[keyCode] = std::chrono::system_clock::now(); std::cout << "Number of Keys: " << keys.size() << std::endl; } void KeyboardScene::onKeyReleased(EventKeyboard::KeyCode keyCode, Event* event) { if (keys.find(keyCode) != keys.end()) { std::chrono::duration<double> elapsed_seconds = std::chrono::system_clock::now() - keys[keyCode]; std::cout << "Key code " << int(keyCode) << " was down for " << elapsed_seconds.count() << "s" << std::endl; } keys.erase(keyCode); std::cout << "Number of Keys: " << keys.size() << std::endl; } void KeyboardScene::update(float delta) { float translate = 10.0 * delta; if (keys.find(EventKeyboard::KeyCode::KEY_LEFT_ARROW) != keys.end()) x -= translate; if (keys.find(EventKeyboard::KeyCode::KEY_RIGHT_ARROW) != keys.end()) x += translate; if (keys.find(EventKeyboard::KeyCode::KEY_UP_ARROW) != keys.end()) y += translate; if (keys.find(EventKeyboard::KeyCode::KEY_DOWN_ARROW) != keys.end()) y -= translate; auto sprite = this->getChildByTag(1); sprite->setPosition(x, y); }
joilnen says
Cool explanation but to get more of one event in the same time, like to pull key combinations used in arcade game for example ?