feat: replace JSX with TSX for type safety
parent
8b08815d5c
commit
d0b2a73185
|
@ -1,4 +1,5 @@
|
|||
# Mario Kart 3.js - JavaScript/WebGL Mario Kart
|
||||
|
||||
[Link](https://mario-kart-3-js.vercel.app/)
|
||||
|
||||
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/Lunakepio/Mario-Kart-3.js)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<title>Mario Kart 3.js</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
29
package.json
29
package.json
|
@ -9,25 +9,30 @@
|
|||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-three/drei": "^9.96.4",
|
||||
"@react-three/fiber": "^8.15.15",
|
||||
"@react-three/postprocessing": "^2.15.11",
|
||||
"@react-three/rapier": "^1.2.1",
|
||||
"@react-three/drei": "^9.99.4",
|
||||
"@react-three/fiber": "^8.15.16",
|
||||
"@react-three/postprocessing": "^2.16.0",
|
||||
"@react-three/rapier": "^1.3.0",
|
||||
"gsap": "^3.12.5",
|
||||
"leva": "^0.9.35",
|
||||
"playroomkit": "^0.0.66",
|
||||
"playroomkit": "^0.0.68",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-gamepad": "^1.0.3",
|
||||
"react-joystick-component": "^6.2.1",
|
||||
"three": "^0.160.1",
|
||||
"three-mesh-bvh": "^0.7.0",
|
||||
"zustand": "^4.5.0"
|
||||
"three": "^0.161.0",
|
||||
"three-mesh-bvh": "^0.7.3",
|
||||
"vite-tsconfig-paths": "^4.3.1",
|
||||
"zustand": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@vitejs/plugin-react-swc": "^3.0.0",
|
||||
"vite": "^4.2.0"
|
||||
"@types/react": "^18.2.60",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@types/three": "^0.161.2",
|
||||
"@typescript-eslint/eslint-plugin": "^7.1.0",
|
||||
"@typescript-eslint/parser": "^7.1.0",
|
||||
"@vitejs/plugin-react-swc": "^3.6.0",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.1.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,9 +11,25 @@ export function ario(props) {
|
|||
const { nodes, materials } = useGLTF('/mariokart-transformed.glb')
|
||||
return (
|
||||
<group {...props} dispose={null}>
|
||||
<mesh castShadow receiveShadow geometry={nodes.mt_kart_Mario_S.geometry} material={materials.mt_kart_Mario_S} />
|
||||
<mesh castShadow receiveShadow geometry={nodes.mt_Kart_Mario_Tire_S001.geometry} material={materials.mt_Kart_Mario_Tire_S} position={[0, 0.22, -0.347]} />
|
||||
<mesh castShadow receiveShadow geometry={nodes.mt_mario.geometry} material={materials.mt_mario} />
|
||||
<mesh
|
||||
castShadow
|
||||
receiveShadow
|
||||
geometry={nodes.mt_kart_Mario_S.geometry}
|
||||
material={materials.mt_kart_Mario_S}
|
||||
/>
|
||||
<mesh
|
||||
castShadow
|
||||
receiveShadow
|
||||
geometry={nodes.mt_Kart_Mario_Tire_S001.geometry}
|
||||
material={materials.mt_Kart_Mario_Tire_S}
|
||||
position={[0, 0.22, -0.347]}
|
||||
/>
|
||||
<mesh
|
||||
castShadow
|
||||
receiveShadow
|
||||
geometry={nodes.mt_mario.geometry}
|
||||
material={materials.mt_mario}
|
||||
/>
|
||||
</group>
|
||||
)
|
||||
}
|
|
@ -24,16 +24,36 @@ type GLTFResult = GLTF & {
|
|||
animations: GLTFAction[]
|
||||
}
|
||||
|
||||
type ContextType = Record<string, React.ForwardRefExoticComponent<JSX.IntrinsicElements['mesh']>>
|
||||
type ContextType = Record<
|
||||
string,
|
||||
React.ForwardRefExoticComponent<JSX.IntrinsicElements['mesh']>
|
||||
>
|
||||
|
||||
export function Model(props: JSX.IntrinsicElements['group']) {
|
||||
const { nodes, materials } = useGLTF('/mariokarttest.glb') as GLTFResult
|
||||
return (
|
||||
<group {...props} dispose={null}>
|
||||
<mesh geometry={nodes.mt_kart_Mario_S.geometry} material={materials.mt_kart_Mario_S} />
|
||||
<mesh geometry={nodes.mt_Kart_Mario_Tire_S001.geometry} material={materials.mt_Kart_Mario_Tire_S} position={[0, 0.22, -0.347]} />
|
||||
<mesh geometry={nodes.mt_Kart_Mario_Tire_S002.geometry} material={materials.mt_Kart_Mario_Tire_S} position={[0.384, 0.193, 0.441]} />
|
||||
<mesh geometry={nodes.mt_Kart_Mario_Tire_S003.geometry} material={materials.mt_Kart_Mario_Tire_S} position={[-0.393, 0.193, 0.441]} rotation={[-Math.PI, 0, 0]} scale={-1} />
|
||||
<mesh
|
||||
geometry={nodes.mt_kart_Mario_S.geometry}
|
||||
material={materials.mt_kart_Mario_S}
|
||||
/>
|
||||
<mesh
|
||||
geometry={nodes.mt_Kart_Mario_Tire_S001.geometry}
|
||||
material={materials.mt_Kart_Mario_Tire_S}
|
||||
position={[0, 0.22, -0.347]}
|
||||
/>
|
||||
<mesh
|
||||
geometry={nodes.mt_Kart_Mario_Tire_S002.geometry}
|
||||
material={materials.mt_Kart_Mario_Tire_S}
|
||||
position={[0.384, 0.193, 0.441]}
|
||||
/>
|
||||
<mesh
|
||||
geometry={nodes.mt_Kart_Mario_Tire_S003.geometry}
|
||||
material={materials.mt_Kart_Mario_Tire_S}
|
||||
position={[-0.393, 0.193, 0.441]}
|
||||
rotation={[-Math.PI, 0, 0]}
|
||||
scale={-1}
|
||||
/>
|
||||
<mesh geometry={nodes.mt_mario.geometry} material={materials.mt_mario} />
|
||||
</group>
|
||||
)
|
||||
|
|
|
@ -2,11 +2,10 @@ import { Canvas } from '@react-three/fiber'
|
|||
import { Experience } from './components/Experience'
|
||||
import { Suspense, useEffect, useMemo } from 'react'
|
||||
import { Physics } from '@react-three/rapier'
|
||||
import { Environment, KeyboardControls, Loader, OrbitControls, Preload, Stats } from '@react-three/drei'
|
||||
import { KeyboardControls, Loader, Preload } from '@react-three/drei'
|
||||
import { insertCoin, onPlayerJoin } from 'playroomkit'
|
||||
import { useStore } from "./components/store";
|
||||
import * as THREE from "three";
|
||||
import { ParisBis } from './components/models/tracks/Paris-bis'
|
||||
import { useStore } from './components/store'
|
||||
import * as THREE from 'three'
|
||||
|
||||
export const Controls = {
|
||||
up: 'up',
|
||||
|
@ -17,8 +16,9 @@ export const Controls = {
|
|||
shoot: 'shoot',
|
||||
slow: 'slow',
|
||||
reset: 'reset',
|
||||
escape: 'escape'
|
||||
}
|
||||
escape: 'escape',
|
||||
jump: 'jump',
|
||||
} as const
|
||||
|
||||
function App() {
|
||||
const map = useMemo(
|
||||
|
@ -31,29 +31,29 @@ function App() {
|
|||
{ name: Controls.slow, keys: ['Shift'] },
|
||||
{ name: Controls.shoot, keys: ['KeyE', 'Click'] },
|
||||
{ name: Controls.reset, keys: ['KeyR'] },
|
||||
{ name: Controls.escape, keys: ['Escape']}
|
||||
{ name: Controls.escape, keys: ['Escape'] },
|
||||
],
|
||||
[]
|
||||
)
|
||||
|
||||
const { actions } = useStore();
|
||||
const { actions } = useStore()
|
||||
|
||||
const start = async () => {
|
||||
await insertCoin();
|
||||
await insertCoin()
|
||||
|
||||
onPlayerJoin((state) => {
|
||||
actions.addPlayer(state);
|
||||
actions.addPlayer(state)
|
||||
|
||||
actions.setId(state.id);
|
||||
actions.setId(state.id)
|
||||
|
||||
state.onQuit(() => {
|
||||
actions.removePlayer(state);
|
||||
|
||||
});
|
||||
});
|
||||
actions.removePlayer(state)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
start();
|
||||
start()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
|
@ -62,18 +62,21 @@ function App() {
|
|||
<Canvas
|
||||
// shadows
|
||||
dpr={1}
|
||||
gl={{ antialias: false, stencil: false, depth:false, powerPreference: 'high-performance' }}
|
||||
gl={{
|
||||
antialias: false,
|
||||
stencil: false,
|
||||
depth: false,
|
||||
powerPreference: 'high-performance',
|
||||
}}
|
||||
mode="concurrent"
|
||||
onCreated={({ gl, camera }) => {
|
||||
onCreated={({ gl }) => {
|
||||
gl.toneMapping = THREE.AgXToneMapping
|
||||
gl.setClearColor(0x000000, 0)
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<Suspense fallback={null}>
|
||||
<Preload all />
|
||||
<Physics
|
||||
gravity={[0, -90, 0]}
|
||||
timeStep={'vary'}
|
||||
>
|
||||
<Physics gravity={[0, -90, 0]} timeStep={'vary'}>
|
||||
<KeyboardControls map={map}>
|
||||
<Experience />
|
||||
</KeyboardControls>
|
144
src/HUD.jsx
144
src/HUD.jsx
|
@ -1,144 +0,0 @@
|
|||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useStore } from "./components/store";
|
||||
import { Joystick } from "react-joystick-component";
|
||||
|
||||
export const HUD = () => {
|
||||
const wheel = useRef();
|
||||
const [image, setImage] = useState("");
|
||||
const { item, gameStarted, actions, controls } = useStore();
|
||||
|
||||
useEffect(() => {
|
||||
const handleMouseMove = (e) => {
|
||||
if (wheel.current) {
|
||||
const { clientX, clientY } = e;
|
||||
const screenWidth = window.innerWidth;
|
||||
const rotation = ((clientX - screenWidth / 2) / screenWidth) * 180;
|
||||
|
||||
wheel.current.style.left = `${clientX - 100}px`;
|
||||
wheel.current.style.top = `${clientY - 100}px`;
|
||||
wheel.current.style.transform = `rotate(${rotation}deg)`;
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("mousemove", handleMouseMove);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("mousemove", handleMouseMove);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleMove = (e) => {
|
||||
actions.setJoystickX(e.x);
|
||||
};
|
||||
|
||||
const handleStop = () => {
|
||||
actions.setJoystickX(0);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
switch (item) {
|
||||
case "banana":
|
||||
setImage("./images/banana.webp");
|
||||
break;
|
||||
case "mushroom":
|
||||
setImage("./images/mushroom.png");
|
||||
break;
|
||||
case "shell":
|
||||
setImage("./images/shell.webp");
|
||||
break;
|
||||
default:
|
||||
setImage("");
|
||||
}
|
||||
}, [item]);
|
||||
|
||||
return (
|
||||
<div className="overlay">
|
||||
{gameStarted && (
|
||||
<>
|
||||
<div className="item">
|
||||
<div className="borderOut">
|
||||
<div className="borderIn">
|
||||
<div className="background">
|
||||
{image && <img src={image} alt="item" width={90} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{controls === "touch" && (
|
||||
<>
|
||||
<div className="controls joystick">
|
||||
<Joystick
|
||||
size={100}
|
||||
sticky={false}
|
||||
baseColor="rgba(255, 255, 255, 0.5)"
|
||||
stickColor="rgba(255, 255, 255, 0.5)"
|
||||
move={handleMove}
|
||||
stop={handleStop}
|
||||
></Joystick>
|
||||
</div>
|
||||
<div
|
||||
className="controls drift"
|
||||
onMouseDown={(e) => {
|
||||
actions.setDriftButton(true);
|
||||
}}
|
||||
onMouseUp={(e) => {
|
||||
actions.setDriftButton(false);
|
||||
}}
|
||||
onTouchStart={(e) => {
|
||||
e.preventDefault();
|
||||
actions.setDriftButton(true);
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.preventDefault();
|
||||
actions.setDriftButton(false);
|
||||
}}
|
||||
>
|
||||
drift
|
||||
</div>
|
||||
<div
|
||||
className="controls itemButton"
|
||||
onMouseDown={(e) => {
|
||||
actions.setItemButton(true);
|
||||
}}
|
||||
onMouseUp={(e) => {
|
||||
actions.setItemButton(false);
|
||||
}}
|
||||
onTouchStart={(e) => {
|
||||
e.preventDefault();
|
||||
actions.setItemButton(true);
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.preventDefault();
|
||||
actions.setItemButton(false);
|
||||
}}
|
||||
|
||||
>
|
||||
item
|
||||
</div>
|
||||
<div
|
||||
className="controls menuButton"
|
||||
onMouseDown={(e) => {
|
||||
actions.setMenuButton(true);
|
||||
}}
|
||||
onMouseUp={(e) => {
|
||||
actions.setMenuButton(false);
|
||||
}}
|
||||
onTouchStart={(e) => {
|
||||
e.preventDefault();
|
||||
actions.setMenuButton(true);
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.preventDefault();
|
||||
actions.setMenuButton(false);
|
||||
}}
|
||||
|
||||
>
|
||||
menu
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,147 @@
|
|||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useStore } from './components/store'
|
||||
import { Joystick } from 'react-joystick-component'
|
||||
import { IJoystickUpdateEvent } from 'react-joystick-component/build/lib/Joystick'
|
||||
|
||||
export const HUD = () => {
|
||||
const wheel = useRef()
|
||||
const [image, setImage] = useState('')
|
||||
const { item, gameStarted, actions, controls } = useStore()
|
||||
|
||||
useEffect(() => {
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (wheel.current) {
|
||||
const { clientX, clientY } = e
|
||||
const screenWidth = window.innerWidth
|
||||
const rotation = ((clientX - screenWidth / 2) / screenWidth) * 180
|
||||
|
||||
if (wheel.current) {
|
||||
;(wheel.current as HTMLDivElement).style.left = `${clientX - 100}px`
|
||||
;(wheel.current as HTMLDivElement).style.top = `${clientY - 100}px`
|
||||
;(
|
||||
wheel.current as HTMLDivElement
|
||||
).style.transform = `rotate(${rotation}deg)`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('mousemove', handleMouseMove)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('mousemove', handleMouseMove)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleMove = (e: IJoystickUpdateEvent) => {
|
||||
actions.setJoystickX(e.x ?? 0)
|
||||
}
|
||||
|
||||
const handleStop = () => {
|
||||
actions.setJoystickX(0)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
switch (item) {
|
||||
case 'banana':
|
||||
setImage('./images/banana.webp')
|
||||
break
|
||||
case 'mushroom':
|
||||
setImage('./images/mushroom.png')
|
||||
break
|
||||
case 'shell':
|
||||
setImage('./images/shell.webp')
|
||||
break
|
||||
default:
|
||||
setImage('')
|
||||
}
|
||||
}, [item])
|
||||
|
||||
return (
|
||||
<div className="overlay">
|
||||
{gameStarted && (
|
||||
<>
|
||||
<div className="item">
|
||||
<div className="borderOut">
|
||||
<div className="borderIn">
|
||||
<div className="background">
|
||||
{image && <img src={image} alt="item" width={90} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{controls === 'touch' && (
|
||||
<>
|
||||
<div className="controls joystick">
|
||||
<Joystick
|
||||
size={100}
|
||||
sticky={false}
|
||||
baseColor="rgba(255, 255, 255, 0.5)"
|
||||
stickColor="rgba(255, 255, 255, 0.5)"
|
||||
move={handleMove}
|
||||
stop={handleStop}
|
||||
></Joystick>
|
||||
</div>
|
||||
<div
|
||||
className="controls drift"
|
||||
onMouseDown={() => {
|
||||
actions.setDriftButton(true)
|
||||
}}
|
||||
onMouseUp={() => {
|
||||
actions.setDriftButton(false)
|
||||
}}
|
||||
onTouchStart={(e) => {
|
||||
e.preventDefault()
|
||||
actions.setDriftButton(true)
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.preventDefault()
|
||||
actions.setDriftButton(false)
|
||||
}}
|
||||
>
|
||||
drift
|
||||
</div>
|
||||
<div
|
||||
className="controls itemButton"
|
||||
onMouseDown={() => {
|
||||
actions.setItemButton(true)
|
||||
}}
|
||||
onMouseUp={() => {
|
||||
actions.setItemButton(false)
|
||||
}}
|
||||
onTouchStart={(e) => {
|
||||
e.preventDefault()
|
||||
actions.setItemButton(true)
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.preventDefault()
|
||||
actions.setItemButton(false)
|
||||
}}
|
||||
>
|
||||
item
|
||||
</div>
|
||||
<div
|
||||
className="controls menuButton"
|
||||
onMouseDown={() => {
|
||||
actions.setMenuButton(true)
|
||||
}}
|
||||
onMouseUp={() => {
|
||||
actions.setMenuButton(false)
|
||||
}}
|
||||
onTouchStart={(e) => {
|
||||
e.preventDefault()
|
||||
actions.setMenuButton(true)
|
||||
}}
|
||||
onTouchEnd={(e) => {
|
||||
e.preventDefault()
|
||||
actions.setMenuButton(false)
|
||||
}}
|
||||
>
|
||||
menu
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
128
src/Landing.jsx
128
src/Landing.jsx
|
@ -1,128 +0,0 @@
|
|||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useStore } from "./components/store";
|
||||
import gsap from "gsap";
|
||||
|
||||
export const Landing = () => {
|
||||
const { gameStarted, actions } = useStore();
|
||||
|
||||
const logo = useRef();
|
||||
const startButton = useRef();
|
||||
const homeRef = useRef();
|
||||
const [setupStatus, setSetupStatus] = useState(0);
|
||||
const [controlStyle, setControlStyle] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const tl = gsap.timeline();
|
||||
|
||||
if (setupStatus === 0) {
|
||||
if (logo.current && startButton.current) {
|
||||
tl.from(logo.current, {
|
||||
scale: 122,
|
||||
opacity: 0,
|
||||
duration: 0,
|
||||
ease: "power4.out",
|
||||
})
|
||||
.to(logo.current, {
|
||||
scale: 1,
|
||||
opacity: 1,
|
||||
duration: 1.5,
|
||||
ease: "power4.out",
|
||||
})
|
||||
.to(startButton.current, {
|
||||
opacity: 1,
|
||||
duration: 3,
|
||||
delay: 1,
|
||||
ease: "power4.out",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
setSetupStatus(1);
|
||||
}
|
||||
};
|
||||
|
||||
document.body.addEventListener('keydown', handleKeyDown);
|
||||
return () => {
|
||||
document.body.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [setupStatus]);
|
||||
|
||||
if (gameStarted) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{setupStatus === 0 && (
|
||||
<div className="home" ref={homeRef}>
|
||||
<div className="logo">
|
||||
<img ref={logo} src="./logo.png" alt="logo" />
|
||||
</div>
|
||||
<div className="start" ref={startButton}>
|
||||
<button className="start-button"
|
||||
onClick={() => setSetupStatus(1)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
setSetupStatus(1);
|
||||
}}} autoFocus>
|
||||
PRESS ENTER TO START
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{setupStatus === 1 && (
|
||||
<div className="home">
|
||||
<div className="glassy">
|
||||
<h1>CHOOSE YOUR CONTROL STYLE</h1>
|
||||
|
||||
<div className="articles">
|
||||
<div className={controlStyle === "keyboard" ? "article selected" : "article"} onClick={() =>
|
||||
setControlStyle("keyboard")}>
|
||||
<img src="./images/keyboard.png" alt="keyboard" />
|
||||
<div className="article_label">
|
||||
<p>Keyboard</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={controlStyle === "gamepad" ? "article selected" : "article"} onClick={() =>
|
||||
setControlStyle("gamepad")}>
|
||||
<img src="./images/gamepad.png" alt="gamepad" />
|
||||
<div className="article_label">
|
||||
<p>Gamepad</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={controlStyle === "mouseKeyboard" ? "article selected" : "article"} onClick={() =>
|
||||
setControlStyle("mouseKeyboard")}>
|
||||
<img src="./images/mousekeyboard.png" alt="mouse & keyboard" />
|
||||
<div className="article_label">
|
||||
<p>Mouse & Keyboard</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={controlStyle === "touch" ? "article selected" : "article"} onClick={() =>
|
||||
setControlStyle("touch")}>
|
||||
<img src="./images/mobile.png" alt="mobile" />
|
||||
<div className="article_label">
|
||||
<p>Mobile</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className={controlStyle != "" ? "submit" : "submit disabled"}>
|
||||
<button
|
||||
className={controlStyle != "" ? "submit-button" : "submit-button disabled"}
|
||||
onClick={() => {
|
||||
actions.setControls(controlStyle);
|
||||
actions.setGameStarted(true);
|
||||
}}
|
||||
>
|
||||
CONFIRM
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
);
|
||||
};
|
|
@ -0,0 +1,152 @@
|
|||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useStore } from './components/store'
|
||||
import gsap from 'gsap'
|
||||
|
||||
export const Landing = () => {
|
||||
const { gameStarted, actions } = useStore()
|
||||
|
||||
const logo = useRef()
|
||||
const startButton = useRef()
|
||||
const homeRef = useRef()
|
||||
const [setupStatus, setSetupStatus] = useState(0)
|
||||
const [controlStyle, setControlStyle] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
const tl = gsap.timeline()
|
||||
|
||||
if (setupStatus === 0) {
|
||||
if (logo.current && startButton.current) {
|
||||
tl.from(logo.current, {
|
||||
scale: 122,
|
||||
opacity: 0,
|
||||
duration: 0,
|
||||
ease: 'power4.out',
|
||||
})
|
||||
.to(logo.current, {
|
||||
scale: 1,
|
||||
opacity: 1,
|
||||
duration: 1.5,
|
||||
ease: 'power4.out',
|
||||
})
|
||||
.to(startButton.current, {
|
||||
opacity: 1,
|
||||
duration: 3,
|
||||
delay: 1,
|
||||
ease: 'power4.out',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
setSetupStatus(1)
|
||||
}
|
||||
}
|
||||
|
||||
document.body.addEventListener('keydown', handleKeyDown)
|
||||
return () => {
|
||||
document.body.removeEventListener('keydown', handleKeyDown)
|
||||
}
|
||||
}, [setupStatus])
|
||||
|
||||
if (gameStarted) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{setupStatus === 0 && (
|
||||
<div className="home" ref={homeRef}>
|
||||
<div className="logo">
|
||||
<img ref={logo} src="./logo.png" alt="logo" />
|
||||
</div>
|
||||
<div className="start" ref={startButton}>
|
||||
<button
|
||||
className="start-button"
|
||||
onClick={() => setSetupStatus(1)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
setSetupStatus(1)
|
||||
}
|
||||
}}
|
||||
autoFocus
|
||||
>
|
||||
PRESS ENTER TO START
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{setupStatus === 1 && (
|
||||
<div className="home">
|
||||
<div className="glassy">
|
||||
<h1>CHOOSE YOUR CONTROL STYLE</h1>
|
||||
|
||||
<div className="articles">
|
||||
<div
|
||||
className={
|
||||
controlStyle === 'keyboard' ? 'article selected' : 'article'
|
||||
}
|
||||
onClick={() => setControlStyle('keyboard')}
|
||||
>
|
||||
<img src="./images/keyboard.png" alt="keyboard" />
|
||||
<div className="article_label">
|
||||
<p>Keyboard</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
controlStyle === 'gamepad' ? 'article selected' : 'article'
|
||||
}
|
||||
onClick={() => setControlStyle('gamepad')}
|
||||
>
|
||||
<img src="./images/gamepad.png" alt="gamepad" />
|
||||
<div className="article_label">
|
||||
<p>Gamepad</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
controlStyle === 'mouseKeyboard'
|
||||
? 'article selected'
|
||||
: 'article'
|
||||
}
|
||||
onClick={() => setControlStyle('mouseKeyboard')}
|
||||
>
|
||||
<img src="./images/mousekeyboard.png" alt="mouse & keyboard" />
|
||||
<div className="article_label">
|
||||
<p>Mouse & Keyboard</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
controlStyle === 'touch' ? 'article selected' : 'article'
|
||||
}
|
||||
onClick={() => setControlStyle('touch')}
|
||||
>
|
||||
<img src="./images/mobile.png" alt="mobile" />
|
||||
<div className="article_label">
|
||||
<p>Mobile</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={controlStyle != '' ? 'submit' : 'submit disabled'}>
|
||||
<button
|
||||
className={
|
||||
controlStyle != ''
|
||||
? 'submit-button'
|
||||
: 'submit-button disabled'
|
||||
}
|
||||
onClick={() => {
|
||||
actions.setControls(controlStyle)
|
||||
actions.setGameStarted(true)
|
||||
}}
|
||||
>
|
||||
CONFIRM
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
Before Width: | Height: | Size: 4.0 KiB |
|
@ -1,62 +1,47 @@
|
|||
import {
|
||||
Environment,
|
||||
OrbitControls,
|
||||
PerspectiveCamera,
|
||||
Lightformer,
|
||||
Bvh,
|
||||
} from "@react-three/drei";
|
||||
import { Ground } from "./Ground";
|
||||
import { PlayerController } from "./PlayerController";
|
||||
import { PlayerControllerGamepad } from "./PlayerControllerGamepad";
|
||||
import { PlayerControllerKeyboard } from "./PlayerControllerKeyboard";
|
||||
import { PlayerControllerTouch } from "./PlayerControllerTouch";
|
||||
import { Paris } from "./models/tracks/Tour_paris_promenade";
|
||||
} from '@react-three/drei'
|
||||
import { Ground } from './Ground'
|
||||
import { PlayerController } from './PlayerController'
|
||||
import { PlayerControllerGamepad } from './PlayerControllerGamepad'
|
||||
import { PlayerControllerKeyboard } from './PlayerControllerKeyboard'
|
||||
import { PlayerControllerTouch } from './PlayerControllerTouch'
|
||||
import {
|
||||
EffectComposer,
|
||||
N8AO,
|
||||
Bloom,
|
||||
TiltShift2,
|
||||
HueSaturation,
|
||||
SMAA,
|
||||
ChromaticAberration,
|
||||
Vignette,
|
||||
LUT,
|
||||
} from "@react-three/postprocessing";
|
||||
import { Banana } from "./models/items/Banana_peel_mario_kart";
|
||||
import { ItemBox } from "./models/misc/Gift";
|
||||
import { useStore } from "./store";
|
||||
import { Shell } from "./models/items/Mario_shell_red";
|
||||
import { Coin } from "./models/misc/Super_mario_bros_coin";
|
||||
} from '@react-three/postprocessing'
|
||||
import { Banana } from './models/items/Banana_peel_mario_kart'
|
||||
import { ItemBox } from './models/misc/Gift'
|
||||
import { useStore } from './store'
|
||||
import { Shell } from './models/items/Mario_shell_red'
|
||||
import { Coin } from './models/misc/Super_mario_bros_coin'
|
||||
import {
|
||||
RPC,
|
||||
getState,
|
||||
insertCoin,
|
||||
isHost,
|
||||
myPlayer,
|
||||
onPlayerJoin,
|
||||
useMultiplayerState,
|
||||
} from "playroomkit";
|
||||
import { PlayerDummies } from "./PlayerDummies";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { useFrame, useLoader } from "@react-three/fiber";
|
||||
import { LUTPass, LUTCubeLoader } from "three-stdlib";
|
||||
import { useCurvedPathPoints } from "./useCurvedPath";
|
||||
import { ParisBis } from "./models/tracks/Paris-bis";
|
||||
} from 'playroomkit'
|
||||
import { PlayerDummies } from './PlayerDummies'
|
||||
import { useEffect, useState, useRef } from 'react'
|
||||
import { useFrame, useLoader } from '@react-three/fiber'
|
||||
import { LUTCubeLoader } from 'three-stdlib'
|
||||
import { useCurvedPathPoints } from './useCurvedPath'
|
||||
import { ParisBis } from './models/tracks/Paris-bis'
|
||||
|
||||
export const Experience = () => {
|
||||
const onCollide = (event) => {};
|
||||
const onCollide = (event) => {}
|
||||
const { gameStarted, bananas, shells, players, id, actions, controls } =
|
||||
useStore();
|
||||
const [networkBananas, setNetworkBananas] = useMultiplayerState(
|
||||
"bananas",
|
||||
[]
|
||||
);
|
||||
useStore()
|
||||
const [networkBananas, setNetworkBananas] = useMultiplayerState('bananas', [])
|
||||
|
||||
const { points, loading, error } = useCurvedPathPoints("./CurvedPath.json");
|
||||
const { points, loading, error } = useCurvedPathPoints('./CurvedPath.json')
|
||||
|
||||
const [networkShells, setNetworkShells] = useMultiplayerState("shells", []);
|
||||
const [pointest, setPointest] = useState([]);
|
||||
const [currentPoint, setCurrentPoint] = useState(0);
|
||||
const [networkShells, setNetworkShells] = useMultiplayerState('shells', [])
|
||||
const [pointest, setPointest] = useState([])
|
||||
const [currentPoint, setCurrentPoint] = useState(0)
|
||||
useEffect(() => {
|
||||
if (points) {
|
||||
//This is adjusted to Paris scale
|
||||
|
@ -64,14 +49,14 @@ export const Experience = () => {
|
|||
x: point.x * 50,
|
||||
y: point.y * 50,
|
||||
z: point.z * 50,
|
||||
}));
|
||||
setPointest(scaledPoints.reverse());
|
||||
}))
|
||||
setPointest(scaledPoints.reverse())
|
||||
}
|
||||
}, [points]);
|
||||
}, [points])
|
||||
|
||||
const testing = getState("bananas");
|
||||
const cam = useRef();
|
||||
const lookAtTarget = useRef();
|
||||
const testing = getState('bananas')
|
||||
const cam = useRef()
|
||||
const lookAtTarget = useRef()
|
||||
// useEffect(() => {
|
||||
// setNetworkBananas(bananas);
|
||||
// }, [bananas]);
|
||||
|
@ -79,41 +64,41 @@ export const Experience = () => {
|
|||
// useEffect(() => {
|
||||
// setNetworkShells(shells);
|
||||
// }, [shells]);
|
||||
const speedFactor = 5;
|
||||
const { texture } = useLoader(LUTCubeLoader, "./cubicle-99.CUBE");
|
||||
const speedFactor = 5
|
||||
const { texture } = useLoader(LUTCubeLoader, './cubicle-99.CUBE')
|
||||
useFrame((state, delta) => {
|
||||
if (!gameStarted) {
|
||||
const camera = cam.current;
|
||||
const camera = cam.current
|
||||
|
||||
if (currentPoint < pointest.length - 1) {
|
||||
camera.position.lerp(pointest[currentPoint], delta * speedFactor);
|
||||
camera.position.lerp(pointest[currentPoint], delta * speedFactor)
|
||||
lookAtTarget.current.position.lerp(
|
||||
pointest[currentPoint + 1],
|
||||
delta * speedFactor
|
||||
);
|
||||
camera.lookAt(lookAtTarget.current.position);
|
||||
)
|
||||
camera.lookAt(lookAtTarget.current.position)
|
||||
|
||||
if (camera.position.distanceTo(pointest[currentPoint]) < 5) {
|
||||
setCurrentPoint(currentPoint + 1);
|
||||
setCurrentPoint(currentPoint + 1)
|
||||
}
|
||||
} else {
|
||||
setCurrentPoint(0);
|
||||
setCurrentPoint(0)
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
{gameStarted &&
|
||||
players.map((player) => {
|
||||
const ControllerComponent =
|
||||
controls === "keyboard"
|
||||
controls === 'keyboard'
|
||||
? PlayerControllerKeyboard
|
||||
: controls === "gamepad"
|
||||
: controls === 'gamepad'
|
||||
? PlayerControllerGamepad
|
||||
: controls === "touch"
|
||||
: controls === 'touch'
|
||||
? PlayerControllerTouch
|
||||
: PlayerController;
|
||||
: PlayerController
|
||||
|
||||
return (
|
||||
<ControllerComponent
|
||||
|
@ -125,7 +110,7 @@ export const Experience = () => {
|
|||
networkBananas={networkBananas}
|
||||
networkShells={networkShells}
|
||||
/>
|
||||
);
|
||||
)
|
||||
})}
|
||||
{gameStarted &&
|
||||
players.map((player) => (
|
||||
|
@ -207,5 +192,5 @@ export const Experience = () => {
|
|||
{/* <Vignette eskil={false} offset={0.1} darkness={0.4} /> */}
|
||||
</EffectComposer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import { CircleCoinParticle } from "./CircleCoinParticle"
|
||||
import { StarCoinParticle } from "./StarCoinParticle"
|
||||
|
||||
export const CoinParticles = ({ coins }) => {
|
||||
return (
|
||||
<>
|
||||
<CircleCoinParticle position={[0,0.8, 0.2]} coins={coins}/>
|
||||
<StarCoinParticle position={[0,0.8, 0.2]} coins={coins} timeModifier={50}/>
|
||||
<StarCoinParticle position={[0,0.8, 0.2]} coins={coins} timeModifier={60}/>
|
||||
<StarCoinParticle position={[0,0.8, 0.2]} coins={coins} timeModifier={40}/>
|
||||
<StarCoinParticle position={[0,0.8, 0.2]} coins={coins} timeModifier={90}/>
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import { CircleCoinParticle } from './CircleCoinParticle'
|
||||
import { StarCoinParticle } from './StarCoinParticle'
|
||||
|
||||
export const CoinParticles = ({ coins }) => {
|
||||
return (
|
||||
<>
|
||||
<CircleCoinParticle position={[0, 0.8, 0.2]} coins={coins} />
|
||||
<StarCoinParticle
|
||||
position={[0, 0.8, 0.2]}
|
||||
coins={coins}
|
||||
timeModifier={50}
|
||||
/>
|
||||
<StarCoinParticle
|
||||
position={[0, 0.8, 0.2]}
|
||||
coins={coins}
|
||||
timeModifier={60}
|
||||
/>
|
||||
<StarCoinParticle
|
||||
position={[0, 0.8, 0.2]}
|
||||
coins={coins}
|
||||
timeModifier={40}
|
||||
/>
|
||||
<StarCoinParticle
|
||||
position={[0, 0.8, 0.2]}
|
||||
coins={coins}
|
||||
timeModifier={90}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
import { useRef } from "react";
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
import * as THREE from 'three';
|
||||
|
||||
export const Particles2 = ({ turboColor, scale, ...props }) => {
|
||||
const ref = useRef();
|
||||
const velocity = useRef({
|
||||
x: Math.random() * 0.05,
|
||||
y: Math.random() * 0.05,
|
||||
z: Math.random() * 0.02,
|
||||
});
|
||||
const gravity = -0.003;
|
||||
|
||||
useFrame((state, delta) => {
|
||||
let position = ref.current.position;
|
||||
let velocityVector = new THREE.Vector3(velocity.current.x, velocity.current.y, velocity.current.z);
|
||||
|
||||
|
||||
velocity.current.y += gravity * delta * 144;
|
||||
|
||||
position.x += velocity.current.x * delta * 144;
|
||||
position.y += velocity.current.y * delta * 144;
|
||||
position.z += velocity.current.z * delta * 144;
|
||||
|
||||
if (!velocityVector.equals(new THREE.Vector3(0, 0, 0))) {
|
||||
const alignmentQuaternion = new THREE.Quaternion();
|
||||
alignmentQuaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), velocityVector.normalize());
|
||||
ref.current.quaternion.slerp(alignmentQuaternion, 0.1);
|
||||
}
|
||||
|
||||
if (position.y < 0.05) {
|
||||
position.set(0.6, 0.05, 0.5);
|
||||
velocity.current = {
|
||||
x: Math.random() * 0.05,
|
||||
y: Math.random() * 0.05,
|
||||
z: Math.random() * 0.02,
|
||||
};
|
||||
}
|
||||
|
||||
ref.current.position.set(position.x, position.y, position.z);
|
||||
});
|
||||
|
||||
|
||||
return (
|
||||
<mesh ref={ref} position={[0.6, 0.05, 0.5]} scale={[scale, scale * 5, scale]}>
|
||||
<sphereGeometry args={[0.01, 16, 16]} />
|
||||
<meshStandardMaterial
|
||||
emissive={turboColor}
|
||||
toneMapped={false}
|
||||
emissiveIntensity={5}
|
||||
/>
|
||||
</mesh>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,63 @@
|
|||
import { useRef } from 'react'
|
||||
import { useFrame } from '@react-three/fiber'
|
||||
import * as THREE from 'three'
|
||||
|
||||
export const Particles2 = ({ turboColor, scale, ...props }) => {
|
||||
const ref = useRef()
|
||||
const velocity = useRef({
|
||||
x: Math.random() * 0.05,
|
||||
y: Math.random() * 0.05,
|
||||
z: Math.random() * 0.02,
|
||||
})
|
||||
const gravity = -0.003
|
||||
|
||||
useFrame((state, delta) => {
|
||||
let position = ref.current.position
|
||||
let velocityVector = new THREE.Vector3(
|
||||
velocity.current.x,
|
||||
velocity.current.y,
|
||||
velocity.current.z
|
||||
)
|
||||
|
||||
velocity.current.y += gravity * delta * 144
|
||||
|
||||
position.x += velocity.current.x * delta * 144
|
||||
position.y += velocity.current.y * delta * 144
|
||||
position.z += velocity.current.z * delta * 144
|
||||
|
||||
if (!velocityVector.equals(new THREE.Vector3(0, 0, 0))) {
|
||||
const alignmentQuaternion = new THREE.Quaternion()
|
||||
alignmentQuaternion.setFromUnitVectors(
|
||||
new THREE.Vector3(0, 1, 0),
|
||||
velocityVector.normalize()
|
||||
)
|
||||
ref.current.quaternion.slerp(alignmentQuaternion, 0.1)
|
||||
}
|
||||
|
||||
if (position.y < 0.05) {
|
||||
position.set(0.6, 0.05, 0.5)
|
||||
velocity.current = {
|
||||
x: Math.random() * 0.05,
|
||||
y: Math.random() * 0.05,
|
||||
z: Math.random() * 0.02,
|
||||
}
|
||||
}
|
||||
|
||||
ref.current.position.set(position.x, position.y, position.z)
|
||||
})
|
||||
|
||||
return (
|
||||
<mesh
|
||||
ref={ref}
|
||||
position={[0.6, 0.05, 0.5]}
|
||||
scale={[scale, scale * 5, scale]}
|
||||
>
|
||||
<sphereGeometry args={[0.01, 16, 16]} />
|
||||
<meshStandardMaterial
|
||||
emissive={turboColor}
|
||||
toneMapped={false}
|
||||
emissiveIntensity={5}
|
||||
/>
|
||||
</mesh>
|
||||
)
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
import { useRef } from "react";
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
import * as THREE from 'three';
|
||||
|
||||
export const Particles4 = ({ turboColor, scale, ...props }) => {
|
||||
const ref = useRef();
|
||||
const velocity = useRef({
|
||||
x: Math.random() * 0.05,
|
||||
y: Math.random() * 0.05,
|
||||
z: Math.random() * 0.02,
|
||||
});
|
||||
const gravity = -0.001;
|
||||
|
||||
useFrame((state, delta) => {
|
||||
let position = ref.current.position;
|
||||
let velocityVector = new THREE.Vector3(velocity.current.x, velocity.current.y, velocity.current.z);
|
||||
|
||||
// Adjust gravity and velocity based on delta
|
||||
velocity.current.y += gravity * delta * 144; // Multiply by 144 to scale for 144 FPS
|
||||
|
||||
// Scale velocity changes by delta
|
||||
position.x += velocity.current.x * delta * 144;
|
||||
position.y += velocity.current.y * delta * 144;
|
||||
position.z += velocity.current.z * delta * 144;
|
||||
|
||||
if (!velocityVector.equals(new THREE.Vector3(0, 0, 0))) {
|
||||
const alignmentQuaternion = new THREE.Quaternion();
|
||||
alignmentQuaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), velocityVector.normalize());
|
||||
ref.current.quaternion.slerp(alignmentQuaternion, 0.1);
|
||||
}
|
||||
|
||||
if (position.y < 0.05) {
|
||||
position.set(0.6, 0.05, 0.5);
|
||||
velocity.current = {
|
||||
x: Math.random() * 0.05,
|
||||
y: Math.random() * 0.05,
|
||||
z: Math.random() * 0.02,
|
||||
};
|
||||
}
|
||||
|
||||
ref.current.position.set(position.x, position.y, position.z);
|
||||
});
|
||||
|
||||
|
||||
return (
|
||||
<mesh ref={ref} position={[0.6, 0.05, 0.5]} scale={[scale, scale * 20, scale]}>
|
||||
<sphereGeometry args={[0.01, 16, 16]} />
|
||||
<meshStandardMaterial
|
||||
emissive={turboColor}
|
||||
toneMapped={false}
|
||||
emissiveIntensity={5}
|
||||
/>
|
||||
</mesh>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,65 @@
|
|||
import { useRef } from 'react'
|
||||
import { useFrame } from '@react-three/fiber'
|
||||
import * as THREE from 'three'
|
||||
|
||||
export const Particles4 = ({ turboColor, scale, ...props }) => {
|
||||
const ref = useRef()
|
||||
const velocity = useRef({
|
||||
x: Math.random() * 0.05,
|
||||
y: Math.random() * 0.05,
|
||||
z: Math.random() * 0.02,
|
||||
})
|
||||
const gravity = -0.001
|
||||
|
||||
useFrame((state, delta) => {
|
||||
let position = ref.current.position
|
||||
let velocityVector = new THREE.Vector3(
|
||||
velocity.current.x,
|
||||
velocity.current.y,
|
||||
velocity.current.z
|
||||
)
|
||||
|
||||
// Adjust gravity and velocity based on delta
|
||||
velocity.current.y += gravity * delta * 144 // Multiply by 144 to scale for 144 FPS
|
||||
|
||||
// Scale velocity changes by delta
|
||||
position.x += velocity.current.x * delta * 144
|
||||
position.y += velocity.current.y * delta * 144
|
||||
position.z += velocity.current.z * delta * 144
|
||||
|
||||
if (!velocityVector.equals(new THREE.Vector3(0, 0, 0))) {
|
||||
const alignmentQuaternion = new THREE.Quaternion()
|
||||
alignmentQuaternion.setFromUnitVectors(
|
||||
new THREE.Vector3(0, 1, 0),
|
||||
velocityVector.normalize()
|
||||
)
|
||||
ref.current.quaternion.slerp(alignmentQuaternion, 0.1)
|
||||
}
|
||||
|
||||
if (position.y < 0.05) {
|
||||
position.set(0.6, 0.05, 0.5)
|
||||
velocity.current = {
|
||||
x: Math.random() * 0.05,
|
||||
y: Math.random() * 0.05,
|
||||
z: Math.random() * 0.02,
|
||||
}
|
||||
}
|
||||
|
||||
ref.current.position.set(position.x, position.y, position.z)
|
||||
})
|
||||
|
||||
return (
|
||||
<mesh
|
||||
ref={ref}
|
||||
position={[0.6, 0.05, 0.5]}
|
||||
scale={[scale, scale * 20, scale]}
|
||||
>
|
||||
<sphereGeometry args={[0.01, 16, 16]} />
|
||||
<meshStandardMaterial
|
||||
emissive={turboColor}
|
||||
toneMapped={false}
|
||||
emissiveIntensity={5}
|
||||
/>
|
||||
</mesh>
|
||||
)
|
||||
}
|
|
@ -2,12 +2,18 @@
|
|||
* FakeFlame material component by Anderson Mancini - Jan 2024.
|
||||
*/
|
||||
|
||||
import React, { useMemo, useRef } from 'react'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { shaderMaterial } from '@react-three/drei'
|
||||
import { extend, useFrame } from '@react-three/fiber'
|
||||
import { Color, DoubleSide, AdditiveBlending } from 'three'
|
||||
|
||||
export default function FakeFlame({ falloff = 3, glowInternalRadius = 1.0, glowColor = 'orange', glowSharpness = 1.0 , isBoosting,}) {
|
||||
export default function FakeFlame({
|
||||
falloff = 3,
|
||||
glowInternalRadius = 1.0,
|
||||
glowColor = 'orange',
|
||||
glowSharpness = 1.0,
|
||||
isBoosting = false,
|
||||
}) {
|
||||
const FakeFlame = useMemo(() => {
|
||||
return shaderMaterial(
|
||||
{
|
|
@ -11,7 +11,13 @@ import FakeFlame from '../../ShaderMaterials/FakeFlame/FakeFlame'
|
|||
import { useStore } from '../../store'
|
||||
import gsap from 'gsap'
|
||||
|
||||
export function Mario({ currentSpeed, steeringAngleWheels, isBoosting, shouldLaunch, ...props }) {
|
||||
export function Mario({
|
||||
currentSpeed,
|
||||
steeringAngleWheels,
|
||||
isBoosting,
|
||||
shouldLaunch,
|
||||
...props
|
||||
}) {
|
||||
const { nodes, materials } = useGLTF('./models/characters/mariokarttest.glb')
|
||||
|
||||
const frontLeftWheel = useRef()
|
||||
|
@ -21,7 +27,7 @@ export function Mario({ currentSpeed, steeringAngleWheels, isBoosting, shouldLau
|
|||
const [scale, setScale] = React.useState(1)
|
||||
const { actions } = useStore()
|
||||
const [shouldSlow, setShouldSlow] = React.useState(false)
|
||||
const mario = useRef();
|
||||
const mario = useRef()
|
||||
// isBoosting = true;
|
||||
|
||||
useFrame((_, delta) => {
|
||||
|
@ -35,22 +41,17 @@ export function Mario({ currentSpeed, steeringAngleWheels, isBoosting, shouldLau
|
|||
} else {
|
||||
setScale(Math.max(scale - 0.03 * 144 * delta, 0))
|
||||
}
|
||||
setShouldSlow(actions.getShouldSlowDown());
|
||||
setShouldSlow(actions.getShouldSlowDown())
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldLaunch) {
|
||||
gsap.to(mario.current.rotation, { duration: 1.5, y: Math.PI * 3 })
|
||||
mario.current.rotation.set(0, 0, 0);
|
||||
mario.current.rotation.set(0, 0, 0)
|
||||
}
|
||||
}, [shouldLaunch])
|
||||
return (
|
||||
<group
|
||||
{...props}
|
||||
rotation={[0, Math.PI, 0]}
|
||||
dispose={null}
|
||||
ref={mario}
|
||||
>
|
||||
<group {...props} rotation={[0, Math.PI, 0]} dispose={null} ref={mario}>
|
||||
<mesh
|
||||
castShadow
|
||||
receiveShadow
|
||||
|
@ -91,10 +92,7 @@ export function Mario({ currentSpeed, steeringAngleWheels, isBoosting, shouldLau
|
|||
geometry={nodes.mt_mario.geometry}
|
||||
material={materials.mt_mario}
|
||||
/>
|
||||
<Sphere
|
||||
position={[0, 0.6, -1.2]}
|
||||
scale={scale * 1.2}
|
||||
>
|
||||
<Sphere position={[0, 0.6, -1.2]} scale={scale * 1.2}>
|
||||
<FakeGlowMaterial />
|
||||
</Sphere>
|
||||
|
||||
|
@ -103,7 +101,6 @@ export function Mario({ currentSpeed, steeringAngleWheels, isBoosting, shouldLau
|
|||
position={[0.3, 0.65, -1.35]}
|
||||
rotation={[Math.PI / 1.5, 0, 0]}
|
||||
scale={scale}
|
||||
|
||||
>
|
||||
<FakeFlame isBoosting={isBoosting} />
|
||||
</Cylinder>
|
||||
|
@ -132,7 +129,6 @@ export function Mario({ currentSpeed, steeringAngleWheels, isBoosting, shouldLau
|
|||
>
|
||||
<FakeFlame isBoosting={isBoosting} />
|
||||
</Cylinder>
|
||||
|
||||
</group>
|
||||
)
|
||||
}
|
|
@ -1,169 +0,0 @@
|
|||
import { create } from "zustand";
|
||||
|
||||
export const playAudio = (path, callback) => {
|
||||
const audio = new Audio(`./sounds/${path}.mp3`);
|
||||
if (callback) {
|
||||
audio.addEventListener("ended", callback);
|
||||
}
|
||||
audio.play();
|
||||
};
|
||||
export const items = [
|
||||
"banana",
|
||||
"shell",
|
||||
]
|
||||
|
||||
export const useStore = create((set, get) => ({
|
||||
gameStarted: false,
|
||||
controls: "",
|
||||
particles1: [],
|
||||
particles2: [],
|
||||
bodyPosition: [0, 0, 0],
|
||||
bodyRotation: [0, 0, 0],
|
||||
pastPositions: [],
|
||||
shouldSlowdown: false,
|
||||
bananas: [],
|
||||
items: ["mushroom", "shell", "banana"],
|
||||
item: "",
|
||||
shells: [],
|
||||
skids: [],
|
||||
coins : 0,
|
||||
players : [],
|
||||
id : "",
|
||||
joystickX: 0,
|
||||
driftButton: false,
|
||||
itemButton: false,
|
||||
menuButton: false,
|
||||
addPastPosition: (position) => {
|
||||
set((state) => ({
|
||||
pastPositions: [position, ...state.pastPositions.slice(0, 499)],
|
||||
}));
|
||||
},
|
||||
actions: {
|
||||
addParticle1: (particle) => {
|
||||
set((state) => ({
|
||||
particles1: [...state.particles1, particle],
|
||||
}));
|
||||
},
|
||||
removeParticle1: (particle) => {
|
||||
set((state) => ({
|
||||
particles1: state.particles1.filter((p) => p.id !== particle.id),
|
||||
}));
|
||||
},
|
||||
addParticle2: (particle) => {
|
||||
set((state) => ({
|
||||
particles2: [...state.particles2, particle],
|
||||
}));
|
||||
},
|
||||
removeParticle2: (particle) => {
|
||||
set((state) => ({
|
||||
particles2: state.particles2.filter((p) => p.id !== particle.id),
|
||||
}));
|
||||
},
|
||||
setBodyPosition: (position) => {
|
||||
set({ bodyPosition: position });
|
||||
},
|
||||
setBodyRotation: (rotation) => {
|
||||
set({ bodyRotation: rotation });
|
||||
},
|
||||
getBodyPosition: () => {
|
||||
return get().bodyPosition;
|
||||
},
|
||||
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((id) => id !== banana.id),
|
||||
}));
|
||||
},
|
||||
getBananas: () => {
|
||||
return get().bananas;
|
||||
},
|
||||
removeBananaById: (id) => {
|
||||
set((state) => ({
|
||||
bananas: state.bananas.filter((b) => b.id !== id),
|
||||
}));
|
||||
},
|
||||
setBananas: (bananas) => {
|
||||
set({ 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),
|
||||
}));
|
||||
},
|
||||
addSkid: (skid) => {
|
||||
set((state) => ({
|
||||
skids: [...state.skids, skid],
|
||||
}));
|
||||
},
|
||||
addCoins : () => {
|
||||
set((state) => ({
|
||||
coins: state.coins + 1,
|
||||
}));
|
||||
},
|
||||
looseCoins : () => {
|
||||
set((state) => ({
|
||||
coins: state.coins - 1,
|
||||
}));
|
||||
},
|
||||
addPlayer : (player) => {
|
||||
set((state) => ({
|
||||
players: [...state.players, player],
|
||||
}));
|
||||
},
|
||||
removePlayer : (player) => {
|
||||
set((state) => ({
|
||||
players: state.players.filter((p) => p.id !== player.id),
|
||||
}));
|
||||
},
|
||||
setId : (id) => {
|
||||
set({id});
|
||||
},
|
||||
setGameStarted: (gameStarted) => {
|
||||
set({ gameStarted });
|
||||
},
|
||||
setControls: (controls) => {
|
||||
set({ controls });
|
||||
},
|
||||
setJoystickX: (joystickX) => {
|
||||
set({ joystickX });
|
||||
},
|
||||
setDriftButton: (driftButton) => {
|
||||
set({ driftButton });
|
||||
},
|
||||
setItemButton: (itemButton) => {
|
||||
set({ itemButton });
|
||||
},
|
||||
setMenuButton: (menuButton) => {
|
||||
set({ menuButton });
|
||||
},
|
||||
},
|
||||
|
||||
}));
|
|
@ -0,0 +1,253 @@
|
|||
import { StoreApi, UseBoundStore, create } from 'zustand'
|
||||
|
||||
type CallbackFunction = () => void
|
||||
|
||||
export const playAudio = (path: string, callback?: CallbackFunction): void => {
|
||||
const audio = new Audio(`./sounds/${path}.mp3`)
|
||||
if (callback) {
|
||||
audio.addEventListener('ended', callback)
|
||||
}
|
||||
audio.play()
|
||||
}
|
||||
|
||||
export const items: string[] = ['banana', 'shell']
|
||||
|
||||
type Position = [number, number, number]
|
||||
|
||||
interface Particle {
|
||||
id: string
|
||||
// Other properties can be added here as needed
|
||||
}
|
||||
|
||||
interface Banana {
|
||||
id: string
|
||||
// Other properties can be added here as needed
|
||||
}
|
||||
|
||||
interface Shell {
|
||||
id: string
|
||||
// Other properties can be added here as needed
|
||||
}
|
||||
|
||||
interface Skid {
|
||||
// Define properties for Skid
|
||||
}
|
||||
|
||||
interface Player {
|
||||
id: string
|
||||
// Other properties can be added here as needed
|
||||
}
|
||||
|
||||
interface StoreState {
|
||||
gameStarted: boolean
|
||||
controls: string
|
||||
particles1: Particle[]
|
||||
particles2: Particle[]
|
||||
bodyPosition: Position
|
||||
bodyRotation: Position
|
||||
pastPositions: Position[] // Define a more specific type if possible
|
||||
shouldSlowdown: boolean
|
||||
bananas: Banana[]
|
||||
items: string[]
|
||||
item: string
|
||||
shells: Shell[]
|
||||
skids: Skid[]
|
||||
coins: number
|
||||
players: Player[]
|
||||
id: string
|
||||
joystickX: number
|
||||
driftButton: boolean
|
||||
itemButton: boolean
|
||||
menuButton: boolean
|
||||
actions: {
|
||||
addPastPosition: (position: Position) => void
|
||||
addParticle1: (particle: Particle) => void
|
||||
removeParticle1: (particle: Particle) => void
|
||||
addParticle2: (particle: Particle) => void
|
||||
removeParticle2: (particle: Particle) => void
|
||||
setBodyPosition: (position: Position) => void
|
||||
setBodyRotation: (rotation: Position) => void
|
||||
getBodyPosition: () => Position
|
||||
getBodyRotation: () => Position
|
||||
setShouldSlowDown: (shouldSlowdown: boolean) => void
|
||||
getShouldSlowDown: () => boolean
|
||||
addBanana: (banana: Banana) => void
|
||||
removeBanana: (banana: Banana) => void
|
||||
getBananas: () => Banana[]
|
||||
removeBananaById: (id: string) => void
|
||||
setBananas: (bananas: Banana[]) => void
|
||||
setItem: () => void
|
||||
useItem: () => void
|
||||
addShell: (shell: Shell) => void
|
||||
removeShell: (shell: Shell) => void
|
||||
addSkid: (skid: Skid) => void
|
||||
addCoins: () => void
|
||||
looseCoins: () => void
|
||||
addPlayer: (player: Player) => void
|
||||
removePlayer: (player: Player) => void
|
||||
setId: (id: string) => void
|
||||
setGameStarted: (gameStarted: boolean) => void
|
||||
setControls: (controls: string) => void
|
||||
setJoystickX: (joystickX: number) => void
|
||||
setDriftButton: (driftButton: boolean) => void
|
||||
setItemButton: (itemButton: boolean) => void
|
||||
setMenuButton: (menuButton: boolean) => void
|
||||
}
|
||||
}
|
||||
|
||||
export const useStore: UseBoundStore<StoreApi<StoreState>> = create(
|
||||
(set, get) => ({
|
||||
gameStarted: false,
|
||||
controls: '',
|
||||
particles1: [],
|
||||
particles2: [],
|
||||
bodyPosition: [0, 0, 0],
|
||||
bodyRotation: [0, 0, 0],
|
||||
pastPositions: [],
|
||||
shouldSlowdown: false,
|
||||
bananas: [],
|
||||
items: ['mushroom', 'shell', 'banana'],
|
||||
item: '',
|
||||
shells: [],
|
||||
skids: [],
|
||||
coins: 0,
|
||||
players: [],
|
||||
id: '',
|
||||
joystickX: 0,
|
||||
driftButton: false,
|
||||
itemButton: false,
|
||||
menuButton: false,
|
||||
actions: {
|
||||
addPastPosition: (position: Position) => {
|
||||
set((state: StoreState) => ({
|
||||
pastPositions: [position, ...state.pastPositions.slice(0, 499)],
|
||||
}))
|
||||
},
|
||||
addParticle1: (particle: Particle) => {
|
||||
set((state: StoreState) => ({
|
||||
particles1: [...state.particles1, particle],
|
||||
}))
|
||||
},
|
||||
removeParticle1: (particle: Particle) => {
|
||||
set((state: StoreState) => ({
|
||||
particles1: state.particles1.filter((p) => p.id !== particle.id),
|
||||
}))
|
||||
},
|
||||
addParticle2: (particle: Particle) => {
|
||||
set((state: StoreState) => ({
|
||||
particles2: [...state.particles2, particle],
|
||||
}))
|
||||
},
|
||||
removeParticle2: (particle: Particle) => {
|
||||
set((state: StoreState) => ({
|
||||
particles2: state.particles2.filter((p) => p.id !== particle.id),
|
||||
}))
|
||||
},
|
||||
setBodyPosition: (position: Position) => {
|
||||
set({ bodyPosition: position })
|
||||
},
|
||||
setBodyRotation: (rotation: Position) => {
|
||||
set({ bodyRotation: rotation })
|
||||
},
|
||||
getBodyPosition: (): Position => {
|
||||
return (get() as StoreState).bodyPosition
|
||||
},
|
||||
getBodyRotation: (): Position => {
|
||||
return (get() as StoreState).bodyRotation
|
||||
},
|
||||
setShouldSlowDown: (shouldSlowdown: boolean) => {
|
||||
set({ shouldSlowdown })
|
||||
},
|
||||
getShouldSlowDown: (): boolean => {
|
||||
return (get() as StoreState).shouldSlowdown
|
||||
},
|
||||
addBanana: (banana: Banana) => {
|
||||
set((state: StoreState) => ({
|
||||
bananas: [...state.bananas, banana],
|
||||
}))
|
||||
},
|
||||
removeBanana: (banana: Banana) => {
|
||||
set((state: StoreState) => ({
|
||||
bananas: state.bananas.filter((b) => b.id !== banana.id),
|
||||
}))
|
||||
},
|
||||
getBananas: () => {
|
||||
return (get() as StoreState).bananas
|
||||
},
|
||||
removeBananaById: (id: string) => {
|
||||
set((state: StoreState) => ({
|
||||
bananas: state.bananas.filter((b) => b.id !== id),
|
||||
}))
|
||||
},
|
||||
setBananas: (bananas: Banana[]) => {
|
||||
set({ bananas })
|
||||
},
|
||||
setItem: () => {
|
||||
set((state: StoreState) => ({
|
||||
item: state.items[Math.floor(Math.random() * state.items.length)],
|
||||
}))
|
||||
},
|
||||
useItem: () => {
|
||||
set((state: StoreState) => ({
|
||||
item: '',
|
||||
}))
|
||||
},
|
||||
addShell: (shell: Shell) => {
|
||||
set((state: StoreState) => ({
|
||||
shells: [...state.shells, shell],
|
||||
}))
|
||||
},
|
||||
removeShell: (shell: Shell) => {
|
||||
set((state: StoreState) => ({
|
||||
shells: state.shells.filter((s) => s.id !== shell.id),
|
||||
}))
|
||||
},
|
||||
addSkid: (skid: Skid) => {
|
||||
set((state: StoreState) => ({
|
||||
skids: [...state.skids, skid],
|
||||
}))
|
||||
},
|
||||
addCoins: () => {
|
||||
set((state: StoreState) => ({
|
||||
coins: state.coins + 1,
|
||||
}))
|
||||
},
|
||||
looseCoins: () => {
|
||||
set((state: StoreState) => ({
|
||||
coins: state.coins - 1,
|
||||
}))
|
||||
},
|
||||
addPlayer: (player: Player) => {
|
||||
set((state: StoreState) => ({
|
||||
players: [...state.players, player],
|
||||
}))
|
||||
},
|
||||
removePlayer: (player: Player) => {
|
||||
set((state: StoreState) => ({
|
||||
players: state.players.filter((p) => p.id !== player.id),
|
||||
}))
|
||||
},
|
||||
setId: (id: string) => {
|
||||
set({ id })
|
||||
},
|
||||
setGameStarted: (gameStarted: boolean) => {
|
||||
set({ gameStarted })
|
||||
},
|
||||
setControls: (controls: string) => {
|
||||
set({ controls })
|
||||
},
|
||||
setJoystickX: (joystickX: number) => {
|
||||
set({ joystickX })
|
||||
},
|
||||
setDriftButton: (driftButton: boolean) => {
|
||||
set({ driftButton })
|
||||
},
|
||||
setItemButton: (itemButton: boolean) => {
|
||||
set({ itemButton })
|
||||
},
|
||||
setMenuButton: (menuButton: boolean) => {
|
||||
set({ menuButton })
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
|
@ -1,26 +0,0 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import * as THREE from 'three';
|
||||
|
||||
export const useCurvedPathPoints = (jsonPath) => {
|
||||
const [points, setPoints] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const loadPointsFromJson = async () => {
|
||||
try {
|
||||
const response = await fetch(jsonPath);
|
||||
const data = await response.json();
|
||||
setPoints(data.points.map(point => new THREE.Vector3(point.x, point.y, point.z)));
|
||||
setLoading(false);
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadPointsFromJson();
|
||||
}, [jsonPath]);
|
||||
|
||||
return { points, loading, error };
|
||||
};
|
|
@ -0,0 +1,40 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import * as THREE from 'three'
|
||||
|
||||
interface Point {
|
||||
x: number
|
||||
y: number
|
||||
z: number
|
||||
}
|
||||
|
||||
interface PointsData {
|
||||
points: Point[]
|
||||
}
|
||||
|
||||
export const useCurvedPathPoints = (jsonPath: string) => {
|
||||
const [points, setPoints] = useState<THREE.Vector3[]>([])
|
||||
const [loading, setLoading] = useState<boolean>(true)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const loadPointsFromJson = async () => {
|
||||
try {
|
||||
const response = await fetch(jsonPath)
|
||||
const data: PointsData = await response.json()
|
||||
setPoints(
|
||||
data.points.map(
|
||||
(point) => new THREE.Vector3(point.x, point.y, point.z)
|
||||
)
|
||||
)
|
||||
setLoading(false)
|
||||
} catch (err) {
|
||||
setError(err as Error)
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
loadPointsFromJson()
|
||||
}, [jsonPath])
|
||||
|
||||
return { points, loading, error }
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
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;
|
||||
};
|
|
@ -0,0 +1,122 @@
|
|||
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
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
@import url("https://fonts.googleapis.com/css2?family=Hanken+Grotesk:ital,wght@0,100..900;1,100..900&display=swap");
|
||||
@import url('https://fonts.googleapis.com/css2?family=Hanken+Grotesk:ital,wght@0,100..900;1,100..900&display=swap');
|
||||
|
||||
#root {
|
||||
#app {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ body::-webkit-scrollbar {
|
|||
border: 2px solid transparent;
|
||||
border-radius: 50em;
|
||||
.background {
|
||||
background-image: url("./scanline.jpg");
|
||||
background-image: url('./scanline.jpg');
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
width: 100%;
|
||||
|
@ -113,7 +113,7 @@ body::-webkit-scrollbar {
|
|||
|
||||
.drift {
|
||||
right: 50px;
|
||||
font-family: "Hanken Grotesk";
|
||||
font-family: 'Hanken Grotesk';
|
||||
border-radius: 100px;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
height: 66.6667px;
|
||||
|
@ -156,7 +156,7 @@ body::-webkit-scrollbar {
|
|||
display: grid;
|
||||
place-content: center;
|
||||
z-index: 2;
|
||||
font-family: "Hanken Grotesk";
|
||||
font-family: 'Hanken Grotesk';
|
||||
|
||||
.logo {
|
||||
img {
|
||||
|
@ -334,12 +334,11 @@ body::-webkit-scrollbar {
|
|||
100% {
|
||||
backdrop-filter: blur(15px);
|
||||
webkit-backdrop-filter: blur(15px);
|
||||
|
||||
}
|
||||
}
|
||||
.controls.itemButton {
|
||||
right: 150px;
|
||||
font-family: "Hanken Grotesk";
|
||||
font-family: 'Hanken Grotesk';
|
||||
border-radius: 100px;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
height: 66.6667px;
|
||||
|
@ -356,7 +355,7 @@ body::-webkit-scrollbar {
|
|||
.controls.menuButton {
|
||||
right: 50px;
|
||||
top: 60px;
|
||||
font-family: "Hanken Grotesk";
|
||||
font-family: 'Hanken Grotesk';
|
||||
border-radius: 100px;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
height: 66.6667px;
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App'
|
||||
import './index.css'
|
||||
import { HUD } from './HUD'
|
||||
import { Landing } from './Landing'
|
||||
import { useStore } from './components/store'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
const container = document.getElementById('app')
|
||||
const root = createRoot(container!)
|
||||
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
<HUD />
|
||||
<Landing />
|
||||
</React.StrictMode>,
|
||||
</React.StrictMode>
|
||||
)
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
Before Width: | Height: | Size: 4.0 KiB |
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
|
@ -1,10 +1,10 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react-swc'
|
||||
import viteTsconfigPaths from 'vite-tsconfig-paths'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
plugins: [react(), viteTsconfigPaths()],
|
||||
optimizeDeps: {
|
||||
exclude: ['js-big-decimal']
|
||||
}
|
||||
exclude: ['js-big-decimal'],
|
||||
},
|
||||
})
|
Loading…
Reference in New Issue