UNPKG

gamepad-plus

Version:

a superb library that extends the Gamepad API with super powers

277 lines (233 loc) 10.2 kB
(function (window) { var gamepadSupport = { // A number of typical buttons recognized by Gamepad API and mapped to // standard controls. Any extraneous buttons will have larger indexes. TYPICAL_BUTTON_COUNT: 16, // A number of typical axes recognized by Gamepad API and mapped to // standard controls. Any extraneous buttons will have larger indexes. TYPICAL_AXIS_COUNT: 4, // Whether we're requestAnimationFrameing like it's 1999. ticking: false, // The canonical list of attached gamepads, without "holes" (always // starting at [0]) and unified between Firefox and Chrome. gamepads: [], // Remembers the connected gamepads at the last check; used in Chrome // to figure out when gamepads get connected or disconnected, since no // events are fired. prevRawGamepadTypes: [], // Previous timestamps for gamepad state; used in Chrome to not bother with // analyzing the polled data if nothing changed (timestamp is the same // as last time). prevTimestamps: [], // Feature detecting for Gecko since currently Firefox is the only browser // that fires gamepad events. hasEvents: 'InstallTrigger' in window, /** * Initialize support for Gamepad API. */ init: function () { var gamepadSupportAvailable = navigator.getGamepads || !!navigator.webkitGetGamepads || !!navigator.webkitGamepads; if (!gamepadSupportAvailable) { // It doesn't seem Gamepad API is available, show a message telling // the visitor about it. window.tester.showNotSupported(); } else { // Check and see if gamepadconnected/gamepaddisconnected is supported. // If so, listen for those events and don't start polling until a // gamepad has been connected. if (gamepadSupport.hasEvents) { // If connection events are not supported, just start polling. gamepadSupport.startPolling(); } else { window.addEventListener('gamepadconnected', gamepadSupport.onGamepadConnect); window.addEventListener('gamepaddisconnected', gamepadSupport.onGamepadDisconnect); } } }, /** * React to the gamepad being connected. */ onGamepadConnect: function (event) { // Add the new gamepad on the list of gamepads to look after. gamepadSupport.gamepads.push(event.gamepad); // Ask the tester to update the screen to show more gamepads. window.tester.updateGamepads(gamepadSupport.gamepads); // Start the polling loop to monitor button changes. gamepadSupport.startPolling(); }, /** * React to the gamepad being disconnected. */ onGamepadDisconnect: function (event) { // Remove the gamepad from the list of gamepads to monitor. for (var i in gamepadSupport.gamepads) { if (gamepadSupport.gamepads[i].index === event.gamepad.index) { gamepadSupport.gamepads.splice(i, 1); break; } } // If no gamepads are left, stop the polling loop (but only if // `gamepadconnected` will get fired when a gamepad is reconnected). if (!gamepadSupport.gamepads.length && gamepadSupport.hasEvents) { gamepadSupport.stopPolling(); } // Ask the tester to update the screen to remove the gamepad. window.tester.updateGamepads(gamepadSupport.gamepads); }, /** * Starts a polling loop to check for gamepad state. */ startPolling: function () { // Don't accidentally start a second loop, man. if (!gamepadSupport.ticking) { gamepadSupport.ticking = true; gamepadSupport.tick(); } }, /** * Stops a polling loop by setting a flag which will prevent the next * requestAnimationFrame() from being scheduled. */ stopPolling: function () { gamepadSupport.ticking = false; }, /** * A function called with each requestAnimationFrame(). Polls the gamepad * status and schedules another poll. */ tick: function () { gamepadSupport.pollStatus(); gamepadSupport.scheduleNextTick(); }, scheduleNextTick: function () { // Only schedule the next frame if we haven't decided to stop via // stopPolling() before. if (gamepadSupport.ticking) { if (window.requestAnimationFrame) { window.requestAnimationFrame(gamepadSupport.tick); } else if (window.mozRequestAnimationFrame) { window.mozRequestAnimationFrame(gamepadSupport.tick); } else if (window.webkitRequestAnimationFrame) { window.webkitRequestAnimationFrame(gamepadSupport.tick); } // Note lack of setTimeout since all the browsers that support // Gamepad API are already supporting requestAnimationFrame(). } }, /** * Checks for the gamepad status. Monitors the necessary data and notices * the differences from previous state (buttons for Chrome/Firefox, * new connects/disconnects for Chrome). If differences are noticed, asks * to update the display accordingly. Should run as close to 60 frames per * second as possible. */ pollStatus: function () { // Poll to see if gamepads are connected or disconnected. Necessary // only on Chrome. gamepadSupport.pollGamepads(); gamepadSupport.gamepads.forEach(function (gamepad, idx) { // Don't do anything if the current timestamp is the same as previous // one, which means that the state of the gamepad hasn't changed. // This is supported by only Chrome right now, so the first check // makes sure we're not doing anything if the timestamps are empty // or `undefined`. if (gamepad.timestamp && gamepad.timestamp === gamepadSupport.prevTimestamps[idx]) { return; } gamepadSupport.prevTimestamps[idx] = gamepad.timestamp; gamepadSupport.updateDisplay(idx); }); }, // This function is called only on Chrome, which does not yet support // connection/disconnection events but requires you to monitor // an array for changes. pollGamepads: function () { // Get the array of gamepads, the first method (getGamepads) // is the most modern one and is supported by Firefox 28+ and // Chrome 35+. The second one (webkitGetGamepads) is a deprecated method // used by older Chrome builds. var rawGamepads = (navigator.getGamepads && navigator.getGamepads()) || (navigator.webkitGetGamepads && navigator.webkitGetGamepads()); if (rawGamepads) { // We don't want to use rawGamepads coming straight from the browser, // since it can have "holes" (e.g. if you plug two gamepads, and then // unplug the first one, the remaining one will be at index [1]). gamepadSupport.gamepads = []; // We only refresh the display when we detect some gamepads are new // or removed; we do it by comparing raw gamepad table entries to // `undefined`. var gamepadsChanged = false; for (var i = 0; i < rawGamepads.length; i++) { if (typeof rawGamepads[i] !== gamepadSupport.prevRawGamepadTypes[i]) { gamepadsChanged = true; gamepadSupport.prevRawGamepadTypes[i] = typeof rawGamepads[i]; } if (rawGamepads[i]) { gamepadSupport.gamepads.push(rawGamepads[i]); } } // Ask the tester to refresh the visual representations of gamepads // on the screen. if (gamepadsChanged) { window.tester.updateGamepads(gamepadSupport.gamepads); } } }, // Call the tester with new state and ask it to update the visual // representation of a given gamepad. updateDisplay: function (gamepadId) { var gamepad = gamepadSupport.gamepads[gamepadId]; // Update all the buttons (and their corresponding labels) on screen. window.tester.updateButton(gamepad.buttons[0], gamepadId, 'button-1'); window.tester.updateButton(gamepad.buttons[1], gamepadId, 'button-2'); window.tester.updateButton(gamepad.buttons[2], gamepadId, 'button-3'); window.tester.updateButton(gamepad.buttons[3], gamepadId, 'button-4'); window.tester.updateButton(gamepad.buttons[4], gamepadId, 'button-left-shoulder-top'); window.tester.updateButton(gamepad.buttons[6], gamepadId, 'button-left-shoulder-bottom'); window.tester.updateButton(gamepad.buttons[5], gamepadId, 'button-right-shoulder-top'); window.tester.updateButton(gamepad.buttons[7], gamepadId, 'button-right-shoulder-bottom'); window.tester.updateButton(gamepad.buttons[8], gamepadId, 'button-select'); window.tester.updateButton(gamepad.buttons[9], gamepadId, 'button-start'); window.tester.updateButton(gamepad.buttons[10], gamepadId, 'stick-1'); window.tester.updateButton(gamepad.buttons[11], gamepadId, 'stick-2'); window.tester.updateButton(gamepad.buttons[12], gamepadId, 'button-dpad-top'); window.tester.updateButton(gamepad.buttons[13], gamepadId, 'button-dpad-bottom'); window.tester.updateButton(gamepad.buttons[14], gamepadId, 'button-dpad-left'); window.tester.updateButton(gamepad.buttons[15], gamepadId, 'button-dpad-right'); // Update all the analogue sticks. window.tester.updateAxis(gamepad.axes[0], gamepadId, 'stick-1-axis-x', 'stick-1', true); window.tester.updateAxis(gamepad.axes[1], gamepadId, 'stick-1-axis-y', 'stick-1', false); window.tester.updateAxis(gamepad.axes[2], gamepadId, 'stick-2-axis-x', 'stick-2', true); window.tester.updateAxis(gamepad.axes[3], gamepadId, 'stick-2-axis-y', 'stick-2', false); // Update extraneous buttons. var extraButtonId = gamepadSupport.TYPICAL_BUTTON_COUNT; while (typeof gamepad.buttons[extraButtonId] !== 'undefined') { window.tester.updateButton(gamepad.buttons[extraButtonId], gamepadId, 'extra-button-' + extraButtonId); extraButtonId++; } // Update extraneous axes. var extraAxisId = gamepadSupport.TYPICAL_AXIS_COUNT; while (typeof gamepad.axes[extraAxisId] !== 'undefined') { window.tester.updateAxis(gamepad.axes[extraAxisId], gamepadId, 'extra-axis-' + extraAxisId); extraAxisId++; } } }; window.gamepadSupport = gamepadSupport; })(window);