Cómo añadir maneadores de eventos a botones en Cocos2d-x 3.x y C++

Recientemente he empezado a desarrollar un port a iOS de mi juego Dark Snake, hecho originalmente para Android. He querido aprovechar la ocasión para aprender algo de C++, por lo que he decidido usar el motor Cocos2d-x. Aunque seguramente lo que explico en este artículo sería algo trivial para un desarrollador que conozca bien C++, para un novato como yo ha supuesto un pequeño quebradero de cabeza ^_^

Ejemplo básico

Para este ejemplo, voy a suponer que tenemos una scena llamada MyScene con un sólo botón. Necesitamos el fichero MyScene.h para declarar la clase y MyScene.cpp donde se define.

MyScene.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef __MyProject__MyScene__
#define __MyProject__MyScene__
 
#include "cocos2d.h"
#include "ui/CocosGUI.h"
 
class MyScene : public cocos2d::Scene {
public:
    bool init();
    CREATE_FUNC(MyScene);
 
    // Callbacks
    void buttonPressed();
};
#endif /* defined(__MyProject__MyScene__) */

De este trozo de código se pueden destacar varias cosas:

  • La linea 4 importa el fichero cabecera de cocos2d-x, lo que nos permitirá usar sus tipos, clases, funciones y macros principales.
  • La linea 5 hace la importación de los componentes de cocos2d-x necesarios para crear Interfaces Gráficas de Usuario (IGU): botones, checkbox, etc.
  • Nuestra clase hereda de la clase Scene de cocos2d.
  • Se utiliza la macro CREATE_FUNC, definida por cocos2d. Cuando esta macro se expanda previamente a la compilación del proyecto, generará un código tal que así:
    static MyScene* create()
    {
        MyScene *pRet = new(std::nothrow) MyScene();
        if (pRet && pRet->init())
        {
            pRet->autorelease();
            return pRet;
        }
        else
        {
            delete pRet;
            pRet = NULL;
            return NULL;
        }
    }
  • Si nos fijamos en el código generado por la macro, tenemos primero una llamada a un constructor que no recibe parámetros (como no hemos declarado ningún constructor, el compilador creará uno por defecto). Además se hace una llamada a un método init() que debe devolver un bool. Este método no existe, así que lo declaramos (linea 9).
  • Finalmente, en la linea 13 tenemos la declaración del método que manejará los eventos de nuestro botón.

MyScene.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "MyScene.h"
#include "iostream"
 
using namespace cocos2d;
 
bool MyScene::init() {
    Sprite* background = Sprite::create("background.png");
    this->addChild(background);
 
    ui::Button* btn = ui::Button::create("button-image.png");
    btn->setPosition( Vec2(200.f, 200.f) );
    btn->addTouchEventListener( CC_CALLBACK_0(MyScene::buttonPressed, this) );
    this->addChild(btn);
 
    return true;
}
 
void MyScene::buttonPressed() {
    std::cout << "PRESSED 0" << std::endl;
}
  • En el método init() añadimos los componentes de la IGU. En este caso sólo añadimos una imagen de fondo (background) y un botón (btn).
  • Usando el método addTouchListener() conseguimos que nuestro método buttonPressed() maneje los eventos que reciba el botón btn.
  • Para indicar que método debe manejar el evento, utilizamos la macro CC_CALLBACK_0 pasando como argumentos el selector del método y el objeto que debe ejecutarlo (en este caso es el propio objeto actual, con lo que pasamos el puntero this).

 

Ampliando el manejador de eventos

En el ejemplo descrito anteriomente, el método que maneja los eventos, MyScene::buttonPressed(), no recibe ningún parámetro. Sin embargo, puede que necesitemos recibir información sobre el evento generado, y para ello existen otras macros CC_CALLBACK_n definidas por cocos2d-x. Ejemplos:

Conocer el widget que ha generado el evento

Supongamos que queremos utilizar un mismo método para manejar eventos en más de un componente gráfico, y queremos actuar en función del elemento que haya sido pulsado. En este caso el método encargado de manejar los eventos debe recibir un parámetro de tipo cocos2d::Ref*

void buttonPressed(cocos2d::Ref* pSender);

El parámetro pSender será un puntero al widget pulsado, y podemos hacer un casting directamente para aprovechar toda su funcionalidad. Por ejemplo:

#define ACTION_PLAY 1
 
.....
 
bool MyScene::init() {
    ....
    ui::Button* btnPlay = ui::Button::create("play-image.png");
    btnPlay->setActionTag( ACTION_PLAY );
    ....
}
.....
 
void MyScene::buttonPressed(cocos2d::Ref* pSender) {
    if (((cocos2d::ui::Button*) pSender)->getActionTag() == ACTION_PLAY) {
        // ... do
    }
}

A la hora de asociar este método a los componentes gráficos, tenemos que usar la macro CC_CALLBACK_1

1
2
btn->addTouchEventListener(
           CC_CALLBACK_1(MyScene::buttonPressed, this) );

 

Saber el tipo de evento generado

Si probamos los maneadores de eventos anteriores, comprobaremos que por cada pulsación al botón se generan dos eventos. Esto se debe a que se genera uno al pulsar el botón y otro al soltarlo. Si nos interesa saber qué tipo de evento es el que se ha generado, podemos utilizar un manejador de eventos que reciba además un parámetro de tipo TouchEventType:

void buttonPressed(cocos2d::Ref *pSender, cocos2d::ui::Widget::TouchEventType eventType);

El objeto TouchEventType es un enumerado que puede tomar los valores BEGAN, MOVED, ENDED o CANCELED. Si quisiéramos actuar, por ejemplo, sólo cuando se pulse el botón podríamos tener un código tal que así:

void MyScene::buttonPressed(cocos2d::Ref *pSender, cocos2d::ui::Widget::TouchEventType eventType) {
    if (cocos2d::ui::Widget::TouchEventType::BEGAN == eventType) {
        // ... do
    }
}

Para asociar este método a los componentes gráficos, tenemos que usar la macro CC_CALLBACK_2

1
2
btn->addTouchEventListener(
          CC_CALLBACK_2(MyScene::buttonPressed, this) );

 

Espero que este artículo te haya resultado útil.

Saludos!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*