Last active
July 13, 2019 15:34
-
-
Save cuylerstuwe/98cd58cfcee694e3bbc08adc3d925080 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Event-Driven Web Controllers Library | |
| // Version: 1.6 | |
| // by Cuyler Stuwe (salembeats) | |
| // | |
| // Purpose: | |
| // | |
| // Simplify usage of responding to controller button change events, to utilize controllers as simple work productivity tools (where ease-of-use trumps performance). | |
| // | |
| // Instructions: | |
| // | |
| // Bind a window listener for the "vilrosButton" event, for example: | |
| // | |
| // window.addEventListener("controllerButton", e => { | |
| // ("Controller", e.detail.controllerIndex, "button", e.detail.button, "pressed?", e.detail.pressed); | |
| // }); | |
| // | |
| // Changelog: | |
| // 1.6 Generate starting button-press events from "gamepad connected" events, initialize library by default on include. | |
| // 1.51 Fixed missing comma that caused crash. | |
| // 1.5 Added controller nicknames. | |
| // 1.42 Added mappings for the buttons under the joysticks for the default Xbox mapping. | |
| // 1.41 Removed extraneous console log. | |
| // 1.4 Watching for analog axes as well as buttons. | |
| // 1.3 Looking at values rather than pressed state for comparisons, to generate streams of events that respect the full range of analog triggers. | |
| // 1.2 Extracted mappings so they can be initialized in a constructor. | |
| // 1.1 Genericized to respond well to an additional controller (Logitech F310). | |
| // 1.0 Initial version. Works with Vilros SNES controllers. | |
| const defaultControllerTypeNicknames = { | |
| "USB Gamepad (STANDARD GAMEPAD Vendor: 0079 Product: 0011)": "vilros snes", | |
| "Xbox 360 Controller (XInput STANDARD GAMEPAD)": "xinput" | |
| }; | |
| const defaultAllControllersIndexToButtonMaps = { | |
| "USB Gamepad (STANDARD GAMEPAD Vendor: 0079 Product: 0011)": { | |
| 0: "b", | |
| 1: "a", | |
| 2: "y", | |
| 3: "x", | |
| 4: "l", | |
| 5: "r", | |
| 8: "select", | |
| 9: "start", | |
| 12: "up", | |
| 13: "down", | |
| 14: "left", | |
| 15: "right" | |
| }, | |
| "Xbox 360 Controller (XInput STANDARD GAMEPAD)": { | |
| 0: "a", | |
| 1: "b", | |
| 2: "x", | |
| 3: "y", | |
| 4: "l", | |
| 5: "r", | |
| 6: "l2", | |
| 7: "r2", | |
| 8: "select", | |
| 9: "start", | |
| 10: "left joystick", | |
| 11: "right joystick", | |
| 12: "up", | |
| 13: "down", | |
| 14: "left", | |
| 15: "right" | |
| } | |
| }; | |
| const defaultAllControllersIndexToAxisMaps = { | |
| "Xbox 360 Controller (XInput STANDARD GAMEPAD)": { | |
| 0: "left x", | |
| 1: "left y", | |
| 2: "right x", | |
| 3: "right y" | |
| } | |
| }; | |
| function InitializeControllerEventLoop( | |
| allControllersIndexToButtonMap = defaultAllControllersIndexToButtonMaps, | |
| allControllersIndexToAxisMaps = defaultAllControllersIndexToAxisMaps, | |
| controllerTypeNicknames = defaultControllerTypeNicknames | |
| ) { | |
| /* // UNUSED -- FOR INVERTING INDEX=>BUTTON-NAME MAPS (TO CREATE BUTTON-NAME=>INDEX MAPS). | |
| const invertKeyValuePairs = obj => ( | |
| Object.keys(obj) | |
| .map(key => ({ | |
| [obj[key]]: key | |
| })) | |
| .reduce((acc, pair) => ({ | |
| ...acc, | |
| ...pair | |
| }), {}) | |
| ); | |
| const allControllersButtonToIndexMap = ( | |
| Object.keys(allControllersIndexToButtonMap) | |
| .map(controllerTypeName => ({ | |
| [controllerTypeName]: invertKeyValuePairs(allControllersIndexToButtonMap[controllerTypeName]) | |
| })) | |
| .reduce((acc, obj) => ({ | |
| ...acc, | |
| ...obj | |
| }), {}) | |
| ); | |
| */ | |
| const allControllersIndexToAxisGroupArrayMaps = ( | |
| Object.keys(allControllersIndexToAxisMaps) | |
| .map(controllerTypeName => { | |
| const indexToAxisMap = allControllersIndexToAxisMaps[controllerTypeName]; | |
| const controllerIndexToAxisGroupMap = Object.keys(indexToAxisMap).reduce((acc, idxKey) => { | |
| const keyWords = indexToAxisMap[idxKey].split(" "); | |
| if(keyWords.includes("left")) { | |
| acc.left.push(+idxKey); | |
| } | |
| else if(keyWords.includes("right")) { | |
| acc.right.push(+idxKey); | |
| } | |
| return acc; | |
| }, {left: [], right: []}); | |
| return { | |
| [controllerTypeName]: controllerIndexToAxisGroupMap | |
| }; | |
| }) | |
| .reduce((acc, obj) => ({ | |
| ...acc, | |
| ...obj | |
| }), {}) | |
| ); | |
| const allControllersIndexToAxisDirectionMaps = ( | |
| Object.keys(allControllersIndexToAxisMaps) | |
| .map(controllerTypeName => { | |
| const indexToAxisMap = allControllersIndexToAxisMaps[controllerTypeName]; | |
| const xyMap = Object.keys(indexToAxisMap).reduce((acc, idxKey) => { | |
| const keyWords = indexToAxisMap[idxKey].split(" "); | |
| if(keyWords.includes("x")) { | |
| acc[idxKey] = "x"; | |
| } | |
| else if(keyWords.includes("y")) { | |
| acc[idxKey] = "y"; | |
| } | |
| return acc; | |
| }, {}); | |
| return { | |
| [controllerTypeName]: xyMap | |
| }; | |
| }) | |
| .reduce((acc, obj) => ({ | |
| ...acc, | |
| ...obj | |
| }), {}) | |
| ); | |
| let formerPolledStatus; | |
| let latestPolledStatus; | |
| const frameCycle = timestamp => { | |
| formerPolledStatus = {...latestPolledStatus}; | |
| latestPolledStatus = [...navigator.getGamepads()]; | |
| latestPolledStatus.forEach((controllerStatus, controllerIdx) => { | |
| const formerControllerStatus = formerPolledStatus[controllerIdx]; | |
| if(!(controllerStatus && formerControllerStatus)) { return; } | |
| controllerStatus.buttons.forEach((button, buttonIdx) => { | |
| const formerButtonValue = formerControllerStatus.buttons[buttonIdx].value; | |
| if(button.value !== formerButtonValue) { | |
| const controllerIndexToButtonMap = allControllersIndexToButtonMap[controllerStatus.id]; | |
| window.dispatchEvent(new CustomEvent("controllerbutton", { | |
| detail: { | |
| controllerTypeNickname: controllerTypeNicknames[controllerStatus.id], | |
| controllerIndex: controllerIdx, | |
| buttonIndex: buttonIdx, | |
| button: controllerIndexToButtonMap[buttonIdx], | |
| pressed: button.pressed, | |
| value: button.value | |
| } | |
| })); | |
| } | |
| }); | |
| const controllerIndexToAxisGroupArrayMap = allControllersIndexToAxisGroupArrayMaps[controllerStatus.id]; | |
| Object.keys(controllerIndexToAxisGroupArrayMap || {}).forEach(controllerIndexToAxisGroupName => { | |
| const axisIndexGroup = controllerIndexToAxisGroupArrayMap[controllerIndexToAxisGroupName]; | |
| const isAxisGroupChanged = axisIndexGroup.some(axisIndex => { | |
| const formerAxisValue = formerControllerStatus.axes[axisIndex]; | |
| const currentAxisValue = controllerStatus.axes[axisIndex]; | |
| return (currentAxisValue !== formerAxisValue); | |
| }); | |
| if(isAxisGroupChanged) { | |
| const axisGroupNewXyPositions = axisIndexGroup.reduce((acc, idxVal) => { | |
| const indexToAxisDirectionMap = allControllersIndexToAxisDirectionMaps[controllerStatus.id]; | |
| return { | |
| ...acc, | |
| [indexToAxisDirectionMap[idxVal]]: controllerStatus.axes[idxVal] | |
| }; | |
| }, {}); | |
| window.dispatchEvent(new CustomEvent("controlleraxis", { | |
| detail: { | |
| controllerTypeNickname: controllerTypeNicknames[controllerStatus.id], | |
| controllerIndex: controllerIdx, | |
| joystick: controllerIndexToAxisGroupName, | |
| x: axisGroupNewXyPositions.x, | |
| y: axisGroupNewXyPositions.y | |
| } | |
| })); | |
| } | |
| }); | |
| }); | |
| window.requestAnimationFrame(frameCycle); | |
| }; | |
| frameCycle(); | |
| window.addEventListener("gamepadconnected", (event) => { | |
| for(const [buttonIdx, button] of event.gamepad.buttons.entries()) { | |
| if(button.pressed) { | |
| window.dispatchEvent(new CustomEvent("controllerbutton", { | |
| detail: { | |
| controllerTypeNickname: controllerTypeNicknames[event.id], | |
| controllerIndex: event.gamepad.index, | |
| buttonIndex: buttonIdx, | |
| button: allControllersIndexToButtonMap[event.gamepad.id][buttonIdx], | |
| pressed: button.pressed, | |
| value: button.value | |
| } | |
| })); | |
| } | |
| } | |
| }); | |
| } | |
| InitializeControllerEventLoop(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment