Saturday, February 1, 2014

Handling GamePad events in BB10 Cascades App

I added GamePad support to my CrazyFlight game for BB10. You can see demo here.



In this post I will describe, how we can add GamePad support to BB10 cascades or Qt app.

First we should add use_gamepad permission to bar-descriptor.xml file. This is not necessary to enable gamepad support but its necessary for AppWorld to detect that your game supports GamePad, this helps in app discovery process.
 <permission>use_gamepad</permission>
We should also add libscreen dependency to our .pro file.
LIBS += -lscreen
As far as I know there is no Cascades API for handling GamePad events, we need to rely on native API to get GamePad events. I created a Helper class that handles Native API call back and send events to Cascades QML items.

Here is definition of my helper class's (GamePadObserver.h) header file.
#ifndef GAMEPADOBSERVER_H_
#define GAMEPADOBSERVER_H_

class GamePadObserver: public QObject {
 Q_OBJECT
 Q_ENUMS(GamePadButton)

 // Structure representing a game controller.
 struct GameController {
  // Static device info.
  screen_device_t handle;
  int type;
  int analogCount;
  int buttonCount;
  char id[64];

  // Current state.
  int buttons;
  int analog0[3];
  int analog1[3];

  // Text to display to the user about this controller.
  char deviceString[256];
 };

public:
 //Enum which we will use to send signal when GamePad event is detected
 enum GamePadButton{
   A_BUTTON=0,
   B_BUTTON,
   C_BUTTON,
   X_BUTTON,
   Y_BUTTON,
   Z_BUTTON,
   MENU1_BUTTON,
   MENU2_BUTTON,
   MENU3_BUTTON,
   MENU4_BUTTON,
   L1_BUTTON,
   L2_BUTTON,
   L3_BUTTON,
   R1_BUTTON,
   R2_BUTTON,
   R3_BUTTON,
   DPAD_UP_BUTTON,
   DPAD_DOWN_BUTTON,
   DPAD_LEFT_BUTTON,
   DPAD_RIGHT_BUTTON,
   NO_BUTTON
 };

public:
 GamePadObserver(QObject* parent = 0);
 virtual ~GamePadObserver();

 //Main event loop should send event to this handler if it can not handle event by itself
 //This handler will try to handle event if its related to GamePad
 void handleScreenEvent(bps_event_t *event);

signals:
        //Signals will be emitted when Gamepad events is detected
 void buttonReleased(int button);
 void buttonPressed(int button);

private:
 //Helper methods to discover the GamePad and device connection
 void discoverControllers();
 void initController(GameController* controller, int player);
 void loadController(GameController* controller);
 void handleDeviceConnection(screen_event_t screen_event);
       
 // Methods to handle gamepad events
 void handleGamePadInput(screen_event_t screen_event);
 QString gamePadButtonAsString(GamePadButton button);

private:
 screen_context_t _screen_ctx;
 GameController _controllers[2];
 bool _conneted;
 GamePadButton _lastButton;
};
#endif /* GAMEPADOBSERVER_H_ */
Now let's see source file.
In constructor we are creating screen context and then trying to discover if there is GamePad connected to device already.
#define SCREEN_API(x, y) rc = x; \
    if (rc)  printf("\n%s in %s: %d, %d", y, __FUNCTION__,__LINE__, errno)

GamePadObserver::GamePadObserver( QObject* parent)
: QObject(parent),_screen_ctx(0),_conneted(false)
{
 // Create a screen context that will be used to create an EGL surface to receive libscreen events.
 SCREEN_API(screen_create_context(&_screen_ctx, SCREEN_APPLICATION_CONTEXT), "create_context");
 discoverControllers();
}

void GamePadObserver::discoverControllers()
{
    // Get an array of all available devices.
    int deviceCount = 0;
    SCREEN_API(screen_get_context_property_iv(_screen_ctx, SCREEN_PROPERTY_DEVICE_COUNT, &deviceCount), "SCREEN_PROPERTY_DEVICE_COUNT");
    screen_device_t* devices = (screen_device_t*) calloc(deviceCount, sizeof(screen_device_t));
    SCREEN_API(screen_get_context_property_pv(_screen_ctx, SCREEN_PROPERTY_DEVICES, (void**)devices), "SCREEN_PROPERTY_DEVICES");

    // Scan the list for gamepad and joystick devices.
    int controllerIndex = 0;
    for (int i = 0; i < deviceCount; i++) {
        int type;
        SCREEN_API(screen_get_device_property_iv(devices[i], SCREEN_PROPERTY_TYPE, &type), "SCREEN_PROPERTY_TYPE");

        if ( !rc && (type == SCREEN_EVENT_GAMEPAD || type == SCREEN_EVENT_JOYSTICK)) {
            // Assign this device to control Player 1 or Player 2.
            GameController* controller = &_controllers[controllerIndex];
            controller->handle = devices[i];
            loadController(controller);

            // We'll just use the first compatible devices we find.
            controllerIndex++;
            if (controllerIndex == MAX_CONTROLLERS) {
                break;
            }
        }
    }
    free(devices);
}
On destructor we should release screen context.
GamePadObserver::~GamePadObserver()
{
 screen_destroy_context(_screen_ctx);
}
loadController setup our GameController structure, that we will use to store currently pressed buttons while handling the events.
void GamePadObserver::loadController(GameController* controller)
{
    // Query libscreen for information about this device.
    SCREEN_API(screen_get_device_property_iv(controller->handle, SCREEN_PROPERTY_TYPE, &controller->type), "SCREEN_PROPERTY_TYPE");
    SCREEN_API(screen_get_device_property_cv(controller->handle, SCREEN_PROPERTY_ID_STRING, sizeof(controller->id), controller->id), "SCREEN_PROPERTY_ID_STRING");
    SCREEN_API(screen_get_device_property_iv(controller->handle, SCREEN_PROPERTY_BUTTON_COUNT, &controller->buttonCount), "SCREEN_PROPERTY_BUTTON_COUNT");

    // Check for the existence of analog sticks.
    if (!screen_get_device_property_iv(controller->handle, SCREEN_PROPERTY_ANALOG0, controller->analog0)) {
     ++controller->analogCount;
    }

    if (!screen_get_device_property_iv(controller->handle, SCREEN_PROPERTY_ANALOG1, controller->analog1)) {
     ++controller->analogCount;
    }

    if (controller->type == SCREEN_EVENT_GAMEPAD) {
        sprintf( controller->deviceString, "Gamepad device ID: %s", controller->id);
        qDebug() << "Gamepad device ID" <<  controller->id;
    } else {
        sprintf( controller->deviceString, "Joystick device: %s", controller->id);
        qDebug() << "Joystick device ID" <<  controller->id;
    }
}
handleScreenEvent function should be called from main event loop when event is related to screen domain. This function check if event type is GamePad device connection or its GamePad button event and handle it accordingly.
void GamePadObserver::handleScreenEvent(bps_event_t *event)
{
    int eventType;

    screen_event_t screen_event = screen_event_get_event(event);
    screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_TYPE, &eventType);

    switch (eventType) {
        case SCREEN_EVENT_GAMEPAD:
        case SCREEN_EVENT_JOYSTICK:
        {
         handleGamePadInput(screen_event);
         break;
        }
        case SCREEN_EVENT_DEVICE:
        {
         // A device was attached or removed.
         handleDeviceConnection(screen_event);
         break;
        }
    }
}
handleDeviceConnection handles GamePad device connection. If it detects new connection then it loads new device, in case of device disconnection it remove device.
void GamePadObserver::handleDeviceConnection(screen_event_t screen_event)
{
    // A device was attached or removed.
    screen_device_t device;
    int attached;
    int type;

    SCREEN_API(screen_get_event_property_pv(screen_event, SCREEN_PROPERTY_DEVICE, (void**)&device), "SCREEN_PROPERTY_DEVICE");
    SCREEN_API(screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_ATTACHED, &attached), "SCREEN_PROPERTY_ATTACHED");

    if ( attached ) {
        SCREEN_API(screen_get_device_property_iv(device, SCREEN_PROPERTY_TYPE, &type), "SCREEN_PROPERTY_TYPE");
    }

    int i;
    if (attached && (type == SCREEN_EVENT_GAMEPAD || type == SCREEN_EVENT_JOYSTICK)) {
        for (i = 0; i < MAX_CONTROLLERS; ++i) {
            if (!_controllers[i].handle) {
                _controllers[i].handle = device;
                loadController(&_controllers[i]);
                break;
            }
        }
    } else {
        for (i = 0; i < MAX_CONTROLLERS; ++i) {
            if (device == _controllers[i].handle) {
                initController(&_controllers[i], i);
                break;
            }
        }
    }
}

void GamePadObserver::initController(GameController* controller, int player)
{
    // Initialize controller values.
    controller->handle = 0;
    controller->type = 0;
    controller->analogCount = 0;
    controller->buttonCount = 0;
    controller->buttons = 0;
    controller->analog0[0] = controller->analog0[1] = controller->analog0[2] = 0;
    controller->analog1[0] = controller->analog1[1] = controller->analog1[2] = 0;
    sprintf(controller->deviceString, "Player %d: No device detected.", player + 1);
}
handleGamePadInput function that handles GamePad events.
void GamePadObserver::handleGamePadInput(screen_event_t /*screen_event*/)
{
    int i;
    for (i = 0; i < MAX_CONTROLLERS; i++) {
        GameController* controller = &_controllers[i];

        if ( controller->handle ) {
            GamePadButton gamePadButton = NO_BUTTON;

            // Get the current state of a gamepad device.
            SCREEN_API(screen_get_device_property_iv(controller->handle, SCREEN_PROPERTY_BUTTONS, &controller->buttons), "SCREEN_PROPERTY_BUTTONS");

            if (controller->analogCount > 0) {
             SCREEN_API(screen_get_device_property_iv(controller->handle, SCREEN_PROPERTY_ANALOG0, controller->analog0), "SCREEN_PROPERTY_ANALOG0");
            }

            if (controller->analogCount == 2) {
             SCREEN_API(screen_get_device_property_iv(controller->handle, SCREEN_PROPERTY_ANALOG1, controller->analog1), "SCREEN_PROPERTY_ANALOG1");
            }

            for(int i = A_BUTTON ; i < NO_BUTTON ; ++i) {
             if( controller->buttons & (1 << i) ) {
              gamePadButton = (GamePadButton)(i);
              break;
             }
            }

            if( gamePadButton == NO_BUTTON ) {
             emit buttonReleased( _lastButton );
             _lastButton = NO_BUTTON;
            }
            else if( _lastButton != gamePadButton ) {
             emit buttonReleased(_lastButton);
             emit buttonPressed(gamePadButton);
             _lastButton = gamePadButton;
            }
        }
    }
}
Now we have all necessary implementation to handle the GamePad connection and GamePad button events. But we should pass events related to GamePad to our helper class when we receive it in our main event handler. To do that, In our main function we should register main bps event loop or filter as below and can use GamePadObserver as described.
#include "GamePadObserver.h"

static QAbstractEventDispatcher::EventFilter previousEventFilter = 0;
static GamePadObserver _gamePadObserver;

static bool bpsEventFilter(void *message)
{
    bps_event_t * const event = static_cast(message);

    if( event && bps_event_get_domain(event) == screen_get_domain()) {
     _gamePadObserver.handleScreenEvent(event);
    }

    if (previousEventFilter)
        return previousEventFilter(message);
    else
        return false;
}

int main(int argc, char **argv)
{
    // Register GamePadObserver so we can use it in QML code
    qmlRegisterUncreatableType("GamePadObserver", 1, 0,"GamePadObserver", "");

    QApplication app(argc, argv);

    previousEventFilter = QAbstractEventDispatcher::instance()->setEventFilter(bpsEventFilter);

    QScopedPointer view(new QDeclarativeView());
    view->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
    view->setResizeMode( QDeclarativeView::SizeRootObjectToView );

    QDeclarativeContext *ctxt = view->rootContext();
    view->setSource(QUrl("app/native/assets/main.qml"));

    ctxt->setContextProperty("GamePad",&_gamePadObserver);
    view->showFullScreen();

    return app.exec();
}
And finally, out GamePadObserver class is ready to deliver GamePad events. To handle GamePad events in QML you can use GamePadObaserver class as below.
        
        Connections{
            target: GamePad
            onButtonPressed: {
                                
                if(button == GamePadObserver.DPAD_UP_BUTTON
                || button == GamePadObserver.DPAD_DOWN_BUTTON ){
                    handleUpButton();
                }else if(button ==  GamePadObserver.DPAD_LEFT_BUTTON 
                || button ==  GamePadObserver.DPAD_RIGHT_BUTTON) {
                    handleLeftButton();
                }          
                else if(button == GamePadObserver.MENU1_BUTTON                 
                || button == GamePadObserver.MENU2_BUTTON
                || button == GamePadObserver.X_BUTTON
                || button == GamePadObserver.Y_BUTTON 
                || button == GamePadObserver.A_BUTTON 
                || button == GamePadObserver.B_BUTTON ) {
                    handleButtonSelection();
                } 
            }        
        }
So, I hope this will help to utilize GamePad events in your games.

No comments:

Post a Comment