Reza Ghobadinic

Keyboard

From the main post, we saw that there are three steps to setup an input device.

  1. Create a listener
  2. Assign callback functions to events
  3. 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.

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);
}

Download Source

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);
}

Download Source

Exit mobile version