feat(game): Added multiplayer mode

pull/3/head
Alex 2024-02-09 14:51:35 +01:00
parent b825b0fc20
commit 42680aa707
7 changed files with 129 additions and 61 deletions

View File

@ -23,34 +23,65 @@ import { ItemBox } from "./models/misc/Gift";
import { useStore } from "./store"; import { useStore } from "./store";
import { Shell } from "./models/items/Mario_shell_red"; import { Shell } from "./models/items/Mario_shell_red";
import { Coin } from "./models/misc/Super_mario_bros_coin"; import { Coin } from "./models/misc/Super_mario_bros_coin";
import { RPC, insertCoin, myPlayer, onPlayerJoin, useMultiplayerState } from "playroomkit"; import {
RPC,
getState,
insertCoin,
isHost,
myPlayer,
onPlayerJoin,
useMultiplayerState,
} from "playroomkit";
import { PlayerDummies } from "./PlayerDummies"; import { PlayerDummies } from "./PlayerDummies";
import { useEffect } from "react"; import { useEffect, useState } from "react";
import { useFrame } from "@react-three/fiber"; import { useFrame } from "@react-three/fiber";
export const Experience = () => { export const Experience = () => {
const onCollide = (event) => { const onCollide = (event) => {
console.log(event); console.log(event);
}; };
const { bananas, shells, players, id, actions} = useStore(); const { bananas, shells, players, id, actions } = useStore();
const [networkBananas, setNetworkBananas] = useMultiplayerState("bananas", []); const [networkBananas, setNetworkBananas] = useMultiplayerState(
"bananas",
[]
);
const [networkShells, setNetworkShells] = useMultiplayerState("shells", []); const [networkShells, setNetworkShells] = useMultiplayerState("shells", []);
const testing = getState("bananas");
// useEffect(() => {
// setNetworkBananas(bananas);
// }, [bananas]);
// useEffect(() => {
// setNetworkShells(shells);
// }, [shells]);
return ( return (
<> <>
{players.map((player) => (
<PlayerController key={player.id} player={player} userPlayer={player.id === myPlayer()?.id} />
))}
{players.map((player) => ( {players.map((player) => (
<PlayerDummies key={player.id} player={player} userPlayer={player.id === myPlayer()?.id} /> <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]} /> <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]} /> */} {/* <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]} />
@ -58,23 +89,25 @@ export const Experience = () => {
<Ground position={[0, 0, 0]} /> <Ground position={[0, 0, 0]} />
<Environment resolution={256} preset="lobby" /> <Environment resolution={256} preset="lobby" />
{networkBananas.map((banana) => (
{/* {bananas.map((banana) => (
<Banana <Banana
key={banana.id} key={banana.id}
position={banana.position} position={banana.position}
banana={banana} setNetworkBananas={setNetworkBananas}
networkBananas={networkBananas}
id={banana.id} id={banana.id}
// rotation={banana.rotation} // rotation={banana.rotation}
/> />
))} */} ))}
{networkShells.map((shell) => (
{shells.map((shell) => (
<Shell <Shell
key={shell.id} key={shell.id}
id={shell.id}
position={shell.position} position={shell.position}
rotation={shell.rotation} rotation={shell.rotation}
setNetworkShells={setNetworkShells}
networkShells={networkShells}
/> />
))} ))}

View File

@ -28,7 +28,7 @@ import { CoinParticles } from "./Particles/coins/CoinParticles";
import { ItemParticles } from "./Particles/items/ItemParticles"; import { ItemParticles } from "./Particles/items/ItemParticles";
import { isHost } from "playroomkit"; import { isHost } from "playroomkit";
export const PlayerController = ( { player, userPlayer }) => { export const PlayerController = ( { player, userPlayer, setNetworkBananas, setNetworkShells, networkBananas, networkShells }) => {
const upPressed = useKeyboardControls((state) => state[Controls.up]); const upPressed = useKeyboardControls((state) => state[Controls.up]);
const downPressed = useKeyboardControls((state) => state[Controls.down]); const downPressed = useKeyboardControls((state) => state[Controls.down]);
@ -330,8 +330,7 @@ export const PlayerController = ( { player, userPlayer }) => {
if (driftLeft.current || driftRight.current) { if (driftLeft.current || driftRight.current) {
const oscillation = Math.sin(time * 1000) * 0.1; const oscillation = Math.sin(time * 1000) * 0.1;
const vibration = oscillation + 0.9; const vibration = oscillation + 0.9;
if (turboColor === 0xffffff) {
if (turboColor === 0xffffff) {
setScale(vibration * 0.8); setScale(vibration * 0.8);
} else { } else {
setScale(vibration); setScale(vibration);
@ -419,36 +418,34 @@ export const PlayerController = ( { player, userPlayer }) => {
// ITEMS // ITEMS
if(shootPressed && item === "banana") { if(shootPressed && item === "banana") {
const distanceBehind = 2; // Adjust this value as needed const distanceBehind = 2;
const scaledBackwardDirection = forwardDirection.multiplyScalar(distanceBehind); const scaledBackwardDirection = forwardDirection.multiplyScalar(distanceBehind);
// Get the current position of the kart
const kartPosition = new THREE.Vector3(...vec3(body.current.translation())); const kartPosition = new THREE.Vector3(...vec3(body.current.translation()));
// Calculate the position for the new banana
const bananaPosition = kartPosition.sub(scaledBackwardDirection); const bananaPosition = kartPosition.sub(scaledBackwardDirection);
const newBanana = { const newBanana = {
id: Math.random() + "-" + +new Date(), id: Math.random() + "-" + +new Date(),
position: bananaPosition, position: bananaPosition,
player: true, player: true,
}; };
actions.addBanana(newBanana); setNetworkBananas([...networkBananas, newBanana]);
actions.useItem();
actions.useItem();
} }
if(shootPressed && item === "shell") { if(shootPressed && item === "shell") {
const distanceBehind = -1; // Adjust this value as needed const distanceBehind = -2;
const scaledBackwardDirection = forwardDirection.multiplyScalar(distanceBehind); const scaledBackwardDirection = forwardDirection.multiplyScalar(distanceBehind);
// Get the current position of the kart
const kartPosition = new THREE.Vector3( const kartPosition = new THREE.Vector3(
body.current.translation().x, body.current.translation().x,
body.current.translation().y, body.current.translation().y,
body.current.translation().z body.current.translation().z
); );
// Calculate the position for the new banana
const shellPosition = kartPosition.sub(scaledBackwardDirection); const shellPosition = kartPosition.sub(scaledBackwardDirection);
const newShell = { const newShell = {
id: Math.random() + "-" + +new Date(), id: Math.random() + "-" + +new Date(),
@ -456,7 +453,7 @@ export const PlayerController = ( { player, userPlayer }) => {
player: true, player: true,
rotation: kartRotation rotation: kartRotation
}; };
actions.addShell(newShell); setNetworkShells([...networkShells, newShell]);
actions.useItem(); actions.useItem();
} }
@ -513,6 +510,7 @@ export const PlayerController = ( { player, userPlayer }) => {
currentSpeed={currentSpeed} currentSpeed={currentSpeed}
steeringAngleWheels={steeringAngleWheels} steeringAngleWheels={steeringAngleWheels}
isBoosting={isBoosting} isBoosting={isBoosting}
shouldLaunch={shouldLaunch}
/> />
<CoinParticles coins={coins}/> <CoinParticles coins={coins}/>
<ItemParticles item={item}/> <ItemParticles item={item}/>

View File

@ -106,6 +106,7 @@ export const PlayerDummies = ( { player, userPlayer }) => {
if(bodyPosition && bodyRotation && kart.current && mario.current){ if(bodyPosition && bodyRotation && kart.current && mario.current){
kart.current.position.set(bodyPosition.x, bodyPosition.y -.5, bodyPosition.z); kart.current.position.set(bodyPosition.x, bodyPosition.y -.5, bodyPosition.z);
kart.current.rotation.set(0, bodyRotation, 0); kart.current.rotation.set(0, bodyRotation, 0);
body.current.setTranslation([bodyPosition.x, bodyPosition.y, bodyPosition.z]);
} }
}); });
@ -113,6 +114,16 @@ export const PlayerDummies = ( { player, userPlayer }) => {
return player.id != id? ( return player.id != id? (
<> <>
<group> <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={kart} rotation={[0, Math.PI / 2, 0]}>
<group ref={mario}> <group ref={mario}>
@ -120,6 +131,7 @@ export const PlayerDummies = ( { player, userPlayer }) => {
currentSpeed={currentSpeed} currentSpeed={currentSpeed}
steeringAngleWheels={steeringAngleWheels} steeringAngleWheels={steeringAngleWheels}
isBoosting={isBoosting} isBoosting={isBoosting}
shouldLaunch={shouldLaunch}
/> />
<CoinParticles coins={coins}/> <CoinParticles coins={coins}/>
<ItemParticles item={item}/> <ItemParticles item={item}/>

View File

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

View File

@ -8,13 +8,13 @@ Source: https://sketchfab.com/3d-models/banana-peel-mario-kart-c7fd163741614859b
Title: Banana Peel (Mario Kart) Title: Banana Peel (Mario Kart)
*/ */
import React, { useRef } from 'react' import React, { useEffect, useRef } from 'react'
import { useGLTF } from '@react-three/drei' import { useGLTF } from '@react-three/drei'
import { BallCollider, RigidBody } from '@react-three/rapier' import { BallCollider, RigidBody } from '@react-three/rapier'
import { useFrame } from '@react-three/fiber'; import { useFrame } from '@react-three/fiber';
import { useStore } from '../../store'; 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 { nodes, materials } = useGLTF('./models/items/banana_peel_mario_kart-transformed.glb');
const rigidBody = useRef(); const rigidBody = useRef();
const ref = useRef(); const ref = useRef();
@ -28,13 +28,13 @@ export function Banana({onCollide, id, ...props}) {
<RigidBody <RigidBody
ref={rigidBody} ref={rigidBody}
type='fixed' type='fixed'
position={props.position} position={[position.x, position.y, position.z]}
sensor sensor
onIntersectionEnter={({other}) => { onIntersectionEnter={({other}) => {
if(other.rigidBodyObject.name === "player"){ if(other.rigidBodyObject.name === "player"){
actions.setShouldSlowDown(true); actions.setShouldSlowDown(true);
setNetworkBananas(networkBananas.filter((banana) => banana.id !== id));
} }
actions.removeBananaById(id);
}} }}
colliders={false} colliders={false}
name='banana' name='banana'
@ -43,7 +43,7 @@ export function Banana({onCollide, id, ...props}) {
</RigidBody> </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_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_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]} /> <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 Title: Mario Shell Red
*/ */
import React, { useEffect, useRef } from 'react' import React, { useEffect, useRef } from "react";
import { useGLTF } from '@react-three/drei' import { useGLTF } from "@react-three/drei";
import { BallCollider, RigidBody, vec3 } from '@react-three/rapier' import { BallCollider, RigidBody, vec3 } from "@react-three/rapier";
import { useFrame } from '@react-three/fiber'; import { useFrame } from "@react-three/fiber";
import { useStore } from "../../store";
export function Shell(props) { export function Shell({ id, position, rotation, setNetworkShells, networkShells, ...props}) {
const { nodes, materials } = useGLTF('./models/items/mario_shell_red.glb') const { nodes, materials } = useGLTF("./models/items/mario_shell_red.glb");
const rigidBody = useRef(); const rigidBody = useRef();
const ref = useRef(); const ref = useRef();
const shell_speed = 100; const shell_speed = 100;
const {actions} = useStore();
useEffect(() => { useEffect(() => {
const velocity = { const velocity = {
x : -Math.sin(props.rotation) * shell_speed, x: -Math.sin(rotation) * shell_speed,
y : 0, y: 0,
z : -Math.cos(props.rotation) * shell_speed, z: -Math.cos(rotation) * shell_speed,
}; };
rigidBody.current.setLinvel(velocity, true); rigidBody.current.setLinvel(velocity, true);
},[]); }, []);
useFrame((state, delta) => { useFrame((state, delta) => {
const rigidBodyPosition = rigidBody.current.translation(); if(rigidBody.current && ref.current){
ref.current.position.set(rigidBodyPosition.x, rigidBodyPosition.y, rigidBodyPosition.z); const rigidBodyPosition = rigidBody.current.translation();
ref.current.rotation.z += 0.2 * delta * 144; ref.current.position.set(
rigidBodyPosition.x,
rigidBodyPosition.y,
rigidBodyPosition.z
);
ref.current.rotation.z += 0.2 * delta * 144;
}
}); });
return ( return (
<group dispose={null}> <group dispose={null}>
<RigidBody
<RigidBody ref={rigidBody} type="dynamic" position={props.position} name="shell" colliders={false}> ref={rigidBody}
<BallCollider args={[0.5]} /> type="dynamic"
position={[position.x, position.y, position.z]}
</RigidBody> name="shell"
<mesh ref={ref} geometry={nodes.defaultMaterial.geometry} material={materials.Shell} rotation={[-Math.PI / 2, 0, 0]} scale={0.6} /> 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> </group>
) );
} }
useGLTF.preload('./models/items/mario_shell_red.glb') useGLTF.preload("./models/items/mario_shell_red.glb");

View File

@ -20,7 +20,7 @@ export const useStore = create((set, get) => ({
pastPositions: [], pastPositions: [],
shouldSlowdown: false, shouldSlowdown: false,
bananas: [], bananas: [],
items: ["banana"], items: ["mushroom"],
item: "", item: "",
shells: [], shells: [],
skids: [], skids: [],