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étodobuttonPressed()maneje los eventos que reciba el botón btn. - Para indicar que método debe manejar el evento, utilizamos la macro
CC_CALLBACK_0pasando 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!