Advertisement

An Introduction to the HTML5 Gamepad API

by

This Cyber Monday Tuts+ courses will be reduced to just $3 (usually $15). Don't miss out.

As HTML games begin to gradually increase in popularity, vendors are starting to introduce some exciting new APIs to make gaming that little bit sweeter for both us developers and our end players. One of these is the GamepadAPI, which allows you to connect your good old console gamepad into your computer and use it for browser based games, plug and play style. Let's dive in!


Introduction: What Is the Gamepad API?

In a nutshell, the Gamepad API allows you to interact with your browser using a video game console controller, AKA a gamepad. This doesn't require a special driver or plugin to work, it's as simple as plug and play!

Being a console gamer rather than a desktop gamer myself, I much prefer to interact with games using a gamepad, and with the upcoming rise of HTML and JavaScript based games, this is going to become a really useful tool in making games more easily accessible for your users.

The Gamepad API is not readily available for public release, but we can start using it for ourselves with preview versions of Firefox. So before we get stuck in, we need a few things.


What You'll Need

As I mentioned, the Gamepad API isn't available for public release just yet so you will need to first get yourself a Nightly build of Firefox and make sure you have the Firebug add-on installed (for debugging purposes only).

Also, you can't forget a gamepad! I'm going to be using a PlayStation 3 controller for this tutorial but an Xbox controller will do just fine.

Once you have installed Nightly and added on Firebug you are ready to go!

(NB. Recent builds of Chromium have Gamepad API support as well, but this tutorial has not been tested against them.)


Step 1: Connecting a Gamepad to Your Browser

Let's start with a basic HTML file (index.html), sourcing "gamepad.js" (a blank JavaScript file).

index.html

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Introduction to the Gamepad API</title>
  </head>
  <body>
    <h1>Gamepad API</h1>

    <script src="gamepad.js"></script>
  </body>
</html>

The connection of a gamepad is detected with a simple JavaScript event listener, the event fired is called "MozGamepadConnected". So the first thing we need to do is add an event listener to the window to detect that event.

I'm also adding a callback function that will log the details of the event to Firebug's console. This is the information we are most interested in and what will actually let us know that we have connected a gamepad successfully.

function gamepadConnected(evt)
{
  console.log(evt); 
}
window.addEventListener('MozGamepadConnected', gamepadConnected);

Run your index.html in Nightly and open up Firebug's console, here we'll be able to see the logging of the event from our callback function.

Make sure your controller is turned off and not connected wirelessly to a games console. Plug it in to your computer via USB and power on the controller, watching the event log in the console.

The logged event of connecting a gamepad

Great, we have a gamepad connecting to a browser, no extra plugins or drivers required!


Step 2: Disconnecting a Gamepad

It's just as important to know whether a gamepad has been disconnected as well, so let's look at the event, "MozGamepadDisconnected".

Similarly to step one, add an event listener for a disconnect event and a callback function to log the event details.

function gamepadDisconnected(evt)
{
  console.log(evt); 
}
window.addEventListener('MozGamepadDisconnected', gamepadDisconnected);

If you're gamepad is still connected, refresh your page (which you'll see connected event be logged) and then disconnect your gamepad by ejecting it from the USB port. You should get an event log like this one.

The logged event of disconnecting a gamepad

Now we know when a gamepad has been connected and disconnected, it's probably a good idea to record the state inside a variable and get ready to detect button events!

var gamepadActive = false;

function gamepadConnected(evt)
{
  console.log(evt);
  gamepadActive = true;
}
function gamepadDisconnected(evt)
{
  console.log(evt);
  gamepadActive = false;
}

window.addEventListener('MozGamepadConnected', gamepadConnected);
window.addEventListener('MozGamepadDisconnected', gamepadDisconnected);

Step 3: Detecting Button Presses

Button presses, again, use an event listener and callback function with two events, "MozGamepadButtonDown" and "MozGamepadButtonUp".

I would suggest logging the entire event from the button press yourself to see what is going on, but the key piece of information we need to get from this event is evt.button. This is the numerical id of the button that was pressed.

The callback function this time takes a second parameter, a boolean value to test if the button was pressed or released. We set this ourselves in the callback functions of the event listeners.

function buttonPressed(evt, pressed)
{
  console.log(evt.button, pressed); 
}
window.addEventListener("MozGamepadButtonDown", function(evt) { buttonPressed(evt, true); } );
window.addEventListener("MozGamepadButtonUp", function(evt) { buttonPressed(evt, false); } );

This should now output the IDs of the buttons that are pressed and whether they were pressed or released (true for button down, false for button up).

The logged button id's

Next we'll create an array with all the PlayStation 3 buttons in. The indices of the array will map to the IDs used on this gamepad, with the values being the name of the button.

var gamepadActive = false,
    ps3Buttons = new Array();

ps3Buttons[12]  = 'triangle',
ps3Buttons[15]  = 'square',
ps3Buttons[14]  = 'cross',
ps3Buttons[13]  = 'circle',
ps3Buttons[4]   = 'up',
ps3Buttons[7]   = 'left',
ps3Buttons[6]   = 'down',
ps3Buttons[5]   = 'right',
ps3Buttons[10]  = 'L1',
ps3Buttons[8]   = 'L2',
ps3Buttons[11]  = 'R1',
ps3Buttons[9]   = 'R2',
ps3Buttons[1]   = 'L3',
ps3Buttons[2]   = 'R3',
ps3Buttons[16]  = 'PS',
ps3Buttons[0]   = 'select',
ps3Buttons[3]   = 'start';

If you're using a different controller, take the time to figure out which index goes with which button, and store that info in a similar array.

If we now modify the buttonPressed() function ever so slightly, we can easily tell which button on the controller has been pressed.

function buttonPressed(evt, pressed)
{
  console.log(ps3Buttons[evt.button] + ' was pressed');
}

Give it a go! Pressing buttons on your controller should now log the name of buttons being pressed. This will be a lot easier to understand than "button 5" (which, in my case, is on the D-pad).


Step 4: Detecting Axis Events

Detecting axis events is basically keeping track of where the left and right analog sticks on the gamepad are positioned using the "MozGamepadAxisMove" event.

Add the new event handler and callback function.

function moveAnalogSticks(evt) {
  console.log(evt.axis, evt.value);
}
window.addEventListener("MozGamepadAxisMove", moveAnalogSticks);

This is what we get - confusing, right?

The logged button id's

There is only one event fired by both analog sticks; each event gives us one of four possible axis and a value between -1.0 and +1.0. Axis 0 and 1 belong to left analog stick and axis 2 and 3 belong to the right.

The logged button id's

In the diagram above you'll see axis 0 and 2 correspond to the x axis, and 1 and 3 correspond to the y axis. By using both the x and y axis for each individual analog stick, you can figure out which way the analog stick is facing!

On different gamepads, you may have other axes. For instance, the shoulder triggers on an Xbox controller are also analog.


Step 5: Putting It Into Practice

That covers all of the events that we can currently take from a gamepad, so let's put what we've learnt into practice.

Now, I don't want to go too heavily into the game development side of things, as we are focusing on what we use to control games themselves. One of the key things to look at, though, is switching control schemes. As not everyone will have a gamepad ready to hand, we need to make sure we provide controls for both the keyboard and gamepad.


Step 6: Setting Up Your Canvas

To get a small demo up and running, create a canvas element in your html file with an id of "game" and set the width to 600 and height to 540. As you may know, the canvas element is commonly used to render HTML games on.

You will also want to copy the "ship.png" and "space.jpg" images from the source download to your working folder as these are what we'll be rendering to the canvas. Alternatively, find some graphics of your own to have a play with!

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Introduction to the Gamepad API</title>
  </head>
  <body>
    <h1>Gamepad API</h1>
    <canvas id="game" width="600" height="540"></canvas>
    <script src="gamepad.js"></script>
  </body>
</html>

Step 7: Creating the Game Loop

Now that the canvas element is in our DOM, we want to create a game loop to render our game.

I'm using a shim for "requestAnimationFrame" by Paul Irish that will be the base for our loop. Next, we get the 2D context of the canvas which we'll use to draw on and create two new image objects, one for the background and one for our spaceship.

// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
window.requestAnimFrame = (function(){
  return  window.requestAnimationFrame       || 
          window.webkitRequestAnimationFrame || 
          window.mozRequestAnimationFrame    || 
          window.oRequestAnimationFrame      || 
          window.msRequestAnimationFrame     || 
          function(/* function */ callback, /* DOMElement */ element){
            window.setTimeout(callback, 1000 / 60);
          };
})();

var canvas  = document.getElementById('game'),
    ctx     = canvas.getContext('2d'),
    ship    = new Image(),
    space   = new Image();

space.src = "space.jpg";
ship.src  = "ship.png";

Next, the player object. It has x and y coordinates which keep track of where it should appear on the canvas; four direction states (up, down, left and right) so we can know which way the ship is moving; a render() function, which first calls updatePosition() and then draws the image of the ship onto the canvas based on the x and y coordinates, and finally the updatePosition() function itself, which tests to see which way the ship is set to move and updates its position accordingly.

var player = {
  x: 200,
  y: 250,
  up: false,
  down: false,
  left: false,
  right: false,
  render: function() {
    this.updatePosition();
    ctx.drawImage(ship,this.x,this.y);
  },
  updatePosition: function() {
    this.up     ? this.y-- : false;
    this.down   ? this.y++ : false;
    this.left   ? this.x-- : false;
    this.right  ? this.x++ : false;
  }
}

After that we have our "renderGame" function which draws the space background image onto the canvas first, then draws our spaceship on top of that.

And finally, our loop. This function calls itself again and again, each time calling our "renderGame" function.

function renderGame()
{
  ctx.drawImage(space,0,0);
  player.render();
}

;(function animloop(){
  requestAnimFrame(animloop);
  renderGame();
})();

Your canvas should now have a nice space looking background with a spaceship sat in the middle of it - not too exciting, I know. So let's add some controls!


Step 8: Hooking Up the Ship's Controls

In our player code we named the four buttons which we want to control our ship with. These match up to the names of the buttons inside the ps3Buttons[] array. So, all we have to do is modify our buttonPressed() function ever so slightly and we'll be moving.

var player = {
  ...
  up: false,
  down: false,
  left: false,
  right: false,
  ...
}

Now when a gamepad button is pressed or released it will set its state within the player object, so when the "up" button is pressed, player.up = true/false will be set.

function buttonPressed(evt, pressed)
{
  console.log(evt.button, pressed);
  player[ps3Buttons[evt.button]] = pressed ? true : false;
}

Head back over to your demo and you should be able to move your ship around!


Step 9: Adding a Keyboard Fallback

As not everyone playing your game will have a gamepad, you'll probably still want to allow them to play the game with a keyboard.

Lets first create a new keys[] array, and map the keyboard's arrow keys' keyCode properties to the equivalent buttons on the gamepad. This will allow us to reuse buttonPressed() function that the gamepad utilises.

var gamepadActive = false,
    ps3Buttons    = new Array(),
    keys          = new Array();

ps3Buttons[12]  = 'triangle',
ps3Buttons[15]  = 'square',
ps3Buttons[14]  = 'cross',
ps3Buttons[13]  = 'circle',
ps3Buttons[4]   = 'up',
ps3Buttons[7]   = 'left',
ps3Buttons[6]   = 'down',
ps3Buttons[5]   = 'right',
ps3Buttons[10]  = 'L1',
ps3Buttons[8]   = 'L2',
ps3Buttons[11]  = 'R1',
ps3Buttons[9]   = 'R2',
ps3Buttons[1]   = 'L3',
ps3Buttons[2]   = 'R3',
ps3Buttons[16]  = 'PS',
ps3Buttons[0]   = 'select',
ps3Buttons[3]   = 'start';

keys[38] = 4;
keys[37] = 7;
keys[40] = 6;
keys[39] = 5;

Now we need a "onkeyup" and "onkeydown" event listener for the arrow keys. When a key is pressed or released, we make sure that a gamepad is not in use. Then we prevent the arrow key from doing its usual task (scrolling the browser window up or down in this case) and then call the same buttonPressed() function that the gamepad calls.

To do this, a fake event object is passed with the key's "keyCode" mapped to an item in the keys[] array, which in turn, passes the corresponding gamepad button ID.

window.onkeydown = function(evt)
  {
    if (gamepadActive == false)
    {
      evt.preventDefault();
      buttonPressed({ button: keys[evt.keyCode] }, true);
    }
  }
window.onkeyup = function(evt)
  {
    if (gamepadActive == false)
    {
      evt.preventDefault();
      buttonPressed({ button: keys[evt.keyCode] }, false);
    }
  }

This should now let you use the arrow keys for controlling the ship when a gamepad isn't plugged in, while still letting the gamepad take over when it's present.


Conclusion

So we've covered the basics of connecting a gamepad to your computer, learnt how to hook into the events that the gamepad fires, and then use them in practice. Not forgetting, the crucial fall-back support for the keyboard!

A quick challenge for those of you with a controller other than a PS3 Dual Shock: adjust the button mapping based on whichever controller is plugged in.

Thank you for taking the time to learn about the Gamepad API. If you have any questions, please leave them in the comments.

Advertisement