diff --git a/package-lock.json b/package-lock.json index 9a53cfb..0a91948 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "react-dom": "^18.2.0", "react-gamepad": "^1.0.3", "three": "^0.160.1", + "three-mesh-bvh": "^0.7.0", "zustand": "^4.5.0" }, "devDependencies": { diff --git a/package.json b/package.json index 110a1e7..0aae4ba 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "react-dom": "^18.2.0", "react-gamepad": "^1.0.3", "three": "^0.160.1", + "three-mesh-bvh": "^0.7.0", "zustand": "^4.5.0" }, "devDependencies": { diff --git a/public/models/items/banana_peel_mario_kart-transformed.glb b/public/models/items/banana_peel_mario_kart-transformed.glb new file mode 100644 index 0000000..be496b5 Binary files /dev/null and b/public/models/items/banana_peel_mario_kart-transformed.glb differ diff --git a/public/models/items/banana_peel_mario_kart.glb b/public/models/items/banana_peel_mario_kart.glb new file mode 100644 index 0000000..4c143ae Binary files /dev/null and b/public/models/items/banana_peel_mario_kart.glb differ diff --git a/public/models/items/mario_shell_red.glb b/public/models/items/mario_shell_red.glb new file mode 100644 index 0000000..0216378 Binary files /dev/null and b/public/models/items/mario_shell_red.glb differ diff --git a/public/models/misc/mario_kart_item_box.glb b/public/models/misc/mario_kart_item_box.glb new file mode 100644 index 0000000..7c974f0 Binary files /dev/null and b/public/models/misc/mario_kart_item_box.glb differ diff --git a/public/circle.png b/public/particles/circle.png similarity index 100% rename from public/circle.png rename to public/particles/circle.png diff --git a/public/fire_01.png b/public/particles/fire_01.png similarity index 100% rename from public/fire_01.png rename to public/particles/fire_01.png diff --git a/public/fire_02.png b/public/particles/fire_02.png similarity index 100% rename from public/fire_02.png rename to public/particles/fire_02.png diff --git a/public/star.png b/public/particles/star.png similarity index 100% rename from public/star.png rename to public/particles/star.png diff --git a/public/particles/star_symbol.png b/public/particles/star_symbol.png new file mode 100644 index 0000000..f8e6332 Binary files /dev/null and b/public/particles/star_symbol.png differ diff --git a/public/particles/test.png b/public/particles/test.png new file mode 100644 index 0000000..16037c9 Binary files /dev/null and b/public/particles/test.png differ diff --git a/src/App.jsx b/src/App.jsx index 159d6dc..6dab505 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -39,6 +39,7 @@ function App() { diff --git a/src/components/Experience.jsx b/src/components/Experience.jsx index 3612092..b56cd78 100644 --- a/src/components/Experience.jsx +++ b/src/components/Experience.jsx @@ -1,21 +1,62 @@ -import { Environment, OrbitControls, PerspectiveCamera, Lightformer } from '@react-three/drei' -import { Ground } from './Ground' -import { PlayerController } from './PlayerController' -import { Paris } from './models/tracks/Tour_paris_promenade' -import { EffectComposer, N8AO, Bloom, TiltShift2, HueSaturation, SMAA, ChromaticAberration, Vignette } from '@react-three/postprocessing' -import { Skid } from './Skid' +import { + Environment, + OrbitControls, + PerspectiveCamera, + Lightformer, + Bvh, +} from "@react-three/drei"; +import { Ground } from "./Ground"; +import { PlayerController } from "./PlayerController"; +import { Paris } from "./models/tracks/Tour_paris_promenade"; +import { + EffectComposer, + N8AO, + Bloom, + TiltShift2, + HueSaturation, + SMAA, + ChromaticAberration, + Vignette, +} from "@react-three/postprocessing"; +import { Skid } from "./Skid"; +import { Banana } from "./models/items/Banana_peel_mario_kart"; +import { ItemBox } from "./models/misc/Mario_kart_item_box"; +import { useStore } from "./store"; +import { Shell } from "./models/items/Mario_shell_red"; export const Experience = () => { + const onCollide = (event) => { + console.log(event); + }; + const { bananas, shells } = useStore(); + return ( <> + + + {/* */} + {/* */} - - /> + {bananas.map((banana) => ( + + ))} + + {shells.map((shell) => ( + + ))} { shadow-camera-left={-300} shadow-camera-right={300} shadow-camera-top={300} - shadow-camera-bottom={-3000} + shadow-camera-bottom={-300} castShadow /> - @@ -46,11 +85,11 @@ export const Experience = () => { intensity={0.5} /> - + - ) -} + ); +}; diff --git a/src/components/Particles/HitParticle.jsx b/src/components/Particles/HitParticle.jsx new file mode 100644 index 0000000..6024598 --- /dev/null +++ b/src/components/Particles/HitParticle.jsx @@ -0,0 +1,139 @@ +import React, { useState, useEffect, useRef } from "react"; +import { Canvas, useLoader, useFrame, extend } from "@react-three/fiber"; +import * as THREE from "three"; +import { shaderMaterial } from "@react-three/drei"; + +export const HitParticle = ({ position, shouldLaunch }) => { + const texture = useLoader(THREE.TextureLoader, "./particles/star_symbol.png"); + const pointsRef = useRef(); + const materialRef = useRef(); + const [size, setSize] = useState(3); + const frames = useRef(50); + + const gravity = -0.03; + const velocity = useRef({ + x: (Math.random() - 0.5) * 33, + y: (Math.random() + 0.3) * 4, + }); + const points = React.useMemo(() => { + const geom = new THREE.BufferGeometry(); + geom.setAttribute( + "position", + new THREE.Float32BufferAttribute(position, 3) + ); + return geom; + }, [position]); + + useEffect(() => { + if (shouldLaunch) { + if (pointsRef.current) { + // Reset position + pointsRef.current.position.set(0, 0, 0); + + // Reset velocity + velocity.current = { + x: (Math.random() - 0.5) * 33, + y: (Math.random() + 0.3) * 4, + }; + + // Reset opacity if needed + if (materialRef.current) { + materialRef.current.opacity = 1; + materialRef.current.size = 3; + } + + frames.current = 50; + } + } + }, [shouldLaunch]); + + useEffect(() => { + if (materialRef.current) { + materialRef.current.color.multiplyScalar(15); + } + }, []); + + useFrame((_state, delta) => { + if (pointsRef.current) { + let position = pointsRef.current.position; + + // Normalized time value for ease-out effect + let t = 1 - Math.max(frames.current / 150, 0); // Ensure t is between 0 and 1 + let easeOutCubic = 1 - Math.pow(1 - t, 3); + + // Apply the velocity to the position + position.x += velocity.current.x * delta * easeOutCubic; + position.y += velocity.current.y * delta * easeOutCubic; + + // Adjust gravity based on delta + velocity.current.y += gravity * delta * 144; + + // Decrease frames + frames.current -= 1; + + if (materialRef.current) { + // materialRef.current.size = 3 + Math.sin(frames.current * 0.1) * 2; + materialRef.current.opacity = Math.abs(Math.sin(frames.current * 0.1)); + } + // Reset the particle position and velocity when it goes too far + if (frames.current < 0) { + // if(materialRef.current.opacity > 0) { + // Math.max(materialRef.current.opacity -= 0.01 * delta * 144, 0); + // } + if (materialRef.current.size > 0) { + Math.max((materialRef.current.size -= 0.1 * delta * 144), 0); + } + } + pointsRef.current.position.set(position.x, position.y, position.z); + } + }); + + return ( + + + + + ); +}; + +export const PointsShaderMaterial = shaderMaterial( + { + time: 0, + tex: undefined, + color: new THREE.Color(0xfafad2), + }, + // Vertex Shader + ` + varying vec2 vUv; + + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `, + // Fragment Shader + ` + varying vec2 vUv; + uniform sampler2D tex; + uniform vec3 color; + uniform float time; // Using the declared 'time' uniform + + void main() { + vec2 uv = vUv; + vec4 texColor = texture2D(tex, uv); + + gl_FragColor = vec4(color, 1.0) * texColor * vec4(1.0, 1.0, 1.0, 1.0); + } + ` +); + +extend({ PointsShaderMaterial }); + diff --git a/src/components/Particles/HitParticleTwo.jsx b/src/components/Particles/HitParticleTwo.jsx new file mode 100644 index 0000000..4139f5c --- /dev/null +++ b/src/components/Particles/HitParticleTwo.jsx @@ -0,0 +1,72 @@ +import React, { useState, useEffect, useRef } from "react"; +import { Canvas, useLoader, useFrame } from "@react-three/fiber"; +import * as THREE from "three"; +import gsap from "gsap"; + + +export const HitParticleTwo = ({ position, shouldLaunch}) => { + const texture = useLoader(THREE.TextureLoader, "./particles/star_symbol.png"); + const pointsRef = useRef(); + const materialRef = useRef(); + const [size, setSize] = useState(1); + const frames = useRef(100); + + const gravity = -0.03; + const velocity = useRef({ + x: (Math.random() - 0.5) * 16, + y: (Math.random() + 0.3) *4, + }); + const points = React.useMemo(() => { + const geom = new THREE.BufferGeometry(); + geom.setAttribute( + "position", + new THREE.Float32BufferAttribute(position, 3) + ); + return geom; + }, [position]); + + useEffect(() => { + if (shouldLaunch) { + if (pointsRef.current) { + // Reset position + pointsRef.current.position.set(0, 0, 0); + + // Reset velocity + velocity.current = { + x: (Math.random() - 0.5) * 16, + y: (Math.random() + 0.3) * 4, + }; + + // Reset opacity if needed + if (materialRef.current) { + materialRef.current.opacity = 1; + } + frames.current = 100; + } + } + + }, [shouldLaunch]); + + useEffect(() => { + if (materialRef.current) { + materialRef.current.color.multiplyScalar(15); + } + }, []); + + + + return ( + + + + ); +}; diff --git a/src/components/Particles/HitParticles.jsx b/src/components/Particles/HitParticles.jsx new file mode 100644 index 0000000..5312c4c --- /dev/null +++ b/src/components/Particles/HitParticles.jsx @@ -0,0 +1,19 @@ +import React from 'react' +import { HitParticle } from './HitParticle' + +export const HitParticles = ({ position, shouldLaunch }) => { + return ( + <> + + + + + + + + + + + + ) +} \ No newline at end of file diff --git a/src/components/Particles/PointParticle.jsx b/src/components/Particles/PointParticle.jsx index 24462bd..09fcd9e 100644 --- a/src/components/Particles/PointParticle.jsx +++ b/src/components/Particles/PointParticle.jsx @@ -5,6 +5,7 @@ import * as THREE from "three"; export const PointParticle = ({ position, png, turboColor }) => { const texture = useLoader(THREE.TextureLoader, png); const pointsRef = useRef(); + const materialRef = useRef(); const [size, setSize] = useState(0); const [opacity, setOpacity] = useState(1); @@ -18,7 +19,9 @@ export const PointParticle = ({ position, png, turboColor }) => { }, [position]); useEffect(() => { - + if (materialRef.current) { + materialRef.current.color.multiplyScalar(10); + } setSize(0); setOpacity(1); }, [turboColor]); @@ -36,6 +39,7 @@ export const PointParticle = ({ position, png, turboColor }) => { return ( { const upPressed = useKeyboardControls((state) => state[Controls.up]); @@ -30,6 +31,8 @@ export const PlayerController = () => { const leftPressed = useKeyboardControls((state) => state[Controls.left]); const rightPressed = useKeyboardControls((state) => state[Controls.right]); const jumpPressed = useKeyboardControls((state) => state[Controls.jump]); + const shootPressed = useKeyboardControls((state) => state[Controls.shoot]); + const [isOnGround, setIsOnGround] = useState(false); const body = useRef(); const kart = useRef(); @@ -73,6 +76,14 @@ export const PlayerController = () => { const landingSound = useRef(); const turboSound = useRef(); const [scale, setScale] = useState(0); + const raycaster = new THREE.Raycaster(); + const downDirection = new THREE.Vector3(0, -1, 0); + const [shouldLaunch, setShouldLaunch] = useState(false); + + + const { actions, shouldSlowDown, item, bananas} = useStore(); + const slowDownDuration = useRef(1500); + useFrame(({ pointer, clock }, delta) => { const time = clock.getElapsedTime(); @@ -86,7 +97,6 @@ export const PlayerController = () => { driftBlueSound.current.setVolume(0.5); driftOrangeSound.current.setVolume(0.6); driftPurpleSound.current.setVolume(0.7); - // HANDLING AND STEERING const kartRotation = kart.current.rotation.y - driftDirection.current * driftForce.current; @@ -121,6 +131,8 @@ export const PlayerController = () => { targetXPosition = -camMaxOffset * -pointer.x; } // ACCELERATING + const shouldSlow = actions.getShouldSlowDown(); + if (upPressed && currentSpeed < maxSpeed) { // Accelerate the kart within the maximum speed limit @@ -147,6 +159,22 @@ export const PlayerController = () => { ); } } + if (shouldSlow) { + setCurrentSpeed(Math.max(currentSpeed - decceleration * 2 * delta * 144, 0)); + setCurrentSteeringSpeed(0); + slowDownDuration.current -= 1500 * delta; + setShouldLaunch(true); + if (slowDownDuration.current <= 1) { + actions.setShouldSlowDown(false); + slowDownDuration.current = 1500; + setShouldLaunch(false); + } + + + } + + + // REVERSING if (downPressed && currentSpeed < -maxSpeed) { setCurrentSpeed( @@ -359,6 +387,55 @@ export const PlayerController = () => { // SOUND WORK + // MISC + + + // ITEMS + if(shootPressed && item === "banana") { + const distanceBehind = 2; // Adjust this value as needed + const scaledBackwardDirection = forwardDirection.multiplyScalar(distanceBehind); + + // Get the current position of the kart + const kartPosition = new THREE.Vector3(...vec3(body.current.translation())); + + // Calculate the position for the new banana + const bananaPosition = kartPosition.sub(scaledBackwardDirection); + const newBanana = { + id: Math.random() + "-" + new Date(), + position: bananaPosition, + player: true, + }; + actions.addBanana(newBanana); + actions.useItem(); + + } + + if(shootPressed && item === "shell") { + const distanceBehind = -1; // Adjust this value as needed + const scaledBackwardDirection = forwardDirection.multiplyScalar(distanceBehind); + + // Get the current position of the kart + const kartPosition = new THREE.Vector3( + body.current.translation().x, + body.current.translation().y, + body.current.translation().z + ); + + // Calculate the position for the new banana + const shellPosition = kartPosition.sub(scaledBackwardDirection); + const newShell = { + id: Math.random() + "-" + new Date(), + position: shellPosition, + player: true, + rotation: kartRotation + }; + actions.addShell(newShell); + actions.useItem(); + + } + + if(item) console.log(item) + // console.lowg(body.current.translation()) }); @@ -367,20 +444,19 @@ export const PlayerController = () => { { + onCollisionEnter={({other}) => { isOnFloor.current = true; }} - // onCollisionExit={(event) => { - // isOnFloor.current = false - // }} + /> @@ -391,12 +467,11 @@ export const PlayerController = () => { steeringAngleWheels={steeringAngleWheels} isBoosting={isBoosting} /> - {/* */} + /> @@ -417,12 +492,11 @@ export const PlayerController = () => { glowSharpness={1} /> - {/* */} + /> { + {/* */} diff --git a/src/components/Skid.jsx b/src/components/Skid.jsx index 06bb461..4ff2fa0 100644 --- a/src/components/Skid.jsx +++ b/src/components/Skid.jsx @@ -57,7 +57,6 @@ function setItemAt(instances, bodyPosition, bodyRotation, index) { .add(bodyPosition); // Apply the offset to position the skid marks behind the body - console.log(bodyPosition); o.position.copy(bodyPosition.x, bodyPosition.y + 2, bodyPosition.z); o.rotation.set(0, bodyRotation, 0); diff --git a/src/components/models/characters/Mario_kart.jsx b/src/components/models/characters/Mario_kart.jsx index 21cd2dc..7179497 100644 --- a/src/components/models/characters/Mario_kart.jsx +++ b/src/components/models/characters/Mario_kart.jsx @@ -3,11 +3,13 @@ Auto-generated by: https://github.com/pmndrs/gltfjsx Command: npx gltfjsx@6.2.16 .\mariokarttest.glb --shadows */ -import React, { useRef } from 'react' +import React, { useEffect, useRef } from 'react' import { Cylinder, OrbitControls, Sphere, useGLTF } from '@react-three/drei' import { useFrame } from '@react-three/fiber' import FakeGlowMaterial from '../../ShaderMaterials/FakeGlow/FakeGlowMaterial' import FakeFlame from '../../ShaderMaterials/FakeFlame/FakeFlame' +import { useStore } from '../../store' +import gsap from 'gsap' export function Mario({ currentSpeed, steeringAngleWheels, isBoosting, ...props }) { const { nodes, materials } = useGLTF('./models/characters/mariokarttest.glb') @@ -17,6 +19,9 @@ export function Mario({ currentSpeed, steeringAngleWheels, isBoosting, ...props const rearWheels = useRef() const frontWheels = useRef() const [scale, setScale] = React.useState(1) + const { actions } = useStore() + const [shouldSlow, setShouldSlow] = React.useState(false) + const mario = useRef(); // isBoosting = true; useFrame((_,delta) => { @@ -30,12 +35,21 @@ export function Mario({ currentSpeed, steeringAngleWheels, isBoosting, ...props } else { setScale(Math.max(scale - 0.03 * 144 * delta, 0)) } + setShouldSlow(actions.getShouldSlowDown()); }) + + useEffect(() => { + if (shouldSlow) { + gsap.to(mario.current.rotation, {duration: 1.5, y: Math.PI * 3}) + mario.current.rotation.set(0, 0, 0); + } + }, [shouldSlow]) return ( C:\Users\mouli\dev\r3f-vite-starter\public\models\items\banana_peel_mario_kart-transformed.glb [20.2KB] (89%) +Author: Anthony Yanez (https://sketchfab.com/paulyanez) +License: CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/) +Source: https://sketchfab.com/3d-models/banana-peel-mario-kart-c7fd163741614859ba02f302ce0bce32 +Title: Banana Peel (Mario Kart) +*/ + +import React, { useRef } from 'react' +import { useGLTF } from '@react-three/drei' +import { BallCollider, RigidBody } from '@react-three/rapier' +import { useFrame } from '@react-three/fiber'; +import { useStore } from '../../store'; + +export function Banana({onCollide, ...props}) { + const { nodes, materials } = useGLTF('./models/items/banana_peel_mario_kart-transformed.glb'); + const rigidBody = useRef(); + const ref = useRef(); + const [scale, setScale] = React.useState(0.002); + + const {actions} = useStore(); + return ( + <> + { + + if(scale === 0.002) { + actions.setShouldSlowDown(true); + console.log(ref.current, rigidBody.current); + ref.current.visible = false; + setScale(0); + rigidBody.setEnable(false); + } + + }} + colliders={false} + name='banana' + > + + + + + + + + + + + ) +} + +useGLTF.preload('./models/items/banana_peel_mario_kart-transformed.glb') diff --git a/src/components/models/items/Mario_shell_red.jsx b/src/components/models/items/Mario_shell_red.jsx new file mode 100644 index 0000000..e6aecce --- /dev/null +++ b/src/components/models/items/Mario_shell_red.jsx @@ -0,0 +1,51 @@ +/* +Auto-generated by: https://github.com/pmndrs/gltfjsx +Command: npx gltfjsx@6.2.16 .\mario_shell_red.glb +Author: Thomas Fugier (https://sketchfab.com/thomas.fugier) +License: CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/) +Source: https://sketchfab.com/3d-models/mario-shell-red-76a81ff57398423d80800259c3d48262 +Title: Mario Shell Red +*/ + +import React, { useEffect, useRef } from 'react' +import { useGLTF } from '@react-three/drei' +import { BallCollider, RigidBody, vec3 } from '@react-three/rapier' +import { useFrame } from '@react-three/fiber'; + +export function Shell(props) { + const { nodes, materials } = useGLTF('./models/items/mario_shell_red.glb') + const rigidBody = useRef(); + const ref = useRef(); + + const shell_speed = 100; + + useEffect(() => { + const velocity = { + x : -Math.sin(props.rotation) * shell_speed, + y : 0, + z : -Math.cos(props.rotation) * shell_speed, + }; + + rigidBody.current.setLinvel(velocity, true); + },[]); + + useFrame((state, delta) => { + const rigidBodyPosition = rigidBody.current.translation(); + ref.current.position.set(rigidBodyPosition.x, rigidBodyPosition.y, rigidBodyPosition.z); + ref.current.rotation.z += 0.2 * delta * 144; + }); + + + return ( + + + + + + + + + ) +} + +useGLTF.preload('./models/items/mario_shell_red.glb') diff --git a/src/components/models/misc/Mario_kart_item_box.jsx b/src/components/models/misc/Mario_kart_item_box.jsx new file mode 100644 index 0000000..c8daf9b --- /dev/null +++ b/src/components/models/misc/Mario_kart_item_box.jsx @@ -0,0 +1,51 @@ +/* +Auto-generated by: https://github.com/pmndrs/gltfjsx +Command: npx gltfjsx@6.2.16 .\mario_kart_item_box.glb +Author: Bscott5 (https://sketchfab.com/Bscott5) +License: CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/) +Source: https://sketchfab.com/3d-models/mario-kart-item-box-8f6a2b6b17b844c5b5dfa38375289975 +Title: Mario Kart Item Box +*/ + +import React, { useRef } from "react"; +import { useGLTF, Float, MeshTransmissionMaterial, RoundedBox } from "@react-three/drei"; +import { RigidBody } from "@react-three/rapier"; +import { useStore } from "../../store"; + +export function ItemBox(props) { + + const { actions } = useStore(); + return ( + <> + { + console.log("item box hit"); + actions.setItem(); + + }} + position={props.position} + > + + + + + + + + + + + + + + + + + ); +} diff --git a/src/components/store.jsx b/src/components/store.jsx index c12aeb1..2fea8c1 100644 --- a/src/components/store.jsx +++ b/src/components/store.jsx @@ -7,6 +7,10 @@ export const playAudio = (path, callback) => { } audio.play(); }; +export const items = [ + "banana", + "shell", +] export const useStore = create((set, get) => ({ particles1: [], @@ -14,6 +18,11 @@ export const useStore = create((set, get) => ({ bodyPosition: [0, 0, 0], bodyRotation: [0, 0, 0], pastPositions: [], + shouldSlowdown: false, + bananas: [], + items: ["banana", "shell"], + item: "", + shells: [], addPastPosition: (position) => { set((state) => ({ pastPositions: [position, ...state.pastPositions.slice(0, 499)], @@ -52,5 +61,45 @@ export const useStore = create((set, get) => ({ getBodyRotation: () => { return get().bodyRotation; }, + setShouldSlowDown: (shouldSlowdown) => { + set({ shouldSlowdown }); + }, + getShouldSlowDown: () => { + return get().shouldSlowdown; + }, + addBanana: (banana) => { + set((state) => ({ + bananas: [...state.bananas, banana], + })); + }, + removeBanana: (banana) => { + set((state) => ({ + bananas: state.bananas.filter((b) => b.id !== banana.id), + })); + }, + getBananas: () => { + return get().bananas; + }, + setItem:() => { + set((state) => ({ + item: state.items[Math.floor(Math.random() * state.items.length)], + })); + }, + useItem:() => { + set((state) => ({ + item: "", + })); + }, + addShell: (shell) => { + set((state) => ({ + shells: [...state.shells, shell], + })); + }, + removeShell: (shell) => { + set((state) => ({ + shells: state.shells.filter((s) => s.id !== shell.id), + })); + } }, + })); diff --git a/src/components/useGamepad.jsx b/src/components/useGamepad.jsx new file mode 100644 index 0000000..2f3a027 --- /dev/null +++ b/src/components/useGamepad.jsx @@ -0,0 +1,68 @@ +import { useState, useEffect } from 'react'; + +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}); + + // Function to update gamepad state + const updateGamepadState = () => { + const gamepads = navigator.getGamepads ? navigator.getGamepads() : []; + const gamepad = gamepads[0]; // Assuming the first gamepad + + 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, + + 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]] + }; + + // Update state only if there's a change + if (JSON.stringify(newGamepadInfo) !== JSON.stringify(gamepadInfo)) { + setGamepadInfo(newGamepadInfo); + } + } else { + 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}); + } + } + }; + + useEffect(() => { + const gamepadConnected = () => { + 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}); + }; + + window.addEventListener('gamepadconnected', gamepadConnected); + window.addEventListener('gamepaddisconnected', gamepadDisconnected); + + const interval = setInterval(updateGamepadState, 100); + + return () => { + window.removeEventListener('gamepadconnected', gamepadConnected); + window.removeEventListener('gamepaddisconnected', gamepadDisconnected); + clearInterval(interval); + }; + }, [gamepadInfo]); + + return gamepadInfo; +}; \ No newline at end of file