Spaces:
Runtime error
Runtime error
| // Some parts of this scripts are based on or designed to be compatible-ish with: | |
| // https://arpruss.github.io/gamepad.js (MIT Licensed) | |
| const ExtensionApi = require("../../util/custom-ext-api-to-core.js"); | |
| const Scratch = new ExtensionApi(true); | |
| const AXIS_DEADZONE = 0.1; | |
| const BUTTON_DEADZONE = 0.05; | |
| /** | |
| * @param {number|'any'} index 1-indexed index | |
| * @returns {Gamepad[]} | |
| */ | |
| const getGamepads = (index) => { | |
| if (index === 'any') { | |
| return navigator.getGamepads().filter(i => i); | |
| } | |
| const gamepad = navigator.getGamepads()[index - 1]; | |
| if (gamepad) { | |
| return [gamepad]; | |
| } | |
| return []; | |
| }; | |
| /** | |
| * @param {Gamepad} gamepad | |
| * @param {number|'any'} buttonIndex 1-indexed index | |
| * @returns {boolean} false if button does not exist | |
| */ | |
| const isButtonPressed = (gamepad, buttonIndex) => { | |
| if (buttonIndex === 'any') { | |
| return gamepad.buttons.some(i => i.pressed); | |
| } | |
| const button = gamepad.buttons[buttonIndex - 1]; | |
| if (!button) { | |
| return false; | |
| } | |
| return button.pressed; | |
| }; | |
| /** | |
| * @param {Gamepad} gamepad | |
| * @param {number} buttonIndex 1-indexed index | |
| * @returns {number} 0 if button does not exist | |
| */ | |
| const getButtonValue = (gamepad, buttonIndex) => { | |
| const button = gamepad.buttons[buttonIndex - 1]; | |
| if (!button) { | |
| return 0; | |
| } | |
| const value = button.value; | |
| if (value < BUTTON_DEADZONE) { | |
| return 0; | |
| } | |
| return value; | |
| }; | |
| /** | |
| * @param {Gamepad} gamepad | |
| * @param {number} axisIndex 1-indexed index | |
| * @returns {number} 0 if axis does not exist | |
| */ | |
| const getAxisValue = (gamepad, axisIndex) => { | |
| const axisValue = gamepad.axes[axisIndex - 1]; | |
| if (typeof axisValue !== 'number') { | |
| return 0; | |
| } | |
| if (Math.abs(axisValue) < AXIS_DEADZONE) { | |
| return 0; | |
| } | |
| return axisValue; | |
| }; | |
| class GamepadExtension { | |
| getInfo() { | |
| return { | |
| id: 'Gamepad', | |
| name: 'Gamepad', | |
| blocks: [ | |
| { | |
| opcode: 'gamepadConnected', | |
| blockType: Scratch.BlockType.BOOLEAN, | |
| text: 'is gamepad [pad] connected?', | |
| arguments: { | |
| pad: { | |
| type: Scratch.ArgumentType.NUMBER, | |
| defaultValue: '1', | |
| menu: 'padMenu' | |
| } | |
| } | |
| }, | |
| { | |
| opcode: 'buttonDown', | |
| blockType: Scratch.BlockType.BOOLEAN, | |
| text: 'button [b] on pad [i] pressed?', | |
| arguments: { | |
| b: { | |
| type: Scratch.ArgumentType.NUMBER, | |
| defaultValue: '1', | |
| menu: 'buttonMenu' | |
| }, | |
| i: { | |
| type: Scratch.ArgumentType.NUMBER, | |
| defaultValue: '1', | |
| menu: 'padMenu' | |
| } | |
| } | |
| }, | |
| { | |
| opcode: 'buttonValue', | |
| blockType: Scratch.BlockType.REPORTER, | |
| text: 'value of button [b] on pad [i]', | |
| arguments: { | |
| b: { | |
| type: Scratch.ArgumentType.NUMBER, | |
| defaultValue: '1', | |
| menu: 'buttonMenu' | |
| }, | |
| i: { | |
| type: Scratch.ArgumentType.NUMBER, | |
| defaultValue: '1', | |
| menu: 'padMenu' | |
| } | |
| } | |
| }, | |
| { | |
| opcode: 'axisValue', | |
| blockType: Scratch.BlockType.REPORTER, | |
| text: 'value of axis [b] on pad [i]', | |
| arguments: { | |
| b: { | |
| type: Scratch.ArgumentType.NUMBER, | |
| defaultValue: '1', | |
| menu: 'axisMenu' | |
| }, | |
| i: { | |
| type: Scratch.ArgumentType.NUMBER, | |
| defaultValue: '1', | |
| menu: 'padMenu' | |
| }, | |
| }, | |
| }, | |
| '---', | |
| { | |
| opcode: 'axisDirection', | |
| blockType: Scratch.BlockType.REPORTER, | |
| text: 'direction of axes [axis] on pad [pad]', | |
| arguments: { | |
| axis: { | |
| type: Scratch.ArgumentType.NUMBER, | |
| defaultValue: '1', | |
| menu: 'axesGroupMenu' | |
| }, | |
| pad: { | |
| type: Scratch.ArgumentType.NUMBER, | |
| defaultValue: '1', | |
| menu: 'padMenu' | |
| } | |
| } | |
| }, | |
| { | |
| opcode: 'axisMagnitude', | |
| blockType: Scratch.BlockType.REPORTER, | |
| text: 'magnitude of axes [axis] on pad [pad]', | |
| arguments: { | |
| axis: { | |
| type: Scratch.ArgumentType.NUMBER, | |
| defaultValue: '1', | |
| menu: 'axesGroupMenu' | |
| }, | |
| pad: { | |
| type: Scratch.ArgumentType.NUMBER, | |
| defaultValue: '1', | |
| menu: 'padMenu' | |
| } | |
| } | |
| }, | |
| /* | |
| { | |
| opcode: 'buttonPressedReleased', | |
| blockType: Scratch.BlockType.HAT, | |
| text: 'button [b] [pr] of pad [i]', | |
| arguments: { | |
| b: { | |
| type: Scratch.ArgumentType.NUMBER, | |
| defaultValue: '1' | |
| }, | |
| pr: { | |
| type: Scratch.ArgumentType.NUMBER, | |
| defaultValue: '1', | |
| menu: 'pressReleaseMenu' | |
| }, | |
| i: { | |
| type: Scratch.ArgumentType.NUMBER, | |
| defaultValue: '1', | |
| menu: 'padMenu' | |
| }, | |
| }, | |
| }, | |
| { | |
| opcode: 'axisMoved', | |
| blockType: Scratch.BlockType.HAT, | |
| text: 'axis [b] of pad [i] moved', | |
| arguments: { | |
| b: { | |
| type: Scratch.ArgumentType.NUMBER, | |
| defaultValue: '1' | |
| }, | |
| i: { | |
| type: Scratch.ArgumentType.NUMBER, | |
| defaultValue: '1', | |
| menu: 'padMenu' | |
| }, | |
| }, | |
| }, | |
| */ | |
| '---', | |
| { | |
| opcode: 'rumble', | |
| blockType: Scratch.BlockType.COMMAND, | |
| text: 'rumble strong [s] and weak [w] for [t] sec. on pad [i]', | |
| arguments: { | |
| s: { | |
| type: Scratch.ArgumentType.NUMBER, | |
| defaultValue: '0.25' | |
| }, | |
| w: { | |
| type: Scratch.ArgumentType.NUMBER, | |
| defaultValue: '0.5' | |
| }, | |
| t: { | |
| type: Scratch.ArgumentType.NUMBER, | |
| defaultValue: '0.25' | |
| }, | |
| i: { | |
| type: Scratch.ArgumentType.NUMBER, | |
| defaultValue: '1', | |
| menu: 'padMenu' | |
| }, | |
| }, | |
| }, | |
| ], | |
| menus: { | |
| padMenu: { | |
| acceptReporters: true, | |
| items: [ | |
| { | |
| text: 'any', | |
| value: 'any' | |
| }, | |
| { | |
| text: '1', | |
| value: '1' | |
| }, | |
| { | |
| text: '2', | |
| value: '2' | |
| }, | |
| { | |
| text: '3', | |
| value: '3' | |
| }, | |
| { | |
| text: '4', | |
| value: '4' | |
| } | |
| ], | |
| }, | |
| buttonMenu: { | |
| acceptReporters: true, | |
| items: [ | |
| // Based on an Xbox controller | |
| { | |
| text: 'any', | |
| value: 'any' | |
| }, | |
| { | |
| text: 'A (1)', | |
| value: '1' | |
| }, | |
| { | |
| text: 'B (2)', | |
| value: '2' | |
| }, | |
| { | |
| text: 'X (3)', | |
| value: '3' | |
| }, | |
| { | |
| text: 'Y (4)', | |
| value: '4' | |
| }, | |
| { | |
| text: 'Left bumper (5)', | |
| value: '5' | |
| }, | |
| { | |
| text: 'Right bumper (6)', | |
| value: '6' | |
| }, | |
| { | |
| text: 'Left trigger (7)', | |
| value: '7' | |
| }, | |
| { | |
| text: 'Right trigger (8)', | |
| value: '8' | |
| }, | |
| { | |
| text: 'Select/View (9)', | |
| value: '9' | |
| }, | |
| { | |
| text: 'Start/Menu (10)', | |
| value: '10' | |
| }, | |
| { | |
| text: 'Left stick (11)', | |
| value: '11' | |
| }, | |
| { | |
| text: 'Right stick (12)', | |
| value: '12' | |
| }, | |
| { | |
| text: 'D-pad up (13)', | |
| value: '13' | |
| }, | |
| { | |
| text: 'D-pad down (14)', | |
| value: '14' | |
| }, | |
| { | |
| text: 'D-pad left (15)', | |
| value: '15' | |
| }, | |
| { | |
| text: 'D-pad right (16)', | |
| value: '16' | |
| }, | |
| ] | |
| }, | |
| axisMenu: { | |
| acceptReporters: true, | |
| items: [ | |
| // Based on an Xbox controller | |
| { | |
| text: 'Left stick horizontal (1)', | |
| value: '1' | |
| }, | |
| { | |
| text: 'Left stick vertical (2)', | |
| value: '2' | |
| }, | |
| { | |
| text: 'Right stick horizontal (3)', | |
| value: '3' | |
| }, | |
| { | |
| text: 'Right stick vertical (4)', | |
| value: '4' | |
| } | |
| ] | |
| }, | |
| axesGroupMenu: { | |
| acceptReporters: true, | |
| items: [ | |
| // Based on an Xbox controller | |
| { | |
| text: 'Left stick (1 & 2)', | |
| value: '1' | |
| }, | |
| { | |
| text: 'Right stick (3 & 4)', | |
| value: '3' | |
| } | |
| ] | |
| }, | |
| /* | |
| pressReleaseMenu: [ | |
| { | |
| text: 'press', | |
| value: 1 | |
| }, | |
| { | |
| text: 'release', | |
| value: 0 | |
| } | |
| ], | |
| */ | |
| } | |
| }; | |
| } | |
| gamepadConnected ({pad}) { | |
| return getGamepads(pad).length > 0; | |
| } | |
| buttonDown ({b, i}) { | |
| for (const gamepad of getGamepads(i)) { | |
| if (isButtonPressed(gamepad, b)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| buttonValue ({b, i}) { | |
| let greatestButton = 0; | |
| for (const gamepad of getGamepads(i)) { | |
| const value = getButtonValue(gamepad, b); | |
| if (value > greatestButton) { | |
| greatestButton = value; | |
| } | |
| } | |
| return greatestButton; | |
| } | |
| axisValue ({b, i}) { | |
| let greatestAxis = 0; | |
| for (const gamepad of getGamepads(i)) { | |
| const axis = getAxisValue(gamepad, b); | |
| if (Math.abs(axis) > Math.abs(greatestAxis)) { | |
| greatestAxis = axis; | |
| } | |
| } | |
| return greatestAxis; | |
| } | |
| axisDirection ({axis, pad}) { | |
| let greatestMagnitude = 0; | |
| let direction = 90; | |
| for (const gamepad of getGamepads(pad)) { | |
| const horizontalAxis = getAxisValue(gamepad, axis); | |
| const verticalAxis = getAxisValue(gamepad, +axis + 1); | |
| const magnitude = Math.sqrt(horizontalAxis ** 2 + verticalAxis ** 2); | |
| if (magnitude > greatestMagnitude) { | |
| greatestMagnitude = magnitude; | |
| direction = Math.atan2(verticalAxis, horizontalAxis) * 180 / Math.PI + 90; | |
| if (direction < 0) { | |
| direction += 360; | |
| } | |
| } | |
| } | |
| return direction; | |
| } | |
| axisMagnitude ({axis, pad}) { | |
| let greatestMagnitude = 0; | |
| for (const gamepad of getGamepads(pad)) { | |
| const horizontalAxis = getAxisValue(gamepad, axis); | |
| const verticalAxis = getAxisValue(gamepad, +axis + 1); | |
| const magnitude = Math.sqrt(horizontalAxis ** 2 + verticalAxis ** 2); | |
| if (magnitude > greatestMagnitude) { | |
| greatestMagnitude = magnitude; | |
| } | |
| } | |
| return greatestMagnitude; | |
| } | |
| rumble ({s, w, t, i}) { | |
| const gamepads = getGamepads(i); | |
| for (const gamepad of gamepads) { | |
| // @ts-ignore | |
| if (gamepad.vibrationActuator) { | |
| // @ts-ignore | |
| gamepad.vibrationActuator.playEffect('dual-rumble', { | |
| startDelay: 0, | |
| duration: t * 1000, | |
| weakMagnitude: w, | |
| strongMagnitude: s | |
| }); | |
| } | |
| } | |
| } | |
| } | |
| module.exports = GamepadExtension; |