Phase 28 Session 1: Foundation - BiomeSystem & ChunkManager
WORLD EXPANSION - Foundation systems created: 1. BiomeSystem.js (250 lines) - 5 biome definitions (Grassland, Forest, Desert, Mountain, Swamp) - 500x500 biome map generation - Region-based biome placement - Feature spawn probability per biome - Biome-specific tile coloring - Transition detection - Statistics tracking 2. ChunkManager.js (200 lines) - 50x50 tile chunk system - 3x3 chunk loading (9 active chunks) - Auto-load/unload based on player position - Performance optimization (loads 22,500 tiles vs 250,000) - 91% memory reduction - Chunk caching and statistics 3. Documentation - PHASE28_WORLD_EXPANSION_PLAN.md (complete roadmap) - PHASE28_SESSION1_LOG.md (progress tracking) Integration: - Both systems added to index.html - Ready for GameScene integration Next steps: - Initialize BiomeSystem in GameScene - Initialize ChunkManager in GameScene - Update Flat2DTerrainSystem for biome support - Expand world to 500x500 - Update camera bounds Status: Foundation complete (60% of Session 1) Time: 40 minutes Files: 5 created, 1 modified
This commit is contained in:
215
src/systems/ChunkManager.js
Normal file
215
src/systems/ChunkManager.js
Normal file
@@ -0,0 +1,215 @@
|
||||
// ChunkManager - Handles chunk-based terrain loading for large maps
|
||||
class ChunkManager {
|
||||
constructor(scene, chunkSize = 50) {
|
||||
this.scene = scene;
|
||||
this.chunkSize = chunkSize; // 50x50 tiles per chunk
|
||||
|
||||
// Active chunks (currently loaded)
|
||||
this.activeChunks = new Map(); // Key: "chunkX,chunkY", Value: chunk data
|
||||
|
||||
// Chunk load radius (how many chunks around player)
|
||||
this.loadRadius = 1; // Load 3x3 = 9 chunks at once
|
||||
|
||||
// Player position tracking
|
||||
this.lastPlayerChunkX = -1;
|
||||
this.lastPlayerChunkY = -1;
|
||||
|
||||
console.log(`💾 ChunkManager initialized (chunk size: ${chunkSize}x${chunkSize})`);
|
||||
}
|
||||
|
||||
// Get chunk coordinates from world coordinates
|
||||
worldToChunk(worldX, worldY) {
|
||||
return {
|
||||
chunkX: Math.floor(worldX / this.chunkSize),
|
||||
chunkY: Math.floor(worldY / this.chunkSize)
|
||||
};
|
||||
}
|
||||
|
||||
// Get chunk key string
|
||||
getChunkKey(chunkX, chunkY) {
|
||||
return `${chunkX},${chunkY}`;
|
||||
}
|
||||
|
||||
// Check if chunk is loaded
|
||||
isChunkLoaded(chunkX, chunkY) {
|
||||
return this.activeChunks.has(this.getChunkKey(chunkX, chunkY));
|
||||
}
|
||||
|
||||
// Load a single chunk
|
||||
loadChunk(chunkX, chunkY) {
|
||||
const key = this.getChunkKey(chunkX, chunkY);
|
||||
|
||||
// Already loaded
|
||||
if (this.activeChunks.has(key)) {
|
||||
return this.activeChunks.get(key);
|
||||
}
|
||||
|
||||
console.log(`📦 Loading chunk (${chunkX}, ${chunkY})`);
|
||||
|
||||
// Create chunk data
|
||||
const chunk = {
|
||||
chunkX,
|
||||
chunkY,
|
||||
key,
|
||||
tiles: [],
|
||||
objects: [], // Trees, rocks, decorations
|
||||
sprites: [] // Phaser sprites for this chunk
|
||||
};
|
||||
|
||||
// Generate or load chunk tiles
|
||||
const startX = chunkX * this.chunkSize;
|
||||
const startY = chunkY * this.chunkSize;
|
||||
|
||||
for (let y = 0; y < this.chunkSize; y++) {
|
||||
for (let x = 0; x < this.chunkSize; x++) {
|
||||
const worldX = startX + x;
|
||||
const worldY = startY + y;
|
||||
|
||||
// Get biome for this tile
|
||||
let biomeId = 'grassland';
|
||||
if (this.scene.biomeSystem) {
|
||||
biomeId = this.scene.biomeSystem.getBiomeAt(worldX, worldY);
|
||||
}
|
||||
|
||||
chunk.tiles.push({
|
||||
x: worldX,
|
||||
y: worldY,
|
||||
biome: biomeId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Store chunk
|
||||
this.activeChunks.set(key, chunk);
|
||||
|
||||
// Render chunk (if terrain system available)
|
||||
if (this.scene.terrainSystem && this.scene.terrainSystem.renderChunk) {
|
||||
this.scene.terrainSystem.renderChunk(chunk);
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
// Unload a single chunk
|
||||
unloadChunk(chunkX, chunkY) {
|
||||
const key = this.getChunkKey(chunkX, chunkY);
|
||||
|
||||
if (!this.activeChunks.has(key)) return;
|
||||
|
||||
console.log(`📤 Unloading chunk (${chunkX}, ${chunkY})`);
|
||||
|
||||
const chunk = this.activeChunks.get(key);
|
||||
|
||||
// Destroy all sprites in chunk
|
||||
if (chunk.sprites) {
|
||||
chunk.sprites.forEach(sprite => {
|
||||
if (sprite && sprite.destroy) {
|
||||
sprite.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Remove chunk
|
||||
this.activeChunks.delete(key);
|
||||
}
|
||||
|
||||
// Update active chunks based on player position
|
||||
updateActiveChunks(playerX, playerY) {
|
||||
const { chunkX, chunkY } = this.worldToChunk(playerX, playerY);
|
||||
|
||||
// Player hasn't changed chunks
|
||||
if (chunkX === this.lastPlayerChunkX && chunkY === this.lastPlayerChunkY) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`🔄 Player moved to chunk (${chunkX}, ${chunkY})`);
|
||||
|
||||
this.lastPlayerChunkX = chunkX;
|
||||
this.lastPlayerChunkY = chunkY;
|
||||
|
||||
// Determine which chunks should be loaded
|
||||
const chunksToLoad = new Set();
|
||||
|
||||
for (let dy = -this.loadRadius; dy <= this.loadRadius; dy++) {
|
||||
for (let dx = -this.loadRadius; dx <= this.loadRadius; dx++) {
|
||||
const targetChunkX = chunkX + dx;
|
||||
const targetChunkY = chunkY + dy;
|
||||
chunksToLoad.add(this.getChunkKey(targetChunkX, targetChunkY));
|
||||
}
|
||||
}
|
||||
|
||||
// Unload chunks that are too far
|
||||
const chunksToUnload = [];
|
||||
for (const [key, chunk] of this.activeChunks) {
|
||||
if (!chunksToLoad.has(key)) {
|
||||
chunksToUnload.push({ x: chunk.chunkX, y: chunk.chunkY });
|
||||
}
|
||||
}
|
||||
|
||||
chunksToUnload.forEach(({ x, y }) => this.unloadChunk(x, y));
|
||||
|
||||
// Load new chunks
|
||||
for (let dy = -this.loadRadius; dy <= this.loadRadius; dy++) {
|
||||
for (let dx = -this.loadRadius; dx <= this.loadRadius; dx++) {
|
||||
const targetChunkX = chunkX + dx;
|
||||
const targetChunkY = chunkY + dy;
|
||||
|
||||
if (!this.isChunkLoaded(targetChunkX, targetChunkY)) {
|
||||
this.loadChunk(targetChunkX, targetChunkY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Force reload all chunks (for debugging)
|
||||
reloadAllChunks() {
|
||||
console.log('🔄 Reloading all chunks...');
|
||||
|
||||
const chunksToReload = [];
|
||||
for (const [key, chunk] of this.activeChunks) {
|
||||
chunksToReload.push({ x: chunk.chunkX, y: chunk.chunkY });
|
||||
}
|
||||
|
||||
// Unload all
|
||||
chunksToReload.forEach(({ x, y }) => this.unloadChunk(x, y));
|
||||
|
||||
// Reload based on player position
|
||||
if (this.scene.player) {
|
||||
const pos = this.scene.player.getPosition();
|
||||
this.updateActiveChunks(pos.x, pos.y);
|
||||
}
|
||||
}
|
||||
|
||||
// Get chunk at world position
|
||||
getChunkAt(worldX, worldY) {
|
||||
const { chunkX, chunkY } = this.worldToChunk(worldX, worldY);
|
||||
return this.activeChunks.get(this.getChunkKey(chunkX, chunkY));
|
||||
}
|
||||
|
||||
// Get statistics
|
||||
getStats() {
|
||||
return {
|
||||
activeChunks: this.activeChunks.size,
|
||||
chunkSize: this.chunkSize,
|
||||
loadRadius: this.loadRadius,
|
||||
maxChunks: Math.pow((this.loadRadius * 2 + 1), 2),
|
||||
totalTilesLoaded: this.activeChunks.size * this.chunkSize * this.chunkSize
|
||||
};
|
||||
}
|
||||
|
||||
// Destroy all chunks
|
||||
destroy() {
|
||||
console.log('💾 ChunkManager destroying all chunks...');
|
||||
|
||||
for (const [key, chunk] of this.activeChunks) {
|
||||
if (chunk.sprites) {
|
||||
chunk.sprites.forEach(sprite => {
|
||||
if (sprite && sprite.destroy) sprite.destroy();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.activeChunks.clear();
|
||||
console.log('💾 ChunkManager destroyed');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user