feat:(game) added a small landing to allow control choice, switch between M&K, K or Gamepad
parent
3ef3ec7d25
commit
c22c9e9012
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
After Width: | Height: | Size: 548 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.3 MiB |
Binary file not shown.
After Width: | Height: | Size: 1.3 MiB |
13
src/App.jsx
13
src/App.jsx
|
@ -5,6 +5,7 @@ import { Physics } from '@react-three/rapier'
|
||||||
import { KeyboardControls, Loader, OrbitControls, Preload, Stats } from '@react-three/drei'
|
import { KeyboardControls, Loader, OrbitControls, Preload, Stats } from '@react-three/drei'
|
||||||
import { insertCoin, onPlayerJoin } from 'playroomkit'
|
import { insertCoin, onPlayerJoin } from 'playroomkit'
|
||||||
import { useStore } from "./components/store";
|
import { useStore } from "./components/store";
|
||||||
|
import * as THREE from "three";
|
||||||
|
|
||||||
export const Controls = {
|
export const Controls = {
|
||||||
up: 'up',
|
up: 'up',
|
||||||
|
@ -37,13 +38,13 @@ function App() {
|
||||||
|
|
||||||
onPlayerJoin((state) => {
|
onPlayerJoin((state) => {
|
||||||
actions.addPlayer(state);
|
actions.addPlayer(state);
|
||||||
console.log('player joined', state);
|
|
||||||
actions.setId(state.id);
|
actions.setId(state.id);
|
||||||
console.log(state)
|
|
||||||
|
|
||||||
state.onQuit(() => {
|
state.onQuit(() => {
|
||||||
actions.removePlayer(state);
|
actions.removePlayer(state);
|
||||||
console.log('player quit', state);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -59,7 +60,11 @@ function App() {
|
||||||
shadows
|
shadows
|
||||||
dpr={1}
|
dpr={1}
|
||||||
gl={{ antialias: false, stencil: false, powerPreference: 'high-performance' }}
|
gl={{ antialias: false, stencil: false, powerPreference: 'high-performance' }}
|
||||||
>
|
mode="concurrent"
|
||||||
|
onCreated={({ gl, camera }) => {
|
||||||
|
gl.toneMapping = THREE.AgXToneMapping
|
||||||
|
// gl.setClearColor(new THREE.Color('#020209'))
|
||||||
|
}}>
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<Preload all />
|
<Preload all />
|
||||||
<Physics
|
<Physics
|
||||||
|
|
22
src/HUD.jsx
22
src/HUD.jsx
|
@ -4,7 +4,7 @@ import { useStore } from "./components/store";
|
||||||
export const HUD = () => {
|
export const HUD = () => {
|
||||||
const wheel = useRef();
|
const wheel = useRef();
|
||||||
const [image, setImage] = useState("");
|
const [image, setImage] = useState("");
|
||||||
const {item} = useStore();
|
const { item, gameStarted } = useStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleMouseMove = (e) => {
|
const handleMouseMove = (e) => {
|
||||||
|
@ -44,9 +44,8 @@ export const HUD = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overlay">
|
<div className="overlay">
|
||||||
<div className="logo">
|
{gameStarted && (
|
||||||
<img src="./logo.png" alt="logo" />
|
<>
|
||||||
</div>
|
|
||||||
<div className="item">
|
<div className="item">
|
||||||
<div className="borderOut">
|
<div className="borderOut">
|
||||||
<div className="borderIn">
|
<div className="borderIn">
|
||||||
|
@ -56,19 +55,8 @@ export const HUD = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="wheel">
|
</>
|
||||||
<img
|
)}
|
||||||
ref={wheel}
|
|
||||||
src="./steering_wheel.png"
|
|
||||||
alt="steering wheel"
|
|
||||||
className="steering-wheel"
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
pointerEvents: "none",
|
|
||||||
transformOrigin: "center",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
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",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [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)}>
|
||||||
|
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")}>
|
||||||
|
<h2>Keyboard</h2>
|
||||||
|
<img src="./images/keyboard.png" alt="keyboard" />
|
||||||
|
</div>
|
||||||
|
<div className={controlStyle === "gamepad" ? "article selected" : "article"} onClick={() =>
|
||||||
|
setControlStyle("gamepad")}>
|
||||||
|
<h2>Gamepad</h2>
|
||||||
|
<img src="./images/gamepad.png" alt="gamepad" />
|
||||||
|
</div>
|
||||||
|
<div className={controlStyle === "mouseKeyboard" ? "article selected" : "article"} onClick={() =>
|
||||||
|
setControlStyle("mouseKeyboard")}>
|
||||||
|
<h2>Mouse & Keybaord</h2>
|
||||||
|
<img src="./images/mousekeyboard.png" alt="mouse & keyboard" />
|
||||||
|
</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>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
|
||||||
|
);
|
||||||
|
};
|
|
@ -7,6 +7,8 @@ import {
|
||||||
} from "@react-three/drei";
|
} from "@react-three/drei";
|
||||||
import { Ground } from "./Ground";
|
import { Ground } from "./Ground";
|
||||||
import { PlayerController } from "./PlayerController";
|
import { PlayerController } from "./PlayerController";
|
||||||
|
import { PlayerControllerGamepad } from "./PlayerControllerGamepad";
|
||||||
|
import { PlayerControllerKeyboard } from "./PlayerControllerKeyboard";
|
||||||
import { Paris } from "./models/tracks/Tour_paris_promenade";
|
import { Paris } from "./models/tracks/Tour_paris_promenade";
|
||||||
import {
|
import {
|
||||||
EffectComposer,
|
EffectComposer,
|
||||||
|
@ -34,24 +36,40 @@ import {
|
||||||
useMultiplayerState,
|
useMultiplayerState,
|
||||||
} from "playroomkit";
|
} from "playroomkit";
|
||||||
import { PlayerDummies } from "./PlayerDummies";
|
import { PlayerDummies } from "./PlayerDummies";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, useRef } from "react";
|
||||||
import { useFrame, useLoader } from "@react-three/fiber";
|
import { useFrame, useLoader } from "@react-three/fiber";
|
||||||
import { LUTPass, LUTCubeLoader } from 'three-stdlib'
|
import { LUTPass, LUTCubeLoader } from "three-stdlib";
|
||||||
|
import { useCurvedPathPoints } from "./useCurvedPath";
|
||||||
|
|
||||||
export const Experience = () => {
|
export const Experience = () => {
|
||||||
const onCollide = (event) => {
|
const onCollide = (event) => {};
|
||||||
console.log(event);
|
const { gameStarted, bananas, shells, players, id, actions, controls } =
|
||||||
};
|
useStore();
|
||||||
const { bananas, shells, players, id, actions } = useStore();
|
|
||||||
const [networkBananas, setNetworkBananas] = useMultiplayerState(
|
const [networkBananas, setNetworkBananas] = useMultiplayerState(
|
||||||
"bananas",
|
"bananas",
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { points, loading, error } = useCurvedPathPoints("./CurvedPath.json");
|
||||||
|
|
||||||
const [networkShells, setNetworkShells] = useMultiplayerState("shells", []);
|
const [networkShells, setNetworkShells] = useMultiplayerState("shells", []);
|
||||||
|
const [pointest, setPointest] = useState([]);
|
||||||
|
const [currentPoint, setCurrentPoint] = useState(0);
|
||||||
|
useEffect(() => {
|
||||||
|
if (points) {
|
||||||
|
//This is adjusted to Paris scale
|
||||||
|
const scaledPoints = points.map((point) => ({
|
||||||
|
x: point.x * 50,
|
||||||
|
y: point.y * 50,
|
||||||
|
z: point.z * 50,
|
||||||
|
}));
|
||||||
|
setPointest(scaledPoints.reverse());
|
||||||
|
}
|
||||||
|
}, [points]);
|
||||||
|
|
||||||
const testing = getState("bananas");
|
const testing = getState("bananas");
|
||||||
|
const cam = useRef();
|
||||||
|
const lookAtTarget = useRef();
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// setNetworkBananas(bananas);
|
// setNetworkBananas(bananas);
|
||||||
// }, [bananas]);
|
// }, [bananas]);
|
||||||
|
@ -59,13 +77,42 @@ export const Experience = () => {
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// setNetworkShells(shells);
|
// setNetworkShells(shells);
|
||||||
// }, [shells]);
|
// }, [shells]);
|
||||||
|
const speedFactor = 5;
|
||||||
|
const { texture } = useLoader(LUTCubeLoader, "./cubicle-99.CUBE");
|
||||||
|
useFrame((state, delta) => {
|
||||||
|
if (!gameStarted) {
|
||||||
|
const camera = cam.current;
|
||||||
|
|
||||||
const {texture}= useLoader(LUTCubeLoader, "./cubicle-99.CUBE");
|
if (currentPoint < pointest.length - 1) {
|
||||||
|
camera.position.lerp(pointest[currentPoint], delta * speedFactor);
|
||||||
|
lookAtTarget.current.position.lerp(
|
||||||
|
pointest[currentPoint + 1],
|
||||||
|
delta * speedFactor
|
||||||
|
);
|
||||||
|
camera.lookAt(lookAtTarget.current.position);
|
||||||
|
|
||||||
|
if (camera.position.distanceTo(pointest[currentPoint]) < 5) {
|
||||||
|
setCurrentPoint(currentPoint + 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setCurrentPoint(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{players.map((player) => (
|
{gameStarted &&
|
||||||
<PlayerController
|
players.map((player) => {
|
||||||
|
const ControllerComponent =
|
||||||
|
controls === "keyboard"
|
||||||
|
? PlayerControllerKeyboard
|
||||||
|
: controls === "gamepad"
|
||||||
|
? PlayerControllerGamepad
|
||||||
|
: PlayerController;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ControllerComponent
|
||||||
key={player.id}
|
key={player.id}
|
||||||
player={player}
|
player={player}
|
||||||
userPlayer={player.id === myPlayer()?.id}
|
userPlayer={player.id === myPlayer()?.id}
|
||||||
|
@ -74,19 +121,29 @@ export const Experience = () => {
|
||||||
networkBananas={networkBananas}
|
networkBananas={networkBananas}
|
||||||
networkShells={networkShells}
|
networkShells={networkShells}
|
||||||
/>
|
/>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
{players.map((player) => (
|
{gameStarted &&
|
||||||
|
players.map((player) => (
|
||||||
<PlayerDummies
|
<PlayerDummies
|
||||||
key={player.id}
|
key={player.id}
|
||||||
player={player}
|
player={player}
|
||||||
userPlayer={player.id === myPlayer()?.id}
|
userPlayer={player.id === myPlayer()?.id}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
{!gameStarted && (
|
||||||
|
<>
|
||||||
|
<mesh ref={lookAtTarget}></mesh>
|
||||||
|
<PerspectiveCamera
|
||||||
|
ref={cam}
|
||||||
|
makeDefault
|
||||||
|
position={[0, 2, 0]}
|
||||||
|
far={5000}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<Paris position={[0, 0, 0]} />
|
<Paris position={[0, 0, 0]} />
|
||||||
{/* <Banana onCollide={onCollide} position={[-10, 1.8, -119]} /> */}
|
|
||||||
{/* <Shell position={[-20, 2, -119]} /> */}
|
|
||||||
<ItemBox position={[-20, 2.5, -119]} />
|
<ItemBox position={[-20, 2.5, -119]} />
|
||||||
<Coin position={[-30, 2, -119]} />
|
<Coin position={[-30, 2, -119]} />
|
||||||
|
|
||||||
|
@ -100,7 +157,6 @@ export const Experience = () => {
|
||||||
setNetworkBananas={setNetworkBananas}
|
setNetworkBananas={setNetworkBananas}
|
||||||
networkBananas={networkBananas}
|
networkBananas={networkBananas}
|
||||||
id={banana.id}
|
id={banana.id}
|
||||||
// rotation={banana.rotation}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{networkShells.map((shell) => (
|
{networkShells.map((shell) => (
|
||||||
|
@ -111,11 +167,10 @@ export const Experience = () => {
|
||||||
rotation={shell.rotation}
|
rotation={shell.rotation}
|
||||||
setNetworkShells={setNetworkShells}
|
setNetworkShells={setNetworkShells}
|
||||||
networkShells={networkShells}
|
networkShells={networkShells}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* <directionalLight
|
<directionalLight
|
||||||
position={[10, 50, -30]}
|
position={[10, 50, -30]}
|
||||||
intensity={1}
|
intensity={1}
|
||||||
shadow-bias={-0.0001}
|
shadow-bias={-0.0001}
|
||||||
|
@ -125,7 +180,7 @@ export const Experience = () => {
|
||||||
shadow-camera-top={300}
|
shadow-camera-top={300}
|
||||||
shadow-camera-bottom={-300}
|
shadow-camera-bottom={-300}
|
||||||
castShadow
|
castShadow
|
||||||
/> */}
|
/>
|
||||||
|
|
||||||
<EffectComposer
|
<EffectComposer
|
||||||
multisampling={0}
|
multisampling={0}
|
||||||
|
|
|
@ -90,7 +90,7 @@ export const PlayerController = ({
|
||||||
const effectiveBoost = useRef(0);
|
const effectiveBoost = useRef(0);
|
||||||
const text = useRef();
|
const text = useRef();
|
||||||
|
|
||||||
const { actions, shouldSlowDown, item, bananas, coins, id } = useStore();
|
const { actions, shouldSlowDown, item, bananas, coins, id, controls } = useStore();
|
||||||
const slowDownDuration = useRef(1500);
|
const slowDownDuration = useRef(1500);
|
||||||
|
|
||||||
useFrame(({ pointer, clock }, delta) => {
|
useFrame(({ pointer, clock }, delta) => {
|
||||||
|
@ -576,6 +576,7 @@ export const PlayerController = ({
|
||||||
position={[0, 2, 8]}
|
position={[0, 2, 8]}
|
||||||
fov={50}
|
fov={50}
|
||||||
ref={cam}
|
ref={cam}
|
||||||
|
far={5000}
|
||||||
/>
|
/>
|
||||||
<PositionalAudio
|
<PositionalAudio
|
||||||
ref={engineSound}
|
ref={engineSound}
|
||||||
|
|
|
@ -0,0 +1,623 @@
|
||||||
|
import { Controls } from "../App";
|
||||||
|
import { BallCollider, RigidBody, useRapier, vec3 } from "@react-three/rapier";
|
||||||
|
import {
|
||||||
|
useKeyboardControls,
|
||||||
|
PerspectiveCamera,
|
||||||
|
PositionalAudio,
|
||||||
|
} from "@react-three/drei";
|
||||||
|
import { useFrame, useThree, extend } from "@react-three/fiber";
|
||||||
|
import { useRef, useState, useEffect, useCallback } from "react";
|
||||||
|
import * as THREE from "three";
|
||||||
|
|
||||||
|
import { Mario } from "./models/characters/Mario_kart";
|
||||||
|
import { DriftParticlesLeft } from "./Particles/drifts/DriftParticlesLeft";
|
||||||
|
import { DriftParticlesRight } from "./Particles/drifts/DriftParticlesRight";
|
||||||
|
|
||||||
|
import { PointParticle } from "./Particles/drifts/PointParticle";
|
||||||
|
|
||||||
|
import { FlameParticles } from "./Particles/flames/FlameParticles";
|
||||||
|
import { useStore } from "./store";
|
||||||
|
import { Cylinder } from "@react-three/drei";
|
||||||
|
import FakeGlowMaterial from "./ShaderMaterials/FakeGlow/FakeGlowMaterial";
|
||||||
|
import { HitParticles } from "./Particles/hits/HitParticles";
|
||||||
|
import { CoinParticles } from "./Particles/coins/CoinParticles";
|
||||||
|
import { ItemParticles } from "./Particles/items/ItemParticles";
|
||||||
|
import { geometry } from "maath";
|
||||||
|
import { useGamepad } from "./useGamepad";
|
||||||
|
extend(geometry);
|
||||||
|
|
||||||
|
export const PlayerControllerGamepad = ({
|
||||||
|
player,
|
||||||
|
userPlayer,
|
||||||
|
setNetworkBananas,
|
||||||
|
setNetworkShells,
|
||||||
|
networkBananas,
|
||||||
|
networkShells,
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const [isOnGround, setIsOnGround] = useState(false);
|
||||||
|
const body = useRef();
|
||||||
|
const kart = useRef();
|
||||||
|
const cam = useRef();
|
||||||
|
const initialSpeed = 0;
|
||||||
|
const maxSpeed = 30;
|
||||||
|
const boostSpeed = 50;
|
||||||
|
const acceleration = 0.1;
|
||||||
|
const decceleration = 0.2;
|
||||||
|
const damping = -0.1;
|
||||||
|
const MaxSteeringSpeed = 0.01;
|
||||||
|
const [currentSteeringSpeed, setCurrentSteeringSpeed] = useState(0);
|
||||||
|
const [currentSpeed, setCurrentSpeed] = useState(initialSpeed);
|
||||||
|
const camMaxOffset = 1;
|
||||||
|
let steeringAngle = 0;
|
||||||
|
const isOnFloor = useRef(false);
|
||||||
|
const jumpForce = useRef(0);
|
||||||
|
const jumpIsHeld = useRef(false);
|
||||||
|
const driftDirection = useRef(0);
|
||||||
|
const driftLeft = useRef(false);
|
||||||
|
const driftRight = useRef(false);
|
||||||
|
const driftForce = useRef(0);
|
||||||
|
const mario = useRef();
|
||||||
|
const accumulatedDriftPower = useRef(0);
|
||||||
|
const blueTurboThreshold = 10;
|
||||||
|
const orangeTurboThreshold = 30;
|
||||||
|
const purpleTurboThreshold = 60;
|
||||||
|
const [turboColor, setTurboColor] = useState(0xffffff);
|
||||||
|
const boostDuration = useRef(0);
|
||||||
|
const [isBoosting, setIsBoosting] = useState(false);
|
||||||
|
let targetXPosition = 0;
|
||||||
|
let targetZPosition = 8;
|
||||||
|
const [steeringAngleWheels, setSteeringAngleWheels] = useState(0);
|
||||||
|
const engineSound = useRef();
|
||||||
|
const driftSound = useRef();
|
||||||
|
const driftTwoSound = useRef();
|
||||||
|
const driftOrangeSound = useRef();
|
||||||
|
const driftPurpleSound = useRef();
|
||||||
|
const driftBlueSound = useRef();
|
||||||
|
const jumpSound = useRef();
|
||||||
|
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 effectiveBoost = useRef(0);
|
||||||
|
const text = useRef();
|
||||||
|
|
||||||
|
const { actions, shouldSlowDown, item, bananas, coins, id, controls } = useStore();
|
||||||
|
const slowDownDuration = useRef(1500);
|
||||||
|
const { buttonA, buttonB, RB, LB, joystick, select} = useGamepad();
|
||||||
|
|
||||||
|
useFrame(({ pointer, clock }, delta) => {
|
||||||
|
if (player.id !== id) return;
|
||||||
|
const time = clock.getElapsedTime();
|
||||||
|
if (!body.current && !mario.current) return;
|
||||||
|
engineSound.current.setVolume(currentSpeed / 300 + 0.2);
|
||||||
|
engineSound.current.setPlaybackRate(currentSpeed / 10 + 0.1);
|
||||||
|
jumpSound.current.setPlaybackRate(1.5);
|
||||||
|
jumpSound.current.setVolume(0.5);
|
||||||
|
driftSound.current.setVolume(0.2);
|
||||||
|
|
||||||
|
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;
|
||||||
|
const forwardDirection = new THREE.Vector3(
|
||||||
|
-Math.sin(kartRotation),
|
||||||
|
0,
|
||||||
|
-Math.cos(kartRotation)
|
||||||
|
);
|
||||||
|
|
||||||
|
// mouse steering
|
||||||
|
|
||||||
|
if (!driftLeft.current && !driftRight.current) {
|
||||||
|
steeringAngle = currentSteeringSpeed * -joystick[0];
|
||||||
|
targetXPosition = -camMaxOffset * -joystick[0];
|
||||||
|
} else if (driftLeft.current && !driftRight.current) {
|
||||||
|
steeringAngle = currentSteeringSpeed * -(joystick[0] - 1);
|
||||||
|
targetXPosition = -camMaxOffset * -joystick[0];
|
||||||
|
} else if (driftRight.current && !driftLeft.current) {
|
||||||
|
steeringAngle = currentSteeringSpeed * -(joystick[0] + 1);
|
||||||
|
targetXPosition = -camMaxOffset * -joystick[0];
|
||||||
|
}
|
||||||
|
// ACCELERATING
|
||||||
|
const shouldSlow = actions.getShouldSlowDown();
|
||||||
|
|
||||||
|
if (buttonA && currentSpeed < maxSpeed) {
|
||||||
|
// Accelerate the kart within the maximum speed limit
|
||||||
|
setCurrentSpeed(
|
||||||
|
Math.min(currentSpeed + acceleration * delta * 144, maxSpeed)
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
buttonA &&
|
||||||
|
currentSpeed > maxSpeed &&
|
||||||
|
effectiveBoost.current > 0
|
||||||
|
) {
|
||||||
|
setCurrentSpeed(
|
||||||
|
Math.max(currentSpeed - decceleration * delta * 144, maxSpeed)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buttonA) {
|
||||||
|
if (currentSteeringSpeed < MaxSteeringSpeed) {
|
||||||
|
setCurrentSteeringSpeed(
|
||||||
|
Math.min(
|
||||||
|
currentSteeringSpeed + 0.0001 * delta * 144,
|
||||||
|
MaxSteeringSpeed
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 (buttonB && currentSpeed < -maxSpeed) {
|
||||||
|
setCurrentSpeed(
|
||||||
|
Math.max(currentSpeed - acceleration * delta * 144, -maxSpeed)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// DECELERATING
|
||||||
|
else if (!buttonA && !buttonB) {
|
||||||
|
if (currentSteeringSpeed > 0) {
|
||||||
|
setCurrentSteeringSpeed(
|
||||||
|
Math.max(currentSteeringSpeed - 0.00005 * delta * 144, 0)
|
||||||
|
);
|
||||||
|
} else if (currentSteeringSpeed < 0) {
|
||||||
|
setCurrentSteeringSpeed(
|
||||||
|
Math.min(currentSteeringSpeed + 0.00005 * delta * 144, 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setCurrentSpeed(Math.max(currentSpeed - decceleration * delta * 144, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the kart's rotation based on the steering angle
|
||||||
|
kart.current.rotation.y += steeringAngle * delta * 144;
|
||||||
|
|
||||||
|
// Apply damping to simulate slowdown when no keys are pressed
|
||||||
|
body.current.applyImpulse(
|
||||||
|
{
|
||||||
|
x: -body.current.linvel().x * (1 - damping) * delta * 144,
|
||||||
|
y: 0,
|
||||||
|
z: -body.current.linvel().z * (1 - damping) * delta * 144,
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
const bodyPosition = body.current.translation();
|
||||||
|
kart.current.position.set(
|
||||||
|
bodyPosition.x,
|
||||||
|
bodyPosition.y - 0.5,
|
||||||
|
bodyPosition.z
|
||||||
|
);
|
||||||
|
|
||||||
|
// JUMPING
|
||||||
|
if (RB && isOnGround && !jumpIsHeld.current) {
|
||||||
|
jumpForce.current += 10;
|
||||||
|
isOnFloor.current = false;
|
||||||
|
jumpIsHeld.current = true;
|
||||||
|
jumpSound.current.play();
|
||||||
|
setIsOnGround(false);
|
||||||
|
|
||||||
|
if (jumpSound.current.isPlaying) {
|
||||||
|
jumpSound.current.stop();
|
||||||
|
jumpSound.current.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOnFloor.current && jumpForce.current > 0) {
|
||||||
|
landingSound.current.play();
|
||||||
|
}
|
||||||
|
if (!isOnGround && jumpForce.current > 0) {
|
||||||
|
jumpForce.current -= 1 * delta * 144;
|
||||||
|
}
|
||||||
|
if (!RB) {
|
||||||
|
jumpIsHeld.current = false;
|
||||||
|
driftDirection.current = 0;
|
||||||
|
driftForce.current = 0;
|
||||||
|
driftLeft.current = false;
|
||||||
|
driftRight.current = false;
|
||||||
|
}
|
||||||
|
// DRIFTING
|
||||||
|
if (
|
||||||
|
jumpIsHeld.current &&
|
||||||
|
currentSteeringSpeed > 0 &&
|
||||||
|
joystick[0] < -0.1 &&
|
||||||
|
!driftRight.current
|
||||||
|
) {
|
||||||
|
driftLeft.current = true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
jumpIsHeld.current &&
|
||||||
|
currentSteeringSpeed > 0 &&
|
||||||
|
joystick[0] > 0.1 &&
|
||||||
|
!driftLeft.current
|
||||||
|
) {
|
||||||
|
driftRight.current = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!jumpIsHeld.current && !driftLeft.current && !driftRight.current) {
|
||||||
|
mario.current.rotation.y = THREE.MathUtils.lerp(
|
||||||
|
mario.current.rotation.y,
|
||||||
|
0,
|
||||||
|
0.0001 * delta * 144
|
||||||
|
);
|
||||||
|
setTurboColor(0xffffff);
|
||||||
|
accumulatedDriftPower.current = 0;
|
||||||
|
driftSound.current.stop();
|
||||||
|
driftTwoSound.current.stop();
|
||||||
|
driftOrangeSound.current.stop();
|
||||||
|
driftPurpleSound.current.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (driftLeft.current) {
|
||||||
|
driftDirection.current = 1;
|
||||||
|
driftForce.current = 0.4;
|
||||||
|
mario.current.rotation.y = THREE.MathUtils.lerp(
|
||||||
|
mario.current.rotation.y,
|
||||||
|
steeringAngle * 25 + 0.4,
|
||||||
|
0.05 * delta * 144
|
||||||
|
);
|
||||||
|
accumulatedDriftPower.current += 0.1 * (steeringAngle + 1) * delta * 144;
|
||||||
|
}
|
||||||
|
if (driftRight.current) {
|
||||||
|
driftDirection.current = -1;
|
||||||
|
driftForce.current = 0.4;
|
||||||
|
mario.current.rotation.y = THREE.MathUtils.lerp(
|
||||||
|
mario.current.rotation.y,
|
||||||
|
-(-steeringAngle * 25 + 0.4),
|
||||||
|
0.05 * delta * 144
|
||||||
|
);
|
||||||
|
accumulatedDriftPower.current += 0.1 * (-steeringAngle + 1) * delta * 144;
|
||||||
|
}
|
||||||
|
if (!driftLeft.current && !driftRight.current) {
|
||||||
|
mario.current.rotation.y = THREE.MathUtils.lerp(
|
||||||
|
mario.current.rotation.y,
|
||||||
|
steeringAngle * 30,
|
||||||
|
0.05 * delta * 144
|
||||||
|
);
|
||||||
|
setScale(0);
|
||||||
|
}
|
||||||
|
if (accumulatedDriftPower.current > blueTurboThreshold) {
|
||||||
|
setTurboColor(0x00ffff);
|
||||||
|
boostDuration.current = 50;
|
||||||
|
driftBlueSound.current.play();
|
||||||
|
}
|
||||||
|
if (accumulatedDriftPower.current > orangeTurboThreshold) {
|
||||||
|
setTurboColor(0xffcf00);
|
||||||
|
boostDuration.current = 100;
|
||||||
|
driftBlueSound.current.stop();
|
||||||
|
driftOrangeSound.current.play();
|
||||||
|
}
|
||||||
|
if (accumulatedDriftPower.current > purpleTurboThreshold) {
|
||||||
|
setTurboColor(0xff00ff);
|
||||||
|
boostDuration.current = 250;
|
||||||
|
driftOrangeSound.current.stop();
|
||||||
|
driftPurpleSound.current.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (driftLeft.current || driftRight.current) {
|
||||||
|
const oscillation = Math.sin(time * 1000) * 0.1;
|
||||||
|
const vibration = oscillation + 0.9;
|
||||||
|
if (turboColor === 0xffffff) {
|
||||||
|
setScale(vibration * 0.8);
|
||||||
|
} else {
|
||||||
|
setScale(vibration);
|
||||||
|
}
|
||||||
|
if (isOnFloor.current && !driftSound.current.isPlaying) {
|
||||||
|
driftSound.current.play();
|
||||||
|
driftTwoSound.current.play();
|
||||||
|
landingSound.current.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// RELEASING DRIFT
|
||||||
|
|
||||||
|
if (boostDuration.current > 1 && !jumpIsHeld.current) {
|
||||||
|
setIsBoosting(true);
|
||||||
|
effectiveBoost.current = boostDuration.current;
|
||||||
|
boostDuration.current = 0;
|
||||||
|
} else if (effectiveBoost.current <= 1) {
|
||||||
|
targetZPosition = 8;
|
||||||
|
setIsBoosting(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBoosting && effectiveBoost.current > 1) {
|
||||||
|
setCurrentSpeed(boostSpeed);
|
||||||
|
effectiveBoost.current -= 1 * delta * 144;
|
||||||
|
targetZPosition = 10;
|
||||||
|
if (!turboSound.current.isPlaying) turboSound.current.play();
|
||||||
|
driftTwoSound.current.play();
|
||||||
|
driftBlueSound.current.stop();
|
||||||
|
driftOrangeSound.current.stop();
|
||||||
|
driftPurpleSound.current.stop();
|
||||||
|
} else if (effectiveBoost.current <= 1) {
|
||||||
|
setIsBoosting(false);
|
||||||
|
targetZPosition = 8;
|
||||||
|
turboSound.current.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// CAMERA WORK
|
||||||
|
|
||||||
|
cam.current.updateMatrixWorld();
|
||||||
|
|
||||||
|
cam.current.position.x = THREE.MathUtils.lerp(
|
||||||
|
cam.current.position.x,
|
||||||
|
targetXPosition,
|
||||||
|
0.01 * delta * 144
|
||||||
|
);
|
||||||
|
|
||||||
|
cam.current.position.z = THREE.MathUtils.lerp(
|
||||||
|
cam.current.position.z,
|
||||||
|
targetZPosition,
|
||||||
|
0.01 * delta * 144
|
||||||
|
);
|
||||||
|
|
||||||
|
body.current.applyImpulse(
|
||||||
|
{
|
||||||
|
x: forwardDirection.x * currentSpeed * delta * 144,
|
||||||
|
y: 0 + jumpForce.current * delta * 144,
|
||||||
|
z: forwardDirection.z * currentSpeed * delta * 144,
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update the kart's rotation based on the steering angle
|
||||||
|
setSteeringAngleWheels(steeringAngle * 25);
|
||||||
|
|
||||||
|
// SOUND WORK
|
||||||
|
|
||||||
|
// MISC
|
||||||
|
|
||||||
|
if (select) {
|
||||||
|
body.current.setTranslation({ x: 8, y: 2, z: -119 });
|
||||||
|
body.current.setLinvel({ x: 0, y: 0, z: 0 });
|
||||||
|
body.current.setAngvel({ x: 0, y: 0, z: 0 });
|
||||||
|
setCurrentSpeed(0);
|
||||||
|
setCurrentSteeringSpeed(0);
|
||||||
|
setIsBoosting(false);
|
||||||
|
effectiveBoost.current = 0;
|
||||||
|
setIsOnGround(false);
|
||||||
|
jumpForce.current = 0;
|
||||||
|
driftDirection.current = 0;
|
||||||
|
kart.current.rotation.y = Math.PI / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ITEMS
|
||||||
|
|
||||||
|
if (LB && item === "banana") {
|
||||||
|
const distanceBehind = 2;
|
||||||
|
const scaledBackwardDirection =
|
||||||
|
forwardDirection.multiplyScalar(distanceBehind);
|
||||||
|
|
||||||
|
const kartPosition = new THREE.Vector3(
|
||||||
|
...vec3(body.current.translation())
|
||||||
|
);
|
||||||
|
|
||||||
|
const bananaPosition = kartPosition.sub(scaledBackwardDirection);
|
||||||
|
const newBanana = {
|
||||||
|
id: Math.random() + "-" + +new Date(),
|
||||||
|
position: bananaPosition,
|
||||||
|
player: true,
|
||||||
|
};
|
||||||
|
setNetworkBananas([...networkBananas, newBanana]);
|
||||||
|
|
||||||
|
actions.useItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LB && item === "shell") {
|
||||||
|
const distanceBehind = -2;
|
||||||
|
const scaledBackwardDirection =
|
||||||
|
forwardDirection.multiplyScalar(distanceBehind);
|
||||||
|
|
||||||
|
const kartPosition = new THREE.Vector3(
|
||||||
|
body.current.translation().x,
|
||||||
|
body.current.translation().y,
|
||||||
|
body.current.translation().z
|
||||||
|
);
|
||||||
|
|
||||||
|
const shellPosition = kartPosition.sub(scaledBackwardDirection);
|
||||||
|
const newShell = {
|
||||||
|
id: Math.random() + "-" + +new Date(),
|
||||||
|
position: shellPosition,
|
||||||
|
player: true,
|
||||||
|
rotation: kartRotation,
|
||||||
|
};
|
||||||
|
setNetworkShells([...networkShells, newShell]);
|
||||||
|
actions.useItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LB && item === "mushroom") {
|
||||||
|
setIsBoosting(true);
|
||||||
|
effectiveBoost.current = 300;
|
||||||
|
actions.useItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
player.setState("position", body.current.translation());
|
||||||
|
player.setState("rotation", kartRotation + mario.current.rotation.y);
|
||||||
|
player.setState("isBoosting", isBoosting);
|
||||||
|
player.setState("shouldLaunch", shouldLaunch);
|
||||||
|
player.setState("turboColor", turboColor);
|
||||||
|
player.setState("scale", scale);
|
||||||
|
player.setState("bananas", bananas);
|
||||||
|
});
|
||||||
|
|
||||||
|
return player.id === id ? (
|
||||||
|
<group>
|
||||||
|
<RigidBody
|
||||||
|
ref={body}
|
||||||
|
colliders={false}
|
||||||
|
position={[8, 60, -119]}
|
||||||
|
centerOfMass={[0, -1, 0]}
|
||||||
|
mass={3}
|
||||||
|
ccd
|
||||||
|
name="player"
|
||||||
|
type={player.id === id ? "dynamic" : "kinematic"}
|
||||||
|
>
|
||||||
|
<BallCollider
|
||||||
|
args={[0.5]}
|
||||||
|
mass={3}
|
||||||
|
onCollisionEnter={({ other }) => {
|
||||||
|
isOnFloor.current = true;
|
||||||
|
setIsOnGround(true);
|
||||||
|
}}
|
||||||
|
onCollisionExit={({ other }) => {
|
||||||
|
isOnFloor.current = false;
|
||||||
|
setIsOnGround(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</RigidBody>
|
||||||
|
|
||||||
|
<group ref={kart} rotation={[0, Math.PI / 2, 0]}>
|
||||||
|
<group ref={mario}>
|
||||||
|
<Mario
|
||||||
|
currentSpeed={currentSpeed}
|
||||||
|
steeringAngleWheels={steeringAngleWheels}
|
||||||
|
isBoosting={isBoosting}
|
||||||
|
shouldLaunch={shouldLaunch}
|
||||||
|
/>
|
||||||
|
<CoinParticles coins={coins} />
|
||||||
|
<ItemParticles item={item} />
|
||||||
|
<mesh position={[0.6, 0.05, 0.5]} scale={scale}>
|
||||||
|
<sphereGeometry args={[0.05, 16, 16]} />
|
||||||
|
<meshStandardMaterial
|
||||||
|
emissive={turboColor}
|
||||||
|
toneMapped={false}
|
||||||
|
emissiveIntensity={100}
|
||||||
|
transparent
|
||||||
|
opacity={0.4}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
<mesh position={[0.6, 0.05, 0.5]} scale={scale * 10}>
|
||||||
|
<sphereGeometry args={[0.05, 16, 16]} />
|
||||||
|
<FakeGlowMaterial
|
||||||
|
falloff={3}
|
||||||
|
glowInternalRadius={1}
|
||||||
|
glowColor={turboColor}
|
||||||
|
glowSharpness={1}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
<mesh position={[-0.6, 0.05, 0.5]} scale={scale}>
|
||||||
|
<sphereGeometry args={[0.05, 16, 16]} />
|
||||||
|
<meshStandardMaterial
|
||||||
|
emissive={turboColor}
|
||||||
|
toneMapped={false}
|
||||||
|
emissiveIntensity={100}
|
||||||
|
transparent
|
||||||
|
opacity={0.4}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
<mesh position={[-0.6, 0.05, 0.5]} scale={scale * 10}>
|
||||||
|
<sphereGeometry args={[0.05, 16, 16]} />
|
||||||
|
<FakeGlowMaterial
|
||||||
|
falloff={3}
|
||||||
|
glowInternalRadius={1}
|
||||||
|
glowColor={turboColor}
|
||||||
|
glowSharpness={1}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
{/* <FlameParticles isBoosting={isBoosting} /> */}
|
||||||
|
<DriftParticlesLeft turboColor={turboColor} scale={scale} />
|
||||||
|
<DriftParticlesRight turboColor={turboColor} scale={scale} />
|
||||||
|
<PointParticle
|
||||||
|
position={[-0.6, 0.05, 0.5]}
|
||||||
|
png="./particles/circle.png"
|
||||||
|
turboColor={turboColor}
|
||||||
|
/>
|
||||||
|
<PointParticle
|
||||||
|
position={[0.6, 0.05, 0.5]}
|
||||||
|
png="./particles/circle.png"
|
||||||
|
turboColor={turboColor}
|
||||||
|
/>
|
||||||
|
<PointParticle
|
||||||
|
position={[-0.6, 0.05, 0.5]}
|
||||||
|
png="./particles/star.png"
|
||||||
|
turboColor={turboColor}
|
||||||
|
/>
|
||||||
|
<PointParticle
|
||||||
|
position={[0.6, 0.05, 0.5]}
|
||||||
|
png="./particles/star.png"
|
||||||
|
turboColor={turboColor}
|
||||||
|
/>
|
||||||
|
<HitParticles shouldLaunch={shouldLaunch} />
|
||||||
|
</group>
|
||||||
|
|
||||||
|
{/* <ContactShadows frames={1} /> */}
|
||||||
|
<PerspectiveCamera
|
||||||
|
makeDefault
|
||||||
|
position={[0, 2, 8]}
|
||||||
|
fov={50}
|
||||||
|
ref={cam}
|
||||||
|
far={5000}
|
||||||
|
/>
|
||||||
|
<PositionalAudio
|
||||||
|
ref={engineSound}
|
||||||
|
url="./sounds/engine.wav"
|
||||||
|
autoplay
|
||||||
|
loop
|
||||||
|
distance={1000}
|
||||||
|
/>
|
||||||
|
<PositionalAudio
|
||||||
|
ref={driftSound}
|
||||||
|
url="./sounds/drifting.mp3"
|
||||||
|
loop
|
||||||
|
distance={1000}
|
||||||
|
/>
|
||||||
|
<PositionalAudio
|
||||||
|
ref={driftTwoSound}
|
||||||
|
url="./sounds/driftingTwo.mp3"
|
||||||
|
loop
|
||||||
|
distance={1000}
|
||||||
|
/>
|
||||||
|
<PositionalAudio
|
||||||
|
ref={driftOrangeSound}
|
||||||
|
url="./sounds/driftOrange.wav"
|
||||||
|
loop={false}
|
||||||
|
distance={1000}
|
||||||
|
/>
|
||||||
|
<PositionalAudio
|
||||||
|
ref={driftBlueSound}
|
||||||
|
url="./sounds/driftBlue.wav"
|
||||||
|
loop={false}
|
||||||
|
distance={1000}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PositionalAudio
|
||||||
|
ref={driftPurpleSound}
|
||||||
|
url="./sounds/driftPurple.wav"
|
||||||
|
loop={false}
|
||||||
|
distance={1000}
|
||||||
|
/>
|
||||||
|
<PositionalAudio
|
||||||
|
ref={jumpSound}
|
||||||
|
url="./sounds/jump.mp3"
|
||||||
|
loop={false}
|
||||||
|
distance={1000}
|
||||||
|
/>
|
||||||
|
<PositionalAudio
|
||||||
|
ref={landingSound}
|
||||||
|
url="./sounds/landing.wav"
|
||||||
|
loop={false}
|
||||||
|
distance={1000}
|
||||||
|
/>
|
||||||
|
<PositionalAudio
|
||||||
|
ref={turboSound}
|
||||||
|
url="./sounds/turbo.wav"
|
||||||
|
loop={false}
|
||||||
|
distance={1000}
|
||||||
|
/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
) : null;
|
||||||
|
};
|
|
@ -0,0 +1,628 @@
|
||||||
|
import { Controls } from "../App";
|
||||||
|
import { BallCollider, RigidBody, useRapier, vec3 } from "@react-three/rapier";
|
||||||
|
import {
|
||||||
|
useKeyboardControls,
|
||||||
|
PerspectiveCamera,
|
||||||
|
PositionalAudio,
|
||||||
|
} from "@react-three/drei";
|
||||||
|
import { useFrame, useThree, extend } from "@react-three/fiber";
|
||||||
|
import { useRef, useState, useEffect, useCallback } from "react";
|
||||||
|
import * as THREE from "three";
|
||||||
|
|
||||||
|
import { Mario } from "./models/characters/Mario_kart";
|
||||||
|
import { DriftParticlesLeft } from "./Particles/drifts/DriftParticlesLeft";
|
||||||
|
import { DriftParticlesRight } from "./Particles/drifts/DriftParticlesRight";
|
||||||
|
|
||||||
|
import { PointParticle } from "./Particles/drifts/PointParticle";
|
||||||
|
|
||||||
|
import { FlameParticles } from "./Particles/flames/FlameParticles";
|
||||||
|
import { useStore } from "./store";
|
||||||
|
import { Cylinder } from "@react-three/drei";
|
||||||
|
import FakeGlowMaterial from "./ShaderMaterials/FakeGlow/FakeGlowMaterial";
|
||||||
|
import { HitParticles } from "./Particles/hits/HitParticles";
|
||||||
|
import { CoinParticles } from "./Particles/coins/CoinParticles";
|
||||||
|
import { ItemParticles } from "./Particles/items/ItemParticles";
|
||||||
|
import { geometry } from "maath";
|
||||||
|
extend(geometry);
|
||||||
|
|
||||||
|
export const PlayerControllerKeyboard = ({
|
||||||
|
player,
|
||||||
|
userPlayer,
|
||||||
|
setNetworkBananas,
|
||||||
|
setNetworkShells,
|
||||||
|
networkBananas,
|
||||||
|
networkShells,
|
||||||
|
}) => {
|
||||||
|
const upPressed = useKeyboardControls((state) => state[Controls.up]);
|
||||||
|
const downPressed = useKeyboardControls((state) => state[Controls.down]);
|
||||||
|
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 resetPressed = useKeyboardControls((state) => state[Controls.reset]);
|
||||||
|
|
||||||
|
const [isOnGround, setIsOnGround] = useState(false);
|
||||||
|
const body = useRef();
|
||||||
|
const kart = useRef();
|
||||||
|
const cam = useRef();
|
||||||
|
const initialSpeed = 0;
|
||||||
|
const maxSpeed = 30;
|
||||||
|
const boostSpeed = 50;
|
||||||
|
const acceleration = 0.1;
|
||||||
|
const decceleration = 0.2;
|
||||||
|
const damping = -0.1;
|
||||||
|
const MaxSteeringSpeed = 0.01;
|
||||||
|
const [currentSteeringSpeed, setCurrentSteeringSpeed] = useState(0);
|
||||||
|
const [currentSpeed, setCurrentSpeed] = useState(initialSpeed);
|
||||||
|
const camMaxOffset = 1;
|
||||||
|
let steeringAngle = 0;
|
||||||
|
const isOnFloor = useRef(false);
|
||||||
|
const jumpForce = useRef(0);
|
||||||
|
const jumpIsHeld = useRef(false);
|
||||||
|
const driftDirection = useRef(0);
|
||||||
|
const driftLeft = useRef(false);
|
||||||
|
const driftRight = useRef(false);
|
||||||
|
const driftForce = useRef(0);
|
||||||
|
const mario = useRef();
|
||||||
|
const accumulatedDriftPower = useRef(0);
|
||||||
|
const blueTurboThreshold = 10;
|
||||||
|
const orangeTurboThreshold = 30;
|
||||||
|
const purpleTurboThreshold = 60;
|
||||||
|
const [turboColor, setTurboColor] = useState(0xffffff);
|
||||||
|
const boostDuration = useRef(0);
|
||||||
|
const [isBoosting, setIsBoosting] = useState(false);
|
||||||
|
let targetXPosition = 0;
|
||||||
|
let targetZPosition = 8;
|
||||||
|
const [steeringAngleWheels, setSteeringAngleWheels] = useState(0);
|
||||||
|
const engineSound = useRef();
|
||||||
|
const driftSound = useRef();
|
||||||
|
const driftTwoSound = useRef();
|
||||||
|
const driftOrangeSound = useRef();
|
||||||
|
const driftPurpleSound = useRef();
|
||||||
|
const driftBlueSound = useRef();
|
||||||
|
const jumpSound = useRef();
|
||||||
|
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 effectiveBoost = useRef(0);
|
||||||
|
const text = useRef();
|
||||||
|
|
||||||
|
const { actions, shouldSlowDown, item, bananas, coins, id, controls } = useStore();
|
||||||
|
const slowDownDuration = useRef(1500);
|
||||||
|
|
||||||
|
useFrame(({ pointer, clock }, delta) => {
|
||||||
|
if (player.id !== id) return;
|
||||||
|
const time = clock.getElapsedTime();
|
||||||
|
if (!body.current && !mario.current) return;
|
||||||
|
engineSound.current.setVolume(currentSpeed / 300 + 0.2);
|
||||||
|
engineSound.current.setPlaybackRate(currentSpeed / 10 + 0.1);
|
||||||
|
jumpSound.current.setPlaybackRate(1.5);
|
||||||
|
jumpSound.current.setVolume(0.5);
|
||||||
|
driftSound.current.setVolume(0.2);
|
||||||
|
|
||||||
|
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;
|
||||||
|
const forwardDirection = new THREE.Vector3(
|
||||||
|
-Math.sin(kartRotation),
|
||||||
|
0,
|
||||||
|
-Math.cos(kartRotation)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (leftPressed && currentSpeed > 0) {
|
||||||
|
steeringAngle = currentSteeringSpeed;
|
||||||
|
targetXPosition = -camMaxOffset;
|
||||||
|
} else if (rightPressed && currentSpeed > 0) {
|
||||||
|
steeringAngle = -currentSteeringSpeed;
|
||||||
|
targetXPosition = camMaxOffset;
|
||||||
|
} else {
|
||||||
|
steeringAngle = 0;
|
||||||
|
targetXPosition = 0;
|
||||||
|
1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ACCELERATING
|
||||||
|
const shouldSlow = actions.getShouldSlowDown();
|
||||||
|
|
||||||
|
if (upPressed && currentSpeed < maxSpeed) {
|
||||||
|
// Accelerate the kart within the maximum speed limit
|
||||||
|
setCurrentSpeed(
|
||||||
|
Math.min(currentSpeed + acceleration * delta * 144, maxSpeed)
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
upPressed &&
|
||||||
|
currentSpeed > maxSpeed &&
|
||||||
|
effectiveBoost.current > 0
|
||||||
|
) {
|
||||||
|
setCurrentSpeed(
|
||||||
|
Math.max(currentSpeed - decceleration * delta * 144, maxSpeed)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (upPressed) {
|
||||||
|
if (currentSteeringSpeed < MaxSteeringSpeed) {
|
||||||
|
setCurrentSteeringSpeed(
|
||||||
|
Math.min(
|
||||||
|
currentSteeringSpeed + 0.0001 * delta * 144,
|
||||||
|
MaxSteeringSpeed
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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(
|
||||||
|
Math.max(currentSpeed - acceleration * delta * 144, -maxSpeed)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// DECELERATING
|
||||||
|
else if (!upPressed && !downPressed) {
|
||||||
|
if (currentSteeringSpeed > 0) {
|
||||||
|
setCurrentSteeringSpeed(
|
||||||
|
Math.max(currentSteeringSpeed - 0.00005 * delta * 144, 0)
|
||||||
|
);
|
||||||
|
} else if (currentSteeringSpeed < 0) {
|
||||||
|
setCurrentSteeringSpeed(
|
||||||
|
Math.min(currentSteeringSpeed + 0.00005 * delta * 144, 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setCurrentSpeed(Math.max(currentSpeed - decceleration * delta * 144, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the kart's rotation based on the steering angle
|
||||||
|
kart.current.rotation.y += steeringAngle * delta * 144;
|
||||||
|
|
||||||
|
// Apply damping to simulate slowdown when no keys are pressed
|
||||||
|
body.current.applyImpulse(
|
||||||
|
{
|
||||||
|
x: -body.current.linvel().x * (1 - damping) * delta * 144,
|
||||||
|
y: 0,
|
||||||
|
z: -body.current.linvel().z * (1 - damping) * delta * 144,
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
const bodyPosition = body.current.translation();
|
||||||
|
kart.current.position.set(
|
||||||
|
bodyPosition.x,
|
||||||
|
bodyPosition.y - 0.5,
|
||||||
|
bodyPosition.z
|
||||||
|
);
|
||||||
|
|
||||||
|
// JUMPING
|
||||||
|
if (jumpPressed && isOnGround && !jumpIsHeld.current) {
|
||||||
|
jumpForce.current += 10;
|
||||||
|
isOnFloor.current = false;
|
||||||
|
jumpIsHeld.current = true;
|
||||||
|
jumpSound.current.play();
|
||||||
|
setIsOnGround(false);
|
||||||
|
|
||||||
|
if (jumpSound.current.isPlaying) {
|
||||||
|
jumpSound.current.stop();
|
||||||
|
jumpSound.current.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOnFloor.current && jumpForce.current > 0) {
|
||||||
|
landingSound.current.play();
|
||||||
|
}
|
||||||
|
if (!isOnGround && jumpForce.current > 0) {
|
||||||
|
jumpForce.current -= 1 * delta * 144;
|
||||||
|
}
|
||||||
|
if (!jumpPressed) {
|
||||||
|
jumpIsHeld.current = false;
|
||||||
|
driftDirection.current = 0;
|
||||||
|
driftForce.current = 0;
|
||||||
|
driftLeft.current = false;
|
||||||
|
driftRight.current = false;
|
||||||
|
}
|
||||||
|
// DRIFTING
|
||||||
|
if (
|
||||||
|
jumpIsHeld.current &&
|
||||||
|
currentSteeringSpeed > 0 &&
|
||||||
|
leftPressed &&
|
||||||
|
!driftRight.current
|
||||||
|
) {
|
||||||
|
driftLeft.current = true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
jumpIsHeld.current &&
|
||||||
|
currentSteeringSpeed > 0 &&
|
||||||
|
rightPressed > 0.1 &&
|
||||||
|
!driftLeft.current
|
||||||
|
) {
|
||||||
|
driftRight.current = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!jumpIsHeld.current && !driftLeft.current && !driftRight.current) {
|
||||||
|
mario.current.rotation.y = THREE.MathUtils.lerp(
|
||||||
|
mario.current.rotation.y,
|
||||||
|
0,
|
||||||
|
0.0001 * delta * 144
|
||||||
|
);
|
||||||
|
setTurboColor(0xffffff);
|
||||||
|
accumulatedDriftPower.current = 0;
|
||||||
|
driftSound.current.stop();
|
||||||
|
driftTwoSound.current.stop();
|
||||||
|
driftOrangeSound.current.stop();
|
||||||
|
driftPurpleSound.current.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (driftLeft.current) {
|
||||||
|
driftDirection.current = 1;
|
||||||
|
driftForce.current = 0.4;
|
||||||
|
mario.current.rotation.y = THREE.MathUtils.lerp(
|
||||||
|
mario.current.rotation.y,
|
||||||
|
steeringAngle * 25 + 0.4,
|
||||||
|
0.05 * delta * 144
|
||||||
|
);
|
||||||
|
accumulatedDriftPower.current += 0.1 * (steeringAngle + 1) * delta * 144;
|
||||||
|
}
|
||||||
|
if (driftRight.current) {
|
||||||
|
driftDirection.current = -1;
|
||||||
|
driftForce.current = 0.4;
|
||||||
|
mario.current.rotation.y = THREE.MathUtils.lerp(
|
||||||
|
mario.current.rotation.y,
|
||||||
|
-(-steeringAngle * 25 + 0.4),
|
||||||
|
0.05 * delta * 144
|
||||||
|
);
|
||||||
|
accumulatedDriftPower.current += 0.1 * (-steeringAngle + 1) * delta * 144;
|
||||||
|
}
|
||||||
|
if (!driftLeft.current && !driftRight.current) {
|
||||||
|
mario.current.rotation.y = THREE.MathUtils.lerp(
|
||||||
|
mario.current.rotation.y,
|
||||||
|
steeringAngle * 30,
|
||||||
|
0.05 * delta * 144
|
||||||
|
);
|
||||||
|
setScale(0);
|
||||||
|
}
|
||||||
|
if (accumulatedDriftPower.current > blueTurboThreshold) {
|
||||||
|
setTurboColor(0x00ffff);
|
||||||
|
boostDuration.current = 50;
|
||||||
|
driftBlueSound.current.play();
|
||||||
|
}
|
||||||
|
if (accumulatedDriftPower.current > orangeTurboThreshold) {
|
||||||
|
setTurboColor(0xffcf00);
|
||||||
|
boostDuration.current = 100;
|
||||||
|
driftBlueSound.current.stop();
|
||||||
|
driftOrangeSound.current.play();
|
||||||
|
}
|
||||||
|
if (accumulatedDriftPower.current > purpleTurboThreshold) {
|
||||||
|
setTurboColor(0xff00ff);
|
||||||
|
boostDuration.current = 250;
|
||||||
|
driftOrangeSound.current.stop();
|
||||||
|
driftPurpleSound.current.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (driftLeft.current || driftRight.current) {
|
||||||
|
const oscillation = Math.sin(time * 1000) * 0.1;
|
||||||
|
const vibration = oscillation + 0.9;
|
||||||
|
if (turboColor === 0xffffff) {
|
||||||
|
setScale(vibration * 0.8);
|
||||||
|
} else {
|
||||||
|
setScale(vibration);
|
||||||
|
}
|
||||||
|
if (isOnFloor.current && !driftSound.current.isPlaying) {
|
||||||
|
driftSound.current.play();
|
||||||
|
driftTwoSound.current.play();
|
||||||
|
landingSound.current.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// RELEASING DRIFT
|
||||||
|
|
||||||
|
if (boostDuration.current > 1 && !jumpIsHeld.current) {
|
||||||
|
setIsBoosting(true);
|
||||||
|
effectiveBoost.current = boostDuration.current;
|
||||||
|
boostDuration.current = 0;
|
||||||
|
} else if (effectiveBoost.current <= 1) {
|
||||||
|
targetZPosition = 8;
|
||||||
|
setIsBoosting(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBoosting && effectiveBoost.current > 1) {
|
||||||
|
setCurrentSpeed(boostSpeed);
|
||||||
|
effectiveBoost.current -= 1 * delta * 144;
|
||||||
|
targetZPosition = 10;
|
||||||
|
if (!turboSound.current.isPlaying) turboSound.current.play();
|
||||||
|
driftTwoSound.current.play();
|
||||||
|
driftBlueSound.current.stop();
|
||||||
|
driftOrangeSound.current.stop();
|
||||||
|
driftPurpleSound.current.stop();
|
||||||
|
} else if (effectiveBoost.current <= 1) {
|
||||||
|
setIsBoosting(false);
|
||||||
|
targetZPosition = 8;
|
||||||
|
turboSound.current.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// CAMERA WORK
|
||||||
|
|
||||||
|
cam.current.updateMatrixWorld();
|
||||||
|
|
||||||
|
cam.current.position.x = THREE.MathUtils.lerp(
|
||||||
|
cam.current.position.x,
|
||||||
|
targetXPosition,
|
||||||
|
0.01 * delta * 144
|
||||||
|
);
|
||||||
|
|
||||||
|
cam.current.position.z = THREE.MathUtils.lerp(
|
||||||
|
cam.current.position.z,
|
||||||
|
targetZPosition,
|
||||||
|
0.01 * delta * 144
|
||||||
|
);
|
||||||
|
|
||||||
|
body.current.applyImpulse(
|
||||||
|
{
|
||||||
|
x: forwardDirection.x * currentSpeed * delta * 144,
|
||||||
|
y: 0 + jumpForce.current * delta * 144,
|
||||||
|
z: forwardDirection.z * currentSpeed * delta * 144,
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update the kart's rotation based on the steering angle
|
||||||
|
setSteeringAngleWheels(steeringAngle * 25);
|
||||||
|
|
||||||
|
// SOUND WORK
|
||||||
|
|
||||||
|
// MISC
|
||||||
|
|
||||||
|
if (resetPressed) {
|
||||||
|
body.current.setTranslation({ x: 8, y: 2, z: -119 });
|
||||||
|
body.current.setLinvel({ x: 0, y: 0, z: 0 });
|
||||||
|
body.current.setAngvel({ x: 0, y: 0, z: 0 });
|
||||||
|
setCurrentSpeed(0);
|
||||||
|
setCurrentSteeringSpeed(0);
|
||||||
|
setIsBoosting(false);
|
||||||
|
effectiveBoost.current = 0;
|
||||||
|
setIsOnGround(false);
|
||||||
|
jumpForce.current = 0;
|
||||||
|
driftDirection.current = 0;
|
||||||
|
kart.current.rotation.y = Math.PI / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ITEMS
|
||||||
|
|
||||||
|
if (shootPressed && item === "banana") {
|
||||||
|
const distanceBehind = 2;
|
||||||
|
const scaledBackwardDirection =
|
||||||
|
forwardDirection.multiplyScalar(distanceBehind);
|
||||||
|
|
||||||
|
const kartPosition = new THREE.Vector3(
|
||||||
|
...vec3(body.current.translation())
|
||||||
|
);
|
||||||
|
|
||||||
|
const bananaPosition = kartPosition.sub(scaledBackwardDirection);
|
||||||
|
const newBanana = {
|
||||||
|
id: Math.random() + "-" + +new Date(),
|
||||||
|
position: bananaPosition,
|
||||||
|
player: true,
|
||||||
|
};
|
||||||
|
setNetworkBananas([...networkBananas, newBanana]);
|
||||||
|
|
||||||
|
actions.useItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shootPressed && item === "shell") {
|
||||||
|
const distanceBehind = -2;
|
||||||
|
const scaledBackwardDirection =
|
||||||
|
forwardDirection.multiplyScalar(distanceBehind);
|
||||||
|
|
||||||
|
const kartPosition = new THREE.Vector3(
|
||||||
|
body.current.translation().x,
|
||||||
|
body.current.translation().y,
|
||||||
|
body.current.translation().z
|
||||||
|
);
|
||||||
|
|
||||||
|
const shellPosition = kartPosition.sub(scaledBackwardDirection);
|
||||||
|
const newShell = {
|
||||||
|
id: Math.random() + "-" + +new Date(),
|
||||||
|
position: shellPosition,
|
||||||
|
player: true,
|
||||||
|
rotation: kartRotation,
|
||||||
|
};
|
||||||
|
setNetworkShells([...networkShells, newShell]);
|
||||||
|
actions.useItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shootPressed && item === "mushroom") {
|
||||||
|
setIsBoosting(true);
|
||||||
|
effectiveBoost.current = 300;
|
||||||
|
actions.useItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
player.setState("position", body.current.translation());
|
||||||
|
player.setState("rotation", kartRotation + mario.current.rotation.y);
|
||||||
|
player.setState("isBoosting", isBoosting);
|
||||||
|
player.setState("shouldLaunch", shouldLaunch);
|
||||||
|
player.setState("turboColor", turboColor);
|
||||||
|
player.setState("scale", scale);
|
||||||
|
player.setState("bananas", bananas);
|
||||||
|
});
|
||||||
|
|
||||||
|
return player.id === id ? (
|
||||||
|
<group>
|
||||||
|
<RigidBody
|
||||||
|
ref={body}
|
||||||
|
colliders={false}
|
||||||
|
position={[8, 60, -119]}
|
||||||
|
centerOfMass={[0, -1, 0]}
|
||||||
|
mass={3}
|
||||||
|
ccd
|
||||||
|
name="player"
|
||||||
|
type={player.id === id ? "dynamic" : "kinematic"}
|
||||||
|
>
|
||||||
|
<BallCollider
|
||||||
|
args={[0.5]}
|
||||||
|
mass={3}
|
||||||
|
onCollisionEnter={({ other }) => {
|
||||||
|
isOnFloor.current = true;
|
||||||
|
setIsOnGround(true);
|
||||||
|
}}
|
||||||
|
onCollisionExit={({ other }) => {
|
||||||
|
isOnFloor.current = false;
|
||||||
|
setIsOnGround(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</RigidBody>
|
||||||
|
|
||||||
|
<group ref={kart} rotation={[0, Math.PI / 2, 0]}>
|
||||||
|
<group ref={mario}>
|
||||||
|
<Mario
|
||||||
|
currentSpeed={currentSpeed}
|
||||||
|
steeringAngleWheels={steeringAngleWheels}
|
||||||
|
isBoosting={isBoosting}
|
||||||
|
shouldLaunch={shouldLaunch}
|
||||||
|
/>
|
||||||
|
<CoinParticles coins={coins} />
|
||||||
|
<ItemParticles item={item} />
|
||||||
|
<mesh position={[0.6, 0.05, 0.5]} scale={scale}>
|
||||||
|
<sphereGeometry args={[0.05, 16, 16]} />
|
||||||
|
<meshStandardMaterial
|
||||||
|
emissive={turboColor}
|
||||||
|
toneMapped={false}
|
||||||
|
emissiveIntensity={100}
|
||||||
|
transparent
|
||||||
|
opacity={0.4}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
<mesh position={[0.6, 0.05, 0.5]} scale={scale * 10}>
|
||||||
|
<sphereGeometry args={[0.05, 16, 16]} />
|
||||||
|
<FakeGlowMaterial
|
||||||
|
falloff={3}
|
||||||
|
glowInternalRadius={1}
|
||||||
|
glowColor={turboColor}
|
||||||
|
glowSharpness={1}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
<mesh position={[-0.6, 0.05, 0.5]} scale={scale}>
|
||||||
|
<sphereGeometry args={[0.05, 16, 16]} />
|
||||||
|
<meshStandardMaterial
|
||||||
|
emissive={turboColor}
|
||||||
|
toneMapped={false}
|
||||||
|
emissiveIntensity={100}
|
||||||
|
transparent
|
||||||
|
opacity={0.4}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
<mesh position={[-0.6, 0.05, 0.5]} scale={scale * 10}>
|
||||||
|
<sphereGeometry args={[0.05, 16, 16]} />
|
||||||
|
<FakeGlowMaterial
|
||||||
|
falloff={3}
|
||||||
|
glowInternalRadius={1}
|
||||||
|
glowColor={turboColor}
|
||||||
|
glowSharpness={1}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
{/* <FlameParticles isBoosting={isBoosting} /> */}
|
||||||
|
<DriftParticlesLeft turboColor={turboColor} scale={scale} />
|
||||||
|
<DriftParticlesRight turboColor={turboColor} scale={scale} />
|
||||||
|
<PointParticle
|
||||||
|
position={[-0.6, 0.05, 0.5]}
|
||||||
|
png="./particles/circle.png"
|
||||||
|
turboColor={turboColor}
|
||||||
|
/>
|
||||||
|
<PointParticle
|
||||||
|
position={[0.6, 0.05, 0.5]}
|
||||||
|
png="./particles/circle.png"
|
||||||
|
turboColor={turboColor}
|
||||||
|
/>
|
||||||
|
<PointParticle
|
||||||
|
position={[-0.6, 0.05, 0.5]}
|
||||||
|
png="./particles/star.png"
|
||||||
|
turboColor={turboColor}
|
||||||
|
/>
|
||||||
|
<PointParticle
|
||||||
|
position={[0.6, 0.05, 0.5]}
|
||||||
|
png="./particles/star.png"
|
||||||
|
turboColor={turboColor}
|
||||||
|
/>
|
||||||
|
<HitParticles shouldLaunch={shouldLaunch} />
|
||||||
|
</group>
|
||||||
|
|
||||||
|
{/* <ContactShadows frames={1} /> */}
|
||||||
|
<PerspectiveCamera
|
||||||
|
makeDefault
|
||||||
|
position={[0, 2, 8]}
|
||||||
|
fov={50}
|
||||||
|
ref={cam}
|
||||||
|
far={5000}
|
||||||
|
/>
|
||||||
|
<PositionalAudio
|
||||||
|
ref={engineSound}
|
||||||
|
url="./sounds/engine.wav"
|
||||||
|
autoplay
|
||||||
|
loop
|
||||||
|
distance={1000}
|
||||||
|
/>
|
||||||
|
<PositionalAudio
|
||||||
|
ref={driftSound}
|
||||||
|
url="./sounds/drifting.mp3"
|
||||||
|
loop
|
||||||
|
distance={1000}
|
||||||
|
/>
|
||||||
|
<PositionalAudio
|
||||||
|
ref={driftTwoSound}
|
||||||
|
url="./sounds/driftingTwo.mp3"
|
||||||
|
loop
|
||||||
|
distance={1000}
|
||||||
|
/>
|
||||||
|
<PositionalAudio
|
||||||
|
ref={driftOrangeSound}
|
||||||
|
url="./sounds/driftOrange.wav"
|
||||||
|
loop={false}
|
||||||
|
distance={1000}
|
||||||
|
/>
|
||||||
|
<PositionalAudio
|
||||||
|
ref={driftBlueSound}
|
||||||
|
url="./sounds/driftBlue.wav"
|
||||||
|
loop={false}
|
||||||
|
distance={1000}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PositionalAudio
|
||||||
|
ref={driftPurpleSound}
|
||||||
|
url="./sounds/driftPurple.wav"
|
||||||
|
loop={false}
|
||||||
|
distance={1000}
|
||||||
|
/>
|
||||||
|
<PositionalAudio
|
||||||
|
ref={jumpSound}
|
||||||
|
url="./sounds/jump.mp3"
|
||||||
|
loop={false}
|
||||||
|
distance={1000}
|
||||||
|
/>
|
||||||
|
<PositionalAudio
|
||||||
|
ref={landingSound}
|
||||||
|
url="./sounds/landing.wav"
|
||||||
|
loop={false}
|
||||||
|
distance={1000}
|
||||||
|
/>
|
||||||
|
<PositionalAudio
|
||||||
|
ref={turboSound}
|
||||||
|
url="./sounds/turbo.wav"
|
||||||
|
loop={false}
|
||||||
|
distance={1000}
|
||||||
|
/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
) : null;
|
||||||
|
};
|
|
@ -102,7 +102,7 @@ export const PlayerDummies = ( { player, userPlayer }) => {
|
||||||
setShouldLaunch(player.getState("shouldLaunch"));
|
setShouldLaunch(player.getState("shouldLaunch"));
|
||||||
setTurboColor(player.getState("turboColor"));
|
setTurboColor(player.getState("turboColor"));
|
||||||
setScale(player.getState("scale"));
|
setScale(player.getState("scale"));
|
||||||
console.log(player.state.profile.name)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,8 @@ export const items = [
|
||||||
]
|
]
|
||||||
|
|
||||||
export const useStore = create((set, get) => ({
|
export const useStore = create((set, get) => ({
|
||||||
|
gameStarted: false,
|
||||||
|
controls: "",
|
||||||
particles1: [],
|
particles1: [],
|
||||||
particles2: [],
|
particles2: [],
|
||||||
bodyPosition: [0, 0, 0],
|
bodyPosition: [0, 0, 0],
|
||||||
|
@ -139,7 +141,14 @@ export const useStore = create((set, get) => ({
|
||||||
},
|
},
|
||||||
setId : (id) => {
|
setId : (id) => {
|
||||||
set({id});
|
set({id});
|
||||||
|
},
|
||||||
|
setGameStarted: (gameStarted) => {
|
||||||
|
set({ gameStarted });
|
||||||
|
},
|
||||||
|
setControls: (controls) => {
|
||||||
|
set({ controls });
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
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 };
|
||||||
|
};
|
146
src/index.css
146
src/index.css
|
@ -30,7 +30,7 @@ body::-webkit-scrollbar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay {
|
/* .overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -51,7 +51,7 @@ body::-webkit-scrollbar {
|
||||||
animation: bounce 0.4s infinite cubic-bezier(0.71, 1.94, 0.5, 0.61);
|
animation: bounce 0.4s infinite cubic-bezier(0.71, 1.94, 0.5, 0.61);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
.item {
|
.item {
|
||||||
width: 152px;
|
width: 152px;
|
||||||
height: 152px;
|
height: 152px;
|
||||||
|
@ -121,3 +121,145 @@ body::-webkit-scrollbar {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.home {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
display: grid;
|
||||||
|
place-content: center;
|
||||||
|
gap: 400px;
|
||||||
|
z-index: 2;
|
||||||
|
font-family: "Hanken Grotesk";
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
img {
|
||||||
|
width: 1200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.glassy {
|
||||||
|
width: 1280px;
|
||||||
|
height: 720px;
|
||||||
|
background: rgba(95, 95, 95, 0.25);
|
||||||
|
/* box-shadow: 0 8px 32px 0 rgba( 31, 38, 135, 0.37 ); */
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
-webkit-backdrop-filter: blur(15px);
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||||
|
color: white;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 40px;
|
||||||
|
.articles {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
padding: 80px;
|
||||||
|
gap: 40px;
|
||||||
|
.article {
|
||||||
|
background: rgba(216, 216, 216, 0.25);
|
||||||
|
width: 300px;
|
||||||
|
height: 300px;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 20px;
|
||||||
|
transition: all 0.2s ease 0s;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background: rgba(216, 216, 216, 0.5);
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
width: 200px;
|
||||||
|
filter: drop-shadow(5px 5px 5px #0000008f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.article.selected {
|
||||||
|
background: rgba(216, 216, 216, 0.7);
|
||||||
|
box-shadow: 0 0 20px 0 rgb(255, 255, 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.submit {
|
||||||
|
font-weight: 900;
|
||||||
|
color: rgba(255, 255, 255, 0.795);
|
||||||
|
|
||||||
|
font-size: 27px;
|
||||||
|
transition: all 0.2s ease 0s;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
text-shadow: 0 0 40px rgba(255, 255, 255, 1);
|
||||||
|
color: white;
|
||||||
|
opacity: 1;
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
all: unset;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.start {
|
||||||
|
opacity: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: 900;
|
||||||
|
color: rgba(255, 255, 255, 0.795);
|
||||||
|
|
||||||
|
font-size: 60px;
|
||||||
|
text-shadow: 0 0 10px rgba(0, 0, 0, 1);
|
||||||
|
background: linear-gradient(
|
||||||
|
to right,
|
||||||
|
rgba(54, 54, 54, 0),
|
||||||
|
rgb(54, 54, 54),
|
||||||
|
rgba(54, 54, 54, 0)
|
||||||
|
);
|
||||||
|
animation: blinking 2s infinite;
|
||||||
|
transition: all 0.2s ease 0s;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
text-shadow: 0 0 40px rgba(255, 255, 255, 1);
|
||||||
|
color: white;
|
||||||
|
opacity: 1;
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
all: unset;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled{
|
||||||
|
pointer-events: none;
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.4;
|
||||||
|
|
||||||
|
}
|
||||||
|
@keyframes blinking {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,10 +3,13 @@ import ReactDOM from 'react-dom/client'
|
||||||
import App from './App'
|
import App from './App'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
import { HUD } from './HUD'
|
import { HUD } from './HUD'
|
||||||
|
import { Landing } from './Landing'
|
||||||
|
import { useStore } from './components/store'
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<App />
|
||||||
<HUD />
|
<HUD />
|
||||||
|
<Landing />
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue