Xbox controller

From Allegro Wiki

Jump to: navigation, search

Using an Xbox 360 Controller with Allegro 5

Allegro supports joysticks out-of-the-box, but the API is quite generic in order to handle all possible joysticks that could be encountered. Other types of controllers, such as the Xbox 360 controller, are classified by Allegro as a joystick, but it can be difficult to add support for these because there's no built-in mechanism for detecting "is the A button pressed?" or "what's the tilt of the left stick?"

This page serves in part to document which joystick values correspond to what on the controller, and in part to provide a small wrapper around Allegro's joystick system that makes it much easier to work specifically with Xbox 360 controllers.

Controller State

The state of a controller can be represented with a simple, albeit somewhat long, struct (I will regularly use "xc" as an abbreviation for "xbox controller"):

typedef struct {
    // axis values
    float left_stick_x;
    float left_stick_y;
    float left_trigger;
    float right_stick_x;
    float right_stick_y;
    float right_trigger;
    float dpad_x;
    float dpad_y;
 
    // buttons pressed?
    bool button_a;
    bool button_b;
    bool button_x;
    bool button_y;
    bool button_left_stick;
    bool button_right_stick;
    bool button_left_shoulder;
    bool button_right_shoulder;
    bool button_start;
    bool button_back;
    bool button_xbox;
 
    // reference to the underlying joystick object
    ALLEGRO_JOYSTICK *joy;
}
XC_STATE;

Seems pretty self-explanatory, right? The float values are for the values of various joystick axes from 0 - 1, including the triggers and d-pad. The bool values simply tell you whether or not a given button is pressed. (IDEA: for maximum space conservation, it should be possible to merge all boolean values into a bit string, then use the bitwise '&' operator to access individual values)

In order to tie each controller to a joystick object, we can create a get method to mirror that in Allegro, which will create a new XC_STATE and initialize it:

/*
 * Resets a controller's state.
 */
void xc_clear_state(XC_STATE *state)
{
    state->left_stick_x = 0;
    state->left_stick_y = 0;
    state->left_trigger = 0;
    state->right_stick_x = 0;
    state->right_stick_y = 0;
    state->right_trigger = 0;
    state->dpad_x = 0;
    state->dpad_y = 0;
    state->button_a = false;
    state->button_b = false;
    state->button_x = false;
    state->button_y = false;
    state->button_left_stick = false;
    state->button_right_stick = false;
    state->button_left_shoulder = false;
    state->button_right_shoulder = false;
    state->button_start = false;
    state->button_back = false;
    state->button_xbox = false;
}
 
/*
 * Get a new state for the given joystick number.
 */
XC_STATE *xc_new_state(int num)
{
    ALLEGRO_JOYSTICK *joy = al_get_joystick(num);
    if (joy == NULL)
        return NULL;
 
    XC_STATE *state = (XC_STATE*)malloc(sizeof(XC_STATE));
    xc_clear_state(state);
    state->joy = joy;
 
    return state;
}

Because there's no easy way to directly tie a joystick object to a controller, it's up to the user to maintain a list of XC_STATE's and to identify the correct one to update when a joystick event occurs.

One solution is to maintain a null-terminated array of XC_STATE pointers and pass it in to a method like this:

XC_STATE *xc_get_state(XC_STATE *states[], ALLEGRO_JOYSTICK *joystick)
{
    int i;
    for (i = 0; states[i]; i++) {
        if (states[i]->joy == joystick)
            return states[i];
    }
 
    return NULL;
}

Updating the State

Your basic Allegro event-handler will look something like this:

bool get_event = al_wait_for_event_until(event_queue, &event, &timeout);
 
if (get_event) {
    switch (event.type) {
        case ALLEGRO_EVENT_TIMER:
            redraw = true;
            break;
        case ALLEGRO_EVENT_DISPLAY_CLOSE:
            running = false;
            break;
 
        // additional cases ...
 
        default:
           fprintf(stderr, "Unsupported event received: %d\n", event.type);
           break;
    }
}

We can update the state by waiting for a joystick event and then passing it to an update method:

case ALLEGRO_EVENT_JOYSTICK_AXIS:
case ALLEGRO_EVENT_JOYSTICK_BUTTON_DOWN:
case ALLEGRO_EVENT_JOYSTICK_BUTTON_UP:
    xc_update(xc_get_state(states, event.joystick.id), event);
    break;

The update method is an entry point to a couple static helper functions, and it will take care of updating the state object for us:

/*
 * Update the controller's axis positions.
 */
static void xc_update_axes(XC_STATE *state, ALLEGRO_EVENT event)
{
    int axis = event.joystick.axis;
    float pos = event.joystick.pos;
 
    switch (event.joystick.stick) {
        case 0:
            if (axis == 0)
                state->left_stick_x = pos;
            else if (axis == 1)
                state->left_stick_y = pos;
            break;
        case 1:
            if (axis == 0)
                state->left_trigger = (pos + 1.0) / 2.0;
            else if (axis == 1)
                state->right_stick_x = pos;
            break;
        case 2:
            if (axis == 0)
                state->right_stick_y = pos;
            else if (axis == 1)
                state->right_trigger = (pos + 1.0) / 2.0;
            break;
        case 3:
            if (axis == 0)
                state->dpad_x = pos;
            else if (axis == 1)
                state->dpad_y = pos;
            break;
    }
}
 
/*
 * Update the controller's button statuses.
 */
static void xc_update_buttons(XC_STATE *state, ALLEGRO_EVENT event)
{
    bool value = (event.type == XC_EVENT_BUTTON_DOWN);
    switch (event.joystick.button) {
        case 0:
            state->button_a = value;
            break;
        case 1:
            state->button_b = value;
            break;
        case 2:
            state->button_x = value;
            break;
        case 3:
            state->button_y = value;
            break;
        case 4:
            state->button_left_shoulder = value;
            break;
        case 5:
            state->button_right_shoulder = value;
            break;
        case 6:
            state->button_back = value;
            break;
        case 7:
            state->button_start = value;
            break;
        case 8:
            state->button_xbox = value;
            break;
        case 9:
            state->button_left_stick = value;
            break;
        case 10:
            state->button_right_stick = value;
            break;
        default:
            fprintf(stderr, "[XC] Error: unexpected button (%d)\n", event.joystick.button);
            break;
    }
}
 
/*
 * Update the controller's state.
 */
void xc_update(XC_STATE *state, ALLEGRO_EVENT event)
{
    switch (event.type) {
        case ALLEGRO_EVENT_JOYSTICK_AXIS:
            xc_update_axes(state, event);
            break;
        case ALLEGRO_EVENT_JOYSTICK_BUTTON_DOWN:
        case ALLEGRO_EVENT_JOYSTICK_BUTTON_UP:
            xc_update_buttons(state, event);
            break;
        default:
            fprintf(stderr, "[XC] Error: unrecognized event (%d)\n", event.type);
            break;
    }
}

The update methods act as a bridge between Allegro's joystick system and the XC_STATE object. Simply browse the example above to see which stick/button values correspond to where on the controller.

A full example can be found here: https://github.com/dradtke/Allegro-360

Personal tools
Adsense