bigger world + worldborder

TODO: fix teleport glitch when exiting car
This commit is contained in:
KaseToatz1337
2025-11-28 18:43:43 +01:00
parent cb05f9834b
commit 87bf9d7d38
4 changed files with 185 additions and 25 deletions

View File

@@ -1,8 +1,9 @@
import { useState, useRef } from 'react'
import { Canvas } from '@react-three/fiber'
import { Sky } from '@react-three/drei'
import { Sky, Grid } from '@react-three/drei'
import { Character } from './Character'
import { Car } from './Car'
import { WorldBorder } from './WorldBorder'
import { Mesh } from 'three'
export default function App() {
@@ -10,17 +11,30 @@ export default function App() {
const carRef = useRef<Mesh>(null)
return (
<Canvas camera={{ position: [0, 5, 10] }}>
<Sky sunPosition={[100, 20, 100]} />
<Canvas camera={{ position: [0, 5, 10] }} gl={{ antialias: true }}>
<Sky sunPosition={[100, 20, 100]} distance={5000} />
<ambientLight intensity={0.5} />
<pointLight position={[10, 10, 10]} />
{/* Ground */}
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0, 0]}>
<planeGeometry args={[500, 500]} />
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.5, 0]}>
<planeGeometry args={[2500, 2500]} />
<meshStandardMaterial color="#55aaff" />
</mesh>
<Grid
args={[2500, 2500]}
fadeDistance={2000}
sectionSize={10}
sectionThickness={1}
sectionColor="#ffffff"
cellSize={1}
cellThickness={0.5}
cellColor="#aaccff"
/>
<WorldBorder />
<Character
isDriving={isDriving}
onEnter={() => setIsDriving(true)}

View File

@@ -1,3 +1,4 @@
import { useRef, useEffect, forwardRef, useImperativeHandle } from 'react'
import { useFrame, useThree } from '@react-three/fiber'
import { Vector3, Mesh, Euler } from 'three'
@@ -13,7 +14,7 @@ export const Car = forwardRef<Mesh, CarProps>(({ isDriving, onExit, position = [
const internalRef = useRef<Mesh>(null)
useImperativeHandle(ref, () => internalRef.current!)
const { scene } = useGLTF('/car.glb')
const gltf = useGLTF('/car.glb')
const { camera } = useThree()
const keys = useRef({ w: false, a: false, s: false, d: false, f: false })
const speed = useRef(0)
@@ -36,8 +37,8 @@ export const Car = forwardRef<Mesh, CarProps>(({ isDriving, onExit, position = [
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
const key = e.key.toLowerCase()
if (keys.current.hasOwnProperty(key)) {
(keys.current as any)[key] = true
if (Object.prototype.hasOwnProperty.call(keys.current, key)) {
(keys.current as Record<string, boolean>)[key] = true
}
if (e.key.toLowerCase() === 'f') {
if (isDriving) {
@@ -47,8 +48,8 @@ export const Car = forwardRef<Mesh, CarProps>(({ isDriving, onExit, position = [
}
const handleKeyUp = (e: KeyboardEvent) => {
const key = e.key.toLowerCase()
if (keys.current.hasOwnProperty(key)) {
(keys.current as any)[key] = false
if (Object.prototype.hasOwnProperty.call(keys.current, key)) {
(keys.current as Record<string, boolean>)[key] = false
}
}
@@ -105,8 +106,29 @@ export const Car = forwardRef<Mesh, CarProps>(({ isDriving, onExit, position = [
// Move
internalRef.current.translateZ(speed.current * delta)
// Clamp position to map boundaries with rotation-aware hitbox
const mapSize = 2500
const halfMapSize = mapSize / 2
const carWidth = 3
const carLength = 7
const halfWidth = carWidth / 2
const halfLength = carLength / 2
const yaw = internalRef.current.rotation.y
const absSin = Math.abs(Math.sin(yaw))
const absCos = Math.abs(Math.cos(yaw))
const extentX = absSin * halfLength + absCos * halfWidth
const extentZ = absCos * halfLength + absSin * halfWidth
const boundaryX = halfMapSize - extentX
const boundaryZ = halfMapSize - extentZ
internalRef.current.position.x = Math.max(-boundaryX, Math.min(boundaryX, internalRef.current.position.x))
internalRef.current.position.z = Math.max(-boundaryZ, Math.min(boundaryZ, internalRef.current.position.z))
// Camera Follow
const { yaw, pitch, distance } = cameraState.current
const { yaw: camYaw, pitch, distance } = cameraState.current
// Target position (above car)
const targetPos = internalRef.current.position.clone().add(new Vector3(0, 2, 0))
@@ -114,7 +136,7 @@ export const Car = forwardRef<Mesh, CarProps>(({ isDriving, onExit, position = [
// Calculate max camera position (relative to target)
const maxCamPos = new Vector3(0, 0, distance)
// Apply rotation: pitch, then yaw + car rotation + 180deg (to look from behind)
maxCamPos.applyEuler(new Euler(pitch, yaw + internalRef.current.rotation.y + Math.PI, 0, 'YXZ'))
maxCamPos.applyEuler(new Euler(pitch, camYaw + internalRef.current.rotation.y + Math.PI, 0, 'YXZ'))
// Check for ground collision
const minCameraHeight = 0.5
@@ -132,7 +154,7 @@ export const Car = forwardRef<Mesh, CarProps>(({ isDriving, onExit, position = [
}
const actualCamPos = new Vector3(0, 0, actualDistance)
actualCamPos.applyEuler(new Euler(pitch, yaw + internalRef.current.rotation.y + Math.PI, 0, 'YXZ'))
actualCamPos.applyEuler(new Euler(pitch, camYaw + internalRef.current.rotation.y + Math.PI, 0, 'YXZ'))
// Lerp to the collided position
// Increased lerp speed from 0.1 to 0.5 to reduce lag
@@ -142,8 +164,14 @@ export const Car = forwardRef<Mesh, CarProps>(({ isDriving, onExit, position = [
})
return (
<mesh ref={internalRef} position={new Vector3(...position)}>
<primitive object={scene} scale={1.5} rotation={[0, 0, 0]} />
</mesh>
<group ref={internalRef} position={new Vector3(...position)}>
<primitive object={gltf.scene} scale={1.5} rotation={[0, 0, 0]} />
{/* Debug Hitbox */}
<mesh position={[0, 0.75, 0]}>
<boxGeometry args={[3, 1.5, 7]} />
<meshBasicMaterial wireframe color="yellow" />
</mesh>
</group>
)
})

View File

@@ -11,7 +11,7 @@ interface CharacterProps {
export function Character({ isDriving, onEnter, carRef }: CharacterProps) {
const ref = useRef<Mesh>(null)
const { scene } = useGLTF('/character.glb')
const gltf = useGLTF('/character.glb')
const { camera } = useThree()
const keys = useRef({ w: false, a: false, s: false, d: false, shift: false, space: false })
const velocity = useRef(new Vector3(0, 0, 0))
@@ -26,8 +26,8 @@ export function Character({ isDriving, onEnter, carRef }: CharacterProps) {
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
const key = e.key.toLowerCase()
if (keys.current.hasOwnProperty(key)) {
(keys.current as any)[key] = true
if (Object.prototype.hasOwnProperty.call(keys.current, key)) {
(keys.current as Record<string, boolean>)[key] = true
}
if (e.key === 'Shift') keys.current.shift = true
if (e.key === ' ') keys.current.space = true
@@ -43,8 +43,8 @@ export function Character({ isDriving, onEnter, carRef }: CharacterProps) {
}
const handleKeyUp = (e: KeyboardEvent) => {
const key = e.key.toLowerCase()
if (keys.current.hasOwnProperty(key)) {
(keys.current as any)[key] = false
if (Object.prototype.hasOwnProperty.call(keys.current, key)) {
(keys.current as Record<string, boolean>)[key] = false
}
if (e.key === 'Shift') keys.current.shift = false
if (e.key === ' ') keys.current.space = false
@@ -100,7 +100,7 @@ export function Character({ isDriving, onEnter, carRef }: CharacterProps) {
ref.current.position.y = 1 // Ensure above ground
velocity.current.set(0, 0, 0)
}
}, [isDriving])
}, [isDriving, carRef])
useFrame((_state, delta) => {
if (!ref.current || isDriving) return
@@ -128,6 +128,15 @@ export function Character({ isDriving, onEnter, carRef }: CharacterProps) {
ref.current.lookAt(ref.current.position.clone().add(direction))
}
// Clamp position to map boundaries with radius
const mapSize = 2500
const halfMapSize = mapSize / 2
const radius = 0.5
const boundary = halfMapSize - radius
ref.current.position.x = Math.max(-boundary, Math.min(boundary, ref.current.position.x))
ref.current.position.z = Math.max(-boundary, Math.min(boundary, ref.current.position.z))
// Physics (Gravity & Jumping)
const gravity = 30
const jumpForce = 12
@@ -190,8 +199,14 @@ export function Character({ isDriving, onEnter, carRef }: CharacterProps) {
})
return (
<mesh ref={ref} position={[0, 0.5, 0]} visible={!isDriving}>
<primitive object={scene} scale={1.5} rotation={[0, 0, 0]} />
</mesh>
<group ref={ref} position={[0, 0.5, 0]} visible={!isDriving}>
<primitive object={gltf.scene} rotation={[0, 0, 0]} />
{/* Debug Hitbox */}
<mesh position={[0, 0.9, 0]}>
<cylinderGeometry args={[0.5, 0.5, 1.8, 16]} />
<meshBasicMaterial wireframe color="cyan" />
</mesh>
</group>
)
}

103
src/WorldBorder.tsx Normal file
View File

@@ -0,0 +1,103 @@
import { useRef, useMemo } from 'react'
import { useFrame, useThree } from '@react-three/fiber'
import { ShaderMaterial, Vector3, DoubleSide } from 'three'
export function WorldBorder() {
const materialRef = useRef<ShaderMaterial>(null)
const { camera } = useThree()
const uniforms = useMemo(
() => ({
uPlayerPos: { value: new Vector3() },
uVisibleDistance: { value: 200.0 },
}),
[]
)
useFrame(() => {
if (materialRef.current) {
materialRef.current.uniforms.uPlayerPos.value.copy(camera.position)
}
})
const vertexShader = `
varying vec3 vWorldPos;
void main() {
vec4 worldPosition = modelMatrix * vec4(position, 1.0);
vWorldPos = worldPosition.xyz;
gl_Position = projectionMatrix * viewMatrix * worldPosition;
}
`
const fragmentShader = `
uniform vec3 uPlayerPos;
uniform float uVisibleDistance;
varying vec3 vWorldPos;
void main() {
// Calculate distance from player to this fragment
float distToPlayer = distance(uPlayerPos, vWorldPos);
// Fade based on distance
float alpha = 1.0 - smoothstep(0.0, uVisibleDistance, distToPlayer);
if (alpha <= 0.0) discard;
gl_FragColor = vec4(1.0, 0.0, 0.0, alpha * 0.8);
}
`
const mapSize = 2500
const halfSize = mapSize / 2
const wallHeight = 100
return (
<group>
{/* North Wall (-Z) */}
<mesh position={[0, wallHeight / 2, -halfSize]}>
<planeGeometry args={[mapSize, wallHeight]} />
<shaderMaterial
ref={materialRef}
vertexShader={vertexShader}
fragmentShader={fragmentShader}
uniforms={uniforms}
transparent
side={DoubleSide}
/>
</mesh>
{/* South Wall (+Z) */}
<mesh position={[0, wallHeight / 2, halfSize]}>
<planeGeometry args={[mapSize, wallHeight]} />
<shaderMaterial
vertexShader={vertexShader}
fragmentShader={fragmentShader}
uniforms={uniforms}
transparent
side={DoubleSide}
/>
</mesh>
{/* East Wall (+X) */}
<mesh position={[halfSize, wallHeight / 2, 0]} rotation={[0, -Math.PI / 2, 0]}>
<planeGeometry args={[mapSize, wallHeight]} />
<shaderMaterial
vertexShader={vertexShader}
fragmentShader={fragmentShader}
uniforms={uniforms}
transparent
side={DoubleSide}
/>
</mesh>
{/* West Wall (-X) */}
<mesh position={[-halfSize, wallHeight / 2, 0]} rotation={[0, Math.PI / 2, 0]}>
<planeGeometry args={[mapSize, wallHeight]} />
<shaderMaterial
vertexShader={vertexShader}
fragmentShader={fragmentShader}
uniforms={uniforms}
transparent
side={DoubleSide}
/>
</mesh>
</group>
)
}