From 22f2461cc7882b12967498253ee0ecd0cdd39f35 Mon Sep 17 00:00:00 2001 From: Spencer Vaughn Date: Mon, 4 Mar 2024 18:23:19 -0600 Subject: [PATCH] Update controller inputs with useRef --- src/components/useGamepad.jsx | 140 ++++++++++++++++++++++++++-------- 1 file changed, 107 insertions(+), 33 deletions(-) diff --git a/src/components/useGamepad.jsx b/src/components/useGamepad.jsx index 2f3a027..77d985b 100644 --- a/src/components/useGamepad.jsx +++ b/src/components/useGamepad.jsx @@ -1,42 +1,115 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; + +const GamepadButtons = { + A: 0, + B: 1, + X: 2, + Y: 3, + LB: 4, + RB: 5, + LT: 6, + RT: 7, + start: 8, + select: 9, + L3: 10, + R3: 11, + up: 12, + down: 13, + left: 14, + right: 15, + count: 16 +} + +const GamepadAxes = { + leftjoy_x: 0, // Steer left/right + leftjoy_y: 1, + rightjoy_x: 2, + rightjoy_y: 3 +} + +const defaultGamepadInfo = { + connected: false, + buttonA: false, + buttonB: false, + buttonX: false, + buttonY: false, + joystick: [0, 0], + joystickRight: [0, 0], + RB: false, + LB: false, + RT: false, + LT: false, + start: false, + select: false, + up: false, + down: false, + left: false, + right: false +}; + +const isButtonActive = (activeButtons, button) => Boolean(activeButtons & (1 << button)); export const useGamepad = () => { - const [gamepadInfo, setGamepadInfo] = useState({ connected: false, buttonA: false, buttonB :false, buttonX: false, buttonY:false, joystick: [0, 0], joystickRight : [0,0], RB: false, LB: false, RT: false, LT: false, start: false, select: false, up: false, down: false, left: false, right: false}); + const gamepadInfo = useRef(defaultGamepadInfo); + const [, forceUpdate] = useState(); + const activeButtonsRef = useRef(0); // Function to update gamepad state const updateGamepadState = () => { - const gamepads = navigator.getGamepads ? navigator.getGamepads() : []; - const gamepad = gamepads[0]; // Assuming the first gamepad + const [gamepad] = navigator.getGamepads ? navigator.getGamepads() : []; // Get the first gamepad + + // newly read active button bitmask + let newActiveButtons = 0; if (gamepad) { - const newGamepadInfo = { - connected: true, - buttonA: gamepad.buttons[0].pressed, - buttonB: gamepad.buttons[1].pressed, - buttonX: gamepad.buttons[2].pressed, - buttonY: gamepad.buttons[3].pressed, - joystickRight: [gamepad.axes[2], gamepad.axes[3]], - LT: gamepad.buttons[6].pressed, - RT: gamepad.buttons[7].pressed, - LB: gamepad.buttons[4].pressed, - RB: gamepad.buttons[5].pressed, + // Buttons + for (let i = GamepadButtons.A; i < gamepad.buttons.length; i++) { + // Set the bit for the button if it's pressed + newActiveButtons |= (gamepad.buttons[i].pressed << i); + } - start: gamepad.buttons[9].pressed, - select: gamepad.buttons[8].pressed, - up: gamepad.buttons[12].pressed, - down: gamepad.buttons[13].pressed, - left: gamepad.buttons[14].pressed, - right: gamepad.buttons[15].pressed, - joystick: [gamepad.axes[0], gamepad.axes[1]] - }; + const hasJoysticksMoved = + gamepad.axes[GamepadAxes.leftjoy_x] !== gamepadInfo.current.joystick[0] || + gamepad.axes[GamepadAxes.leftjoy_y] !== gamepadInfo.current.joystick[1] || + gamepad.axes[GamepadAxes.rightjoy_x] !== gamepadInfo.current.joystickRight[0] || + gamepad.axes[GamepadAxes.rightjoy_y] !== gamepadInfo.current.joystickRight[1]; - // Update state only if there's a change - if (JSON.stringify(newGamepadInfo) !== JSON.stringify(gamepadInfo)) { - setGamepadInfo(newGamepadInfo); + // Update state only if there's a button change or joystick change + if (newActiveButtons !== activeButtonsRef.current || hasJoysticksMoved) { + // Update gamepad info for the current gamepad + gamepadInfo.current = { + connected: true, + buttonA: isButtonActive(newActiveButtons, GamepadButtons.A), + buttonB: isButtonActive(newActiveButtons, GamepadButtons.B), + buttonX: isButtonActive(newActiveButtons, GamepadButtons.X), + buttonY: isButtonActive(newActiveButtons, GamepadButtons.Y), + LB: isButtonActive(newActiveButtons, GamepadButtons.LB), + RB: isButtonActive(newActiveButtons, GamepadButtons.RB), + LT: isButtonActive(newActiveButtons, GamepadButtons.LT), + RT: isButtonActive(newActiveButtons, GamepadButtons.RT), + L3: isButtonActive(newActiveButtons, GamepadButtons.L3), + R3: isButtonActive(newActiveButtons, GamepadButtons.R3), + start: isButtonActive(newActiveButtons, GamepadButtons.start), + select: isButtonActive(newActiveButtons, GamepadButtons.select), + up: isButtonActive(newActiveButtons, GamepadButtons.up), + down: isButtonActive(newActiveButtons, GamepadButtons.down), + left: isButtonActive(newActiveButtons, GamepadButtons.left), + right: isButtonActive(newActiveButtons, GamepadButtons.right), + joystick: [gamepad.axes[0], gamepad.axes[1]], + joystickRight: [gamepad.axes[2], gamepad.axes[3]] + }; + + // Update the active buttons ref for the next iteration + activeButtonsRef.current = newActiveButtons; + + // useGamepad will only cause re-render when a value is changed + forceUpdate({}); } } else { + // If the gamepad has disconnected, reset the gamepad info if (gamepadInfo.connected) { - setGamepadInfo({ connected: false, buttonA: false, buttonB :false, buttonX: false, buttonY:false, joystick: [0, 0], joystickRight : [0,0], RB: false, LB: false, RT: false, LT: false, start: false, select: false, up: false, down: false, left: false, right: false}); + gamepadInfo.current = defaultGamepadInfo; + forceUpdate({}); } } }; @@ -46,23 +119,24 @@ export const useGamepad = () => { console.log('Gamepad connected!'); updateGamepadState(); }; - + const gamepadDisconnected = () => { console.log('Gamepad disconnected!'); - setGamepadInfo({ connected : false, buttonA: false, buttonB :false, buttonX: false, buttonY:false, joystick: [0, 0], joystickRight : [0,0], RB: false, LB: false, RT: false, LT: false, start: false, select: false, up: false, down: false, left: false, right: false}); + gamepadInfo.current = defaultGamepadInfo; }; window.addEventListener('gamepadconnected', gamepadConnected); window.addEventListener('gamepaddisconnected', gamepadDisconnected); - const interval = setInterval(updateGamepadState, 100); + // Read gamepad state every frame + const interval = setInterval(updateGamepadState, 1000 / 60); return () => { window.removeEventListener('gamepadconnected', gamepadConnected); window.removeEventListener('gamepaddisconnected', gamepadDisconnected); clearInterval(interval); }; - }, [gamepadInfo]); + }, []); - return gamepadInfo; -}; \ No newline at end of file + return gamepadInfo.current; +};