feat: Implement procedural terrain generation with hills, lakes, and vegetation
This commit is contained in:
parent
3f4f8bc09c
commit
a5702a210f
184
script.js
184
script.js
@ -53,6 +53,7 @@ let chunks = new Map(); // Map to store chunks with key "x,y"
|
||||
let metadata = new Map(); // Map to store metadata for pixels
|
||||
let debugMode = false;
|
||||
let fireUpdateCounter = 0;
|
||||
let generatedChunks = new Set(); // Set to track which chunks have been generated
|
||||
|
||||
// Initialize the simulation
|
||||
window.onload = function() {
|
||||
@ -94,8 +95,9 @@ window.onload = function() {
|
||||
canvas.addEventListener('touchmove', handleTouchMove);
|
||||
canvas.addEventListener('touchend', handleMouseUp);
|
||||
|
||||
// Initialize the first chunk
|
||||
// Initialize the first chunk and generate terrain around it
|
||||
getOrCreateChunk(0, 0);
|
||||
generateChunksAroundPlayer();
|
||||
|
||||
// Start the simulation loop
|
||||
requestAnimationFrame(simulationLoop);
|
||||
@ -288,6 +290,9 @@ function moveWorld(dx, dy) {
|
||||
worldOffsetX += dx;
|
||||
worldOffsetY += dy;
|
||||
updateCoordinatesDisplay();
|
||||
|
||||
// Generate terrain for chunks around the current view
|
||||
generateChunksAroundPlayer();
|
||||
}
|
||||
|
||||
function updateCoordinatesDisplay() {
|
||||
@ -1040,6 +1045,183 @@ function toggleDebug() {
|
||||
document.getElementById('debug-btn').classList.toggle('active');
|
||||
}
|
||||
|
||||
// Terrain generation functions
|
||||
function generateChunksAroundPlayer() {
|
||||
const centerChunkX = Math.floor(worldOffsetX / CHUNK_SIZE);
|
||||
const centerChunkY = Math.floor(worldOffsetY / CHUNK_SIZE);
|
||||
const radius = 3; // Generate chunks within 3 chunks of the player
|
||||
|
||||
// Generate chunks in a square around the player
|
||||
for (let dy = -radius; dy <= radius; dy++) {
|
||||
for (let dx = -radius; dx <= radius; dx++) {
|
||||
const chunkX = centerChunkX + dx;
|
||||
const chunkY = centerChunkY + dy;
|
||||
|
||||
// Only generate terrain for chunks at or above y=0
|
||||
if (chunkY >= 0) {
|
||||
getOrCreateChunk(chunkX, chunkY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function generateTerrain(chunkX, chunkY, chunkData) {
|
||||
// Use a seeded random number generator based on chunk coordinates
|
||||
const seed = chunkX * 10000 + chunkY;
|
||||
const random = createSeededRandom(seed);
|
||||
|
||||
// Generate base terrain (hills)
|
||||
generateHills(chunkX, chunkY, chunkData, random);
|
||||
|
||||
// Add lakes
|
||||
if (random() < 0.3) { // 30% chance for a lake in a chunk
|
||||
generateLake(chunkX, chunkY, chunkData, random);
|
||||
}
|
||||
|
||||
// Add stone formations
|
||||
if (random() < 0.4) { // 40% chance for stone formations
|
||||
generateStoneFormation(chunkX, chunkY, chunkData, random);
|
||||
}
|
||||
|
||||
// Add vegetation (seeds and trees)
|
||||
addVegetation(chunkX, chunkY, chunkData, random);
|
||||
}
|
||||
|
||||
function createSeededRandom(seed) {
|
||||
// Simple seeded random function
|
||||
return function() {
|
||||
seed = (seed * 9301 + 49297) % 233280;
|
||||
return seed / 233280;
|
||||
};
|
||||
}
|
||||
|
||||
function generateHills(chunkX, chunkY, chunkData, random) {
|
||||
// Generate a height map for this chunk using simplex-like noise
|
||||
const heightMap = new Array(CHUNK_SIZE).fill(0);
|
||||
|
||||
// Base height (higher for chunks further from origin)
|
||||
const baseHeight = Math.max(0, 50 - Math.sqrt(chunkX*chunkX + chunkY*chunkY) * 5);
|
||||
|
||||
// Generate a smooth height map
|
||||
for (let x = 0; x < CHUNK_SIZE; x++) {
|
||||
// Use multiple frequencies for more natural looking terrain
|
||||
const noise1 = Math.sin(x * 0.02 + chunkX * CHUNK_SIZE * 0.02 + random() * 10) * 15;
|
||||
const noise2 = Math.sin(x * 0.05 + chunkX * CHUNK_SIZE * 0.05 + random() * 10) * 7;
|
||||
const noise3 = Math.sin(x * 0.1 + chunkX * CHUNK_SIZE * 0.1 + random() * 10) * 3;
|
||||
|
||||
// Combine noise at different frequencies
|
||||
heightMap[x] = Math.floor(baseHeight + noise1 + noise2 + noise3);
|
||||
|
||||
// Ensure height is positive
|
||||
heightMap[x] = Math.max(0, heightMap[x]);
|
||||
}
|
||||
|
||||
// Fill the terrain based on the height map
|
||||
for (let x = 0; x < CHUNK_SIZE; x++) {
|
||||
const height = heightMap[x];
|
||||
|
||||
for (let y = CHUNK_SIZE - 1; y >= 0; y--) {
|
||||
const worldY = chunkY * CHUNK_SIZE + y;
|
||||
const depth = CHUNK_SIZE - 1 - y;
|
||||
|
||||
if (depth < height) {
|
||||
const index = y * CHUNK_SIZE + x;
|
||||
|
||||
// Top layer is grass
|
||||
if (depth === 0) {
|
||||
chunkData[index] = GRASS;
|
||||
}
|
||||
// Next few layers are dirt
|
||||
else if (depth < 5) {
|
||||
chunkData[index] = DIRT;
|
||||
}
|
||||
// Deeper layers are stone
|
||||
else {
|
||||
chunkData[index] = STONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function generateLake(chunkX, chunkY, chunkData, random) {
|
||||
// Lake parameters
|
||||
const lakeX = Math.floor(random() * (CHUNK_SIZE - 60)) + 30;
|
||||
const lakeY = Math.floor(random() * (CHUNK_SIZE - 60)) + 30;
|
||||
const lakeWidth = Math.floor(random() * 40) + 20;
|
||||
const lakeHeight = Math.floor(random() * 20) + 10;
|
||||
|
||||
// Create an elliptical lake
|
||||
for (let y = 0; y < CHUNK_SIZE; y++) {
|
||||
for (let x = 0; x < CHUNK_SIZE; x++) {
|
||||
// Calculate distance from lake center (normalized to create an ellipse)
|
||||
const dx = (x - lakeX) / lakeWidth;
|
||||
const dy = (y - lakeY) / lakeHeight;
|
||||
const distance = Math.sqrt(dx*dx + dy*dy);
|
||||
|
||||
if (distance < 1) {
|
||||
const index = y * CHUNK_SIZE + x;
|
||||
|
||||
// Water in the center
|
||||
if (distance < 0.8) {
|
||||
chunkData[index] = WATER;
|
||||
}
|
||||
// Sand around the edges
|
||||
else {
|
||||
chunkData[index] = SAND;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function generateStoneFormation(chunkX, chunkY, chunkData, random) {
|
||||
// Stone formation parameters
|
||||
const formationX = Math.floor(random() * (CHUNK_SIZE - 40)) + 20;
|
||||
const formationWidth = Math.floor(random() * 30) + 10;
|
||||
const formationHeight = Math.floor(random() * 40) + 20;
|
||||
|
||||
// Create a stone hill/mountain
|
||||
for (let x = formationX - formationWidth; x < formationX + formationWidth; x++) {
|
||||
if (x < 0 || x >= CHUNK_SIZE) continue;
|
||||
|
||||
// Calculate height at this x position (higher in the middle)
|
||||
const dx = (x - formationX) / formationWidth;
|
||||
const height = Math.floor(formationHeight * (1 - dx*dx));
|
||||
|
||||
for (let y = CHUNK_SIZE - 1; y >= CHUNK_SIZE - height; y--) {
|
||||
if (y < 0 || y >= CHUNK_SIZE) continue;
|
||||
|
||||
const index = y * CHUNK_SIZE + x;
|
||||
chunkData[index] = STONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addVegetation(chunkX, chunkY, chunkData, random) {
|
||||
// Add vegetation on grass
|
||||
for (let y = 0; y < CHUNK_SIZE; y++) {
|
||||
for (let x = 0; x < CHUNK_SIZE; x++) {
|
||||
const index = y * CHUNK_SIZE + x;
|
||||
|
||||
// Only add vegetation on grass
|
||||
if (chunkData[index] === GRASS) {
|
||||
// Check if there's empty space above
|
||||
if (y > 0 && chunkData[(y-1) * CHUNK_SIZE + x] === EMPTY) {
|
||||
// Random chance to add different types of vegetation
|
||||
const roll = random();
|
||||
|
||||
if (roll < 0.01) { // 1% chance for a tree seed
|
||||
chunkData[(y-1) * CHUNK_SIZE + x] = TREE_SEED;
|
||||
} else if (roll < 0.05) { // 4% chance for a regular seed
|
||||
chunkData[(y-1) * CHUNK_SIZE + x] = SEED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function render() {
|
||||
// Clear the canvas
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
Loading…
x
Reference in New Issue
Block a user