pixelboyz logo
Desarrollo de Videojuegos

Serie de tutoriales Cocos2d-x: manejo del teclado

KeyboardSS

Índice de contenido


En esta parte de la serie de tutoriales de Cocos2d-x, vamos a echar un vistazo a lo que implica el manejo de eventos de teclado. Si pasaste por el ratón/táctil tutorial, mucho de esto va a parecer muy familiar, ya que el proceso es bastante similar. Dicho esto, el manejo del teclado tiene su propio conjunto especial de problemas con los que lidiar.

Vayamos directamente a un ejemplo. Una vez más, asumo que ya sabes cómo crear tu propio AppDelegate, si no puedes, te sugiero que retrocedas. a esta parte primero.

Manejo de eventos de teclado

Nuestro primer ejemplo simplemente responderá a WASD y las teclas de flecha para mover el logotipo de Cocos2d-x por la pantalla. En este ejemplo, no realicé modificaciones especiales a una escena estándar, por lo que el encabezado no se modifica con respecto a los tutoriales anteriores.

KeyboardScene.cpp

#include "KeyboardScene.h"

USING_NS_CC;

Scene* KeyboardScene::createScene()
{
    auto scene = Scene::create();
    
    auto layer = KeyboardScene::create();
    scene->addChild(layer);
    return scene;
}

bool KeyboardScene::init()
{
    if ( !Layer::init() )
    {
        return false;
    }
    
    auto sprite = Sprite::create("HelloWorld.png");
    sprite->setPosition(this->getContentSize().width/2, this->getContentSize().height/2);

    this->addChild(sprite, 0);

    auto eventListener = EventListenerKeyboard::create();



    eventListener->onKeyPressed = [](EventKeyboard::KeyCode keyCode, Event* event){

        Vec2 loc = event->getCurrentTarget()->getPosition();
        switch(keyCode){
            case EventKeyboard::KeyCode::KEY_LEFT_ARROW:
            case EventKeyboard::KeyCode::KEY_A:
                event->getCurrentTarget()->setPosition(--loc.x,loc.y);
                break;
            case EventKeyboard::KeyCode::KEY_RIGHT_ARROW:
            case EventKeyboard::KeyCode::KEY_D:
                event->getCurrentTarget()->setPosition(++loc.x,loc.y);
                break;
            case EventKeyboard::KeyCode::KEY_UP_ARROW:
            case EventKeyboard::KeyCode::KEY_W:
                event->getCurrentTarget()->setPosition(loc.x,++loc.y);
                break;
            case EventKeyboard::KeyCode::KEY_DOWN_ARROW:
            case EventKeyboard::KeyCode::KEY_S:
                event->getCurrentTarget()->setPosition(loc.x,--loc.y);
                break;
        }
    };

    this->_eventDispatcher->addEventListenerWithSceneGraphPriority(eventListener,sprite);

    return true;
}

Cuando se ejecuta, ve el logotipo centrado y puede moverlo usando WASD o las teclas de flecha.

El código funciona casi de manera idéntica a nuestros ejemplos anteriores de Touch. Crea un EventListener, en este caso un EventListenerKeyboard, implementa el controlador de eventos onKeyPressed. El primer parámetro que se pasa es la enumeración EventKeyboard::KeyCode, que es un valor que representa la tecla que se presionó. El segundo valor fue el objetivo del Evento, en este caso nuestro sprite. Usamos el puntero de evento para obtener el nodo de destino y actualizar su posición en una dirección según la tecla que se presione. Finalmente, conectamos el _eventDispatcher de nuestra escena para recibir eventos. Nada realmente inesperado aquí.

Sondeo del teclado

Sin embargo, puede preguntarse… ¿qué pasa si quiero sondear eventos de teclado? Por ejemplo, ¿qué sucede si desea verificar si se presionó la barra espaciadora en un momento dado?

La respuesta corta es que no puedes. Cocos2d-x está completamente impulsado por eventos.

Sin embargo, la respuesta larga es que es relativamente fácil implementar su propia solución, así que hagámoslo ahora. Saltaré directamente con el código y lo discutiré después.

KeyboardScene.h

#pragma once

#include "cocos2d.h"
#include <map>


class KeyboardScene : public cocos2d::Layer
{
public:

    static cocos2d::Scene* createScene();
    virtual bool init();

    bool isKeyPressed(cocos2d::EventKeyboard::KeyCode);
    double keyPressedDuration(cocos2d::EventKeyboard::KeyCode);

    CREATE_FUNC(KeyboardScene);

private:
    static std::map<cocos2d::EventKeyboard::KeyCode,
        std::chrono::high_resolution_clock::time_point> keys;
    cocos2d::Label * label;
public:
    virtual void update(float delta) override;
};

KeyboardScene.cpp

#include "KeyboardScene.h"

USING_NS_CC;

Scene* KeyboardScene::createScene()
{
    auto scene = Scene::create();
    
    KeyboardScene* layer = KeyboardScene::create();
    scene->addChild(layer);
    return scene;
}

bool KeyboardScene::init()
{
    if ( !Layer::init() )
    {
        return false;
    }

    label = cocos2d::Label::createWithSystemFont("Press the CTRL Key","Arial",32);
    label->setPosition(this->getBoundingBox().getMidX(),this->getBoundingBox().getMidY());
    addChild(label);
    auto eventListener = EventListenerKeyboard::create();



    Director::getInstance()->getOpenGLView()->setIMEKeyboardState(true);
    eventListener->onKeyPressed = [=](EventKeyboard::KeyCode keyCode, Event* event){
        // If a key already exists, do nothing as it will already have a time stamp
        // Otherwise, set's the timestamp to now
        if(keys.find(keyCode) == keys.end()){
            keys[keyCode] = std::chrono::high_resolution_clock::now();
        }
    };
    eventListener->onKeyReleased = [=](EventKeyboard::KeyCode keyCode, Event* event){
        // remove the key.  std::map.erase() doesn't care if the key doesnt exist
        keys.erase(keyCode);
    };

    this->_eventDispatcher->addEventListenerWithSceneGraphPriority(eventListener,this);

    // Let cocos know we have an update function to be called.
    // No worries, ill cover this in more detail later on
    this->scheduleUpdate();
    return true;
}

bool KeyboardScene::isKeyPressed(EventKeyboard::KeyCode code) {
    // Check if the key is currently pressed by seeing it it's in the std::map keys
    // In retrospect, keys is a terrible name for a key/value paried datatype isnt it?
    if(keys.find(code) != keys.end())
        return true;
    return false;
}

double KeyboardScene::keyPressedDuration(EventKeyboard::KeyCode code) {
    if(!isKeyPressed(EventKeyboard::KeyCode::KEY_CTRL))
        return 0;  // Not pressed, so no duration obviously

    // Return the amount of time that has elapsed between now and when the user
    // first started holding down the key in milliseconds
    // Obviously the start time is the value we hold in our std::map keys
    return std::chrono::duration_cast<std::chrono::milliseconds>
            (std::chrono::high_resolution_clock::now() - keys[code]).count();
}

void KeyboardScene::update(float delta) {
    // Register an update function that checks to see if the CTRL key is pressed
    // and if it is displays how long, otherwise tell the user to press it
    Node::update(delta);
    if(isKeyPressed(EventKeyboard::KeyCode::KEY_CTRL)) {
        std::stringstream ss;
        ss << "Control key has been pressed for " << 
            keyPressedDuration(EventKeyboard::KeyCode::KEY_CTRL) << " ms";
        label->setString(ss.str().c_str());
    }
    else
        label->setString("Press the CTRL Key");
}
// Because cocos2d-x requres createScene to be static, we need to make other non-pointer members static
std::map<cocos2d::EventKeyboard::KeyCode,
        std::chrono::high_resolution_clock::time_point> KeyboardScene::keys;

Y cuando lo ejecutas:

Tecla de control

¿Entonces que hacemos aqui? Bueno, esencialmente registramos eventos clave a medida que entran. Tenemos dos eventos con los que trabajar, onKeyPressed y onKeyReleased. Cuando se presiona una tecla, la almacenamos en un std::map, usando KeyCode como clave y la hora actual como valor. Cuando se suelta la clave, eliminamos la clave liberada del mapa. Por tanto, en cada momento sabemos qué teclas se pulsan y durante cuánto tiempo. En este ejemplo particular, en la función actualizar () (ignore eso por ahora, ¡lo abordaré más tarde!) Hacemos una encuesta para ver si se presiona la tecla Control. Si es así, averiguamos por cuánto tiempo y mostramos una cadena.

Por lo tanto, aunque el sondeo no está integrado en Cocos2d-x, es relativamente fácil de agregar.

Manejo de teclados en dispositivos móviles

Entonces, ¿qué pasa con los teclados en los dispositivos móviles? Todos los teléfonos Android y dispositivos iOS pueden mostrar un teclado virtual (el teclado en pantalla), ¿podemos usarlo? La respuesta es… más o menos.

¿Qué pasa con los teclados físicos en los dispositivos móviles?

Quizás se esté preguntando, ¿cómo funciona un teclado físico en un dispositivo móvil con Cocos2d-x? En el caso de un iPad, la respuesta es que no. Cuando conecté un teclado Bluetooth, no pasó absolutamente nada. Lo mismo ocurrió cuando emparejé el teclado con mi teléfono Android. Sin embargo, no tengo un dispositivo Android con un teclado físico, como el Asus Transformer, pero mi instinto dice que tampoco funcionaría. Al menos, no contigo haciendo un montón de trabajo preliminar que es

Más o menos no es realmente una gran respuesta, así que entraré en un poco más de detalle. Sí, puede usar el teclado en pantalla, pero de manera muy limitada. Básicamente, puede usarlo solo para la entrada de texto. Sin embargo, la verdad es que esto debería ser suficiente, ya que controlar un juego con un teclado virtual sería una experiencia horrible.

Echemos un vistazo a un ejemplo usando campo de texto TTF e implementando un Delegado de campo de texto:

KeyTabletScene.h

#pragma once
#include "cocos2d.h"

class KeyTabletScene : public cocos2d::Layer, public cocos2d::TextFieldDelegate
{
public:
    virtual ~KeyTabletScene();

    virtual bool onTextFieldAttachWithIME(cocos2d::TextFieldTTF *sender) override;

    virtual bool onTextFieldDetachWithIME(cocos2d::TextFieldTTF *sender) override;

    virtual bool onTextFieldInsertText(cocos2d::TextFieldTTF *sender, const char *text, size_t nLen) override;

    virtual bool onTextFieldDeleteBackward(cocos2d::TextFieldTTF *sender, const char *delText, size_t nLen) 
         override;

    virtual bool onVisit(cocos2d::TextFieldTTF *sender, cocos2d::Renderer *renderer, 
      cocos2d::Mat4 const &transform, uint32_t flags) override;

    static cocos2d::Scene* createScene();
    virtual bool init();

    CREATE_FUNC(KeyTabletScene);
};

KeyTabletScene.cpp

#include "KeyTabletScene.h"

USING_NS_CC;

Scene* KeyTabletScene::createScene()
{
    auto scene = Scene::create();
    
    auto layer = KeyTabletScene::create();
    scene->addChild(layer);

    return scene;
}

bool KeyTabletScene::init()
{
    if ( !Layer::init() )
    {
        return false;
    }


    // Create a text field
    TextFieldTTF* textField = cocos2d::TextFieldTTF::textFieldWithPlaceHolder("Click here to type",
            cocos2d::Size(400,200),TextHAlignment::LEFT , "Arial", 42.0);
    textField->setPosition(this->getBoundingBox().getMidX(),
            this->getBoundingBox().getMaxY() - 20);
    textField->setColorSpaceHolder(Color3B::GREEN);
    textField->setDelegate(this);

    this->addChild(textField);

    // Add a touch handler to our textfield that will show a keyboard when touched
    auto touchListener = EventListenerTouchOneByOne::create();

    touchListener->onTouchBegan = [](cocos2d::Touch* touch, cocos2d::Event * event) -> bool {
        try {
            // Show the on screen keyboard
            auto textField = dynamic_cast<TextFieldTTF *>(event->getCurrentTarget());
            textField->attachWithIME();
            return true;
        }
        catch(std::bad_cast & err){
            return true;
        }
    };

    this->_eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, textField);

    return true;
}

KeyTabletScene::~KeyTabletScene() {

}

bool KeyTabletScene::onTextFieldAttachWithIME(TextFieldTTF *sender) {
    return TextFieldDelegate::onTextFieldAttachWithIME(sender);
}

bool KeyTabletScene::onTextFieldDetachWithIME(TextFieldTTF *sender) {
    return TextFieldDelegate::onTextFieldDetachWithIME(sender);
}

bool KeyTabletScene::onTextFieldInsertText(TextFieldTTF *sender, const char *text, size_t nLen) {
    return TextFieldDelegate::onTextFieldInsertText(sender, text, nLen);
}

bool KeyTabletScene::onTextFieldDeleteBackward(TextFieldTTF *sender, const char *delText, size_t nLen) {
    return TextFieldDelegate::onTextFieldDeleteBackward(sender, delText, nLen);
}

bool KeyTabletScene::onVisit(TextFieldTTF *sender, Renderer *renderer, const Mat4 &transform, uint32_t flags) {
    return TextFieldDelegate::onVisit(sender, renderer, transform, flags);
}

Y cuando lo ejecutas:

TabletaTecladoDisparo

Esencialmente, cuando el usuario toca la pantalla, mostramos el teclado en pantalla con una llamada a attachWithIME(), el campo de texto maneja el resto.

Tengo la sensación de que este método se depreciará en algún momento en el futuro y será reemplazado por las clases cocos::ui, pero por ahora funciona bien. Para que conste, en realidad es posible forzar el teclado en pantalla llamando a Director::getInstance()->getOpenGLView()->setIMEKeyboardState(true), pero aparentemente empuja su escena a un segundo plano, por lo que no es viable opción para controlar un juego. Iba a buscar una solución alternativa, pero luego pensé, de verdad… esto es algo francamente estúpido de hacer. Hacer cualquier otra cosa que no sea la entrada de texto con un teclado virtual es simplemente una mala idea.


Parte anterior Tabla de contenido siguiente parte



Source link

Tags :
Cocos2dx,del,manejo,serie,teclado,tutoriales

Comparte :

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *