mirror of
https://github.com/Floriansylvain/generic-threejs-game.git
synced 2026-08-19 11:43:18 +02:00
feat: better optimized chunk generation
This commit is contained in:
+2
-16
@@ -2,16 +2,13 @@ import "./App.css";
|
||||
|
||||
import { Canvas } from "@react-three/fiber";
|
||||
import { KeyboardControls } from "@react-three/drei";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { PCFSoftShadowMap, Vector3 } from "three";
|
||||
import { Player } from "./components/Player";
|
||||
import { Bloom, EffectComposer, Vignette } from "@react-three/postprocessing";
|
||||
import { Cubes } from "./components/Cubes";
|
||||
import { GameContext } from "./hooks/GameContext";
|
||||
import { ChunkGenerator } from "./components/ChunkGenerator";
|
||||
|
||||
const CUBES_QT = 10;
|
||||
|
||||
function App() {
|
||||
const container = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
@@ -19,15 +16,6 @@ function App() {
|
||||
container.current?.focus();
|
||||
});
|
||||
|
||||
const [cubes] = useState<Vector3[]>(
|
||||
Array.from({ length: CUBES_QT }, (_, i) =>
|
||||
Array.from(
|
||||
{ length: CUBES_QT },
|
||||
(_, j) => new Vector3(i + 20 - CUBES_QT / 2, 0, j - CUBES_QT / 2)
|
||||
)
|
||||
).flat()
|
||||
);
|
||||
|
||||
return (
|
||||
<main
|
||||
className="container"
|
||||
@@ -37,10 +25,8 @@ function App() {
|
||||
className="canvas"
|
||||
ref={container}
|
||||
shadows={{ type: PCFSoftShadowMap }}
|
||||
camera={{ fov: 50, frustumCulled: true, near: 0.1, far: 200 }}
|
||||
camera={{ fov: 50, frustumCulled: true, near: 0.1, far: 2000 }}
|
||||
>
|
||||
<Cubes cubesPosition={cubes}></Cubes>
|
||||
|
||||
<GameContext.Provider value={{ playerPosition: new Vector3() }}>
|
||||
<KeyboardControls
|
||||
map={[
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect } from "react";
|
||||
import { memo, useEffect } from "react";
|
||||
import {
|
||||
ClampToEdgeWrapping,
|
||||
FrontSide,
|
||||
@@ -15,8 +15,8 @@ const grassTexture = new TextureLoader().load("./texture_grass.jpg");
|
||||
grassTexture.wrapS = grassTexture.wrapT = RepeatWrapping;
|
||||
grassTexture.repeat.set(100, 100);
|
||||
|
||||
const SCALE = 5;
|
||||
const AMPLITUDE = 10;
|
||||
const SCALE = 1;
|
||||
const AMPLITUDE = 100;
|
||||
const GEOMETRY = 50;
|
||||
|
||||
const SIZE = 100; // CHANGE WITH CAUTION
|
||||
@@ -27,7 +27,8 @@ const ADJUSTED_GEOMETRY = GEOMETRY + OVERLAP * 2;
|
||||
const ADJUSTED_SIZE = SIZE + OVERLAP * 2;
|
||||
const ADJUSTED_SCALE = SIZE / SCALE;
|
||||
|
||||
export function Chunk(props: { position: Vector2; seed: number }): JSX.Element {
|
||||
export const Chunk = memo(
|
||||
(props: { position: Vector2; seed: number }): JSX.Element => {
|
||||
const noise = (x: number, y: number): number => {
|
||||
const noise = new ImprovedNoise();
|
||||
return noise.noise(x, y, props.seed);
|
||||
@@ -46,7 +47,10 @@ export function Chunk(props: { position: Vector2; seed: number }): JSX.Element {
|
||||
for (let x = 0; x < ADJUSTED_SIZE; x++) {
|
||||
for (let y = 0; y < ADJUSTED_SIZE; y++) {
|
||||
const n =
|
||||
noise((x + offsetX) / ADJUSTED_SCALE, (y + offsetY) / ADJUSTED_SCALE) *
|
||||
noise(
|
||||
(x + offsetX) / ADJUSTED_SCALE,
|
||||
(y + offsetY) / ADJUSTED_SCALE
|
||||
) *
|
||||
127 +
|
||||
127;
|
||||
const id = (x + y * ADJUSTED_SIZE) * 4;
|
||||
@@ -76,7 +80,12 @@ export function Chunk(props: { position: Vector2; seed: number }): JSX.Element {
|
||||
>
|
||||
<planeGeometry
|
||||
attach="geometry"
|
||||
args={[CHUNK_SIZE, CHUNK_SIZE, ADJUSTED_GEOMETRY, ADJUSTED_GEOMETRY]}
|
||||
args={[
|
||||
CHUNK_SIZE,
|
||||
CHUNK_SIZE,
|
||||
ADJUSTED_GEOMETRY,
|
||||
ADJUSTED_GEOMETRY,
|
||||
]}
|
||||
></planeGeometry>
|
||||
<meshStandardMaterial
|
||||
toneMapped={false}
|
||||
@@ -89,4 +98,5 @@ export function Chunk(props: { position: Vector2; seed: number }): JSX.Element {
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,53 +1,111 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { useContext, useRef, useState } from "react";
|
||||
import { GameContext } from "../hooks/GameContext";
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
import { Vector2 } from "three";
|
||||
import { Chunk } from "./Chunk";
|
||||
|
||||
const CHUNK_RADIUS = 20;
|
||||
const CHUNK_PER_FRAME = 3;
|
||||
|
||||
export function ChunkGenerator(): JSX.Element {
|
||||
const { playerPosition } = useContext(GameContext);
|
||||
const [lastPlayerPosition] = useState(new Vector2(0, 0));
|
||||
const [lastPlayerPosition] = useState(new Vector2());
|
||||
|
||||
const [chunks, setChunks] = useState([] as JSX.Element[]);
|
||||
const [chunkQueue, setChunkQueue] = useState([] as JSX.Element[]);
|
||||
const generatedChunkKeys = useRef(new Set<string>());
|
||||
|
||||
const chunkPosVec2 = new Vector2();
|
||||
|
||||
const isChunkAlreadyGenerated = (x: number, y: number) =>
|
||||
chunks.some((chunk) => chunk.key === `${x}-${y}`);
|
||||
const getChunkCoordinates = (x: number, y: number): number[] => {
|
||||
return [Math.floor(x / 100 + 0.5), Math.floor(y / 100 + 0.5)];
|
||||
};
|
||||
|
||||
function generateNewChunks(playerX: number, playerY: number) {
|
||||
const newChunks = [];
|
||||
for (let x = -2; x <= 2; x++) {
|
||||
for (let y = -2; y <= 2; y++) {
|
||||
if (isChunkAlreadyGenerated(playerX + x, playerY + y)) continue;
|
||||
const isChunkAlreadyGenerated = (x: number, y: number, key: string) =>
|
||||
generatedChunkKeys.current.has(`${x}-${y}`) ||
|
||||
chunkQueue.some((chunk) => chunk.key === key);
|
||||
|
||||
const isChunkInPlayerRadius = (chunkPos: number[], playerPos: number[]) =>
|
||||
chunkPos[0] < playerPos[0] - CHUNK_RADIUS ||
|
||||
chunkPos[0] > playerPos[0] + CHUNK_RADIUS ||
|
||||
chunkPos[1] < playerPos[1] - CHUNK_RADIUS ||
|
||||
chunkPos[1] > playerPos[1] + CHUNK_RADIUS;
|
||||
|
||||
function generateNewChunks(playerX: number, playerY: number): void {
|
||||
const newChunks = [] as JSX.Element[];
|
||||
for (let x = -CHUNK_RADIUS; x <= CHUNK_RADIUS; x++) {
|
||||
for (let y = -CHUNK_RADIUS; y <= CHUNK_RADIUS; y++) {
|
||||
const key = `${playerX + x}-${playerY + y}`;
|
||||
if (isChunkAlreadyGenerated(playerX + x, playerY + y, key)) continue;
|
||||
newChunks.push(
|
||||
<Chunk
|
||||
key={`${playerX + x}-${playerY + y}`}
|
||||
key={key}
|
||||
position={chunkPosVec2
|
||||
.clone()
|
||||
.set((playerX + x) * 100, (playerY + y) * 100)}
|
||||
seed={0.25386}
|
||||
/>
|
||||
);
|
||||
generatedChunkKeys.current.add(key);
|
||||
}
|
||||
}
|
||||
setChunks([...chunks, ...newChunks]);
|
||||
setChunkQueue((prevQueue) => [...prevQueue, ...newChunks]);
|
||||
}
|
||||
|
||||
function getFilteredChunks(prevChunks: JSX.Element[], playerPos: number[]) {
|
||||
const filteredPrevChunks = [] as JSX.Element[];
|
||||
|
||||
let qtDeleted = 0;
|
||||
prevChunks.forEach((chunk) => {
|
||||
const chunkPos = getChunkCoordinates(
|
||||
chunk.props.position.x,
|
||||
chunk.props.position.y
|
||||
);
|
||||
|
||||
if (
|
||||
qtDeleted < CHUNK_PER_FRAME &&
|
||||
isChunkInPlayerRadius(chunkPos, playerPos)
|
||||
) {
|
||||
generatedChunkKeys.current.delete(`${chunkPos[0]}-${chunkPos[1]}`);
|
||||
qtDeleted++;
|
||||
} else {
|
||||
filteredPrevChunks.push(chunk);
|
||||
}
|
||||
});
|
||||
return filteredPrevChunks;
|
||||
}
|
||||
|
||||
function setNewChunks(queuedChunks: JSX.Element[], playerPos: number[]) {
|
||||
setChunks((prevChunks) => {
|
||||
const newChunks = queuedChunks
|
||||
.splice(-CHUNK_PER_FRAME)
|
||||
.filter(
|
||||
(chunk) =>
|
||||
!prevChunks.some((prevChunk) => prevChunk.key === chunk.key)
|
||||
);
|
||||
const filteredPrevChunks = getFilteredChunks(prevChunks, playerPos);
|
||||
return [...filteredPrevChunks, ...newChunks];
|
||||
});
|
||||
}
|
||||
|
||||
useFrame(() => {
|
||||
const playerX = Math.floor(playerPosition.x / 100 + 0.5);
|
||||
const playerY = Math.floor(playerPosition.z / 100 + 0.5);
|
||||
const playerPos = getChunkCoordinates(playerPosition.x, playerPosition.z);
|
||||
|
||||
if (lastPlayerPosition.x === playerX && lastPlayerPosition.y === playerY)
|
||||
return;
|
||||
|
||||
generateNewChunks(playerX, playerY);
|
||||
|
||||
lastPlayerPosition.set(playerX, playerY);
|
||||
if (chunkQueue.length > 0) {
|
||||
setChunkQueue((prevQueue) => {
|
||||
setNewChunks(prevQueue, playerPos);
|
||||
return prevQueue;
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
generateNewChunks(0, 0);
|
||||
}, []);
|
||||
if (
|
||||
lastPlayerPosition.x !== playerPos[0] ||
|
||||
lastPlayerPosition.y !== playerPos[1]
|
||||
) {
|
||||
generateNewChunks(playerPos[0], playerPos[1]);
|
||||
lastPlayerPosition.set(playerPos[0], playerPos[1]);
|
||||
}
|
||||
});
|
||||
|
||||
return <>{chunks}</>;
|
||||
}
|
||||
|
||||
@@ -21,8 +21,7 @@ export interface KeyPressed {
|
||||
sprint: boolean;
|
||||
}
|
||||
|
||||
const PLAYER_SPEED = 4.75 * 10;
|
||||
const DRAW_DISTANCE = 128;
|
||||
const PLAYER_SPEED = 4.5 * 100;
|
||||
const SHADOW_RESOLUTION = 2048;
|
||||
const MOUSE_X_SENSITIVITY = 0.002;
|
||||
const MOUSE_Y_SENSITIVITY = 0.001;
|
||||
@@ -30,7 +29,7 @@ const ANIM_SPEED = 0.2;
|
||||
const CAMERA_RADIUS = 2;
|
||||
const CAMERA_HEIGHT = 1.5;
|
||||
|
||||
const SHADOW_DRAW_DISTANCE = DRAW_DISTANCE * 1.2;
|
||||
const SHADOW_DRAW_DISTANCE = 128;
|
||||
|
||||
export function Player(): JSX.Element {
|
||||
const model = useGLTF("/Adventurer.glb");
|
||||
@@ -181,7 +180,7 @@ export function Player(): JSX.Element {
|
||||
function setDirLightPosition(): void {
|
||||
dirLightRef.current?.position.set(
|
||||
model.scene.position.x + 100,
|
||||
70,
|
||||
model.scene.position.y + 100,
|
||||
model.scene.position.z + 100
|
||||
);
|
||||
}
|
||||
@@ -203,7 +202,7 @@ export function Player(): JSX.Element {
|
||||
child.receiveShadow = true;
|
||||
}
|
||||
});
|
||||
model.scene.position.y = 10;
|
||||
model.scene.position.y = 70;
|
||||
if (dirLightRef.current) {
|
||||
dirLightRef.current.shadow.bias = -0.0001;
|
||||
}
|
||||
@@ -234,10 +233,7 @@ export function Player(): JSX.Element {
|
||||
]}
|
||||
/>
|
||||
</directionalLight>
|
||||
<fog
|
||||
attach={"fog"}
|
||||
args={["lightblue", DRAW_DISTANCE * 0.8, DRAW_DISTANCE]}
|
||||
></fog>
|
||||
<fog attach={"fog"} args={["lightblue", 1000, 2000]}></fog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user