Skip to content

Instantly share code, notes, and snippets.

@videlais videlais/Gamepad.js
Last active Feb 18, 2018

Embed
What would you like to do?
Complete Gamepad.js code
/**
* (Note: Depends on window.requestAnimationFrame for polling.)
*
* An experimental Gamepad object for detecting
* and parsing gamepad input.
*
* Current code borrows heavily from Marcin Wichary's work:
* http://www.html5rocks.com/en/tutorials/doodles/gamepad/
*
* Also uses deadzone values from
* http://msdn.microsoft.com/en-us/library/windows/desktop/ee417001(v=vs.85).aspx
* Left Stick: 8689/32767.0
* Right Stick: 7849.0/32767.0
* Shoulder0: 0.5
* Shoulder1: 30.0/255.0
*
* @property {boolean} supported If gamepads are supported in the current context
* @property {boolean} ticking If polling is currently taking place
* @property {array} gamepads The currently connected gamepads, if any
* @property {float} SHOULDER0_BUTTON_THRESHOLD The Shoulder0 ('LEFT_SHOULDER') deadzone for when a button is 'pressed'
* @property {float} SHOULDER1_BUTTON_THRESHOLD The Shoulder1 ('LEFT_SHOULDER_BOTTOM') deadzone for when a button is 'pressed'
* @property {float} LEFT_AXIS_THRESHOLD The left axis deadzone for when an analogue input has 'moved'
* @property {float} RIGHT_AXIS_THRESHOLD The right axis deadzone for when an analogue input has 'moved'
* if(gamepad.supported) {
* if(gamepad.pressed(0, "FACE_1") {
* //Depending on the layout, either 'X', 'A', or 'O' was pressed on the first controller
* }
* }
*/
var Gamepad = (function(self) {
self.supported = (navigator.webkitGetGamepads && navigator.webkitGetGamepads()) ||
!!navigator.webkitGamepads || !!navigator.mozGamepads ||
!!navigator.msGamepads || !!navigator.gamepads ||
(navigator.getGamepads && navigator.getGamepads());
self.ticking = false;
var BUTTONS = {
FACE_1: 0,
FACE_2: 1,
FACE_3: 2,
FACE_4: 3,
LEFT_SHOULDER: 4,
RIGHT_SHOULDER: 5,
LEFT_SHOULDER_BOTTOM: 6,
RIGHT_SHOULDER_BOTTOM: 7,
SELECT: 8,
START: 9,
LEFT_ANALOGUE_STICK: 10,
RIGHT_ANALOGUE_STICK: 11,
PAD_UP: 12,
PAD_DOWN: 13,
PAD_LEFT: 14,
PAD_RIGHT: 15,
CENTER_BUTTON: 16
};
self.SHOULDER0_BUTTON_THRESHOLD = .5;
self.SHOULDER1_BUTTON_THRESHOLD = 30.0 / 255.0;
self.RIGHT_AXIS_THRESHOLD = 7849.0 / 32767.0;
self.LEFT_AXIS_THRESHOLD = 8689 / 32767.0;
self.gamepads = [];
var prevRawGamepadTypes = [];
var prevTimestamps = [];
if (self.supported) {
// Older Firefox
window.addEventListener('MozGamepadConnected',
onGamepadConnect, false);
window.addEventListener('MozGamepadDisconnected',
onGamepadDisconnect, false);
//W3C Specification
window.addEventListener('gamepadconnected', onGamepadConnect, false);
window.addEventListener('gamepaddisconnected', onGamepadDisconnect, false);
// Chrome
if (navigator.webkitGetGamepads && navigator.webkitGetGamepads()) {
startPolling();
}
//CocoonJS
if(navigator.getGamepads && navigator.getGamepads()) {
startPolling();
}
}
/**
* Starts the polling
* @private
* @see onGamepadConnect
*/
function startPolling() {
if (!self.ticking) {
self.ticking = true;
tick();
}
}
/**
* Does one 'tick' and prepares for the next
* @private
* @see pollStatus
*/
function tick() {
pollStatus();
if (self.ticking) {
window.requestAnimationFrame(tick);
}
}
/**
* Stops the polling
* @private
*/
function stopPolling() {
self.ticking = false;
}
/**
* Compares timestamps for changes
* @see pollGamepads()
*/
function pollStatus() {
pollGamepads();
for (var i in self.gamepads) {
var gamepad = self.gamepads[i];
if (gamepad.timestamp &&
(gamepad.timestamp === prevTimestamps[i])) {
continue;
}
prevTimestamps[i] = gamepad.timestamp;
}
}
/**
* Polls the navigator.*Gamepads object for all gamepads connected
*/
function pollGamepads() {
var rawGamepads =
(navigator.webkitGetGamepads && navigator.webkitGetGamepads()) ||
navigator.webkitGamepads || navigator.mozGamepads ||
navigator.msGamepads || navigator.gamepads ||
(navigator.getGamepads && navigator.getGamepads());
if (rawGamepads) {
self.gamepads = [];
for (var i = 0; i < rawGamepads.length; i++) {
if (typeof rawGamepads[i] !== prevRawGamepadTypes[i]) {
prevRawGamepadTypes[i] = typeof rawGamepads[i];
}
if (rawGamepads[i]) {
self.gamepads.push(rawGamepads[i]);
}
}
}
}
/**
* Returns if a specific button on a certain gamepad was pressed
* @param {number} pad The Gamepad to check
* @param {string} buttonId The button to check
* @returns {boolean} If the button on the specific gamepad is currently pressed
*/
self.pressed = function(pad, buttonId) {
if (self.gamepads[pad] && BUTTONS[buttonId]) {
var buttonIndex = BUTTONS[buttonId];
if (buttonIndex === 4 || buttonIndex === 5) {
return self.gamepads[pad].buttons[buttonIndex] > self.SHOULDER0_BUTTON_THRESHOLD;
} else if (buttonIndex === 6 || buttonIndex === 7) {
return self.gamepads[pad].buttons[buttonIndex] > self.SHOULDER1_BUTTON_THRESHOLD;
} else {
return self.gamepads[pad].buttons[buttonIndex] > 0.5;
}
} else {
return false;
}
};
/**
* Returns the amount of movement from the deadzone (-1 to 1)
* @param {number} pad The Gamepad to check
* @param {string} axisId The axis and dimension to check
* @returns {number} The amount of movement, if any
*/
self.moved = function(pad, axisId) {
if (self.gamepads[pad]) {
if (axisId === "LEFT_X") {
if (self.gamepads[pad].axes[0] < -self.LEFT_AXIS_THRESHOLD ||
self.gamepads[pad].axes[0] > self.LEFT_AXIS_THRESHOLD) {
return self.gamepads[pad].axes[0];
}
} else if (axisId === "LEFT_Y") {
if (self.gamepads[pad].axes[1] < -self.LEFT_AXIS_THRESHOLD ||
self.gamepads[pad].axes[1] > self.LEFT_AXIS_THRESHOLD) {
return self.gamepads[pad].axes[1];
}
} else if (axisId === "RIGHT_X") {
if (self.gamepads[pad].axes[2] < -self.RIGHT_AXIS_THRESHOLD ||
self.gamepads[pad].axes[2] > self.RIGHT_AXIS_THRESHOLD) {
return self.gamepads[pad].axes[2];
}
} else if (axisId === "RIGHT_Y") {
if (self.gamepads[pad].axes[3] < -self.RIGHT_AXIS_THRESHOLD ||
self.gamepads[pad].axes[3] > self.RIGHT_AXIS_THRESHOLD) {
return self.gamepads[pad].axes[3];
}
}
} else {
return 0;
}
};
/**
* Adds a gamepad when connected and starts the polling
* @param {EventObject} event A 'MozGamepadConnected' or 'gamepadconnected' event object
*/
function onGamepadConnect(event) {
var gamepad = event.gamepad;
self.gamepads[event.gamepad.id] = gamepad;
self.startPolling();
}
/**
* Sets a disconnected gamepad to 'null'
* @param {EventObject} event A 'MozGamepadDisconnected' or 'gamepaddisconnected' event object
*/
function onGamepadDisconnect(event) {
self.gamepads[event.gamepad.id] = null;
if (self.gamepads.length === 0) {
stopPolling();
}
}
return self;
})(Gamepad || {});
@JonathanRowley

This comment has been minimized.

Copy link

commented Mar 9, 2014

Just tried using this with:

window.requestAnimationFrame(checkPressed);

function checkPressed(){
    if(Gamepad.pressed(0, "FACE_1")) {
        console.log('Pressed!');
    }else{
        console.log('0');
    }
    window.requestAnimationFrame(checkPressed);
}

But when I'm using my 360 controller the 'A' button is not being recognised...? The 'B' button AKA "FACE_2" works fine. My controller is OK and this demo: http://www.html5rocks.com/en/tutorials/doodles/gamepad/gamepad-tester/tester.html works fine with my 'A' button. I'm really stumped as I had a look at your source and couldn't see the issue? Any ideas? thanks.

@JonathanRowley

This comment has been minimized.

Copy link

commented Mar 9, 2014

Figured it out. :) this line was causing the issue:https://gist.github.com/videlais/8110000#file-gamepad-js-L165

Basically "FACE_1" == 0 from the BUTTONS object and by comparing the BUTTONS[buttonId] to boolean, javascript decided 0 == false so never passed this test.

I amended the code to set some default values for 'pad' and 'buttonId' (incase they aren't passed in)

   self.pressed = function(pad, buttonId) {
    var pad = pad || 0,
    buttonId = buttonId || undefined;

    if ((typeof self.gamepads[pad] !== 'undefined') && (typeof BUTTONS[buttonId] !== 'undefined')) {
      var buttonIndex = BUTTONS[buttonId];
      if (buttonIndex === 4 || buttonIndex === 5) {
        return self.gamepads[pad].buttons[buttonIndex] > self.SHOULDER0_BUTTON_THRESHOLD;
      } else if (buttonIndex === 6 || buttonIndex === 7) {
        return self.gamepads[pad].buttons[buttonIndex] > self.SHOULDER1_BUTTON_THRESHOLD;
      } else {
        return self.gamepads[pad].buttons[buttonIndex] > 0.5;
      }
    } else {
      return false;
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.