diff --git a/src/runtime-three/player-input-bindings.ts b/src/runtime-three/player-input-bindings.ts new file mode 100644 index 00000000..e2c5d283 --- /dev/null +++ b/src/runtime-three/player-input-bindings.ts @@ -0,0 +1,128 @@ +import type { + PlayerStartGamepadBinding, + PlayerStartInputBindings +} from "../entities/entity-instances"; + +const GAMEPAD_AXIS_DEADZONE = 0.18; + +export interface PlayerStartMovementActionState { + moveForward: number; + moveBackward: number; + moveLeft: number; + moveRight: number; +} + +function clampUnitInterval(value: number): number { + return Math.max(0, Math.min(1, value)); +} + +function readGamepadButtonStrength(button: GamepadButton | undefined): number { + if (button === undefined) { + return 0; + } + + return clampUnitInterval(button.pressed ? Math.max(button.value, 1) : button.value); +} + +function readPositiveAxisStrength(value: number | undefined): number { + if (value === undefined || !Number.isFinite(value) || value <= 0) { + return 0; + } + + if (value <= GAMEPAD_AXIS_DEADZONE) { + return 0; + } + + return clampUnitInterval( + (value - GAMEPAD_AXIS_DEADZONE) / (1 - GAMEPAD_AXIS_DEADZONE) + ); +} + +function readNegativeAxisStrength(value: number | undefined): number { + return readPositiveAxisStrength( + value === undefined || !Number.isFinite(value) ? value : -value + ); +} + +function readSingleGamepadBinding( + gamepad: Gamepad, + binding: PlayerStartGamepadBinding +): number { + switch (binding) { + case "leftStickUp": + return readNegativeAxisStrength(gamepad.axes[1]); + case "leftStickDown": + return readPositiveAxisStrength(gamepad.axes[1]); + case "leftStickLeft": + return readNegativeAxisStrength(gamepad.axes[0]); + case "leftStickRight": + return readPositiveAxisStrength(gamepad.axes[0]); + case "dpadUp": + return readGamepadButtonStrength(gamepad.buttons[12]); + case "dpadDown": + return readGamepadButtonStrength(gamepad.buttons[13]); + case "dpadLeft": + return readGamepadButtonStrength(gamepad.buttons[14]); + case "dpadRight": + return readGamepadButtonStrength(gamepad.buttons[15]); + } +} + +function readGamepadBindingStrength( + gamepads: ArrayLike | null | undefined, + binding: PlayerStartGamepadBinding +): number { + if (gamepads === undefined || gamepads === null) { + return 0; + } + + let strength = 0; + + for (let index = 0; index < gamepads.length; index += 1) { + const gamepad = gamepads[index]; + + if (gamepad === null || gamepad === undefined || gamepad.connected === false) { + continue; + } + + strength = Math.max(strength, readSingleGamepadBinding(gamepad, binding)); + } + + return strength; +} + +export function getAvailableGamepads(): ArrayLike | undefined { + if ( + typeof navigator === "undefined" || + typeof navigator.getGamepads !== "function" + ) { + return undefined; + } + + return navigator.getGamepads(); +} + +export function resolvePlayerStartMovementActions( + pressedKeys: ReadonlySet, + bindings: PlayerStartInputBindings, + gamepads: ArrayLike | null | undefined = getAvailableGamepads() +): PlayerStartMovementActionState { + return { + moveForward: Math.max( + pressedKeys.has(bindings.keyboard.moveForward) ? 1 : 0, + readGamepadBindingStrength(gamepads, bindings.gamepad.moveForward) + ), + moveBackward: Math.max( + pressedKeys.has(bindings.keyboard.moveBackward) ? 1 : 0, + readGamepadBindingStrength(gamepads, bindings.gamepad.moveBackward) + ), + moveLeft: Math.max( + pressedKeys.has(bindings.keyboard.moveLeft) ? 1 : 0, + readGamepadBindingStrength(gamepads, bindings.gamepad.moveLeft) + ), + moveRight: Math.max( + pressedKeys.has(bindings.keyboard.moveRight) ? 1 : 0, + readGamepadBindingStrength(gamepads, bindings.gamepad.moveRight) + ) + }; +} \ No newline at end of file