Merge pull request #3 from Lunakepio/multiplayer

Multiplayer
pull/8/head
Lunakepio 2024-02-12 09:42:30 +01:00 committed by GitHub
commit 310e471fab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 36561 additions and 154 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) [2022] [Ron Waller]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

77
README.md Normal file
View File

@ -0,0 +1,77 @@
# Mario Kart 3.js - JavaScript/WebGL Mario Kart
[Link](https://mario-kart-3-js.vercel.app/)
DISCLAIMER : This is not a completed project, I would say around 50% of the work has been done up to now. It takes a lot of time so please enjoy.
## How to install
Clone the repository or download it
Open your terminal and inside the project folder, run :
```bash
npm install
```
Start the dev server
```bash
npm run dev
```
## How to use (Gameplay)
- Use the W key to accelerate, the mouse to steer for now (will be updated for mobile/gamepad/keyboard).
- Steer with the mouse.
- Press and hold the space bar to initiate a drift. Steer and counter steer to maintain the drift. release it to get a mini - turbo.
- Press E key to use the current item.
- Press R to reset your position, usable anytime.
## How to use (Code)
- Anything needs update.
- You can also edit the README to add elements to the To-do List.
- Feel free to bring your ideas to the project even if you can't code them.
## TO - DO
- Design Landing page
- Add items
- Add texture to the flame shaders
- Add curve/length modifiers to drift particles 3/4
- Add Skid marks
- Add smokes
- Add wind screen effect when boosting
- Improve sound design quality
- Design UI for HUD
- Make Time Trial mode
- Design tracks and checkpoints
- Improve code quality
- Items
- Tennis ball
- Bomb
- Real red shell
- Treats
- ?
## License
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

View File

@ -1,46 +0,0 @@
/*
Auto-generated by: https://github.com/pmndrs/gltfjsx
Command: npx gltfjsx@6.2.16 c:\Users\mouli\Downloads\mario_kart.glb -o c:\Users\mouli\Desktop\mario_kart.tsx --types
Author: TheShibeLord (https://sketchfab.com/TheShibeLord)
License: CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)
Source: https://sketchfab.com/3d-models/mario-kart-66cc131575344ab69238ec5872f24927
Title: Mario Kart
*/
import * as THREE from 'three'
import React, { useRef } from 'react'
import { useGLTF } from '@react-three/drei'
import { GLTF } from 'three-stdlib'
type GLTFResult = GLTF & {
nodes: {
mt_mario: THREE.Mesh
mt_kart_Mario_S: THREE.Mesh
mt_Kart_Mario_Tire_S: THREE.Mesh
}
materials: {
mt_mario: THREE.MeshStandardMaterial
mt_kart_Mario_S: THREE.MeshStandardMaterial
mt_Kart_Mario_Tire_S: THREE.MeshStandardMaterial
}
animations: GLTFAction[]
}
type ContextType = Record<string, React.ForwardRefExoticComponent<JSX.IntrinsicElements['mesh']>>
export function Model(props: JSX.IntrinsicElements['group']) {
const { nodes, materials } = useGLTF('/mario_kart.glb') as GLTFResult
return (
<group {...props} dispose={null}>
<group rotation={[-Math.PI / 2, 0, 0]}>
<group rotation={[Math.PI / 2, 0, 0]}>
<mesh geometry={nodes.mt_mario.geometry} material={materials.mt_mario} />
<mesh geometry={nodes.mt_kart_Mario_S.geometry} material={materials.mt_kart_Mario_S} />
<mesh geometry={nodes.mt_Kart_Mario_Tire_S.geometry} material={materials.mt_Kart_Mario_Tire_S} />
</group>
</group>
</group>
)
}
useGLTF.preload('/mario_kart.glb')

10
package-lock.json generated
View File

@ -14,6 +14,7 @@
"@react-three/rapier": "^1.2.1",
"gsap": "^3.12.5",
"leva": "^0.9.35",
"playroomkit": "^0.0.61",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-gamepad": "^1.0.3",
@ -2423,6 +2424,15 @@
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
},
"node_modules/playroomkit": {
"version": "0.0.61",
"resolved": "https://registry.npmjs.org/playroomkit/-/playroomkit-0.0.61.tgz",
"integrity": "sha512-BUjGkZZcV5OQ29sTmjjI3Ad0SZLRvuBYWAdu4MqhnCjiaHTDJp4KvacVx6rJEJe9jdamtCimkZ/5XXuGm9h2AQ==",
"peerDependencies": {
"react": ">=17.0.2 <= 18",
"react-dom": ">=17.0.2 <= 18"
}
},
"node_modules/postcss": {
"version": "8.4.33",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",

View File

@ -15,6 +15,7 @@
"@react-three/rapier": "^1.2.1",
"gsap": "^3.12.5",
"leva": "^0.9.35",
"playroomkit": "^0.0.61",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-gamepad": "^1.0.3",

35943
public/cubicle-99.CUBE Normal file

File diff suppressed because it is too large Load Diff

BIN
public/fonts/HK.ttf Normal file

Binary file not shown.

View File

@ -1,8 +1,10 @@
import { Canvas } from '@react-three/fiber'
import { Experience } from './components/Experience'
import { Suspense, useMemo } from 'react'
import { Suspense, useEffect, useMemo } from 'react'
import { Physics } from '@react-three/rapier'
import { KeyboardControls, Loader, OrbitControls, Preload, Stats } from '@react-three/drei'
import { insertCoin, onPlayerJoin } from 'playroomkit'
import { useStore } from "./components/store";
export const Controls = {
up: 'up',
@ -29,6 +31,27 @@ function App() {
[]
)
const { actions } = useStore();
const start = async () => {
await insertCoin();
onPlayerJoin((state) => {
actions.addPlayer(state);
console.log('player joined', state);
actions.setId(state.id);
console.log(state)
state.onQuit(() => {
actions.removePlayer(state);
console.log('player quit', state);
});
});
}
useEffect(() => {
start();
}, [])
return (
<>
<Loader />

View File

@ -40,7 +40,6 @@ export const HUD = () => {
default:
setImage("");
}
console.log(item);
}, [item]);
return (

View File

@ -17,24 +17,75 @@ import {
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";
import {
RPC,
getState,
insertCoin,
isHost,
myPlayer,
onPlayerJoin,
useMultiplayerState,
} from "playroomkit";
import { PlayerDummies } from "./PlayerDummies";
import { useEffect, useState } from "react";
import { useFrame, useLoader } from "@react-three/fiber";
import { LUTPass, LUTCubeLoader } from 'three-stdlib'
export const Experience = () => {
const onCollide = (event) => {
console.log(event);
};
const { bananas, shells} = useStore();
const { bananas, shells, players, id, actions } = useStore();
const [networkBananas, setNetworkBananas] = useMultiplayerState(
"bananas",
[]
);
const [networkShells, setNetworkShells] = useMultiplayerState("shells", []);
const testing = getState("bananas");
// useEffect(() => {
// setNetworkBananas(bananas);
// }, [bananas]);
// useEffect(() => {
// setNetworkShells(shells);
// }, [shells]);
const {texture}= useLoader(LUTCubeLoader, "./cubicle-99.CUBE");
return (
<>
<PlayerController />
{players.map((player) => (
<PlayerController
key={player.id}
player={player}
userPlayer={player.id === myPlayer()?.id}
setNetworkBananas={setNetworkBananas}
setNetworkShells={setNetworkShells}
networkBananas={networkBananas}
networkShells={networkShells}
/>
))}
{players.map((player) => (
<PlayerDummies
key={player.id}
player={player}
userPlayer={player.id === myPlayer()?.id}
/>
))}
<Paris position={[0, 0, 0]} />
<Banana onCollide={onCollide} position={[-10, 1.8, -119]} />
{/* <Banana onCollide={onCollide} position={[-10, 1.8, -119]} /> */}
{/* <Shell position={[-20, 2, -119]} /> */}
<ItemBox position={[-20, 2.5, -119]} />
<Coin position={[-30, 2, -119]} />
@ -42,27 +93,29 @@ export const Experience = () => {
<Ground position={[0, 0, 0]} />
<Environment resolution={256} preset="lobby" />
{bananas.map((banana) => (
{networkBananas.map((banana) => (
<Banana
key={banana.id}
position={banana.position}
banana={banana}
setNetworkBananas={setNetworkBananas}
networkBananas={networkBananas}
id={banana.id}
// rotation={banana.rotation}
/>
))}
{shells.map((shell) => (
{networkShells.map((shell) => (
<Shell
key={shell.id}
id={shell.id}
position={shell.position}
rotation={shell.rotation}
setNetworkShells={setNetworkShells}
networkShells={networkShells}
/>
))}
<directionalLight
{/* <directionalLight
position={[10, 50, -30]}
intensity={1}
shadow-bias={-0.0001}
@ -72,7 +125,7 @@ export const Experience = () => {
shadow-camera-top={300}
shadow-camera-bottom={-300}
castShadow
/>
/> */}
<EffectComposer
multisampling={0}
@ -88,11 +141,10 @@ export const Experience = () => {
luminanceSmoothing={0.01}
intensity={0.5}
/>
<TiltShift2 />
<ChromaticAberration offset={[0.0006, 0.0006]} />
<HueSaturation saturation={0.1} />
<Vignette eskil={false} offset={0.1} darkness={0.4} />
{/* <ChromaticAberration offset={[0.0006, 0.0006]} /> */}
<HueSaturation saturation={0.05} />
{/* <Vignette eskil={false} offset={0.1} darkness={0.4} /> */}
</EffectComposer>
</>
);

View File

@ -3,13 +3,9 @@ import { BallCollider, RigidBody, useRapier, vec3 } from "@react-three/rapier";
import {
useKeyboardControls,
PerspectiveCamera,
ContactShadows,
Sphere,
OrbitControls,
Trail,
PositionalAudio,
} from "@react-three/drei";
import { useFrame, useThree } from "@react-three/fiber";
import { useFrame, useThree, extend } from "@react-three/fiber";
import { useRef, useState, useEffect, useCallback } from "react";
import * as THREE from "three";
@ -26,8 +22,17 @@ 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 PlayerController = () => {
export const PlayerController = ({
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]);
@ -35,7 +40,7 @@ export const PlayerController = () => {
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();
@ -83,13 +88,13 @@ export const PlayerController = () => {
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} = useStore();
const { actions, shouldSlowDown, item, bananas, coins, id } = 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);
@ -128,15 +133,14 @@ export const PlayerController = () => {
steeringAngle = currentSteeringSpeed * -pointer.x;
targetXPosition = -camMaxOffset * -pointer.x;
} else if (driftLeft.current && !driftRight.current) {
steeringAngle = currentSteeringSpeed * -(pointer.x - 0.5);
steeringAngle = currentSteeringSpeed * -(pointer.x - 1);
targetXPosition = -camMaxOffset * -pointer.x;
} else if (driftRight.current && !driftLeft.current) {
steeringAngle = currentSteeringSpeed * -(pointer.x + 0.5);
steeringAngle = currentSteeringSpeed * -(pointer.x + 1);
targetXPosition = -camMaxOffset * -pointer.x;
}
// ACCELERATING
const shouldSlow = actions.getShouldSlowDown();
if (upPressed && currentSpeed < maxSpeed) {
// Accelerate the kart within the maximum speed limit
@ -164,7 +168,9 @@ export const PlayerController = () => {
}
}
if (shouldSlow) {
setCurrentSpeed(Math.max(currentSpeed - decceleration * 2 * delta * 144, 0));
setCurrentSpeed(
Math.max(currentSpeed - decceleration * 2 * delta * 144, 0)
);
setCurrentSteeringSpeed(0);
slowDownDuration.current -= 1500 * delta;
setShouldLaunch(true);
@ -173,12 +179,8 @@ export const PlayerController = () => {
slowDownDuration.current = 1500;
setShouldLaunch(false);
}
}
// REVERSING
if (downPressed && currentSpeed < -maxSpeed) {
setCurrentSpeed(
@ -235,7 +237,7 @@ export const PlayerController = () => {
if (isOnFloor.current && jumpForce.current > 0) {
landingSound.current.play();
}
if (!isOnGround && jumpForce.current > 0 ) {
if (!isOnGround && jumpForce.current > 0) {
jumpForce.current -= 1 * delta * 144;
}
if (!jumpPressed) {
@ -282,7 +284,7 @@ export const PlayerController = () => {
driftForce.current = 0.4;
mario.current.rotation.y = THREE.MathUtils.lerp(
mario.current.rotation.y,
steeringAngle * 50 + 0.5,
steeringAngle * 25 + 0.4,
0.05 * delta * 144
);
accumulatedDriftPower.current += 0.1 * (steeringAngle + 1) * delta * 144;
@ -292,7 +294,7 @@ export const PlayerController = () => {
driftForce.current = 0.4;
mario.current.rotation.y = THREE.MathUtils.lerp(
mario.current.rotation.y,
-(-steeringAngle * 50 + 0.5),
-(-steeringAngle * 25 + 0.4),
0.05 * delta * 144
);
accumulatedDriftPower.current += 0.1 * (-steeringAngle + 1) * delta * 144;
@ -326,7 +328,6 @@ export const PlayerController = () => {
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 {
@ -353,7 +354,7 @@ export const PlayerController = () => {
setCurrentSpeed(boostSpeed);
effectiveBoost.current -= 1 * delta * 144;
targetZPosition = 10;
if(!turboSound.current.isPlaying) turboSound.current.play();
if (!turboSound.current.isPlaying) turboSound.current.play();
driftTwoSound.current.play();
driftBlueSound.current.stop();
driftOrangeSound.current.stop();
@ -396,10 +397,10 @@ export const PlayerController = () => {
// 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});
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);
@ -410,63 +411,66 @@ export const PlayerController = () => {
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())
);
// ITEMS
if(shootPressed && item === "banana") {
const distanceBehind = 2; // Adjust this value as needed
const scaledBackwardDirection = forwardDirection.multiplyScalar(distanceBehind);
// Get the current position of the kart
const kartPosition = new THREE.Vector3(...vec3(body.current.translation()));
// Calculate the position for the new banana
const bananaPosition = kartPosition.sub(scaledBackwardDirection);
const newBanana = {
id: Math.random() + "-" + +new Date(),
position: bananaPosition,
player: true,
};
actions.addBanana(newBanana);
actions.useItem();
setNetworkBananas([...networkBananas, newBanana]);
actions.useItem();
}
if(shootPressed && item === "shell") {
const distanceBehind = -1; // Adjust this value as needed
const scaledBackwardDirection = forwardDirection.multiplyScalar(distanceBehind);
// Get the current position of the kart
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
);
// Calculate the position for the new banana
const shellPosition = kartPosition.sub(scaledBackwardDirection);
const newShell = {
id: Math.random() + "-" + +new Date(),
position: shellPosition,
player: true,
rotation: kartRotation
rotation: kartRotation,
};
actions.addShell(newShell);
setNetworkShells([...networkShells, newShell]);
actions.useItem();
}
if(shootPressed && item === "mushroom") {
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 (
return player.id === id ? (
<group>
<RigidBody
ref={body}
@ -476,19 +480,19 @@ export const PlayerController = () => {
mass={3}
ccd
name="player"
type={player.id === id ? "dynamic" : "kinematic"}
>
<BallCollider
args={[0.5]}
mass={3}
onCollisionEnter={({other}) => {
onCollisionEnter={({ other }) => {
isOnFloor.current = true;
setIsOnGround(true);
}}
onCollisionExit={({other}) => {
onCollisionExit={({ other }) => {
isOnFloor.current = false;
setIsOnGround(false);
}}
/>
</RigidBody>
@ -498,9 +502,10 @@ export const PlayerController = () => {
currentSpeed={currentSpeed}
steeringAngleWheels={steeringAngleWheels}
isBoosting={isBoosting}
shouldLaunch={shouldLaunch}
/>
<CoinParticles coins={coins}/>
<ItemParticles item={item}/>
<CoinParticles coins={coins} />
<ItemParticles item={item} />
<mesh position={[0.6, 0.05, 0.5]} scale={scale}>
<sphereGeometry args={[0.05, 16, 16]} />
<meshStandardMaterial
@ -539,7 +544,6 @@ export const PlayerController = () => {
glowSharpness={1}
/>
</mesh>
{/* <FlameParticles isBoosting={isBoosting} /> */}
<DriftParticlesLeft turboColor={turboColor} scale={scale} />
<DriftParticlesRight turboColor={turboColor} scale={scale} />
@ -563,7 +567,7 @@ export const PlayerController = () => {
png="./particles/star.png"
turboColor={turboColor}
/>
<HitParticles shouldLaunch={shouldLaunch}/>
<HitParticles shouldLaunch={shouldLaunch} />
</group>
{/* <ContactShadows frames={1} /> */}
@ -631,5 +635,5 @@ export const PlayerController = () => {
/>
</group>
</group>
);
) : null;
};

View File

@ -0,0 +1,269 @@
import { Controls } from "../App";
import { BallCollider, RigidBody, useRapier, vec3 } from "@react-three/rapier";
import {
useKeyboardControls,
PerspectiveCamera,
ContactShadows,
Sphere,
OrbitControls,
Trail,
PositionalAudio,
Text,
Billboard,
} from "@react-three/drei";
import { useFrame, useThree } 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 { isHost } from "playroomkit";
import { Banana } from "./models/items/Banana_peel_mario_kart";
export const PlayerDummies = ( { player, userPlayer }) => {
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 [networkBananas, setNetworkBananas] = useState([]);
const [networkShells, setNetworkShells] = useState([]);
const { actions, shouldSlowDown, item, coins, id} = useStore();
const slowDownDuration = useRef(1500);
useFrame((state, delta) => {
const bodyPosition = player.getState("position");
const bodyRotation = player.getState("rotation");
setIsBoosting(player.getState("isBoosting"));
setShouldLaunch(player.getState("shouldLaunch"));
setTurboColor(player.getState("turboColor"));
setScale(player.getState("scale"));
if(bodyPosition && bodyRotation && kart.current && mario.current){
kart.current.position.set(bodyPosition.x, bodyPosition.y -.5, bodyPosition.z);
kart.current.rotation.set(0, bodyRotation, 0);
body.current.setTranslation([bodyPosition.x, bodyPosition.y, bodyPosition.z]);
}
});
return player.id != id? (
<>
<Billboard>
<Text font={"./fonts/HK.ttf"} ref={text} fontSize={0.4} outlineWidth={0.03} position={[0, 2, 0]}>{player.state.profile.name}</Text>
</Billboard>
<group>
<RigidBody
type="kinematic"
ref={body}
position={[0, 0, 0]}
rotation={[0, 0, 0]}
colliders={false}
name="player"
>
<BallCollider args={[0.5]} />
</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} /> */}
<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;
};

View File

@ -11,7 +11,7 @@ import FakeFlame from '../../ShaderMaterials/FakeFlame/FakeFlame'
import { useStore } from '../../store'
import gsap from 'gsap'
export function Mario({ currentSpeed, steeringAngleWheels, isBoosting, ...props }) {
export function Mario({ currentSpeed, steeringAngleWheels, isBoosting, shouldLaunch, ...props }) {
const { nodes, materials } = useGLTF('./models/characters/mariokarttest.glb')
const frontLeftWheel = useRef()
@ -39,11 +39,11 @@ export function Mario({ currentSpeed, steeringAngleWheels, isBoosting, ...props
})
useEffect(() => {
if (shouldSlow) {
if (shouldLaunch) {
gsap.to(mario.current.rotation, {duration: 1.5, y: Math.PI * 3})
mario.current.rotation.set(0, 0, 0);
}
}, [shouldSlow])
}, [shouldLaunch])
return (
<group
{...props}

View File

@ -8,13 +8,13 @@ Source: https://sketchfab.com/3d-models/banana-peel-mario-kart-c7fd163741614859b
Title: Banana Peel (Mario Kart)
*/
import React, { useRef } from 'react'
import React, { useEffect, useRef } from 'react'
import { useGLTF } from '@react-three/drei'
import { BallCollider, RigidBody } from '@react-three/rapier'
import { useFrame } from '@react-three/fiber';
import { useStore } from '../../store';
export function Banana({onCollide, id, ...props}) {
export function Banana({onCollide, id, position, setNetworkBananas, networkBananas}) {
const { nodes, materials } = useGLTF('./models/items/banana_peel_mario_kart-transformed.glb');
const rigidBody = useRef();
const ref = useRef();
@ -28,13 +28,13 @@ export function Banana({onCollide, id, ...props}) {
<RigidBody
ref={rigidBody}
type='fixed'
position={props.position}
position={[position.x, position.y, position.z]}
sensor
onIntersectionEnter={({other}) => {
if(other.rigidBodyObject.name === "player"){
actions.setShouldSlowDown(true);
setNetworkBananas(networkBananas.filter((banana) => banana.id !== id));
}
actions.removeBananaById(id);
}}
colliders={false}
name='banana'
@ -43,7 +43,7 @@ export function Banana({onCollide, id, ...props}) {
</RigidBody>
<group {...props} ref={ref} scale={scale} dispose={null}>
<group position={[position.x, position.y, position.z]} ref={ref} scale={scale} dispose={null}>
<mesh castShadow receiveShadow geometry={nodes['Banana_Peel_02_-_Default_0'].geometry} material={materials['02_-_Default']} position={[39.973, -25.006, -0.017]} rotation={[-Math.PI / 2, 0, 0]} />
<mesh castShadow receiveShadow geometry={nodes['Banana_Peel_07_-_Default_0'].geometry} material={materials['07_-_Default']} position={[39.973, -25.006, -0.017]} rotation={[-Math.PI / 2, 0, 0]} />
<mesh castShadow receiveShadow geometry={nodes['Banana_Peel_03_-_Default_0'].geometry} material={materials['03_-_Default']} position={[39.973, -25.006, -0.017]} rotation={[-Math.PI / 2, 0, 0]} />

View File

@ -7,45 +7,70 @@ Source: https://sketchfab.com/3d-models/mario-shell-red-76a81ff57398423d80800259
Title: Mario Shell Red
*/
import React, { useEffect, useRef } from 'react'
import { useGLTF } from '@react-three/drei'
import { BallCollider, RigidBody, vec3 } from '@react-three/rapier'
import { useFrame } from '@react-three/fiber';
import React, { useEffect, useRef } from "react";
import { useGLTF } from "@react-three/drei";
import { BallCollider, RigidBody, vec3 } from "@react-three/rapier";
import { useFrame } from "@react-three/fiber";
import { useStore } from "../../store";
export function Shell(props) {
const { nodes, materials } = useGLTF('./models/items/mario_shell_red.glb')
export function Shell({ id, position, rotation, setNetworkShells, networkShells, ...props}) {
const { nodes, materials } = useGLTF("./models/items/mario_shell_red.glb");
const rigidBody = useRef();
const ref = useRef();
const shell_speed = 100;
const {actions} = useStore();
useEffect(() => {
const velocity = {
x : -Math.sin(props.rotation) * shell_speed,
y : 0,
z : -Math.cos(props.rotation) * shell_speed,
x: -Math.sin(rotation) * shell_speed,
y: 0,
z: -Math.cos(rotation) * shell_speed,
};
rigidBody.current.setLinvel(velocity, true);
},[]);
}, []);
useFrame((state, delta) => {
const rigidBodyPosition = rigidBody.current.translation();
ref.current.position.set(rigidBodyPosition.x, rigidBodyPosition.y, rigidBodyPosition.z);
ref.current.rotation.z += 0.2 * delta * 144;
if(rigidBody.current && ref.current){
const rigidBodyPosition = rigidBody.current.translation();
ref.current.position.set(
rigidBodyPosition.x,
rigidBodyPosition.y,
rigidBodyPosition.z
);
ref.current.rotation.z += 0.2 * delta * 144;
}
});
return (
<group dispose={null}>
<RigidBody ref={rigidBody} type="dynamic" position={props.position} name="shell" colliders={false}>
<BallCollider args={[0.5]} />
</RigidBody>
<mesh ref={ref} geometry={nodes.defaultMaterial.geometry} material={materials.Shell} rotation={[-Math.PI / 2, 0, 0]} scale={0.6} />
<RigidBody
ref={rigidBody}
type="dynamic"
position={[position.x, position.y, position.z]}
name="shell"
colliders={false}
onCollisionEnter={(other) => {
if (other.rigidBodyObject.name === "player") {
actions.setShouldSlowDown(true);
setNetworkShells(
networkShells.filter((shell) => shell.id !== id)
);
}
}}
>
<BallCollider args={[0.5]} />
</RigidBody>
<mesh
ref={ref}
geometry={nodes.defaultMaterial.geometry}
material={materials.Shell}
rotation={[-Math.PI / 2, 0, 0]}
scale={0.6}
/>
</group>
)
);
}
useGLTF.preload('./models/items/mario_shell_red.glb')
useGLTF.preload("./models/items/mario_shell_red.glb");

View File

@ -20,11 +20,13 @@ export const useStore = create((set, get) => ({
pastPositions: [],
shouldSlowdown: false,
bananas: [],
items: ["banana", "shell", "mushroom"],
items: ["mushroom", "shell", "banana"],
item: "",
shells: [],
skids: [],
coins : 0,
players : [],
id : "",
addPastPosition: (position) => {
set((state) => ({
pastPositions: [position, ...state.pastPositions.slice(0, 499)],
@ -87,6 +89,9 @@ export const useStore = create((set, get) => ({
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)],
@ -122,6 +127,19 @@ export const useStore = create((set, get) => ({
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});
}
},
}));

View File

@ -110,3 +110,14 @@ body::-webkit-scrollbar {
transform: scale(1.2);
}
}
.annotation{
display:flex;
justify-content: center;
align-items: center;
background:none;
backdrop-filter: blur(10px);
pointer-events: none;
}