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!


  1. Completely agree. The worst part about the client JS libraries having hardware databases is that it requires all games be updated each time a new piece of hardware comes out, even if the hardware can easily map to an existing one. The browsers should really be attempting to normalize classes of hardware to prevent this. XInput has a decent design for this. I'd rather have 3 supported device categories that were guaranteed to work everywhere than an infinite number that never worked.

  2. Hey Brandon, sorry this sucks. I'm trying to improve it within Chrome by working on building a remapping database (and including "STANDARD GAMEPAD" in the .id field when it's been remapped to not suck). I've discussed with the FF guys a bit, and while the original plan was to leave it up to JS I think we can make progress by having some of the database live in the browsers. Agreed that (my) gamepad.js and similar should die.

    That said, PS3 controller on Chrome should be in a sane order ( Could you try Canary if you're not already? Also, what DirectInput pad are you using?

  3. Actually I don't mind the JS library situation, it's the fact that the browsers report the raw data differently that's concerning.

    I've got mixed feelings about the browser trying to map the gamepad for you. On the one hand, if every browser does it consistently then it has the potential to make life better, but it also means that users are largely dependent on the browser developers to have access to (and care about) their specific device to make it useable. And even if the browsers all do decide to take notice of your favored device, it can be 6 weeks minimum before support lands in a stable version.

    If the mapping is left to a javascript library it's slightly more hassle for the dev but it also gives them the ability to respond to user requests for device support much more quickly, which can be a huge plus. Not to mention, this approach is most likely to yield support for devices that don't look like a console controller, as it's unlikely that more traditional joystick configurations will catch the eye of browser vendors. This approach would work out best, however, all of the browsers report device input consistently.

    At it's core, though, the real concern is nobody wants to write completely different input handling for Chrome and Firefox. Whatever approach gives us consistent data, I'll be happy with.

    As for the gamepads, I was unfortunately just borrowing the devices in question. The PS3 controller on Chrome seemed to work well, it was mostly the direct input pad that was being weird about axis data. The device in question was a Logitech F510 (, which can run either as an XInput device or DirectInput via a switch on the back. My Mac would not recognize it in XInput mode (even with the third party driver) so Direct Input was all I could test. Playing with an actual 360 pad yields decent results on Chrome, but I think the buttons are still ordered differently than Firefox. (Need to double check that.)

    And yes, you can assume that anything I post in my blog is tested against Canary and the Firefox Nightlies :)

  4. Scott and I definitely know this is the biggest problem with the current implementations. The problem is that the only solution is to push this same mapping down into the browser implementations. The sad fact is that the same device looks different on different platforms due to different drivers being used. We can pretty easily solve this for the most popular devices (like the Xbox 360 and PS3 controllers), but I get the feeling that fixing that will mean that web games will support those and just not work with other controllers. Maybe that's the best we can reasonably do?

  5. Its more about ego of the browser vendors rather than technical difficulty of the problem.. that's the ridiculous part of this situation..

  6. Completely agree, it's bonkers at the mo. I've added Mozilla (with the specific Nightly build) support and Chrome support to my dual-stick HTML5 shooter: << but it needs specific code for each pad and each OS...!