feat:(game) added interraction and particles for items/coins

pull/3/head
Alex 2024-02-05 10:50:04 +01:00
parent cff143c925
commit db52f49e09
33 changed files with 516 additions and 44 deletions

Binary file not shown.

BIN
public/models/misc/gift.glb Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -30,24 +30,26 @@ function App() {
)
return (
<>
<Loader />
<Canvas
shadows
dpr={1}
gl={{ antialias: false, stencil: false, powerPreference: 'high-performance' }}
>
<Suspense fallback={null}>
<Preload all />
<Physics
gravity={[0, -90, 0]}
timeStep={'vary'}
// debug
>
<KeyboardControls map={map}>
<Experience />
</KeyboardControls>
<Stats />
</Physics>
</Suspense>
</Canvas>
</>
)
}

View File

@ -30,7 +30,7 @@ export const HUD = () => {
</div>
<div className="wheel">
<img
ref={wheel}
ref={wheel}
src="./steering_wheel.png"
alt="steering wheel"
className="steering-wheel"

View File

@ -19,7 +19,7 @@ import {
Vignette,
} from "@react-three/postprocessing";
import { Banana } from "./models/items/Banana_peel_mario_kart";
import { ItemBox } from "./models/misc/Mario_kart_item_box";
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";
@ -36,7 +36,7 @@ export const Experience = () => {
<Paris position={[0, 0, 0]} />
<Banana onCollide={onCollide} position={[-10, 1.8, -119]} />
{/* <Shell position={[-20, 2, -119]} /> */}
<ItemBox position={[-20, 2, -119]} />
<ItemBox position={[-20, 2.5, -119]} />
<Coin position={[-30, 2, -119]} />
<Ground position={[0, 0, 0]} />

View File

@ -0,0 +1,56 @@
import React, { useEffect,useRef, useState } from "react";
import { useLoader, useFrame } from "@react-three/fiber";
import * as THREE from "three";
export const CircleCoinParticle = ({ position, coins }) => {
const texture = useLoader(THREE.TextureLoader, "./particles/circle_coin.png");
const pointsRef = useRef();
const materialRef = useRef();
const [size, setSize] = useState(1);
const [opacity, setOpacity] = useState(1);
const points = React.useMemo(() => {
const geom = new THREE.BufferGeometry();
geom.setAttribute(
"position",
new THREE.Float32BufferAttribute(position, 3)
);
return geom;
}, [position]);
useEffect(() => {
if (materialRef.current) {
materialRef.current.color.multiplyScalar(4);
}
}, []);
useEffect(() => {
setSize(0);
setOpacity(1);
}, [coins]);
useFrame((_, delta) => {
if (size < 5) {
setSize((size) => Math.min(size + 0.2 * delta * 144, 5));
} else if (opacity > 0) {
setOpacity((opacity) => Math.max(opacity - 0.1 * delta * 144, 0));
setSize((size) => Math.max(size - 0.1 * delta * 144, 0));
}
});
return (
<points ref={pointsRef} geometry={points}>
<pointsMaterial
ref={materialRef}
size={size}
alphaMap={texture}
transparent={true}
depthWrite={false}
opacity={opacity}
toneMapped={false}
color={0xbf8717}
/>
</points>
);
};

View File

@ -0,0 +1,15 @@
import { CircleCoinParticle } from "./CircleCoinParticle"
import { StarCoinParticle } from "./StarCoinParticle"
export const CoinParticles = ({ coins }) => {
return (
<>
<CircleCoinParticle position={[0,0.8, 0.2]} coins={coins}/>
<StarCoinParticle position={[0,0.8, 0.2]} coins={coins} timeModifier={50}/>
<StarCoinParticle position={[0,0.8, 0.2]} coins={coins} timeModifier={60}/>
<StarCoinParticle position={[0,0.8, 0.2]} coins={coins} timeModifier={40}/>
<StarCoinParticle position={[0,0.8, 0.2]} coins={coins} timeModifier={90}/>
</>
)
}

View File

@ -0,0 +1,68 @@
import React, { useEffect, useRef } from "react";
import { useLoader, useFrame } from "@react-three/fiber";
import * as THREE from "three";
export const StarCoinParticle = ({ position, coins, timeModifier }) => {
const texture = useLoader(THREE.TextureLoader, "./particles/star_coin.png");
const pointsRef = useRef();
const materialRef = useRef();
const sizeRef = useRef(1);
const opacityRef = useRef(1);
const originalYpos = useRef(0);
const points = React.useMemo(() => {
const geom = new THREE.BufferGeometry();
geom.setAttribute(
"position",
new THREE.Float32BufferAttribute(position, 3)
);
return geom;
}, [position]);
useEffect(() => {
if (materialRef.current) {
materialRef.current.color.multiplyScalar(6);
}
}, []);
useEffect(() => {
sizeRef.current = 0;
opacityRef.current = 1;
pointsRef.current.position.x = Math.random() * 1 - 0.5;
pointsRef.current.position.y = Math.random() * 0.5 - 0.25;
originalYpos.current = pointsRef.current.position.y;
}, [coins]);
useFrame((state, delta) => {
const time = state.clock.getElapsedTime();
pointsRef.current.position.y += 0.008 * delta * 144;
if (sizeRef.current < 1) {
sizeRef.current = Math.min(sizeRef.current + 0.01 * delta * 144, 1);
}
if (pointsRef.current.position.y > originalYpos.current + 0.01) {
opacityRef.current = Math.max(opacityRef.current - 0.01 * delta * 144, 0);
} else {
opacityRef.current = Math.abs(Math.sin(time * timeModifier * 1500));
}
// Update material properties directly
if (materialRef.current) {
materialRef.current.size = sizeRef.current;
materialRef.current.opacity = opacityRef.current;
}
});
return (
<points ref={pointsRef} geometry={points}>
<pointsMaterial
ref={materialRef}
alphaMap={texture}
transparent={true}
depthWrite={false}
toneMapped={false}
color={0xbf8717}
/>
</points>
);
};

View File

@ -0,0 +1,61 @@
import React, { useEffect,useRef, useState } from "react";
import { useLoader, useFrame } from "@react-three/fiber";
import * as THREE from "three";
export const CircleItemParticle = ({ position, item, color }) => {
const texture = useLoader(THREE.TextureLoader, "./particles/circle_coin.png");
const pointsRef = useRef();
const materialRef = useRef();
const [size, setSize] = useState(1);
const [opacity, setOpacity] = useState(1);
const [currentColor, setCurrentColor] = useState(color);
const points = React.useMemo(() => {
const geom = new THREE.BufferGeometry();
geom.setAttribute(
"position",
new THREE.Float32BufferAttribute(position, 3)
);
return geom;
}, [position]);
useEffect(() => {
if (materialRef.current) {
materialRef.current.color.multiplyScalar(4);
}
}, []);
useEffect(() => {
if(item){
setSize(0);
setOpacity(1);
}
}, [item]);
useFrame((_, delta) => {
if (size < 5) {
setSize((size) => Math.min(size + 0.2 * delta * 144, 5));
} else if (opacity > 0) {
setOpacity((opacity) => Math.max(opacity - 0.1 * delta * 144, 0));
setSize((size) => Math.max(size - 0.1 * delta * 144, 0));
}
});
return (
<points ref={pointsRef} geometry={points}>
<pointsMaterial
ref={materialRef}
size={size}
alphaMap={texture}
transparent={true}
depthWrite={false}
opacity={opacity}
toneMapped={false}
color={currentColor}
/>
</points>
);
};

View File

@ -0,0 +1,20 @@
import { CircleItemParticle } from "./CircleItemParticle"
import { StarItemParticle } from "./StarItemParticle"
import { SmallCircleParticle } from "./SmallCircleParticle"
export const ItemParticles = ({item}) => {
return (
<>
<CircleItemParticle position={[0,0.8, 0.2]} item={item} color={0x75ff9a}/>
<StarItemParticle position={[0,0.8, 0.2]} item={item} timeModifier={50} color={0x75ff9a}/>
<StarItemParticle position={[0,0.8, 0.2]} item={item} timeModifier={60} color={0xe872fc}/>
<StarItemParticle position={[0,0.8, 0.2]} item={item} timeModifier={40} color={0x72e5fc}/>
<StarItemParticle position={[0,0.8, 0.2]} item={item} timeModifier={90} color={0xf0db7f}/>
<SmallCircleParticle position={[0,0.8, 0.2]} item={item} timeModifier={50} color={0x75ff9a}/>
<SmallCircleParticle position={[0,0.8, 0.2]} item={item} timeModifier={60} color={0xe872fc}/>
<SmallCircleParticle position={[0,0.8, 0.2]} item={item} timeModifier={40} color={0x72e5fc}/>
<SmallCircleParticle position={[0,0.8, 0.2]} item={item} timeModifier={90} color={0xf0db7f}/>
</>
)
}

View File

@ -0,0 +1,70 @@
import React, { useEffect, useRef } from "react";
import { useLoader, useFrame } from "@react-three/fiber";
import * as THREE from "three";
export const SmallCircleParticle = ({ position, item, timeModifier, color }) => {
const texture = useLoader(THREE.TextureLoader, "./particles/circle_01.png");
const pointsRef = useRef();
const materialRef = useRef();
const sizeRef = useRef(1);
const opacityRef = useRef(1);
const originalYpos = useRef(0);
const points = React.useMemo(() => {
const geom = new THREE.BufferGeometry();
geom.setAttribute(
"position",
new THREE.Float32BufferAttribute(position, 3)
);
return geom;
}, [position]);
useEffect(() => {
if (materialRef.current) {
materialRef.current.color.multiplyScalar(6);
}
}, []);
useEffect(() => {
if(item){
sizeRef.current = 0;
opacityRef.current = 1;
pointsRef.current.position.x = Math.random() * 1 - 0.5;
pointsRef.current.position.y = Math.random() * 0.5 - 0.25;
originalYpos.current = pointsRef.current.position.y;
}
}, [item]);
useFrame((state, delta) => {
const time = state.clock.getElapsedTime();
pointsRef.current.position.y += 0.008 * delta * 144;
if (sizeRef.current < 1) {
sizeRef.current = Math.min(sizeRef.current + 0.01 * delta * 144, 1);
}
if (pointsRef.current.position.y > originalYpos.current + 0.01) {
opacityRef.current = Math.max(opacityRef.current - 0.01 * delta * 144, 0);
} else {
opacityRef.current = Math.abs(Math.sin(time * timeModifier * 1500));
}
// Update material properties directly
if (materialRef.current) {
materialRef.current.size = sizeRef.current;
materialRef.current.opacity = opacityRef.current;
}
});
return (
<points ref={pointsRef} geometry={points}>
<pointsMaterial
ref={materialRef}
alphaMap={texture}
transparent={true}
depthWrite={false}
toneMapped={false}
color={color}
/>
</points>
);
};

View File

@ -0,0 +1,70 @@
import React, { useEffect, useRef } from "react";
import { useLoader, useFrame } from "@react-three/fiber";
import * as THREE from "three";
export const StarItemParticle = ({ position, item, timeModifier, color }) => {
const texture = useLoader(THREE.TextureLoader, "./particles/star_coin.png");
const pointsRef = useRef();
const materialRef = useRef();
const sizeRef = useRef(1);
const opacityRef = useRef(1);
const originalYpos = useRef(0);
const points = React.useMemo(() => {
const geom = new THREE.BufferGeometry();
geom.setAttribute(
"position",
new THREE.Float32BufferAttribute(position, 3)
);
return geom;
}, [position]);
useEffect(() => {
if (materialRef.current) {
materialRef.current.color.multiplyScalar(6);
}
}, []);
useEffect(() => {
if(item){
sizeRef.current = 0;
opacityRef.current = 1;
pointsRef.current.position.x = Math.random() * 1 - 0.5;
pointsRef.current.position.y = Math.random() * 0.5 - 0.25;
originalYpos.current = pointsRef.current.position.y;
}
}, [item]);
useFrame((state, delta) => {
const time = state.clock.getElapsedTime();
pointsRef.current.position.y += 0.008 * delta * 144;
if (sizeRef.current < 1) {
sizeRef.current = Math.min(sizeRef.current + 0.01 * delta * 144, 1);
}
if (pointsRef.current.position.y > originalYpos.current + 0.01) {
opacityRef.current = Math.max(opacityRef.current - 0.01 * delta * 144, 0);
} else {
opacityRef.current = Math.abs(Math.sin(time * timeModifier * 1500));
}
// Update material properties directly
if (materialRef.current) {
materialRef.current.size = sizeRef.current;
materialRef.current.opacity = opacityRef.current;
}
});
return (
<points ref={pointsRef} geometry={points}>
<pointsMaterial
ref={materialRef}
alphaMap={texture}
transparent={true}
depthWrite={false}
toneMapped={false}
color={color}
/>
</points>
);
};

View File

@ -14,16 +14,18 @@ import { useRef, useState, useEffect, useCallback } from "react";
import * as THREE from "three";
import { Mario } from "./models/characters/Mario_kart";
import { DriftParticlesLeft } from "./Particles/DriftParticlesLeft";
import { DriftParticlesRight } from "./Particles/DriftParticlesRight";
import { DriftParticlesLeft } from "./Particles/drifts/DriftParticlesLeft";
import { DriftParticlesRight } from "./Particles/drifts/DriftParticlesRight";
import { PointParticle } from "./Particles/PointParticle";
import { PointParticle } from "./Particles/drifts/PointParticle";
import { FlameParticles } from "./Particles/FlameParticles";
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/HitParticles";
import { HitParticles } from "./Particles/hits/HitParticles";
import { CoinParticles } from "./Particles/coins/CoinParticles";
import { ItemParticles } from "./Particles/items/ItemParticles";
export const PlayerController = () => {
const upPressed = useKeyboardControls((state) => state[Controls.up]);
@ -83,7 +85,7 @@ export const PlayerController = () => {
const effectiveBoost = useRef(0);
const { actions, shouldSlowDown, item, bananas} = useStore();
const { actions, shouldSlowDown, item, bananas, coins} = useStore();
const slowDownDuration = useRef(1500);
@ -461,7 +463,7 @@ export const PlayerController = () => {
actions.useItem();
}
console.log("coins", coins);
});
return (
@ -497,12 +499,8 @@ export const PlayerController = () => {
steeringAngleWheels={steeringAngleWheels}
isBoosting={isBoosting}
/>
<pointLight
position={[0.6, 0.05, 0.5]}
intensity={scale}
color={turboColor}
/>
<CoinParticles coins={coins}/>
<ItemParticles item={item}/>
<mesh position={[0.6, 0.05, 0.5]} scale={scale}>
<sphereGeometry args={[0.05, 16, 16]} />
<meshStandardMaterial
@ -522,11 +520,6 @@ export const PlayerController = () => {
glowSharpness={1}
/>
</mesh>
<pointLight
position={[-0.6, 0.05, 0.5]}
intensity={scale}
color={turboColor}
/>
<mesh position={[-0.6, 0.05, 0.5]} scale={scale}>
<sphereGeometry args={[0.05, 16, 16]} />
<meshStandardMaterial

View File

@ -0,0 +1,71 @@
/*
Auto-generated by: https://github.com/pmndrs/gltfjsx
Command: npx gltfjsx@6.2.16 .\gift.glb --shadows --transform
Files: .\gift.glb [694.14KB] > C:\Users\mouli\dev\r3f-vite-starter\public\models\misc\gift-transformed.glb [86.34KB] (88%)
Author: gorzi (https://sketchfab.com/gorzi90)
License: CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)
Source: https://sketchfab.com/3d-models/gift-f3d8abcd3b9442f39a9a2017d59b56a1
Title: Gift
*/
import React, { useRef } from 'react'
import { useGLTF, Float } from '@react-three/drei'
import { CuboidCollider, RigidBody } from "@react-three/rapier";
import { useStore } from "../../store";
import { useFrame } from '@react-three/fiber';
export function ItemBox(props) {
const { nodes, materials } = useGLTF('./models/misc/gift-transformed.glb');
const { actions } = useStore();
const ref = useRef();
const [scale, setScale] = React.useState(0.6);
const frames = useRef(0);
const body = useRef();
useFrame((state, delta) => {
const time = state.clock.getElapsedTime();
ref.current.position.y = Math.sin(time) * 0.1 + 2.5;
ref.current.rotation.x = Math.sin(time) * 0.1;
ref.current.rotation.y += delta;
ref.current.rotation.z = Math.sin(time) * 0.5;
if(scale < 0.6 && frames.current > 0){
frames.current -= 1 * delta * 144;
}
if(frames.current <= 0){
setScale(Math.min(scale + 0.5 * delta, 0.6));
if(body.current){
body.current.setEnabled(true);
}
}
}
);
return (
<>
<RigidBody type="fixed" name="itemBox"
sensor
ref={body}
onIntersectionEnter={({other}) => {
if(other.rigidBodyObject.name === "player"){
console.log("item box hit");
actions.setItem();
setScale(0);
frames.current = 400;
body.current.setEnabled(false);
}
}}
position={props.position}
colliders={false}
>
<CuboidCollider args={[1.5, 1.5, 1.5]} />
</RigidBody>
<group ref={ref} position={props.position} scale={scale} dispose={null}>
<mesh castShadow receiveShadow geometry={nodes.Cube000.geometry} material={materials.Material} position={[0.077, 0.5, -0.019]} rotation={[-Math.PI / 2, 0, 0]} />
<mesh castShadow receiveShadow geometry={nodes.Cube000_1.geometry} material={materials['Material.001']} position={[0.077, 0.5, -0.019]} rotation={[-Math.PI / 2, 0, 0]} />
</group>
</>
)
}
useGLTF.preload('./models/misc/gift-transformed.glb')

View File

@ -8,30 +8,64 @@ Source: https://sketchfab.com/3d-models/super-mario-bros-coin-aa97e093847a439f9f
Title: Super Mario Bros Coin
*/
import React, { useEffect, useRef } from 'react'
import { useGLTF, useAnimations } from '@react-three/drei'
import { useFrame } from '@react-three/fiber'
import React, { useEffect, useRef } from "react";
import { useGLTF, useAnimations } from "@react-three/drei";
import { useFrame } from "@react-three/fiber";
import { RigidBody } from "@react-three/rapier";
import { useStore } from "../../store";
export function Coin(props) {
const group = useRef()
const { nodes, materials, animations } = useGLTF('./models/misc/super_mario_bros_coin-transformed.glb')
const { actions } = useAnimations(animations, group)
const group = useRef();
const { nodes, materials } = useGLTF(
"./models/misc/super_mario_bros_coin-transformed.glb"
);
const { actions } = useStore();
const [scale, setScale] = React.useState(0.424);
const frames = useRef(0);
useFrame((state, delta) => {
group.current.rotation.y += 4 * delta
})
group.current.rotation.y += 4 * delta;
if(scale < 0.424 && frames.current > 0){
frames.current -= 1 * delta * 144;
}
if(frames.current <= 0){
setScale(Math.min(scale + 0.5 * delta, 0.424));
if(body.current){
body.current.setEnable(true);
}
}
});
const body = useRef();
return (
<group ref={group} {...props} dispose={null}>
<group name="Sketchfab_Scene">
<group name="RootNode" scale={0.588}>
<group name="Coin" scale={0.424}>
<mesh name="Coin_CoinBlinn_0" castShadow receiveShadow geometry={nodes.Coin_CoinBlinn_0.geometry} material={materials.CoinBlinn} />
</group>
</group>
</group>
</group>
)
<>
<RigidBody
type="fixed"
name="coin"
sensor
onIntersectionEnter={({ manifold, target, other}) => {
if(other.rigidBodyObject.name === "player"){
actions.addCoins();
setScale(0);
frames.current = 600;
body.current.setEnable(false);
}
}}
position={props.position}
>
<mesh
ref={group}
castShadow
receiveShadow
geometry={nodes.Coin_CoinBlinn_0.geometry}
material={materials.CoinBlinn}
scale={scale}
/>
</RigidBody>
</>
);
}
useGLTF.preload('./models/misc/super_mario_bros_coin-transformed.glb')
useGLTF.preload("./models/misc/super_mario_bros_coin-transformed.glb");

View File

@ -24,6 +24,7 @@ export const useStore = create((set, get) => ({
item: "",
shells: [],
skids: [],
coins : 0,
addPastPosition: (position) => {
set((state) => ({
pastPositions: [position, ...state.pastPositions.slice(0, 499)],
@ -111,6 +112,16 @@ export const useStore = create((set, get) => ({
skids: [...state.skids, skid],
}));
},
addCoins : () => {
set((state) => ({
coins: state.coins + 1,
}));
},
looseCoins : () => {
set((state) => ({
coins: state.coins - 1,
}));
},
},
}));

View File

@ -39,6 +39,7 @@ pointer-events: none;
}
.logo{
display: none;
position:absolute;
top:150px;
left:500px;

View File

@ -7,6 +7,6 @@ import { HUD } from './HUD'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
{/* <HUD /> */}
<HUD />
</React.StrictMode>,
)