Repository: alvaromontoro/gamecontroller.js
Branch: master
Commit: a97889aeb587
Files: 28
Total size: 108.2 KB
Directory structure:
gitextract_1uoi5g71/
├── .babelrc
├── .editorconfig
├── .gitignore
├── .prettierrc
├── .travis.yml
├── changelog.md
├── dist/
│ └── gamecontroller.js
├── examples/
│ ├── example-0-connectivity.html
│ ├── example-3-buttons-and-joysticks.html
│ ├── example-4-snes-controller.html
│ ├── example-5-alvanoid.html
│ ├── example-6-multiplayer.html
│ ├── example-7-joystick-threshold.html
│ ├── example-8-vibration.html
│ ├── example-9-before-and-after.html
│ └── examples.css
├── license.md
├── package.json
├── readme.md
├── src/
│ ├── constants.js
│ ├── gamecontrol.js
│ ├── gamepad.js
│ ├── index.js
│ └── tools.js
└── tests/
├── gamecontrol.test.js
├── gamepad.test.js
├── mock.gamepads.js
└── tools.test.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"env": {
"test": {
"presets": [["@babel/preset-env"]]
}
}
}
================================================
FILE: .editorconfig
================================================
# top-most EditorConfig file
root = true
[*.md]
trim_trailing_whitespace = false
[*.js]
trim_trailing_whitespace = true
# Unix-style newlines with a newline ending every file
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
insert_final_newline = true
max_line_length = 100
================================================
FILE: .gitignore
================================================
.DS_Store
.github
node_modules
coverage
================================================
FILE: .prettierrc
================================================
{
"printWidth": 100,
"singleQuote": true,
"useTabs": false,
"tabWidth": 2,
"trailingComma": "none",
"bracketSpacing": true,
"insertPragma": false,
"jsxBracketSameLine": false,
"semi": true,
"requirePragma": false,
"proseWrap": "preserve",
"arrowParens": "avoid"
}
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- 'node'
dist: trusty
cache:
directories:
- node_modules
================================================
FILE: changelog.md
================================================
# Changelog
All notable changes to this project will be documented in this file.
## [v1.4.4] - 2019-12-08
### Updated
- Add tests
## [v1.4.3] - 2019-12-08
### Updated
- Dependency versions
## [v1.4.2] - 2019-09-03
### Added
- Bug fixes
- Update tests for gamecontrol
- Update readme file
## [v1.4.1] - 2019-09-02
### Added
- Bug fixes
- Tests to complete 95% statement coverage (missing event)
## [v1.4.0] - 2019-09-02
### Added
- After event for buttons and axe/joysticks
- Before event for buttons and axe/joysticks
- Refactor code to make library 20% smaller
## [v1.3.0] - 2019-09-01
### Added
- New vibration capability (experimental feature)
- New demo page with example for axe/joystick threshold
## [v1.2.1] - 2019-08-31
### Added
- Bug fix for Firefox (additional unexisting axe detected)
## [v1.2.0] - 2019-08-30
### Added
- Readable axe/joystick value properties (axeValues)
- Ability to set the axe/joystick sensitivity threshold (axeThreshold)
- Tests for new functionality
- New demo page with example for axe/joystick threshold
## [v1.1.2] - 2019-08-29
### Added
- Default value/existence check for gamepad to prevent potential bug
- Updated existing tests and added new ones for gamepad class
## [v1.1.1] - 2019-08-27
### Added
- Bug fix: when multiple gamepads are connected, all of them work indidually (instead of only the last one)
- Added a new demo for multiplayer (the classic Pong)
### Removed
- Deleted a couple of demos that were confusing (buttons and joysticks) and merged them into a single one
## [v1.1.0] - 2019-08-26
### Added
- Added missing aliases for directional event handlers
- New aliases for front buttons (R1, L1, R2, L2)
- Practical examples based on some from CodePen
## [v1.0.0] - 2019-08-25
### Added
- Basic functionality
- Event management for buttons and joystick/axes
- Examples
================================================
FILE: dist/gamecontroller.js
================================================
/*
* ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ "./src/constants.js":
/*!**************************!*\
!*** ./src/constants.js ***!
\**************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"MESSAGES\": () => (/* binding */ MESSAGES)\n/* harmony export */ });\nconst MESSAGES = {\n ON: 'Gamepad detected.',\n OFF: 'Gamepad disconnected.',\n INVALID_PROPERTY: 'Invalid property.',\n INVALID_VALUE_NUMBER: 'Invalid value. It must be a number between 0.00 and 1.00.',\n INVALID_BUTTON: 'Button does not exist.',\n UNKNOWN_EVENT: 'Unknown event name.',\n NO_SUPPORT: 'Your web browser does not support the Gamepad API.'\n};\n\n\n\n\n//# sourceURL=webpack://gamecontroller.js/./src/constants.js?");
/***/ }),
/***/ "./src/gamecontrol.js":
/*!****************************!*\
!*** ./src/gamecontrol.js ***!
\****************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tools */ \"./src/tools.js\");\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./constants */ \"./src/constants.js\");\n/* harmony import */ var _gamepad__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./gamepad */ \"./src/gamepad.js\");\n\n\n\n\nconst gameControl = {\n gamepads: {},\n axeThreshold: [1.0], // this is an array so it can be expanded without breaking in the future\n isReady: (0,_tools__WEBPACK_IMPORTED_MODULE_0__.isGamepadSupported)(),\n onConnect: function() {},\n onDisconnect: function() {},\n onBeforeCycle: function() {},\n onAfterCycle: function() {},\n getGamepads: function() {\n return this.gamepads;\n },\n getGamepad: function(id) {\n if (this.gamepads[id]) {\n return this.gamepads[id];\n }\n return null;\n },\n set: function(property, value) {\n const properties = ['axeThreshold'];\n if (properties.indexOf(property) >= 0) {\n if (property === 'axeThreshold' && (!parseFloat(value) || value < 0.0 || value > 1.0)) {\n (0,_tools__WEBPACK_IMPORTED_MODULE_0__.error)(_constants__WEBPACK_IMPORTED_MODULE_1__.MESSAGES.INVALID_VALUE_NUMBER);\n return;\n }\n\n this[property] = value;\n\n if (property === 'axeThreshold') {\n const gps = this.getGamepads();\n const ids = Object.keys(gps);\n for (let x = 0; x < ids.length; x++) {\n gps[ids[x]].set('axeThreshold', this.axeThreshold);\n }\n }\n } else {\n (0,_tools__WEBPACK_IMPORTED_MODULE_0__.error)(_constants__WEBPACK_IMPORTED_MODULE_1__.MESSAGES.INVALID_PROPERTY);\n }\n },\n checkStatus: function() {\n const requestAnimationFrame =\n window.requestAnimationFrame || window.webkitRequestAnimationFrame;\n const gamepadIds = Object.keys(gameControl.gamepads);\n\n gameControl.onBeforeCycle();\n\n for (let x = 0; x < gamepadIds.length; x++) {\n gameControl.gamepads[gamepadIds[x]].checkStatus();\n }\n\n gameControl.onAfterCycle();\n\n if (gamepadIds.length > 0) {\n requestAnimationFrame(gameControl.checkStatus);\n }\n },\n init: function() {\n window.addEventListener('gamepadconnected', e => {\n const egp = e.gamepad || e.detail.gamepad;\n (0,_tools__WEBPACK_IMPORTED_MODULE_0__.log)(_constants__WEBPACK_IMPORTED_MODULE_1__.MESSAGES.ON);\n if (!window.gamepads) window.gamepads = {};\n if (egp) {\n if (!window.gamepads[egp.index]) {\n window.gamepads[egp.index] = egp;\n const gp = _gamepad__WEBPACK_IMPORTED_MODULE_2__.default.init(egp);\n gp.set('axeThreshold', this.axeThreshold);\n this.gamepads[gp.id] = gp;\n this.onConnect(this.gamepads[gp.id]);\n }\n if (Object.keys(this.gamepads).length === 1) this.checkStatus();\n }\n });\n window.addEventListener('gamepaddisconnected', e => {\n const egp = e.gamepad || e.detail.gamepad;\n (0,_tools__WEBPACK_IMPORTED_MODULE_0__.log)(_constants__WEBPACK_IMPORTED_MODULE_1__.MESSAGES.OFF);\n if (egp) {\n delete window.gamepads[egp.index];\n delete this.gamepads[egp.index];\n this.onDisconnect(egp.index);\n }\n });\n },\n on: function(eventName, callback) {\n switch (eventName) {\n case 'connect':\n this.onConnect = callback;\n break;\n case 'disconnect':\n this.onDisconnect = callback;\n break;\n case 'beforeCycle':\n case 'beforecycle':\n this.onBeforeCycle = callback;\n break;\n case 'afterCycle':\n case 'aftercycle':\n this.onAfterCycle = callback;\n break;\n default:\n (0,_tools__WEBPACK_IMPORTED_MODULE_0__.error)(_constants__WEBPACK_IMPORTED_MODULE_1__.MESSAGES.UNKNOWN_EVENT);\n break;\n }\n return this;\n },\n off: function(eventName) {\n switch (eventName) {\n case 'connect':\n this.onConnect = function() {};\n break;\n case 'disconnect':\n this.onDisconnect = function() {};\n break;\n case 'beforeCycle':\n case 'beforecycle':\n this.onBeforeCycle = function() {};\n break;\n case 'afterCycle':\n case 'aftercycle':\n this.onAfterCycle = function() {};\n break;\n default:\n (0,_tools__WEBPACK_IMPORTED_MODULE_0__.error)(_constants__WEBPACK_IMPORTED_MODULE_1__.MESSAGES.UNKNOWN_EVENT);\n break;\n }\n return this;\n }\n};\n\ngameControl.init();\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (gameControl);\n\n\n//# sourceURL=webpack://gamecontroller.js/./src/gamecontrol.js?");
/***/ }),
/***/ "./src/gamepad.js":
/*!************************!*\
!*** ./src/gamepad.js ***!
\************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tools */ \"./src/tools.js\");\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./constants */ \"./src/constants.js\");\n\n\n\nconst gamepad = {\n init: function(gpad) {\n let gamepadPrototype = {\n id: gpad.index,\n buttons: gpad.buttons.length,\n axes: Math.floor(gpad.axes.length / 2),\n axeValues: [],\n axeThreshold: [1.0],\n hapticActuator: null,\n vibrationMode: -1,\n vibration: false,\n mapping: gpad.mapping,\n buttonActions: {},\n axesActions: {},\n pressed: {},\n set: function(property, value) {\n const properties = ['axeThreshold'];\n if (properties.indexOf(property) >= 0) {\n if (property === 'axeThreshold' && (!parseFloat(value) || value < 0.0 || value > 1.0)) {\n (0,_tools__WEBPACK_IMPORTED_MODULE_0__.error)(_constants__WEBPACK_IMPORTED_MODULE_1__.MESSAGES.INVALID_VALUE_NUMBER);\n return;\n }\n this[property] = value;\n } else {\n (0,_tools__WEBPACK_IMPORTED_MODULE_0__.error)(_constants__WEBPACK_IMPORTED_MODULE_1__.MESSAGES.INVALID_PROPERTY);\n }\n },\n vibrate: function(value = 0.75, duration = 500) {\n if (this.hapticActuator) {\n switch (this.vibrationMode) {\n case 0:\n return this.hapticActuator.pulse(value, duration);\n case 1:\n return this.hapticActuator.playEffect('dual-rumble', {\n duration: duration,\n strongMagnitude: value,\n weakMagnitude: value\n });\n }\n }\n },\n triggerDirectionalAction: function(id, axe, condition, x, index) {\n if (condition && x % 2 === index) {\n if (!this.pressed[`${id}${axe}`]) {\n this.pressed[`${id}${axe}`] = true;\n this.axesActions[axe][id].before();\n }\n this.axesActions[axe][id].action();\n } else if (this.pressed[`${id}${axe}`] && x % 2 === index) {\n delete this.pressed[`${id}${axe}`];\n this.axesActions[axe][id].after();\n }\n },\n checkStatus: function() {\n let gp = {};\n const gps = navigator.getGamepads\n ? navigator.getGamepads()\n : navigator.webkitGetGamepads\n ? navigator.webkitGetGamepads()\n : [];\n\n if (gps.length) {\n gp = gps[this.id];\n if (gp.buttons) {\n for (let x = 0; x < this.buttons; x++) {\n if (gp.buttons[x].pressed === true) {\n if (!this.pressed[`button${x}`]) {\n this.pressed[`button${x}`] = true;\n this.buttonActions[x].before();\n }\n this.buttonActions[x].action();\n } else if (this.pressed[`button${x}`]) {\n delete this.pressed[`button${x}`];\n this.buttonActions[x].after();\n }\n }\n }\n if (gp.axes) {\n const modifier = gp.axes.length % 2; // Firefox hack: detects one additional axe\n for (let x = 0; x < this.axes * 2; x++) {\n const val = gp.axes[x + modifier].toFixed(4);\n const axe = Math.floor(x / 2);\n this.axeValues[axe][x % 2] = val;\n\n this.triggerDirectionalAction('right', axe, val >= this.axeThreshold[0], x, 0);\n this.triggerDirectionalAction('left', axe, val <= -this.axeThreshold[0], x, 0);\n this.triggerDirectionalAction('down', axe, val >= this.axeThreshold[0], x, 1);\n this.triggerDirectionalAction('up', axe, val <= -this.axeThreshold[0], x, 1);\n }\n }\n }\n },\n associateEvent: function(eventName, callback, type) {\n if (eventName.match(/^button\\d+$/)) {\n const buttonId = parseInt(eventName.match(/^button(\\d+)$/)[1]);\n if (buttonId >= 0 && buttonId < this.buttons) {\n this.buttonActions[buttonId][type] = callback;\n } else {\n (0,_tools__WEBPACK_IMPORTED_MODULE_0__.error)(_constants__WEBPACK_IMPORTED_MODULE_1__.MESSAGES.INVALID_BUTTON);\n }\n } else if (eventName === 'start') {\n this.buttonActions[9][type] = callback;\n } else if (eventName === 'select') {\n this.buttonActions[8][type] = callback;\n } else if (eventName === 'r1') {\n this.buttonActions[5][type] = callback;\n } else if (eventName === 'r2') {\n this.buttonActions[7][type] = callback;\n } else if (eventName === 'l1') {\n this.buttonActions[4][type] = callback;\n } else if (eventName === 'l2') {\n this.buttonActions[6][type] = callback;\n } else if (eventName === 'power') {\n if (this.buttons >= 17) {\n this.buttonActions[16][type] = callback;\n } else {\n (0,_tools__WEBPACK_IMPORTED_MODULE_0__.error)(_constants__WEBPACK_IMPORTED_MODULE_1__.MESSAGES.INVALID_BUTTON);\n }\n } else if (eventName.match(/^(up|down|left|right)(\\d+)$/)) {\n const matches = eventName.match(/^(up|down|left|right)(\\d+)$/);\n const direction = matches[1];\n const axe = parseInt(matches[2]);\n if (axe >= 0 && axe < this.axes) {\n this.axesActions[axe][direction][type] = callback;\n } else {\n (0,_tools__WEBPACK_IMPORTED_MODULE_0__.error)(_constants__WEBPACK_IMPORTED_MODULE_1__.MESSAGES.INVALID_BUTTON);\n }\n } else if (eventName.match(/^(up|down|left|right)$/)) {\n const direction = eventName.match(/^(up|down|left|right)$/)[1];\n this.axesActions[0][direction][type] = callback;\n }\n return this;\n },\n on: function(eventName, callback) {\n return this.associateEvent(eventName, callback, 'action');\n },\n off: function(eventName) {\n return this.associateEvent(eventName, function() {}, 'action');\n },\n after: function(eventName, callback) {\n return this.associateEvent(eventName, callback, 'after');\n },\n before: function(eventName, callback) {\n return this.associateEvent(eventName, callback, 'before');\n }\n };\n\n for (let x = 0; x < gamepadPrototype.buttons; x++) {\n gamepadPrototype.buttonActions[x] = (0,_tools__WEBPACK_IMPORTED_MODULE_0__.emptyEvents)();\n }\n for (let x = 0; x < gamepadPrototype.axes; x++) {\n gamepadPrototype.axesActions[x] = {\n down: (0,_tools__WEBPACK_IMPORTED_MODULE_0__.emptyEvents)(),\n left: (0,_tools__WEBPACK_IMPORTED_MODULE_0__.emptyEvents)(),\n right: (0,_tools__WEBPACK_IMPORTED_MODULE_0__.emptyEvents)(),\n up: (0,_tools__WEBPACK_IMPORTED_MODULE_0__.emptyEvents)()\n };\n gamepadPrototype.axeValues[x] = [0, 0];\n }\n\n // check if vibration actuator exists\n if (gpad.hapticActuators) {\n // newer standard\n if (typeof gpad.hapticActuators.pulse === 'function') {\n gamepadPrototype.hapticActuator = gpad.hapticActuators;\n gamepadPrototype.vibrationMode = 0;\n gamepadPrototype.vibration = true;\n } else if (gpad.hapticActuators[0] && typeof gpad.hapticActuators[0].pulse === 'function') {\n gamepadPrototype.hapticActuator = gpad.hapticActuators[0];\n gamepadPrototype.vibrationMode = 0;\n gamepadPrototype.vibration = true;\n }\n } else if (gpad.vibrationActuator) {\n // old chrome stuff\n if (typeof gpad.vibrationActuator.playEffect === 'function') {\n gamepadPrototype.hapticActuator = gpad.vibrationActuator;\n gamepadPrototype.vibrationMode = 1;\n gamepadPrototype.vibration = true;\n }\n }\n\n return gamepadPrototype;\n }\n};\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (gamepad);\n\n\n//# sourceURL=webpack://gamecontroller.js/./src/gamepad.js?");
/***/ }),
/***/ "./src/index.js":
/*!**********************!*\
!*** ./src/index.js ***!
\**********************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tools */ \"./src/tools.js\");\n/* harmony import */ var _constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./constants */ \"./src/constants.js\");\n/* harmony import */ var _gamecontrol__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./gamecontrol */ \"./src/gamecontrol.js\");\n// This file is the entry point\n\n\n\n\nif ((0,_tools__WEBPACK_IMPORTED_MODULE_0__.isGamepadSupported)()) {\n window.gameControl = _gamecontrol__WEBPACK_IMPORTED_MODULE_2__.default;\n} else {\n (0,_tools__WEBPACK_IMPORTED_MODULE_0__.error)(_constants__WEBPACK_IMPORTED_MODULE_1__.MESSAGES.NO_SUPPORT);\n}\n\n\n//# sourceURL=webpack://gamecontroller.js/./src/index.js?");
/***/ }),
/***/ "./src/tools.js":
/*!**********************!*\
!*** ./src/tools.js ***!
\**********************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"isGamepadSupported\": () => (/* binding */ isGamepadSupported),\n/* harmony export */ \"log\": () => (/* binding */ log),\n/* harmony export */ \"error\": () => (/* binding */ error),\n/* harmony export */ \"emptyEvents\": () => (/* binding */ emptyEvents)\n/* harmony export */ });\nconst log = (message, type = 'log') => {\n if (type === 'error') {\n if (console && typeof console.error === 'function') console.error(message);\n } else {\n if (console && typeof console.info === 'function') console.info(message);\n }\n};\n\nconst error = message => log(message, 'error');\n\nconst isGamepadSupported = () =>\n (navigator.getGamepads && typeof navigator.getGamepads === 'function') ||\n (navigator.getGamepads && typeof navigator.webkitGetGamepads === 'function') ||\n false;\n\nconst emptyEvents = () => ({ action: () => {}, after: () => {}, before: () => {} });\n\n\n\n\n//# sourceURL=webpack://gamecontroller.js/./src/tools.js?");
/***/ })
/******/ });
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __webpack_require__.r = (exports) => {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/ })();
/******/
/************************************************************************/
/******/
/******/ // startup
/******/ // Load entry module and return exports
/******/ // This entry module can't be inlined because the eval devtool is used.
/******/ var __webpack_exports__ = __webpack_require__("./src/index.js");
/******/
/******/ })()
;
================================================
FILE: examples/example-0-connectivity.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<title>GameControl Example: Connectivity</title>
<link rel="stylesheet" href="./examples.css" />
<style>
#info {
text-align: center;
margin: 2rem;
}
#round-button {
width: 100px;
height: 100px;
border: 5px solid rgba(0, 0, 0, 0.5);
border-radius: 100%;
background: #ccc;
margin: auto auto;
}
#round-button.disconnected {
background: #dd4433;
}
#round-button.connected {
background: #33aa44;
}
#status {
margin-top: 0.5rem;
}
</style>
</head>
<body>
<main>
<h1>Example: Connectivity</h1>
<p>Plug and unplug your gamepad(s) and see the changes below:</p>
<div id="info">
<div id="round-button"></div>
<div id="status">Waiting...</div>
</div>
<p class="note">
In some browsers, you may need to press a button after plugging in the gamepad.
</p>
<h2>How it works</h2>
<p>There are two event associated to gameControl that you can use:</p>
<ul>
<li><code>connect</code>: triggered when a gamepad is connected to the browser.</li>
<li><code>disconnect</code>: triggered when a gamepad is disconnected from the browser.</li>
</ul>
<p>
And an event handler can be easily associated to any of them using the
<code>.on()</code> method:
</p>
<p class="code">
<code>
gameControl.on(EVENT_NAME, CALLBACK);
</code>
</p>
<p>As an example, here is the code that manages the interaction above:</p>
<pre class="code"><code>gameControl.on('connect', function() {
document.querySelector('#round-button').className = 'connected';
document.querySelector('#status').textContent = 'Device connected!';
});
gameControl.on('disconnect', function() {
document.querySelector('#round-button').className = 'disconnected';
document.querySelector('#status').textContent = 'Device disconnected!';
});</code></pre>
</main>
<script src="../dist/gamecontroller.min.js"></script>
<script>
gameControl.on('connect', function() {
document.querySelector('#round-button').className = 'connected';
document.querySelector('#status').textContent = 'Gamepad connected!';
});
gameControl.on('disconnect', function() {
document.querySelector('#round-button').className = 'disconnected';
document.querySelector('#status').textContent = 'Gamepad disconnected!';
});
</script>
</body>
</html>
================================================
FILE: examples/example-3-buttons-and-joysticks.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<title>GameControl Example: Buttons and Joysticks</title>
<link rel="stylesheet" href="./examples.css" />
<style>
svg {
display: block;
margin: 1rem auto;
max-width: 500px;
width: 100%;
}
.active {
fill: yellow;
}
path.active {
fill: red;
}
</style>
</head>
<body>
<main>
<h1>Example: Buttons and Joysticks</h1>
<p>
Connect a gamepad to the computer, and click the buttons or move the different
joysticks/axes. They will highlight as they are pressed.
</p>
<svg viewBox="0 0 600 300">
<g fill="#ddd" stroke="#222" stroke-width="3">
<rect x="150" y="10" width="100" height="100" rx="5" ry="5" id="button-6" />
<rect x="350" y="10" width="100" height="100" rx="5" ry="5" id="button-7" />
<rect x="100" y="25" width="100" height="40" rx="5" ry="5" id="button-4" />
<rect x="400" y="25" width="100" height="40" rx="5" ry="5" id="button-5" />
<path
d="M135,50 C 45,50 20,180 20,240 20,300 80,330 175,220 175,220
425,220 425,220 520,330 580,300 580,240 580,180 555,50 465,50 Z"
/>
<circle cx="200" cy="200" r="35" />
<circle cx="400" cy="200" r="35" />
<circle cx="200" cy="200" r="15" id="button-10" />
<circle cx="400" cy="200" r="15" id="button-11" />
<path d="M190,182 210,182 200,168 Z" stroke-width="0" id="axe-0-up" />
<path d="M190,218 210,218 200,232 Z" stroke-width="0" id="axe-0-down" />
<path d="M218,190 218,210 232,200 Z" stroke-width="0" id="axe-0-right" />
<path d="M182,190 182,210 168,200 Z" stroke-width="0" id="axe-0-left" />
<path d="M390,182 410,182 400,168 Z" stroke-width="0" id="axe-1-up" />
<path d="M390,218 410,218 400,232 Z" stroke-width="0" id="axe-1-down" />
<path d="M418,190 418,210 432,200 Z" stroke-width="0" id="axe-1-right" />
<path d="M382,190 382,210 368,200 Z" stroke-width="0" id="axe-1-left" />
<circle cx="480" cy="160" r="15" id="button-0" />
<circle cx="510" cy="130" r="15" id="button-1" />
<circle cx="450" cy="130" r="15" id="button-2" />
<circle cx="480" cy="100" r="15" id="button-3" />
<rect x="105" y="85" width="30" height="90" fill="#aaa" stroke="#aaa" />
<rect x="75" y="115" width="90" height="30" fill="#aaa" stroke="#aaa" />
<rect x="245" y="145" width="50" height="18" rx="9" ry="9" id="button-8" />
<rect x="305" y="145" width="50" height="18" rx="9" ry="9" id="button-9" />
<circle cx="120" cy="160" r="15" id="button-13" />
<circle cx="90" cy="130" r="15" id="button-14" />
<circle cx="150" cy="130" r="15" id="button-15" />
<circle cx="120" cy="100" r="15" id="button-12" />
<circle cx="300" cy="90" r="20" id="button-16" />
</g>
<g
dominant-baseline="middle"
text-anchor="middle"
fill="#222"
font-size="16"
font-family="Arial,sans-serif"
>
<text x="480" y="160">0</text>
<text x="510" y="130">1</text>
<text x="450" y="130">2</text>
<text x="480" y="100">3</text>
<text x="150" y="40">4</text>
<text x="450" y="40">5</text>
<text x="225" y="30">6</text>
<text x="375" y="30">7</text>
<text x="270" y="156">8</text>
<text x="330" y="156">9</text>
<text x="200" y="200">10</text>
<text x="400" y="200">11</text>
<text x="120" y="100">12</text>
<text x="120" y="160">13</text>
<text x="90" y="130">14</text>
<text x="150" y="130">15</text>
<text x="300" y="90">16</text>
<text x="270" y="175" font-size="10">SELECT</text>
<text x="330" y="175" font-size="10">START</text>
</g>
</svg>
<p class="note">
This is a 17-button
<a href="https://w3c.github.io/gamepad/#remapping"
>standard gamepad layout as defined on the W3C Gamepad API definition</a
>. <strong>It may not match the gamepad that is connected</strong>, but it can be used to
see how the buttons/joysticks in that gamepad match the buttons in the standard gamepad.
</p>
<h2>How it works</h2>
<p>Event handlers can be associated to the buttons using the <code>.on()</code> method:</p>
<p class="code"><code>gamepad.on(DIRECTION_NAME+AXE_ID, CALLBACK);</code></p>
<p></p>
<p>
Only one action is allowed by button/joystick/axe. If you use several
<code>.on()</code> with the same direction, the latest call to it will be the one that is
applied when the joystick is pressed.
</p>
<pre class="code"><code>gameControl.on('connect', function(gamepad) {
gamepad.on('select', function() {
// do something
});
});</code></pre>
<p>
Some buttons/directions have aliases, so it is easier to associate events to them. For
example, if we only use the name of the direction ("up", "dowm", "right", or "left"), the
event handler will be associated to that direction in the first joystick/axe:
</p>
<pre class="code"><code>gamepad.on('up0', function() {
// do something
});
gamepad.on('up', function() {
// do something
});</code></pre>
<p class="note">
Using aliases doesn't mean that you will be able to use more than one action per direction,
as they are the same.
</p>
<p>
The available direction aliases are:
</p>
<ul>
<li><code>up</code>: equivalent to <code>up0</code>.</li>
<li><code>down</code>: equivalent to <code>down0</code>.</li>
<li><code>right</code>: equivalent to <code>right0</code>.</li>
<li><code>left</code>: equivalent to <code>left0</code>.</li>
</ul>
<p>
The available button aliases are:
</p>
<ul>
<li><code>l1</code>: equivalent to <code>button4</code>. Left back button 1</li>
<li><code>l2</code>: equivalent to <code>button6</code>. Left back button 2</li>
<li><code>r1</code>: equivalent to <code>button5</code>. Right back button 1</li>
<li><code>r2</code>: equivalent to <code>button7</code>. Right back button 2</li>
<li>
<code>select</code>: equivalent to <code>button8</code>. (Works for <kbd>Select</kbd>/<kbd
>Back</kbd
>
in some gamepads)
</li>
<li>
<code>start</code>: equivalent to <code>button9</code>. (Works for <kbd>Start</kbd>/<kbd
>Forward</kbd
>
in some gamepads)
</li>
<li>
<code>power</code>: equivalent to <code>button16</code>. It is the power button available
on some gamepads (e.g. the XBox logo button)
</li>
</ul>
</main>
<script src="../dist/gamecontroller.min.js"></script>
<script>
gameControl
.on('connect', function(gp) {
for (let x = 0; x < Math.min(17, gp.buttons); x++) {
gp.on('button' + x, function() {
document.querySelector('#button-' + x).classList.toggle('active', true);
});
}
for (let x = 0; x < Math.min(2, gp.axes); x++) {
const directions = ['up', 'down', 'right', 'left'];
for (let d = 0; d < directions.length; d++) {
gp.on(directions[d] + x, function() {
document
.querySelector('#axe-' + x + '-' + directions[d])
.classList.toggle('active', true);
});
}
}
})
.on('beforeCycle', function() {
const active = document.querySelectorAll('.active');
for (let x = 0; x < active.length; x++) {
active[x].classList.toggle('active', false);
}
});
</script>
</body>
</html>
================================================
FILE: examples/example-4-snes-controller.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<title>GameControl Example: SNES controller</title>
<link
href="https://fonts.googleapis.com/css?family=Press+Start+2P&display=swap"
rel="stylesheet"
/>
<style>
html,
body {
font-size: 16px;
font-family: 'Press Start 2P', arial, sans-serif;
}
.scrim {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
color: white;
display: none;
}
.scrim.open {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
text-align: center;
}
.scrim h2 {
max-width: 80%;
font-size: 1.5rem;
line-height: 2rem;
}
.scrim svg {
width: 80%;
max-width: 600px;
}
.active {
fill: yellow;
stroke: yellow;
}
footer {
position: absolute;
bottom: 0.5rem;
left: 0.5rem;
color: #ddd;
font-family: arial, sans-serif;
font-size: 0.85rem;
}
footer a {
color: white;
text-decoration: underline;
}
</style>
</head>
<body>
<main>
<section id="step-1" class="scrim open" data-intstep="0">
<h2>Connect your gamepad<br />and press any button...</h2>
<svg viewBox="0 0 250 110">
<g fill="#aaaaaa">
<rect
width="60"
height="30"
rx="10"
ry="10"
x="25"
y="5"
id="button-l"
class="button"
/>
<rect
width="60"
height="30"
rx="10"
ry="10"
x="165"
y="5"
id="button-r"
class="button"
/>
</g>
<rect x="50" y="10" width="150" height="90" fill="#cfcfcd" />
<circle cx="50" cy="60" r="50" fill="#cfcfcd" />
<circle cx="200" cy="60" r="50" fill="#cfcfcd" />
<circle cx="200" cy="60" r="45" fill="#a5a7a4" />
<circle cx="50" cy="60" r="28" fill="#c8c8c8" />
<g fill="#cfcfcd" stroke="#cfcfcd" stroke-width="25" stroke-linecap="round">
<path d="M180,60 200,40" />
<path d="M202,82 222,62" />
</g>
<circle cx="200" cy="40" r="10" fill="#000080" id="button-x" class="button" />
<circle cx="180" cy="60" r="10" fill="#009922" id="button-y" class="button" />
<circle cx="222" cy="62" r="10" fill="#cc0000" id="button-a" class="button" />
<circle cx="202" cy="82" r="10" fill="#ccbb00" id="button-b" class="button" />
<g fill="#444" stroke="#444" stroke-width="5" stroke-linecap="round">
<path d="M100,70 110,60" id="button-select" class="button" />
<path d="M125,70 135,60" id="button-start" class="button" />
</g>
<path
d="M43,53 43,39 57,39 57,53 71,53 71,67 57,67 57,81 43,81 43,67 29,67 29,53 43,53Z"
fill="#444"
stroke-linecap="round"
stroke-linejoin="round"
/>
<g fill="#333">
<path d="M 45,51 50,41 55,51 Z" id="button-up" class="button" />
<path d="M 59,55 69,60 59,65 Z" id="button-right" class="button" />
<path d="M 45,69 50,79 55,69 Z" id="button-down" class="button" />
<path d="M 41,55 31,60 41,65 Z" id="button-left" class="button" />
<circle cx="50" cy="60" r="3" id="button-center" class="button" />
</g>
<g
dominant-baseline="middle"
text-anchor="middle"
fill="#cfcfcd"
font-size="8"
font-family="'Arial Narrow',Arial,sans-serif"
>
<text x="215" y="30">X</text>
<text x="235" y="49">A</text>
<text x="167" y="75">Y</text>
<text x="190" y="97">B</text>
<text x="130" y="80" fill="#999" font-size="6">START</text>
<text x="105" y="80" fill="#999" font-size="6">SELECT</text>
</g>
</svg>
</section>
<footer>
This example is based on
<a href="https://codepen.io/alvaromontoro/full/bGbpmvR">Alvaro Montoro's CodePen</a>.
</footer>
</main>
<script src="../dist/gamecontroller.min.js"></script>
<script>
gameControl
.on('connect', gamepad => {
gamepad.on('up', () => document.querySelector('#button-up').classList.add('active'));
gamepad.on('down', () => document.querySelector('#button-down').classList.add('active'));
gamepad.on('left', () => document.querySelector('#button-left').classList.add('active'));
gamepad.on('right', () => document.querySelector('#button-right').classList.add('active'));
gamepad.on('start', () => document.querySelector('#button-start').classList.add('active'));
gamepad.on('select', () => document.querySelector('#button-select').classList.add('active'));
gamepad.on('l1', () => document.querySelector('#button-l').classList.add('active'));
gamepad.on('r1', () => document.querySelector('#button-r').classList.add('active'));
gamepad.on('button0', () => document.querySelector('#button-b').classList.add('active'));
gamepad.on('button1', () => document.querySelector('#button-a').classList.add('active'));
gamepad.on('button2', () => document.querySelector('#button-y').classList.add('active'));
gamepad.on('button3', () => document.querySelector('#button-x').classList.add('active'));
})
.on('beforeCycle', () => {
document.querySelectorAll('.active').forEach(e => e.classList.remove('active'));
});
</script>
</body>
</html>
================================================
FILE: examples/example-5-alvanoid.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<title>GameControl Example: Alvanoid</title>
<link
href="https://fonts.googleapis.com/css?family=Press+Start+2P&display=swap"
rel="stylesheet"
/>
<style>
html,
body {
background: #eee;
color: #123;
font-family: 'Press Start 2P', Arial, sans-serif;
font-size: 16px;
}
h1 {
font-size: 3.15rem;
margin: 0;
text-transform: uppercase;
text-align: center;
margin-bottom: 0.5rem;
}
h1 + div {
margin-bottom: 0.5rem;
font-size: 1.25rem;
}
#frame {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
-webkit-transform: translate(-50%, -50%);
}
#board {
width: 400px;
height: 600px;
background: #123;
position: relative;
}
.tile {
width: 60px;
height: 30px;
box-sizing: border-box;
border: 2px outset rgba(255, 255, 255, 0.75);
position: absolute;
border-radius: 4px;
}
.tile.hidden {
opacity: 0.1;
}
.tile[id$='0'] {
left: 20px;
}
.tile[id$='1'] {
left: 80px;
}
.tile[id$='2'] {
left: 140px;
}
.tile[id$='3'] {
left: 200px;
}
.tile[id$='4'] {
left: 260px;
}
.tile[id$='5'] {
left: 320px;
}
.tile.red {
background: #d50000;
}
.tile.blue {
background: #0000d5;
}
.tile.green {
background: #009500;
}
.tile.yellow {
background: #d5d500;
}
.tile[data-row='0'] {
top: 100px;
}
.tile[data-row='1'] {
top: 130px;
}
.tile[data-row='2'] {
top: 160px;
}
.tile[data-row='3'] {
top: 190px;
}
#paddle {
width: 70px;
height: 15px;
background: #222;
border-radius: 100px;
box-sizing: border-box;
border: 2px outset rgba(255, 255, 255, 0.75);
position: absolute;
top: 550px;
left: 165px;
}
#ball {
width: 10px;
height: 10px;
border-radius: 100%;
border-radius: 100px;
box-sizing: border-box;
background: white;
border: 2px outset rgba(0, 0, 0, 0.125);
position: absolute;
top: 540px;
left: 195px;
}
footer {
position: absolute;
bottom: 0.5rem;
left: 0.5rem;
color: #333;
font-family: arial, sans-serif;
font-size: 0.85rem;
}
footer a {
color: #000;
text-decoration: underline;
}
</style>
</head>
<body>
<main>
<div id="frame">
<h1>Alvanoid</h1>
<div>Points: <span id="points">0</span></div>
<div id="board">
<div id="tile-00" class="tile red" data-row="0"></div>
<div id="tile-01" class="tile red" data-row="0"></div>
<div id="tile-02" class="tile red" data-row="0"></div>
<div id="tile-03" class="tile red" data-row="0"></div>
<div id="tile-04" class="tile red" data-row="0"></div>
<div id="tile-05" class="tile red" data-row="0"></div>
<div id="tile-10" class="tile blue" data-row="1"></div>
<div id="tile-11" class="tile blue" data-row="1"></div>
<div id="tile-12" class="tile blue" data-row="1"></div>
<div id="tile-13" class="tile blue" data-row="1"></div>
<div id="tile-14" class="tile blue" data-row="1"></div>
<div id="tile-15" class="tile blue" data-row="1"></div>
<div id="tile-20" class="tile green" data-row="2"></div>
<div id="tile-21" class="tile green" data-row="2"></div>
<div id="tile-22" class="tile green" data-row="2"></div>
<div id="tile-23" class="tile green" data-row="2"></div>
<div id="tile-24" class="tile green" data-row="2"></div>
<div id="tile-25" class="tile green" data-row="2"></div>
<div id="ball"></div>
<div id="paddle"></div>
</div>
</div>
<footer>
This example is based on
<a href="https://codepen.io/alvaromontoro/full/487a1ec87ee94b38ee9a3073dd7ead1c"
>Alvaro Montoro's CodePen</a
>.
</footer>
</main>
<script src="../dist/gamecontroller.min.js"></script>
<script>
const boardBox = document.querySelector('#board').getBoundingClientRect();
let gamepad = null;
alvanoid = {
points: 0,
speed: 9,
offset: 165,
active: false,
paddle: null,
ball: null,
speedBall: [0, 0],
posBall: [195, 540],
gameOver: false,
calculatePositionBall: function(speed, position, active) {
let x = position[0] + speed[0];
let y = position[1] + speed[1];
if (active) {
if (x < 0) {
x = -x;
this.speedBall[0] = -this.speedBall[0];
}
if (x > 390) {
x = 390 - (x - 390);
this.speedBall[0] = -this.speedBall[0];
}
if (y > 590) {
this.gameOver = true;
}
if (y < 0) {
y = -y;
this.speedBall[1] = -this.speedBall[1];
}
if (y >= 540 && y < 550) {
if (x >= this.offset && x <= this.offset + 70) {
y = 540 - (y - 540);
this.speedBall[1] = -this.speedBall[1];
}
}
const tiles = document.querySelectorAll('.tile:not(.hidden)');
for (let i = 0; i < tiles.length; i++) {
const pos = tiles[i].getBoundingClientRect();
const tilePosX = pos.left - boardBox.left;
const tilePosY = pos.top - boardBox.top;
let change = false;
if (this.speedBall[0] > 0) {
if (x >= tilePosX && x <= tilePosX + 3 && y >= tilePosY && y <= tilePosY + 30) {
tiles[i].classList.add('hidden');
x = tilePosX - (x - tilePosX);
this.speedBall[0] = -this.speedBall[0];
change = true;
}
} else {
if (
x <= tilePosX + 60 &&
x >= tilePosX + 57 &&
y >= tilePosY &&
y <= tilePosY + 30
) {
tiles[i].classList.add('hidden');
x = tilePosX + 60 - (tilePosX + 60 - x);
this.speedBall[0] = -this.speedBall[0];
change = true;
}
}
if (this.speedBall[1] > 0 && !change) {
if (y >= tilePosY && y <= tilePosY + 3 && x >= tilePosX && x <= tilePosX + 60) {
tiles[i].classList.add('hidden');
y = tilePosY - (y - tilePosY);
this.speedBall[1] = -this.speedBall[1];
change = true;
}
} else if (!change) {
if (
y <= tilePosY + 30 &&
y >= tilePosY + 27 &&
x >= tilePosX &&
x <= tilePosX + 60
) {
tiles[i].classList.add('hidden');
y = tilePosY - (tilePosY - y);
this.speedBall[1] = -this.speedBall[1];
change = true;
}
}
if (change) {
this.points += 100;
document.querySelector('#points').textContent = this.points;
}
}
}
return { x, y };
},
paintItems: function() {
const coordinates = alvanoid.calculatePositionBall(
alvanoid.speedBall,
alvanoid.posBall,
alvanoid.active
);
alvanoid.posBall = [coordinates.x, coordinates.y];
if (!alvanoid.active) {
alvanoid.posBall = [alvanoid.offset + 30, 540];
}
alvanoid.paddle.style.left = alvanoid.offset + 'px';
alvanoid.ball.style.left = alvanoid.posBall[0] + 'px';
alvanoid.ball.style.top = alvanoid.posBall[1] + 'px';
if (!alvanoid.gameOver) {
requestAnimationFrame(alvanoid.paintItems);
}
},
move: function(change) {
this.offset += change;
if (this.offset < 0) this.offset = 0;
if (this.offset > 330) this.offset = 330;
},
restart: function() {
if (alvanoid.gameOver) {
setTimeout(alvanoid.paintItems, 100);
}
alvanoid.points = 0;
alvanoid.speed = 10;
alvanoid.offset = 165;
alvanoid.active = false;
alvanoid.speedBall = [0, 0];
alvanoid.posBall = [195, 540];
alvanoid.gameOver = false;
document.querySelector('#points').textContent = '0';
const tiles = document.querySelectorAll('.tile.hidden');
for (let x = 0; x < tiles.length; x++) {
tiles[x].classList.remove('hidden');
}
},
startGame: function() {
if (!alvanoid.active) {
alvanoid.speedBall = [3, -3];
alvanoid.active = true;
alvanoid.gameOver = false;
}
},
moveRight: function() {
alvanoid.move(alvanoid.speed);
},
moveLeft: function() {
alvanoid.move(-alvanoid.speed);
},
init: function() {
const self = this;
self.paddle = document.querySelector('#paddle');
self.ball = document.querySelector('#ball');
requestAnimationFrame(alvanoid.paintItems);
}
};
gameControl.on('connect', gamepad => {
alvanoid.init();
gamepad.on('select', alvanoid.restart);
gamepad.on('start', alvanoid.startGame);
gamepad.on('right', alvanoid.moveRight);
gamepad.on('left', alvanoid.moveLeft);
});
</script>
</body>
</html>
================================================
FILE: examples/example-6-multiplayer.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<title>GameControl Example: Multiplayer (Pong)</title>
<link
href="https://fonts.googleapis.com/css?family=Press+Start+2P&display=swap"
rel="stylesheet"
/>
<style>
html,
body {
background: #222;
font-family: 'Press Start 2P', Arial, sans-serif;
font-size: 16px;
color: #eee;
}
#board {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 600px;
height: 400px;
background: #222;
border: 5px solid #eee;
}
#board::before {
content: '';
position: absolute;
top: 0;
left: 50%;
height: 100%;
width: 5px;
border-left: 5px dashed #eee;
transform: translate(-50%, 0%);
}
.paddle {
height: 80px;
width: 10px;
background: #eee;
position: absolute;
}
#paddle-1 {
top: 160px;
left: 20px;
}
#paddle-2 {
top: 160px;
right: 20px;
}
#ball {
width: 10px;
height: 10px;
position: absolute;
background: #eee;
top: 190px;
left: 40px;
z-index: 1;
}
.score {
font-size: 4rem;
color: #aaa;
position: absolute;
top: 10px;
}
#score-1 {
right: 320px;
}
#score-2 {
left: 320px;
}
</style>
</head>
<body>
<main>
<div id="board">
<div class="paddle" id="paddle-1"></div>
<div class="paddle" id="paddle-2"></div>
<div id="ball"></div>
<div class="score" id="score-1">0</div>
<div class="score" id="score-2">0</div>
</div>
</main>
<script src="../dist/gamecontroller.min.js"></script>
<script>
const pong = {
points: [0, 0],
ball: [40, 195],
speed: [5, -5],
paddles: [160, 160],
paused: 0,
paintBoard: function() {
//calculate new ball position
pong.ball[0] += pong.speed[0];
pong.ball[1] += pong.speed[1];
if (pong.ball[1] <= 0) {
pong.ball[1] *= -1;
pong.speed[1] *= -1;
}
if (pong.ball[1] >= 390) {
pong.ball[1] = 390 - (pong.ball[1] - 390);
pong.speed[1] *= -1;
}
if (pong.ball[0] <= 0) {
pong.ball[0] = 0;
pong.points[1]++;
document.querySelector('#score-2').textContent = pong.points[1];
pong.paused = 1;
}
if (pong.ball[0] >= 590) {
pong.ball[0] = 590;
pong.points[0]++;
document.querySelector('#score-1').textContent = pong.points[0];
pong.paused = 2;
}
if (
pong.speed[0] > 0 &&
pong.ball[0] >= 560 &&
pong.ball[0] < 570 &&
pong.paddles[1] <= pong.ball[1] &&
pong.paddles[1] + 80 >= pong.ball[1]
) {
pong.ball[0] = 570 - (570 - pong.ball[0]);
pong.speed[0] *= -1;
}
if (
pong.speed[0] < 0 &&
pong.ball[0] > 20 &&
pong.ball[0] <= 30 &&
pong.paddles[0] <= pong.ball[1] &&
pong.paddles[0] + 80 >= pong.ball[1]
) {
pong.ball[0] = 30 + (pong.ball[0] - 30);
pong.speed[0] *= -1;
}
document.querySelector('#paddle-1').style.top = pong.paddles[0] + 'px';
document.querySelector('#paddle-2').style.top = pong.paddles[1] + 'px';
document.querySelector('#ball').style.left = pong.ball[0] + 'px';
document.querySelector('#ball').style.top = pong.ball[1] + 'px';
if (pong.paused == 0) {
requestAnimationFrame(pong.paintBoard);
}
},
movePaddle: function(paddle, direction) {
if (this.paddles[paddle] + direction < 0) {
this.paddles[paddle] = 0;
} else if (this.paddles[paddle] + direction > 320) {
this.paddles[paddle] = 320;
} else {
this.paddles[paddle] += direction;
}
},
restart: function() {
if (pong.paused > 0) {
setTimeout(pong.paintBoard, 100);
}
let speedHorizontal = -pong.speed[0];
let speedVertical = -pong.speed[1];
let horizontal = pong.ball[0];
const vertical = pong.ball[1];
if (horizontal > 295) {
horizontal = 550;
speedHorizontal = -5;
} else {
horizontal = 40;
speedHorizontal = 5;
}
pong.ball = [horizontal, vertical];
pong.speed = [speedHorizontal, speedVertical];
pong.paused = 0;
},
init: function() {
this.paintBoard();
}
};
pong.init();
gameControl.on('connect', gamepad => {
if (gamepad.id < 2) {
gamepad.on('up', function() {
pong.movePaddle(gamepad.id, -1);
});
gamepad.on('down', function() {
pong.movePaddle(gamepad.id, 1);
});
gamepad.on('start', pong.restart);
}
});
</script>
</body>
</html>
================================================
FILE: examples/example-7-joystick-threshold.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<title>GameControl Example: Joystick Sensitivity Threshold</title>
<link rel="stylesheet" href="./examples.css" />
<style>
.demo {
text-align: center;
}
#joystick {
width: 200px;
height: 200px;
background-color: #ddd;
background-image: radial-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.2));
border-radius: 50%;
display: inline-block;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.25);
position: relative;
}
#joystick.active {
background-color: #0a0;
}
#threshold-line {
position: absolute;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
box-shadow: inset 0 0 0 1px #f0f;
width: 200px;
height: 200px;
border-radius: 50%;
}
#dot {
position: absolute;
top: 100px;
left: 100px;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
background: #d00;
width: 10px;
height: 10px;
border-radius: 50%;
}
</style>
</head>
<body>
<main>
<h1>Example: Joystick Sensitivity Threshold</h1>
<p>
Adjust the level of sensitivity for the axe/joystick, then move the primary axe (see
<a href="./example-3-buttons-and-joysticks.html">diagram of layout</a>), and see when when
the action would trigger (the circle will turn green).
</p>
<div class="demo">
<input id="threshold" type="range" min="0" max="1" step="0.01" value="1.00" />
<div id="threshold-value">1.00</div>
<div id="joystick">
<div id="threshold-line"></div>
<div id="dot"></div>
</div>
</div>
<h2>How it works</h2>
<p>
Changing the axe/joystick sensitivity with gameController.js is really simple. Use
<code>.set(PROPERTY, VALUE)</code> to change the value of <code>"axeThreshold"</code>
</p>
<p>
The passed value can range from 0 to 1, and it can be set at the <code>gamepad</code> level
or at the <code>gameControl</code> level (and all the gamepads will be updated with that
value.) Here is an example of how it can be done:
</p>
<pre class="code"><code>gameControl.on('connect', function(gamepad) {
// all the existing gamepads will have a threshold of 0.75
this.set('axeThreshold', 0.75);
// the newly detected gamepad will have a threshold of 0.5
gamepad.set('axeThreshold', 0.5);
});</code></pre>
</main>
<script src="../dist/gamecontroller.min.js"></script>
<script>
document.querySelector('#threshold').addEventListener('input', function() {
const val = parseFloat(this.value).toFixed(2);
document.querySelector('#threshold-value').textContent = val;
const line = document.querySelector('#threshold-line');
line.style.width = val * 200 + 'px';
line.style.height = val * 200 + 'px';
gameControl.set('axeThreshold', [val]);
const gps = gameControl.getGamepads();
for (let x = 0; x < Object.keys(gps).length; x++) {
gps[x].set('axeThreshold', [val]);
}
});
function activateJoystick() {
document.querySelector('#joystick').classList.toggle('active', true);
}
const dot = document.querySelector('#dot');
gameControl
.on('afterCycle', function() {
const gp = gameControl.getGamepad(0).axeValues[0];
const angle = Math.atan(gp[0] / gp[1]);
const varX = gp[0] >= 0 ? 1 : -1;
const varY = gp[1] >= 0 ? 1 : -1;
const x = varY * varX * gp[0] * 100 * Math.sin(angle) + 100;
const y = gp[1] * 100 * Math.cos(angle) + 100;
dot.style.top = y + 'px';
dot.style.left = x + 'px';
})
.on('beforeCycle', function() {
document.querySelector('#joystick').classList.toggle('active', false);
})
.on('connect', function(gp) {
gp.on('up', activateJoystick)
.on('down', activateJoystick)
.on('left', activateJoystick)
.on('right', activateJoystick);
});
</script>
</body>
</html>
================================================
FILE: examples/example-8-vibration.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<title>GameControl Example: Vibration</title>
<link rel="stylesheet" href="./examples.css" />
<style>
#vibration-button {
background: green;
color: white;
display: block;
margin: auto auto;
min-height: 60px;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 3px;
cursor: pointer;
font-size: 1rem;
}
#vibration-button:disabled {
background: #ddd;
color: #555;
cursor: auto;
}
</style>
</head>
<body>
<main>
<h1>Example: Vibration</h1>
<p class="note">
<strong
>This is an experimental feature. Not all gamepad/joysticks will support vibration on a
browser.</strong
>
Even the ones that can vibrate, may not be able to vibrate on a browser because this is a
not widely supported feature.
</p>
<p>Connect your gamepad/joystick, and click on the button to trigger the vibration.</p>
<p>
<button id="vibration-button" disabled>No gamepad/joystick connected</button>
</p>
<h2>How it works</h2>
<p>
Vibration of the gamepad can be triggered using the <code>.vibrate()</code> method of the
<code>gamepad</code> object. That method takes two parameters:
</p>
<ul>
<li><code>Intensity</code>: the strength of the vibration. It is value between 0 and 1.</li>
<li><code>Duration</code>: the duration in milliseconds of the vibration.</li>
</ul>
<p>
The <code>.vibrate()</code> method can be called without parameters. In that case, the
intensity will be 0.75 by default, and the duration will be 500 milliseconds (half a
second.)
</p>
<pre class="code"><code>gameControl.on('connect', function(gamepad) {
if (gamepad.vibration) {
gamepad.vibrate(0.5, 1000);
}
});</code></pre>
<p>
If several vibrations are called, they won't be chained. Instead the last one will overwrite
the existing one.
</p>
</main>
<script src="../dist/gamecontroller.min.js"></script>
<script>
document.querySelector('#vibration-button').addEventListener('click', function() {
gameControl.getGamepad(0).vibrate(1.0, 1000);
});
gameControl.on('connect', function(gp) {
const button = document.querySelector('#vibration-button');
if (gp.vibration) {
button.textContent = 'Click to trigger vibration.';
button.removeAttribute('disabled');
} else {
button.textContent = 'Sorry, this gamepad does not support vibration.';
}
});
</script>
</body>
</html>
================================================
FILE: examples/example-9-before-and-after.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<title>GameControl Example: Before and After Events</title>
<link rel="stylesheet" href="./examples.css" />
<style>
#log {
height: 300px;
overflow: auto;
border: 1px solid #ccc;
}
#demo-content {
width: 80%;
margin: auto auto;
}
</style>
</head>
<body>
<main>
<h1>Example: Before and After Events</h1>
<p>
Connect a gamepad to the computer, then press the Start and Select buttons and see the
numbers update below.
</p>
<div id="demo-content">
<p>
<kbd>Select</kbd> pressed: <span id="select">0</span> times.<br />
<kbd>Start</kbd> pressed: <span id="start">0</span> times.<br />
</p>
<p id="log"></p>
</div>
<p>
This is achieved using the <code>.before()</code> and <code>.after()</code> event handlers.
And it can be really convenient in many situations: setting up timers, mimic a single press
instead of a continuous one, etc.
</p>
<h2>How it works</h2>
<p>
The <code>.before()</code> and <code>.after()</code> methods work in a similar way to how
<code>.on()</code> does:
</p>
<pre class="code"><code>gamepad.before(EVENTNAME, CALLBACK);
gamepad.on(EVENTNAME, CALLBACK);
gamepad.after(EVENTNAME, CALLBACK);</code></pre>
<p>
These methods can be chained to allow a more fluid development. So the example above could
also be written as:
</p>
<pre class="code"><code>gamepad.before(EVENTNAME, CALLBACK);
.on(EVENTNAME, CALLBACK);
.after(EVENTNAME, CALLBACK);</code></pre>
</main>
<script src="../dist/gamecontroller.min.js"></script>
<script>
const log = document.querySelector('#log');
let start = 0;
let select = 0;
gameControl.on('connect', function(gp) {
gp.on('start', function() {
log.insertAdjacentHTML('afterbegin', '<div>Start is on</div>');
})
.before('start', function() {
log.insertAdjacentHTML('afterbegin', '<div>Start is pressed</div>');
})
.after('start', function() {
log.insertAdjacentHTML('afterbegin', '<div>Start was released</div>');
start++;
document.querySelector('#start').textContent = start;
});
gp.on('select', function() {
log.insertAdjacentHTML('afterbegin', '<div>Select is on</div>');
})
.before('select', function() {
log.insertAdjacentHTML('afterbegin', '<div>Select is pressed</div>');
})
.after('select', function() {
log.insertAdjacentHTML('afterbegin', '<div>Select was released</div>');
select++;
document.querySelector('#select').textContent = select;
});
});
</script>
</body>
</html>
================================================
FILE: examples/examples.css
================================================
html,
body {
background: #666;
border: 0;
display: flex;
flex-direction: column;
font-family: Arial, Verdana, sans-serif;
font-size: 16px;
margin: 0;
min-height: 100vh;
min-width: 100vw;
padding: 0;
}
main {
background: #eee;
box-sizing: border-box;
border: 1rem solid #333;
border-radius: 0.25rem;
color: #222;
flex: 1;
margin: 3rem auto;
max-width: 1000px;
min-width: 320px;
padding: 2rem;
width: 100%;
width: calc(100% - 6rem);
}
h1 {
font-size: 2rem;
margin: 0 auto 1rem auto;
}
h2 {
font-size: 1.75rem;
margin: 2rem auto 1rem auto;
}
p,
ul,
pre.code {
font-size: 1.1rem;
margin: 0 0 1rem 0;
line-height: 1.5rem;
}
p.note {
background: rgba(0, 0, 0, 0.075);
border-left: 0.25rem solid #222;
padding: 0.5rem;
}
p.note::before {
content: 'Note: ';
font-weight: bold;
}
p.code {
margin-left: 2rem;
}
pre.code {
background: #333;
color: #ddd;
overflow-x: auto;
padding: 1rem;
}
================================================
FILE: license.md
================================================
Copyright (c) 2019 - Alvaro Montoro (alvaromontoro@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: package.json
================================================
{
"name": "gamecontroller.js",
"version": "1.5.0",
"description": "A JavaScript library that lets you handle, configure, and use gamepad and controllers on a browser, using the Gamepad API",
"main": "dist/gamecontroller.min.js",
"scripts": {
"build": "webpack --output-filename gamecontroller.min.js --mode production",
"build:dev": "webpack --output-filename gamecontroller.js --mode development",
"test": "jest --coverage"
},
"devDependencies": {
"@babel/preset-env": "^7.13.8",
"jest": "^26.6.3",
"webpack": "^5.4.0",
"webpack-cli": "^4.5.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/alvaromontoro/gamecontrol.git"
},
"bugs": {
"url": "https://github.com/alvaromontoro/gamecontrol/issues"
},
"homepage": "https://github.com/alvaromontoro/gamecontrol",
"author": "Alvaro Montoro",
"license": "MIT",
"keywords": [
"game control",
"gamepad",
"game controller",
"game control",
"controller",
"gaming",
"web api",
"html5 api",
"gamepad api"
],
"directories": {
"example": "examples",
"test": "tests"
}
}
================================================
FILE: readme.md
================================================
# gameController.js
A JavaScript library that lets you handle, configure, and use gamepad and controllers on a browser.
[](https://travis-ci.org/alvaromontoro/gamecontroller.js)
[](https://www.npmjs.com/package/gamecontroller.js)
[](https://www.npmjs.com/package/gamecontroller.js)
## Getting started
GameController.js is a lightweight library (~6KB) that uses JavaScript and the standard [Gamepad API](https://w3c.github.io/gamepad/), and does not have any plugin/library dependencies.
## Installation
From npm:
```
npm i gamecontroller.js
```
From yarn:
```
yarn add gamecontroller.js
```
Directly into your webpage (check [latest release on github](https://github.com/alvaromontoro/gamecontroller.js/releases)):
```
<script src="./gamecontroller.min.js"></script>
```
## Usage
After importing the library into your webpage/project, `gameControl` will be available to use. This object comes with a series of properties and methods that will allow to handle the different gamepads connected to the computer.
The connected gamepads will be stored in a list of `gamepad` objects in `gameControl`. **This `gamepad` object is not the default one returned by the browser** but a higher-level interface to interact with it and simplify its usability.
Once the file is imported into the project, the object `gameControl` will be available and ready to be used.
```javascript
gameControl.on('connect', function(gamepad) {
gamepad.on('up', moveCharacterUp);
});
```
[Visit the Wiki for a full list of the properties, methods and events](https://github.com/alvaromontoro/gamecontroller.js/wiki) of these two objects.
### Events for gameControl
For the object `gameControl`, events are associated using the `.on()` method:
```javascript
gameControl.on('connect', gamepad => {
console.log('A new gamepad was connected!');
});
```
Here is a list of the events that can be associated using `.on()`:
<table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td valign="top"><code>connect</code></td>
<td>Triggered every time that a gamepad is connected to the browser. It returns an instance of the `gamepad` object described below.</td>
</tr>
<tr>
<td valign="top"><code>disconnect</code></td>
<td>Triggered when a gamepad is disconnected from the browser.</td>
</tr>
<tr>
<td valign="top"><code>beforeCycle</code></td>
<td>Triggered before the gamepads are checked for pressed buttons/joysticks movement (before those events are triggered).</td>
</tr>
<tr>
<td valign="top"><code>afterCycle</code></td>
<td>Triggered after the gamepads are checked for pressed buttons/joysticks movement (after those events have been triggered).</td>
</tr>
</tbody>
</table>
### Events for gamepad
The events for the `gamepad` objects work a little bit different. The event name, is the name of the button/direction that was activated (e.g. `button0`, `up`, etc.) And there are three functions that can be used to associate event handlers for them in different situations:
- `.on()`: triggered every cycle, while the button/joystick is pressed/active.
- `.before()`: triggered the first cycle that a button/joystick is pressed.
- `.after()`: triggered the first cycle after a button/joystick stopped being pressed.
All three functions can be chained and allow two parameters: the first one is the button/direction that was activated, and the second parameter is the callback function. Example:
```javascript
gamepad.on('button0', () => { console.log('Button 0 still pressed...'); })
.before('button0', () => { console.log('Button 0 pressed...'); })
.after('button0', () => { console.log('Button 0 was released'; });
```
To see the event flow and how the different events are lined-up and interact with each other, visit the [Event Flow wikipage](../EventFlow).
Thisus
These are the _events_ that can be passed as first parameter to the event functions:
<table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td valign="top"><code>button0</code></td>
<td>Triggered when button 0 is pressed.</td>
</tr>
<tr>
<td valign="top"><code>button1</code></td>
<td>Triggered when button 1 is pressed.</td>
</tr>
<tr>
<td valign="top"><code>button2</code></td>
<td>Triggered when button 2 is pressed.</td>
</tr>
<tr>
<td valign="top"><code>button3</code></td>
<td>Triggered when button 3 is pressed.</td>
</tr>
<tr>
<td valign="top"><code>button4</code></td>
<td>Triggered when button 4 is pressed.</td>
</tr>
<tr>
<td valign="top"><code>button5</code></td>
<td>Triggered when button 5 is pressed.</td>
</tr>
<tr>
<td valign="top"><code>button6</code></td>
<td>Triggered when button 6 is pressed.</td>
</tr>
<tr>
<td valign="top"><code>button7</code></td>
<td>Triggered when button 7 is pressed.</td>
</tr>
<tr>
<td valign="top"><code>button8</code></td>
<td>Triggered when button 8 is pressed.</td>
</tr>
<tr>
<td valign="top"><code>button9</code></td>
<td>Triggered when button 9 is pressed.</td>
</tr>
<tr>
<td valign="top"><code>button10</code></td>
<td>Triggered when button 10 is pressed.</td>
</tr>
<tr>
<td valign="top"><code>button11</code></td>
<td>Triggered when button 11 is pressed.</td>
</tr>
<tr>
<td valign="top"><code>button12</code></td>
<td>Triggered when button 12 is pressed.</td>
</tr>
<tr>
<td valign="top"><code>button13</code></td>
<td>Triggered when button 13 is pressed.</td>
</tr>
<tr>
<td valign="top"><code>button14</code></td>
<td>Triggered when button 14 is pressed.</td>
</tr>
<tr>
<td valign="top"><code>button15</code></td>
<td>Triggered when button 15 is pressed.</td>
</tr>
<tr>
<td valign="top"><code>button16</code></td>
<td>Triggered when button 16 is pressed.</td>
</tr>
<tr>
<td valign="top"><code>up0</code></td>
<td>Triggered when the first axe/joystick is moved up.</td>
</tr>
<tr>
<td valign="top"><code>down0</code></td>
<td>Triggered when the first axe/joystick is moved down.</td>
</tr>
<tr>
<td valign="top"><code>right0</code></td>
<td>Triggered when the first axe/joystick is moved right.</td>
</tr>
<tr>
<td valign="top"><code>left0</code></td>
<td>Triggered when the first axe/joystick is moved left.</td>
</tr>
<tr>
<td valign="top"><code>up1</code></td>
<td>Triggered when the second axe/joystick is moved up.</td>
</tr>
<tr>
<td valign="top"><code>down1</code></td>
<td>Triggered when the second axe/joystick is moved down.</td>
</tr>
<tr>
<td valign="top"><code>right1</code></td>
<td>Triggered when the second axe/joystick is moved right.</td>
</tr>
<tr>
<td valign="top"><code>left1</code></td>
<td>Triggered when the second axe/joystick is moved left.</td>
</tr>
<tr>
<td valign="top"><code>start</code></td>
<td>Triggered when Start button is pressed.<br>This is an alias for event <code>button9</code>.</td>
</tr>
<tr>
<td valign="top"><code>select</code></td>
<td>Triggered when Select button is pressed.<br>This is an alias for event <code>button8</code>.</td>
</tr>
<tr>
<td valign="top"><code>power</code></td>
<td>Triggered when Power button is pressed (e.g. the Xbox logo in an Xbox controller).<br>This is an alias for event <code>button16</code>.</td>
</tr>
<tr>
<td valign="top"><code>l1</code></td>
<td>Triggered when the left back button 1 is pressed.<br>This is an alias for event <code>button4</code>.</td>
</tr>
<tr>
<td valign="top"><code>l2</code></td>
<td>Triggered when left back button 2 is pressed.<br>This is an alias for event <code>button6</code>.</td>
</tr>
<tr>
<td valign="top"><code>r1</code></td>
<td>Triggered when right back button 1 is pressed.<br>This is an alias for event <code>button5</code>.</td>
</tr>
<tr>
<td valign="top"><code>r2</code></td>
<td>Triggered when right back button 2 is pressed.<br>This is an alias for event <code>button7</code>.</td>
</tr>
<tr>
<td valign="top"><code>up</code></td>
<td>Triggered when the main/first axe/joystick is moved up.<br>This is an alias for event <code>up0</code>.</td>
</tr>
<tr>
<td valign="top"><code>down</code></td>
<td>Triggered when the main/first axe/joystick is moved down.<br>This is an alias for event <code>down0</code>.</td>
</tr>
<tr>
<td valign="top"><code>right</code></td>
<td>Triggered when the main/first axe/joystick is moved right.<br>This is an alias for event <code>right0</code>.</td>
</tr>
<tr>
<td valign="top"><code>left</code></td>
<td>Triggered when the main/first axe/joystick is moved left.<br>This is an alias for event <code>left0</code>.</td>
</tr>
</tbody>
</table>
These names are not arbitrary. They match the buttons and axes described in the [W3C Gamepad API specicification](https://w3c.github.io/gamepad/#fig-visual-representation-of-a-standard-gamepad-layout):

## Browser Support
| <br>Edge | <br>Firefox | <br>Chrome | <br>Safari | <br>Opera |
| ---- | ------- | ------ | ------ | ----- |
| 12+ | 29+ | 25+ | 10.1+ | 24+ |
## Examples
The `examples` folder contains different examples to showcase how to use the library:
- [Connectivity](https://htmlpreview.github.io/?https://github.com/alvaromontoro/gamecontroller.js/blob/master/examples/example-0-connectivity.html): shows how to detect if a gamepad was connected/disconnected.
- [Buttons and Joysticks](https://htmlpreview.github.io/?https://github.com/alvaromontoro/gamecontroller.js/blob/master/examples/example-3-buttons-and-joysticks.html): see how the buttons from your gamepad map to the default gamepad.
- [SNES Controller](https://htmlpreview.github.io/?https://github.com/alvaromontoro/gamecontroller.js/blob/master/examples/example-4-snes-controller.html): replica of a SNES controller (based on a previous CodePen demo).
- [Alvanoid](https://htmlpreview.github.io/?https://github.com/alvaromontoro/gamecontroller.js/blob/master/examples/example-5-alvanoid.html): small Arkanoid-based game (based on a previous CodePen demo).
- [Pong](https://htmlpreview.github.io/?https://github.com/alvaromontoro/gamecontroller.js/blob/master/examples/example-6-multiplayer.html): multiplayer demo with the classic game Pong for 2 players on 2 gamepads.
================================================
FILE: src/constants.js
================================================
const MESSAGES = {
ON: 'Gamepad detected.',
OFF: 'Gamepad disconnected.',
INVALID_PROPERTY: 'Invalid property.',
INVALID_VALUE_NUMBER: 'Invalid value. It must be a number between 0.00 and 1.00.',
INVALID_BUTTON: 'Button does not exist.',
UNKNOWN_EVENT: 'Unknown event name.',
NO_SUPPORT: 'Your web browser does not support the Gamepad API.'
};
export { MESSAGES };
================================================
FILE: src/gamecontrol.js
================================================
import { log, error, isGamepadSupported } from './tools';
import { MESSAGES } from './constants';
import gamepad from './gamepad';
const gameControl = {
gamepads: {},
axeThreshold: [1.0], // this is an array so it can be expanded without breaking in the future
isReady: isGamepadSupported(),
onConnect: function() {},
onDisconnect: function() {},
onBeforeCycle: function() {},
onAfterCycle: function() {},
getGamepads: function() {
return this.gamepads;
},
getGamepad: function(id) {
if (this.gamepads[id]) {
return this.gamepads[id];
}
return null;
},
set: function(property, value) {
const properties = ['axeThreshold'];
if (properties.indexOf(property) >= 0) {
if (property === 'axeThreshold' && (!parseFloat(value) || value < 0.0 || value > 1.0)) {
error(MESSAGES.INVALID_VALUE_NUMBER);
return;
}
this[property] = value;
if (property === 'axeThreshold') {
const gps = this.getGamepads();
const ids = Object.keys(gps);
for (let x = 0; x < ids.length; x++) {
gps[ids[x]].set('axeThreshold', this.axeThreshold);
}
}
} else {
error(MESSAGES.INVALID_PROPERTY);
}
},
checkStatus: function() {
const requestAnimationFrame =
window.requestAnimationFrame || window.webkitRequestAnimationFrame;
const gamepadIds = Object.keys(gameControl.gamepads);
gameControl.onBeforeCycle();
for (let x = 0; x < gamepadIds.length; x++) {
gameControl.gamepads[gamepadIds[x]].checkStatus();
}
gameControl.onAfterCycle();
if (gamepadIds.length > 0) {
requestAnimationFrame(gameControl.checkStatus);
}
},
init: function() {
window.addEventListener('gamepadconnected', e => {
const egp = e.gamepad || e.detail.gamepad;
log(MESSAGES.ON);
if (!window.gamepads) window.gamepads = {};
if (egp) {
if (!window.gamepads[egp.index]) {
window.gamepads[egp.index] = egp;
const gp = gamepad.init(egp);
gp.set('axeThreshold', this.axeThreshold);
this.gamepads[gp.id] = gp;
this.onConnect(this.gamepads[gp.id]);
}
if (Object.keys(this.gamepads).length === 1) this.checkStatus();
}
});
window.addEventListener('gamepaddisconnected', e => {
const egp = e.gamepad || e.detail.gamepad;
log(MESSAGES.OFF);
if (egp) {
delete window.gamepads[egp.index];
delete this.gamepads[egp.index];
this.onDisconnect(egp.index);
}
});
},
on: function(eventName, callback) {
switch (eventName) {
case 'connect':
this.onConnect = callback;
break;
case 'disconnect':
this.onDisconnect = callback;
break;
case 'beforeCycle':
case 'beforecycle':
this.onBeforeCycle = callback;
break;
case 'afterCycle':
case 'aftercycle':
this.onAfterCycle = callback;
break;
default:
error(MESSAGES.UNKNOWN_EVENT);
break;
}
return this;
},
off: function(eventName) {
switch (eventName) {
case 'connect':
this.onConnect = function() {};
break;
case 'disconnect':
this.onDisconnect = function() {};
break;
case 'beforeCycle':
case 'beforecycle':
this.onBeforeCycle = function() {};
break;
case 'afterCycle':
case 'aftercycle':
this.onAfterCycle = function() {};
break;
default:
error(MESSAGES.UNKNOWN_EVENT);
break;
}
return this;
}
};
gameControl.init();
export default gameControl;
================================================
FILE: src/gamepad.js
================================================
import { error, emptyEvents } from './tools';
import { MESSAGES } from './constants';
const gamepad = {
init: function(gpad) {
let gamepadPrototype = {
id: gpad.index,
buttons: gpad.buttons.length,
axes: Math.floor(gpad.axes.length / 2),
axeValues: [],
axeThreshold: [1.0],
hapticActuator: null,
vibrationMode: -1,
vibration: false,
mapping: gpad.mapping,
buttonActions: {},
axesActions: {},
pressed: {},
set: function(property, value) {
const properties = ['axeThreshold'];
if (properties.indexOf(property) >= 0) {
if (property === 'axeThreshold' && (!parseFloat(value) || value < 0.0 || value > 1.0)) {
error(MESSAGES.INVALID_VALUE_NUMBER);
return;
}
this[property] = value;
} else {
error(MESSAGES.INVALID_PROPERTY);
}
},
vibrate: function(value = 0.75, duration = 500) {
if (this.hapticActuator) {
switch (this.vibrationMode) {
case 0:
return this.hapticActuator.pulse(value, duration);
case 1:
return this.hapticActuator.playEffect('dual-rumble', {
duration: duration,
strongMagnitude: value,
weakMagnitude: value
});
}
}
},
triggerDirectionalAction: function(id, axe, condition, x, index) {
if (condition && x % 2 === index) {
if (!this.pressed[`${id}${axe}`]) {
this.pressed[`${id}${axe}`] = true;
this.axesActions[axe][id].before();
}
this.axesActions[axe][id].action();
} else if (this.pressed[`${id}${axe}`] && x % 2 === index) {
delete this.pressed[`${id}${axe}`];
this.axesActions[axe][id].after();
}
},
checkStatus: function() {
let gp = {};
const gps = navigator.getGamepads
? navigator.getGamepads()
: navigator.webkitGetGamepads
? navigator.webkitGetGamepads()
: [];
if (gps.length) {
gp = gps[this.id];
if (gp.buttons) {
for (let x = 0; x < this.buttons; x++) {
if (gp.buttons[x].pressed === true) {
if (!this.pressed[`button${x}`]) {
this.pressed[`button${x}`] = true;
this.buttonActions[x].before();
}
this.buttonActions[x].action();
} else if (this.pressed[`button${x}`]) {
delete this.pressed[`button${x}`];
this.buttonActions[x].after();
}
}
}
if (gp.axes) {
const modifier = gp.axes.length % 2; // Firefox hack: detects one additional axe
for (let x = 0; x < this.axes * 2; x++) {
const val = gp.axes[x + modifier].toFixed(4);
const axe = Math.floor(x / 2);
this.axeValues[axe][x % 2] = val;
this.triggerDirectionalAction('right', axe, val >= this.axeThreshold[0], x, 0);
this.triggerDirectionalAction('left', axe, val <= -this.axeThreshold[0], x, 0);
this.triggerDirectionalAction('down', axe, val >= this.axeThreshold[0], x, 1);
this.triggerDirectionalAction('up', axe, val <= -this.axeThreshold[0], x, 1);
}
}
}
},
associateEvent: function(eventName, callback, type) {
if (eventName.match(/^button\d+$/)) {
const buttonId = parseInt(eventName.match(/^button(\d+)$/)[1]);
if (buttonId >= 0 && buttonId < this.buttons) {
this.buttonActions[buttonId][type] = callback;
} else {
error(MESSAGES.INVALID_BUTTON);
}
} else if (eventName === 'start') {
this.buttonActions[9][type] = callback;
} else if (eventName === 'select') {
this.buttonActions[8][type] = callback;
} else if (eventName === 'r1') {
this.buttonActions[5][type] = callback;
} else if (eventName === 'r2') {
this.buttonActions[7][type] = callback;
} else if (eventName === 'l1') {
this.buttonActions[4][type] = callback;
} else if (eventName === 'l2') {
this.buttonActions[6][type] = callback;
} else if (eventName === 'power') {
if (this.buttons >= 17) {
this.buttonActions[16][type] = callback;
} else {
error(MESSAGES.INVALID_BUTTON);
}
} else if (eventName.match(/^(up|down|left|right)(\d+)$/)) {
const matches = eventName.match(/^(up|down|left|right)(\d+)$/);
const direction = matches[1];
const axe = parseInt(matches[2]);
if (axe >= 0 && axe < this.axes) {
this.axesActions[axe][direction][type] = callback;
} else {
error(MESSAGES.INVALID_BUTTON);
}
} else if (eventName.match(/^(up|down|left|right)$/)) {
const direction = eventName.match(/^(up|down|left|right)$/)[1];
this.axesActions[0][direction][type] = callback;
}
return this;
},
on: function(eventName, callback) {
return this.associateEvent(eventName, callback, 'action');
},
off: function(eventName) {
return this.associateEvent(eventName, function() {}, 'action');
},
after: function(eventName, callback) {
return this.associateEvent(eventName, callback, 'after');
},
before: function(eventName, callback) {
return this.associateEvent(eventName, callback, 'before');
}
};
for (let x = 0; x < gamepadPrototype.buttons; x++) {
gamepadPrototype.buttonActions[x] = emptyEvents();
}
for (let x = 0; x < gamepadPrototype.axes; x++) {
gamepadPrototype.axesActions[x] = {
down: emptyEvents(),
left: emptyEvents(),
right: emptyEvents(),
up: emptyEvents()
};
gamepadPrototype.axeValues[x] = [0, 0];
}
// check if vibration actuator exists
if (gpad.hapticActuators) {
// newer standard
if (typeof gpad.hapticActuators.pulse === 'function') {
gamepadPrototype.hapticActuator = gpad.hapticActuators;
gamepadPrototype.vibrationMode = 0;
gamepadPrototype.vibration = true;
} else if (gpad.hapticActuators[0] && typeof gpad.hapticActuators[0].pulse === 'function') {
gamepadPrototype.hapticActuator = gpad.hapticActuators[0];
gamepadPrototype.vibrationMode = 0;
gamepadPrototype.vibration = true;
}
} else if (gpad.vibrationActuator) {
// old chrome stuff
if (typeof gpad.vibrationActuator.playEffect === 'function') {
gamepadPrototype.hapticActuator = gpad.vibrationActuator;
gamepadPrototype.vibrationMode = 1;
gamepadPrototype.vibration = true;
}
}
return gamepadPrototype;
}
};
export default gamepad;
================================================
FILE: src/index.js
================================================
// This file is the entry point
import { error, isGamepadSupported } from './tools';
import { MESSAGES } from './constants';
import gameControl from './gamecontrol';
if (isGamepadSupported()) {
window.gameControl = gameControl;
} else {
error(MESSAGES.NO_SUPPORT);
}
================================================
FILE: src/tools.js
================================================
const log = (message, type = 'log') => {
if (type === 'error') {
if (console && typeof console.error === 'function') console.error(message);
} else {
if (console && typeof console.info === 'function') console.info(message);
}
};
const error = message => log(message, 'error');
const isGamepadSupported = () =>
(navigator.getGamepads && typeof navigator.getGamepads === 'function') ||
(navigator.getGamepads && typeof navigator.webkitGetGamepads === 'function') ||
false;
const emptyEvents = () => ({ action: () => {}, after: () => {}, before: () => {} });
export { isGamepadSupported, log, error, emptyEvents };
================================================
FILE: tests/gamecontrol.test.js
================================================
import gameControl from '../src/gamecontrol';
import gamepad from '../src/gamepad';
import { gamepads } from './mock.gamepads';
function generateGamepads() {
const auxGamepads = {};
for (let x = 0; x < gamepads.length; x++) {
auxGamepads[x] = gamepad.init(gamepads[x]);
auxGamepads[x].set('axeThreshold', gameControl.axeThreshold);
}
gameControl.gamepads = auxGamepads;
}
describe('gameControl', () => {
// these cases should probably not happen but should fail gracefully
test('Check status when nothing has been connected yet', () => {
global.webkitRequestAnimationFrame = global.requestAnimationFrame;
global.requestAnimationFrame = null;
gameControl.checkStatus();
global.requestAnimationFrame = global.webkitRequestAnimationFrame;
});
test('Check gameControl gamepads', () => {
expect(gameControl.gamepads).toEqual({});
expect(gameControl.getGamepads()).toEqual({});
expect(gameControl.getGamepad(0)).toEqual(null);
});
test('trigger event gamepadconnected', () => {
const event = new CustomEvent('gamepadconnected', {
detail: { gamepad: gamepads[0] },
gamepad: gamepads[0]
});
global.dispatchEvent(event);
});
test('trigger event gamepaddisconnected', () => {
const event = new CustomEvent('gamepaddisconnected', {
detail: { gamepad: gamepads[0] }
});
global.dispatchEvent(event);
});
// this definitely should not happen
test('trigger event gamepadconnected (no gamepad)', () => {
const event = new CustomEvent('gamepadconnected', {
detail: {}
});
global.dispatchEvent(event);
});
// this should not happen
test('trigger event gamepaddisconnected (no gamepad)', () => {
const event = new CustomEvent('gamepaddisconnected', {
detail: {}
});
global.dispatchEvent(event);
});
test('trigger event gamepadconnected for three gamepads', () => {
const event = new CustomEvent('gamepadconnected', {
detail: { gamepad: gamepads[0] },
gamepad: gamepads[0]
});
global.dispatchEvent(event);
const event2 = new CustomEvent('gamepadconnected', {
detail: { gamepad: gamepads[1] },
gamepad: gamepads[1]
});
global.dispatchEvent(event2);
// this probably shouldn't happen
const event3 = new CustomEvent('gamepadconnected', {
detail: { gamepad: gamepads[0] },
gamepad: gamepads[0]
});
global.dispatchEvent(event3);
});
test('Function getGamepads()', () => {
generateGamepads();
const gamepadList = gameControl.getGamepads();
expect(Object.keys(gamepadList).length).toEqual(gamepads.length);
});
test('Function getGamepad(id)', () => {
generateGamepads();
const gp = gameControl.getGamepad(0);
expect(gp.id).toEqual(0);
expect(gp.mapping).toEqual('standard');
});
test('Function getGamepad(id) incorrect id', () => {
generateGamepads();
const gp = gameControl.getGamepad(100);
expect(gp).toEqual(null);
});
test('Verify sensitivity threshold', () => {
generateGamepads();
const gp = gameControl.getGamepad(0);
expect(gp.axeThreshold[0]).toEqual(1.0);
gp.set('axeThreshold', [0.3]);
expect(gp.axeThreshold[0]).toEqual(0.3);
});
test('Verify sensitivity threshold', () => {
gameControl.axeThreshold = [0.5];
generateGamepads();
const gp = gameControl.getGamepad(0);
expect(gp.axeThreshold[0]).toEqual(0.5);
gp.set('axeThreshold', [0.3]);
expect(gp.axeThreshold[0]).toEqual(0.3);
gameControl.axeThreshold = [0.5];
expect(gp.axeThreshold[0]).toEqual(0.3);
});
test('event associattion and deassociation', () => {
gameControl
.on('connect', () => 'gamepad connected')
.on('disconnect', () => 'gamepad disconnected')
.on('beforecycle', () => 'before cycle')
.on('aftercycle', () => 'after cycle')
.on('afterCycle', () => 'after Cycle')
.on('beforeCycle', () => 'before Cycle');
expect(gameControl.onConnect()).toEqual('gamepad connected');
gameControl
.off('connect')
.off('disconnect')
.off('beforecycle')
.off('aftercycle')
.off('afterCycle')
.off('beforeCycle');
expect(gameControl.onConnect()).toEqual(undefined);
});
test('event association/deassociation of unknown event', () => {
gameControl.on('invalidEvent', () => 'invalid event');
gameControl.off('invalidEvent');
});
test('checkStatus', () => {
generateGamepads();
gameControl.checkStatus();
});
test('set invalid property', () => {
gameControl.set('invalidProperty', true);
});
test('set axeThreshold', () => {
gameControl.set('axeThreshold', [1.0]);
expect(gameControl.axeThreshold[0]).toEqual(1.0);
gameControl.set('axeThreshold', [0.5]);
expect(gameControl.axeThreshold[0]).toEqual(0.5);
gameControl.set('axeThreshold', [10.5]);
expect(gameControl.axeThreshold[0]).toEqual(0.5);
});
});
================================================
FILE: tests/gamepad.test.js
================================================
import gamepad from '../src/gamepad';
import { gamepads, gamepadsFirefox } from './mock.gamepads';
describe('gamepad', () => {
test('Check default values (17-button gamepad)', () => {
const gp = gamepad.init(gamepads[0]);
expect(gp.id).toEqual(0);
expect(gp.buttons).toEqual(17);
expect(gp.axes).toEqual(2);
expect(gp.mapping).toEqual('standard');
expect(Object.keys(gp.buttonActions).length).toEqual(gp.buttons);
expect(Object.keys(gp.axesActions).length).toEqual(gp.axes);
});
test('Check default values (10-button gamepad)', () => {
const gp = gamepad.init(gamepads[1]);
expect(gp.id).toEqual(1);
expect(gp.buttons).toEqual(10);
expect(gp.axes).toEqual(1);
expect(gp.mapping).toEqual('');
expect(Object.keys(gp.buttonActions).length).toEqual(gp.buttons);
expect(Object.keys(gp.axesActions).length).toEqual(gp.axes);
});
test('Check button pressed (manual)', () => {
const gp = gamepad.init(gamepads[0]);
const message = 'Button0 pressed';
gp.on('button0', function() {
return message;
});
expect(gp.buttonActions[0].action()).toEqual(message);
});
test('Verify sensitivity threshold', () => {
const gp = gamepad.init(gamepads[0]);
expect(gp.axeThreshold[0]).toEqual(1.0);
gp.set('axeThreshold', [0.3]);
expect(gp.axeThreshold[0]).toEqual(0.3);
gp.set('axeThreshold', [12.0]); // invalid value
expect(gp.axeThreshold[0]).toEqual(0.3);
});
test('set invalid property', () => {
const gp = gamepad.init(gamepads[0]);
gp.set('invalidProperty', true);
});
test('Vibration in Chrome', () => {
const gp = gamepad.init(gamepads[0]);
expect(gp.vibrate(0.5, 500)).toEqual('dual-rumble - 500');
expect(gp.vibrate()).toEqual('dual-rumble - 500');
});
// this case should never happen
test('Vibration in Chrome (no vibration)', () => {
const gp = gamepad.init(gamepads[2]);
expect(gp.vibrate(0.5, 500)).toEqual(undefined);
});
test('Vibration in Firefox', () => {
const gp = gamepad.init(gamepadsFirefox[0]);
expect(gp.vibrate(0.5, 500)).toEqual('vibrate at 0.5 for 500ms');
});
test('Vibration in Firefox (no vibration)', () => {
const gp = gamepad.init(gamepadsFirefox[1]);
expect(gp.vibrate(0.5, 500)).toEqual(undefined);
});
// this case should never happen
test('Vibration in Firefox (wrong type)', () => {
const gp = gamepad.init(gamepadsFirefox[3]);
expect(gp.vibrate(0.5, 500)).toEqual(undefined);
});
test('Vibration in Firefox (array of actuators)', () => {
const gp = gamepad.init(gamepadsFirefox[2]);
expect(gp.vibrate(0.5, 500)).toEqual('vibrate at 0.5 for 500ms');
});
test('after event', () => {
const gp = gamepad.init(gamepads[0]);
gp.after('button0', function() {
return 'button0 released';
});
expect(gp.buttonActions[0].after()).toEqual('button0 released');
});
test('on event', () => {
const gp = gamepad.init(gamepads[0]);
gp.on('button0', function() {
return 'button0 is on';
});
expect(gp.buttonActions[0].action()).toEqual('button0 is on');
});
test('on wrong event', () => {
const gp = gamepad.init(gamepads[0]);
gp.on('fakeevent', () => {});
});
test('before event', () => {
const gp = gamepad.init(gamepads[0]);
gp.before('button0', function() {
return 'button0 pressed';
});
expect(gp.buttonActions[0].before()).toEqual('button0 pressed');
});
test('off event', () => {
const gp = gamepad.init(gamepads[0]);
gp.on('button0', function() {
return 'button0 is on';
});
expect(gp.buttonActions[0].action()).toEqual('button0 is on');
gp.off('button0');
expect(gp.buttonActions[0].action()).toEqual(undefined);
});
test('on directional event', () => {
const gp = gamepad.init(gamepads[0]);
gp.on('up0', function() {
return 'up0 is on';
});
expect(gp.axesActions[0].up.action()).toEqual('up0 is on');
gp.off('up0');
expect(gp.axesActions[0].up.action()).toEqual(undefined);
});
test('on directional event (alias)', () => {
const gp = gamepad.init(gamepads[0]);
gp.on('up', function() {
return 'up is on';
});
expect(gp.axesActions[0].up.action()).toEqual('up is on');
gp.off('up');
expect(gp.axesActions[0].up.action()).toEqual(undefined);
});
test('on directional event (incorrect)', () => {
const gp = gamepad.init(gamepads[0]);
gp.on('up4', function() {
return 'up4 is on';
});
});
test('on button aliases', () => {
const gp = gamepad.init(gamepads[0]);
gp.on('select', () => 'select')
.on('start', () => 'start')
.on('l1', () => 'l1')
.on('l2', () => 'l2')
.on('r1', () => 'r1')
.on('r2', () => 'r2')
.on('power', () => 'power');
expect(gp.buttonActions[8].action()).toEqual('select');
expect(gp.buttonActions[9].action()).toEqual('start');
expect(gp.buttonActions[4].action()).toEqual('l1');
expect(gp.buttonActions[5].action()).toEqual('r1');
expect(gp.buttonActions[6].action()).toEqual('l2');
expect(gp.buttonActions[7].action()).toEqual('r2');
expect(gp.buttonActions[16].action()).toEqual('power');
});
test('on button power when no button power', () => {
const gp = gamepad.init(gamepads[1]);
gp.on('power', () => 'power');
});
test('on button outside of range', () => {
const gp = gamepad.init(gamepads[1]);
gp.on('button1234', () => 'event on incorrect button');
});
test('cycle check status', () => {
const gp = gamepad.init(gamepads[1]);
const mockGamepads = () => gamepads;
global.navigator.getGamepads = mockGamepads;
gp.checkStatus();
gamepads[1].buttons[0].pressed = false;
gamepads[1].axes[0] = 0.0;
gp.checkStatus();
});
// this should not happen
test('cycle check status (no axes)', () => {
const gp = gamepad.init(gamepads[2]);
gamepads[2].axes = null;
gamepads[2].buttons = null;
const mockGamepads = () => gamepads;
global.navigator.getGamepads = mockGamepads;
gp.checkStatus();
global.navigator.getGamepads = null;
global.navigator.webkitGetGamepads = mockGamepads;
gp.checkStatus();
});
});
================================================
FILE: tests/mock.gamepads.js
================================================
const gamepads = [
{
axes: [0, 0, 0, 0],
buttons: [
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 }
],
connected: true,
id: '17-button gamepad mockup (STANDARD Vendor: Alvaro Product: Montoro)',
index: 0,
mapping: 'standard',
timestamp: 5200,
vibrationActuator: {
playEffect: (type, obj) => `${type} - ${obj.duration}`
}
},
{
axes: [1.0, 1.0],
buttons: [
{ pressed: true, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: true, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 }
],
connected: true,
id: '10-button gamepad mockup (Vendor: Alvaro Product: Montoro10)',
index: 1,
mapping: '',
timestamp: 5200,
vibrationActuator: null
},
{
axes: [],
buttons: [],
connected: true,
id: 'No-button No-axe gamepad mockup (Vendor: Alvaro Product: Montoro10)',
index: 2,
mapping: '',
timestamp: 5200,
vibrationActuator: { playEffect: 'error' }
}
];
const gamepadsFirefox = [
{
axes: [0, 0, 0, 0, 0],
buttons: [
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 }
],
connected: true,
id: '17-button gamepad mockup (STANDARD Vendor: Alvaro Product: Montoro)',
index: 0,
mapping: 'standard',
timestamp: 5200,
hapticActuators: {
pulse: (intensity, duration) => `vibrate at ${intensity} for ${duration}ms`
}
},
{
axes: [0, 0, 0],
buttons: [
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 }
],
connected: true,
id: '10-button gamepad mockup (Vendor: Alvaro Product: Montoro10)',
index: 1,
mapping: '',
timestamp: 5200,
hapticActuators: null
},
{
axes: [0, 0, 0],
buttons: [
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 },
{ pressed: false, touched: false, value: 0 }
],
connected: true,
id: '10-button gamepad mockup (Vendor: Alvaro Product: Montoro10)',
index: 2,
mapping: '',
timestamp: 5200,
hapticActuators: [
{
pulse: (intensity, duration) => `vibrate at ${intensity} for ${duration}ms`
},
null
]
},
{
axes: [0, 0, 0],
buttons: [{ pressed: false, touched: false, value: 0 }],
connected: true,
id: '1-button gamepad mockup (Vendor: Alvaro Product: Montoro10)',
index: 3,
mapping: '',
timestamp: 5200,
hapticActuators: [
{
pulse: 'error'
},
null
]
}
];
export { gamepads, gamepadsFirefox };
================================================
FILE: tests/tools.test.js
================================================
import { isGamepadSupported, log, error, emptyEvents } from '../src/tools';
import { gamepads } from './mock.gamepads';
describe('log', () => {
it('log works without parameters', () => {
log('Sentence test');
});
it('error log works', () => {
log('Error sentence test', 'error');
});
it('log works with different parameters', () => {
log('Info sentence test', 'info');
log('Log sentence test', 'log');
});
it('error log works', () => {
error('Error sentence test');
});
it('branch test console and error', () => {
const auxconsole = console;
console = null;
log('Console sentence test');
log('Error sentence test', 'error');
log('Info sentence test', 'info');
log('Info sentence test', 'log');
error('Error sentence test');
console = auxconsole;
});
});
describe('isGamepadSupported', () => {
it('check if gamepad is supported', () => {
const aux = isGamepadSupported();
expect(aux).toEqual(false);
});
it('check if gamepad is supported', () => {
const mockGamepads = () => gamepads;
global.navigator.getGamepads = mockGamepads;
const aux1 = isGamepadSupported();
expect(aux1).toEqual(true);
global.navigator.getGamepads = 'error';
const aux2 = isGamepadSupported();
expect(aux2).toEqual(false);
});
it('check if gamepad is supported (webkit)', () => {
const webkitGetGamepads = () => gamepads;
global.navigator.webkitGetGamepads = webkitGetGamepads;
const aux = isGamepadSupported();
expect(aux).toEqual(true);
global.navigator.webkitGetGamepads = 'error';
const aux2 = isGamepadSupported();
expect(aux2).toEqual(false);
});
});
describe('emptyEvents', () => {
it('check all events return to be an object', () => {
const aux = emptyEvents();
expect(typeof aux).toEqual('object');
expect(typeof aux.action).toEqual('function');
expect(typeof aux.before).toEqual('function');
expect(typeof aux.after).toEqual('function');
});
});
gitextract_1uoi5g71/
├── .babelrc
├── .editorconfig
├── .gitignore
├── .prettierrc
├── .travis.yml
├── changelog.md
├── dist/
│ └── gamecontroller.js
├── examples/
│ ├── example-0-connectivity.html
│ ├── example-3-buttons-and-joysticks.html
│ ├── example-4-snes-controller.html
│ ├── example-5-alvanoid.html
│ ├── example-6-multiplayer.html
│ ├── example-7-joystick-threshold.html
│ ├── example-8-vibration.html
│ ├── example-9-before-and-after.html
│ └── examples.css
├── license.md
├── package.json
├── readme.md
├── src/
│ ├── constants.js
│ ├── gamecontrol.js
│ ├── gamepad.js
│ ├── index.js
│ └── tools.js
└── tests/
├── gamecontrol.test.js
├── gamepad.test.js
├── mock.gamepads.js
└── tools.test.js
SYMBOL INDEX (3 symbols across 3 files)
FILE: dist/gamecontroller.js
function __webpack_require__ (line 69) | function __webpack_require__(moduleId) {
FILE: src/constants.js
constant MESSAGES (line 1) | const MESSAGES = {
FILE: tests/gamecontrol.test.js
function generateGamepads (line 5) | function generateGamepads() {
Condensed preview — 28 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (117K chars).
[
{
"path": ".babelrc",
"chars": 80,
"preview": "{\n \"env\": {\n \"test\": {\n \"presets\": [[\"@babel/preset-env\"]]\n }\n }\n}\n"
},
{
"path": ".editorconfig",
"chars": 301,
"preview": "# top-most EditorConfig file\nroot = true\n\n[*.md]\ntrim_trailing_whitespace = false\n\n[*.js]\ntrim_trailing_whitespace = tru"
},
{
"path": ".gitignore",
"chars": 39,
"preview": ".DS_Store\n.github\nnode_modules\ncoverage"
},
{
"path": ".prettierrc",
"chars": 288,
"preview": "{\n \"printWidth\": 100,\n \"singleQuote\": true,\n \"useTabs\": false,\n \"tabWidth\": 2,\n \"trailingComma\": \"none\",\n \"bracket"
},
{
"path": ".travis.yml",
"chars": 92,
"preview": "language: node_js\nnode_js:\n - 'node'\ndist: trusty\ncache:\n directories:\n - node_modules\n"
},
{
"path": "changelog.md",
"chars": 1866,
"preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n## [v1.4.4] - 2019-12-08\n\n### Updated"
},
{
"path": "dist/gamecontroller.js",
"chars": 20095,
"preview": "/*\n * ATTENTION: The \"eval\" devtool has been used (maybe by default in mode: \"development\").\n * This devtool is neither "
},
{
"path": "examples/example-0-connectivity.html",
"chars": 2619,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <title>GameControl Example: Connectivity</title>\n <link rel=\"stylesheet"
},
{
"path": "examples/example-3-buttons-and-joysticks.html",
"chars": 8129,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <title>GameControl Example: Buttons and Joysticks</title>\n <link rel=\"s"
},
{
"path": "examples/example-4-snes-controller.html",
"chars": 5944,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <title>GameControl Example: SNES controller</title>\n <link\n href=\""
},
{
"path": "examples/example-5-alvanoid.html",
"chars": 10304,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <title>GameControl Example: Alvanoid</title>\n <link\n href=\"https:/"
},
{
"path": "examples/example-6-multiplayer.html",
"chars": 5388,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <title>GameControl Example: Multiplayer (Pong)</title>\n <link\n hre"
},
{
"path": "examples/example-7-joystick-threshold.html",
"chars": 4356,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <title>GameControl Example: Joystick Sensitivity Threshold</title>\n <li"
},
{
"path": "examples/example-8-vibration.html",
"chars": 2756,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <title>GameControl Example: Vibration</title>\n <link rel=\"stylesheet\" h"
},
{
"path": "examples/example-9-before-and-after.html",
"chars": 2949,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <title>GameControl Example: Before and After Events</title>\n <link rel="
},
{
"path": "examples/examples.css",
"chars": 964,
"preview": "html,\nbody {\n background: #666;\n border: 0;\n display: flex;\n flex-direction: column;\n font-family: Arial, Verdana, "
},
{
"path": "license.md",
"chars": 1086,
"preview": "Copyright (c) 2019 - Alvaro Montoro (alvaromontoro@gmail.com)\n\nPermission is hereby granted, free of charge, to any pers"
},
{
"path": "package.json",
"chars": 1147,
"preview": "{\n \"name\": \"gamecontroller.js\",\n \"version\": \"1.5.0\",\n \"description\": \"A JavaScript library that lets you handle, conf"
},
{
"path": "readme.md",
"chars": 11771,
"preview": "# gameController.js\n\nA JavaScript library that lets you handle, configure, and use gamepad and controllers on a browser."
},
{
"path": "src/constants.js",
"chars": 380,
"preview": "const MESSAGES = {\n ON: 'Gamepad detected.',\n OFF: 'Gamepad disconnected.',\n INVALID_PROPERTY: 'Invalid property.',\n "
},
{
"path": "src/gamecontrol.js",
"chars": 3673,
"preview": "import { log, error, isGamepadSupported } from './tools';\nimport { MESSAGES } from './constants';\nimport gamepad from '."
},
{
"path": "src/gamepad.js",
"chars": 7042,
"preview": "import { error, emptyEvents } from './tools';\nimport { MESSAGES } from './constants';\n\nconst gamepad = {\n init: functio"
},
{
"path": "src/index.js",
"chars": 272,
"preview": "// This file is the entry point\nimport { error, isGamepadSupported } from './tools';\nimport { MESSAGES } from './constan"
},
{
"path": "src/tools.js",
"chars": 636,
"preview": "const log = (message, type = 'log') => {\n if (type === 'error') {\n if (console && typeof console.error === 'function"
},
{
"path": "tests/gamecontrol.test.js",
"chars": 4954,
"preview": "import gameControl from '../src/gamecontrol';\nimport gamepad from '../src/gamepad';\nimport { gamepads } from './mock.gam"
},
{
"path": "tests/gamepad.test.js",
"chars": 6257,
"preview": "import gamepad from '../src/gamepad';\nimport { gamepads, gamepadsFirefox } from './mock.gamepads';\n\ndescribe('gamepad', "
},
{
"path": "tests/mock.gamepads.js",
"chars": 5387,
"preview": "const gamepads = [\n {\n axes: [0, 0, 0, 0],\n buttons: [\n { pressed: false, touched: false, value: 0 },\n "
},
{
"path": "tests/tools.test.js",
"chars": 2012,
"preview": "import { isGamepadSupported, log, error, emptyEvents } from '../src/tools';\nimport { gamepads } from './mock.gamepads';\n"
}
]
About this extraction
This page contains the full source code of the alvaromontoro/gamecontroller.js GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 28 files (108.2 KB), approximately 30.8k tokens, and a symbol index with 3 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.