Friday, February 17, 2012

The Gamepad API and the case of the shifting buttons

Took the opportunity to look at the Gamepad API for the first time yesterday while at the Mozilla Games Work Week. It's a simple API at it's core, and one that should be very straightforward to use. I gave a stab at it by sneaking gamepad controls into my Quake 3 demo (Have you noticed that tends to be the testbed for all my API fiddlings?) and am semi-pleased with the result. If you plug in most controllers (Xbox, PS3, etc) you should get at least be able to move with one stick and look with the other, though depending on the OS/gamepad/browser some things may not work quite as expected.

And that brings me to my grip with the Gamepad API as it exists now. It IS OS/gamepad/browser specific. My current code looks roughly like this:

for (var i = 0; i < navigator.gamepads.length; ++i) {
    var pad = navigator.gamepads[i];
    if(pad) {
        walk(pad.axes[0], pad.axes[1]);
        look(pad.axes[2], pad.axes[3]);
            
        if(pad.buttons[0]) { playerMover.jump(); }
    }
}

The basic idea here is that we loop over all the available gamepads and when we find one pull from axis 0 and 1 (hopefully left stick on your basic 360/PS3 style pad) for movement, axis 2 and 3 (right stick?) for looking around, and button 0 (???) to jump.

That's the theory, anyway. Here's what actually happens: In Chrome on my Mac the DirectInput gamepad I have returns the sticks as axis 0/1 for the left stick and 2/5 for the right. This means that looking up and down is broken. With my Gamepad-enabled Firefox build, though, the axes are actually 0/1 and 2/3. If you use the PS3 controller, the axes match up to what you would expect on both browsers, so that's nice, but the button mapping is COMPLETELY different between browsers. Firefox considers the "X" button to be button 0, while Chrome things button 0 is D-Pad up. (Firefox thinks the D-Pad is an axis, actually.) So if you're going to use gamepads on your page, um... you're kinda screwed. It's like if typing a "W" on one browser gave you a "W" and on another it gave you a "U". Madness!

There are projects out there already that try to normalize this situation. Gamepad.js and input.js are the big ones right now. Both do some checking against the browser/os/gamepad model and map them into a more sensible gamepad structure that looks a lot like XInput. (That's a good thing, by the way!) The biggest problem with these libraries is that if the library author hasn't gotten their hands on your exact controller you're out of luck. Also if you have a controller that happens to not look at home on a modern console, like an actual joystick, these libraries will do nothing for you.

I'm not trying to be down on the gamepad abstraction libs, because I actually think they're great and would encourage people to use them. What I feel is problematic is that the browsers aren't returning consistent data for the same device. If we could remove the question of OS and Browser from the equation and just focus on the device that's connected developers would be able to cope with the vast array of input devices out there with the minimal amount of pain required by dealing with such a varied field. If things stay the way they are now, we can still get everything to work, eventually, but we'll be working three times as hard for it.

So please, browser peoples, get everyone together on a conference call and all agree that you'll poll the hardware the same way. It will make our lives as developers much easier!