Files
novafarma/src/systems/Flat2DTerrainSystem.js
NovaFarma Dev de18cda507 Phase 28 Session 3: Debug borders for chunk visualization
Added visual debugging to verify chunk rendering:

Flat2DTerrainSystem.js changes:
- Added red border around each rendered chunk
- Border: 2px red, semi-transparent (0.5 alpha)
- High depth (100) to ensure visibility
- Console log for each border created

Purpose:
- Verify chunks are actually being rendered
- See chunk boundaries visually
- Debug if chunks overlap or have gaps
- Confirm 3x3 chunk grid (9 chunks = 9 red boxes)

Expected visual:
- 9 red rectangles around player (3x3 grid)
- Each rectangle = 50x50 tiles = 2400x2400 pixels
- Player should be in center rectangle

Session 3: Visual testing in progress
Next: Reload and verify red borders appear
2025-12-15 17:19:42 +01:00

980 lines
34 KiB
JavaScript

// Flat2DTerrainSystem - Complete 2D top-down tile rendering
// Replaces isometric TerrainSystem for Stardew Valley style
class Flat2DTerrainSystem {
constructor(scene) {
this.scene = scene;
this.tileSize = 48;
this.width = 500; // 🌍 PHASE 28: Expanded to 500x500!
this.height = 500;
// Tile map data
this.tiles = [];
// Rendering containers
this.groundLayer = null;
this.pathsLayer = null;
this.decorLayer = null;
// Decoration tracking (for interaction system)
this.decorationsMap = new Map();
// Textures ready flag
this.texturesReady = false;
// 🌍 PHASE 28: Biome support
this.biomeSystem = null; // Will be set by GameScene
this.chunkManager = null; // Will be set by GameScene
console.log('🎨 Flat2DTerrainSystem initialized (500x500 world)');
}
async generate() {
console.log('🗺️ Generating flat 2D map...');
// Create textures first
this.createTileTextures();
// 🌍 PHASE 28: Use BiomeSystem if available
if (this.biomeSystem && this.chunkManager) {
console.log('🌍 Using BiomeSystem for chunk-based terrain generation');
// Biome-based generation will happen via ChunkManager
// No need to generate full map here!
// Create simple grass background as fallback
this.createBiomeBackground();
} else {
// Fallback to old Map2DData system
console.log('📊 Using Map2DData for terrain generation (fallback)');
// Load map data
if (typeof Map2DData !== 'undefined') {
this.tiles = Map2DData.generateMap();
console.log('✅ Map data generated:', this.tiles.length, 'rows');
} else {
console.error('❌ Map2DData not loaded!');
this.createFallbackMap();
}
// Render the old-style map
this.renderMap();
}
console.log('✅ Flat 2D map ready!');
}
// 🌍 PHASE 28: Create simple biome-aware background
createBiomeBackground() {
const size = this.tileSize;
const mapWidth = this.width * size;
const mapHeight = this.height * size;
console.log('🎨 Creating biome-aware background...');
// Create solid background (will be covered by chunks)
const background = this.scene.add.rectangle(0, 0, mapWidth, mapHeight, 0x3CB371);
background.setOrigin(0, 0);
background.setDepth(0);
// Create containers for chunks
this.pathsLayer = this.scene.add.container(0, 0);
this.pathsLayer.setDepth(2);
this.decorLayer = this.scene.add.container(0, 0);
this.decorLayer.setDepth(3);
this.groundLayer = background;
console.log('✅ Biome background created, ready for chunks');
}
createTileTextures() {
// 🎨 Create SOLID, OPAQUE tiles (no transparency!)
const graphics = this.scene.make.graphics({ x: 0, y: 0, add: false });
const size = this.tileSize;
// Check if PNG tilesets are loaded
const hasGrass = this.scene.textures.exists('tileset_grass');
const hasWater = this.scene.textures.exists('tileset_water');
const hasDirt = this.scene.textures.exists('tileset_dirt');
if (hasGrass && hasWater && hasDirt) {
console.log('✅ PNG Tilesets found! Creating beautiful tiles...');
// 🌍 PHASE 28: Create BIOME-SPECIFIC GRASS TILES
// GRASSLAND - SUPER VIBRANT GREEN! 🌿
graphics.clear();
graphics.fillStyle(0x3CB371, 1.0); // Medium sea green
graphics.fillRect(0, 0, size, size);
// Darker spots for contrast
for (let i = 0; i < 20; i++) {
graphics.fillStyle(0x2E8B57, 0.5);
graphics.fillCircle(Math.random() * size, Math.random() * size, 2 + Math.random() * 4);
}
// Lighter highlights
for (let i = 0; i < 15; i++) {
graphics.fillStyle(0x90EE90, 0.4);
graphics.fillCircle(Math.random() * size, Math.random() * size, 1.5);
}
graphics.generateTexture('tile2d_grass', size, size);
// FOREST BIOME - DARK GREEN
graphics.clear();
graphics.fillStyle(0x2d5016, 1.0); // Dark green
graphics.fillRect(0, 0, size, size);
for (let i = 0; i < 25; i++) {
graphics.fillStyle(0x1a3010, 0.4);
graphics.fillCircle(Math.random() * size, Math.random() * size, 3);
}
graphics.generateTexture('tile2d_forest', size, size);
// DESERT BIOME - TAN/SAND
graphics.clear();
graphics.fillStyle(0xd4c4a1, 1.0); // Sand color
graphics.fillRect(0, 0, size, size);
for (let i = 0; i < 20; i++) {
graphics.fillStyle(0xc4b491, 0.3);
graphics.fillCircle(Math.random() * size, Math.random() * size, 4);
}
graphics.generateTexture('tile2d_desert', size, size);
// MOUNTAIN BIOME - GRAY STONE
graphics.clear();
graphics.fillStyle(0x808080, 1.0); // Gray
graphics.fillRect(0, 0, size, size);
for (let i = 0; i < 30; i++) {
graphics.fillStyle(0x606060, 0.5);
graphics.fillCircle(Math.random() * size, Math.random() * size, 2 + Math.random() * 3);
}
graphics.generateTexture('tile2d_mountain', size, size);
// SWAMP BIOME - DARK GREEN/BROWN
graphics.clear();
graphics.fillStyle(0x3d5a3d, 1.0); // Murky green
graphics.fillRect(0, 0, size, size);
for (let i = 0; i < 20; i++) {
graphics.fillStyle(0x2d4a2d, 0.6);
graphics.fillCircle(Math.random() * size, Math.random() * size, 3 + Math.random() * 4);
}
graphics.generateTexture('tile2d_swamp', size, size);
// GRASS WITH FLOWERS (VIBRANT!)
graphics.clear();
graphics.fillStyle(0x3CB371, 1.0);
graphics.fillRect(0, 0, size, size);
for (let i = 0; i < 12; i++) {
graphics.fillStyle(0x2E8B57, 0.3);
graphics.fillCircle(Math.random() * size, Math.random() * size, 2);
}
// BRIGHT colorful flowers!
const flowerColors = [0xFF1493, 0xFFD700, 0x00BFFF, 0xFF69B4];
for (let i = 0; i < 4; i++) {
graphics.fillStyle(flowerColors[Math.floor(Math.random() * 4)], 1.0);
graphics.fillCircle(Math.random() * size, Math.random() * size, 3);
}
graphics.generateTexture('tile2d_grass_flowers', size, size);
// DIRT - RICH BROWN! 🟤
graphics.clear();
graphics.fillStyle(0x8B4513, 1.0); // Saddle brown
graphics.fillRect(0, 0, size, size);
for (let i = 0; i < 25; i++) {
graphics.fillStyle(0x654321, 0.5);
graphics.fillCircle(Math.random() * size, Math.random() * size, 3 + Math.random() * 5);
}
for (let i = 0; i < 15; i++) {
graphics.fillStyle(0xA0826D, 0.4);
graphics.fillCircle(Math.random() * size, Math.random() * size, 2);
}
graphics.generateTexture('tile2d_dirt', size, size);
graphics.clear();
graphics.fillStyle(0x8b4513, 1.0);
graphics.fillRect(0, 0, size, size);
graphics.fillStyle(0x3CB371, 0.3);
graphics.fillRect(0, 0, size, size);
graphics.generateTexture('tile2d_dirt_edge', size, size);
// WATER - BRIGHT TEAL! 💧 (WITH OUTLINE!)
graphics.clear();
graphics.fillStyle(0x20B2AA, 1.0); // Light sea green - vibrant teal!
graphics.fillRect(0, 0, size, size);
// Dark outline border (2D style!)
graphics.lineStyle(2, 0x006994, 0.8);
graphics.strokeRect(1, 1, size - 2, size - 2);
for (let i = 0; i < 10; i++) {
graphics.fillStyle(0x008B8B, 0.4);
graphics.fillCircle(Math.random() * size, Math.random() * size, 5 + Math.random() * 8);
}
for (let i = 0; i < 15; i++) {
graphics.fillStyle(0x48D1CC, 0.5);
graphics.fillCircle(Math.random() * size, Math.random() * size, 2);
}
for (let i = 0; i < 12; i++) {
graphics.fillStyle(0xFFFFFF, 0.6);
graphics.fillCircle(Math.random() * size, Math.random() * size, 1);
}
graphics.generateTexture('tile2d_water', size, size);
graphics.clear();
graphics.fillStyle(0x20B2AA, 1.0);
graphics.fillRect(0, 0, size, size);
graphics.fillStyle(0x48D1CC, 0.5);
graphics.fillRect(0, 0, size, size);
graphics.generateTexture('tile2d_water_edge', size, size);
} else {
console.warn('⚠️ PNG Tilesets not loaded! Using fallback colors');
}
// STONE (FULLY OPAQUE!)
graphics.clear();
graphics.fillStyle(0x808080, 1.0); // 100% opacity!
graphics.fillRect(0, 0, size, size);
for (let i = 0; i < 12; i++) {
graphics.fillStyle(0x606060, 0.6);
graphics.fillCircle(Math.random() * size, Math.random() * size, 2 + Math.random() * 3);
}
graphics.generateTexture('tile2d_stone', size, size);
graphics.destroy();
this.texturesReady = true;
console.log('✅ Tile textures created - FULLY OPAQUE!');
}
extractTilesFromTileset(sourceKey, targetKey, tileX, tileY, tileSize) {
// Extract a single tile from tileset PNG and create new texture
if (!this.scene.textures.exists(sourceKey)) {
console.warn(`⚠️ Tileset ${sourceKey} not found, using fallback`);
return;
}
const source = this.scene.textures.get(sourceKey).getSourceImage();
const canvas = document.createElement('canvas');
canvas.width = tileSize;
canvas.height = tileSize;
const ctx = canvas.getContext('2d');
// Draw specific tile from tileset
ctx.drawImage(
source,
tileX * tileSize, tileY * tileSize, // Source position
tileSize, tileSize, // Source size
0, 0, // Dest position
tileSize, tileSize // Dest size
);
// Create new texture from extracted tile
this.scene.textures.addCanvas(targetKey, canvas);
}
renderMap() {
// 🎨 SIMPLE & CLEAN: Use TileSprite for seamless background!
const size = this.tileSize;
const mapWidth = this.width * size;
const mapHeight = this.height * size;
console.log('🎨 Rendering seamless 2D map...');
// Create solid grass background (NO TRANSPARENCY!)
const grassBG = this.scene.add.tileSprite(0, 0, mapWidth, mapHeight, 'tile2d_grass');
grassBG.setOrigin(0, 0);
grassBG.setDepth(1);
// Create containers for paths and decorations
this.pathsLayer = this.scene.add.container(0, 0);
this.pathsLayer.setDepth(2);
this.decorLayer = this.scene.add.container(0, 0);
this.decorLayer.setDepth(3);
const graphics = this.scene.make.graphics({ x: 0, y: 0, add: false });
// Array to store water sprites for animation
this.waterSprites = [];
// Render all tiles on top of grass background
for (let y = 0; y < this.height; y++) {
for (let x = 0; x < this.width; x++) {
const tile = this.tiles[y][x];
const worldX = x * size;
const worldY = y * size;
// WATER tiles - back to Image, animate with tint!
if (tile.base === 4 || tile.base === 5) {
const waterSprite = this.scene.add.image(worldX, worldY, 'tile2d_water');
waterSprite.setOrigin(0, 0);
waterSprite.setDisplaySize(size, size);
this.pathsLayer.add(waterSprite);
this.waterSprites.push(waterSprite); // Store for animation!
}
// DIRT PATH tiles
else if (tile.base === 2 || tile.base === 3) {
const dirtSprite = this.scene.add.image(worldX, worldY, 'tile2d_dirt');
dirtSprite.setOrigin(0, 0);
dirtSprite.setDisplaySize(size, size);
// Edge blending
if (tile.base === 3) {
dirtSprite.setAlpha(0.8); // Blend with grass
}
this.pathsLayer.add(dirtSprite);
}
// DECORATIONS
if (tile.decoration) {
this.addDecoration(x, y, tile.decoration);
}
}
}
graphics.destroy();
// Store reference
this.groundLayer = grassBG;
console.log('✅ Seamless map rendered (NO GRID!)');
console.log(`🌊 Water tiles animated: ${this.waterSprites.length}`);
}
// 🌍 PHASE 28: Render a single chunk with biome support
renderChunk(chunk) {
if (!chunk || !this.biomeSystem) {
console.warn('⚠️ Cannot render chunk: missing data or biomeSystem');
return;
}
const size = this.tileSize;
// Render ground tiles based on biome
for (const tileData of chunk.tiles) {
const { x, y, biome } = tileData;
const worldX = x * size;
const worldY = y * size;
// Get biome-specific tile texture
let tileTexture = 'tile2d_grass'; // Default
if (biome === 'forest') {
tileTexture = 'tile2d_forest';
} else if (biome === 'desert') {
tileTexture = 'tile2d_desert';
} else if (biome === 'mountain') {
tileTexture = 'tile2d_mountain';
} else if (biome === 'swamp') {
tileTexture = 'tile2d_swamp';
}
// Create tile sprite
const tileSprite = this.scene.add.image(worldX, worldY, tileTexture);
tileSprite.setOrigin(0, 0);
tileSprite.setDisplaySize(size, size);
tileSprite.setDepth(1);
// Store in chunk for cleanup
if (!chunk.sprites) chunk.sprites = [];
chunk.sprites.push(tileSprite);
// Apply biome features (trees, rocks, etc.)
if (this.biomeSystem) {
const features = this.biomeSystem.applyBiomeFeatures(x, y);
features.forEach(feature => {
this.addBiomeFeature(x, y, feature, chunk);
});
}
}
console.log(`✅ Chunk (${chunk.chunkX}, ${chunk.chunkY}) rendered with biomes`);
// 🐛 DEBUG: Add visible border around chunk
if (this.scene.add) {
const chunkWorldX = chunk.chunkX * (this.chunkSize * size);
const chunkWorldY = chunk.chunkY * (this.chunkSize * size);
const chunkPixelSize = this.chunkSize * size;
const border = this.scene.add.rectangle(
chunkWorldX,
chunkWorldY,
chunkPixelSize,
chunkPixelSize
);
border.setOrigin(0, 0);
border.setStrokeStyle(2, 0xFF0000, 0.5); // Red semi-transparent border
border.setDepth(100); // High depth to see it
border.setFillStyle(0x000000, 0); // No fill
if (!chunk.sprites) chunk.sprites = [];
chunk.sprites.push(border);
console.log(`🔲 Debug border added at (${chunkWorldX}, ${chunkWorldY})`);
}
}
// Add biome-specific feature
addBiomeFeature(gridX, gridY, feature, chunk) {
const size = this.tileSize;
const worldX = gridX * size + size / 2;
const worldY = gridY * size + size / 2;
let sprite;
switch (feature.type) {
case 'tree':
sprite = this.createTree(worldX, worldY);
break;
case 'rock':
sprite = this.createRock(worldX, worldY, feature.size);
break;
case 'bush':
sprite = this.createBush(worldX, worldY);
break;
case 'cactus':
sprite = this.createCactus(worldX, worldY);
break;
case 'deadTree':
sprite = this.createDeadTree(worldX, worldY);
break;
case 'boulder':
sprite = this.createBoulder(worldX, worldY);
break;
case 'mushroom':
sprite = this.createMushroom(worldX, worldY);
break;
case 'vine':
sprite = this.createVine(worldX, worldY);
break;
}
if (sprite && chunk) {
sprite.setDepth(10 + worldY);
if (!chunk.sprites) chunk.sprites = [];
chunk.sprites.push(sprite);
}
}
// Simple rock creation
createRock(x, y, size = 'small') {
const graphics = this.scene.add.graphics();
const rockSize = size === 'large' ? 15 : 8;
graphics.fillStyle(0x808080);
graphics.fillCircle(x, y, rockSize);
graphics.fillStyle(0x606060, 0.6);
graphics.fillCircle(x - 3, y - 3, rockSize * 0.6);
return graphics;
}
// Boulder (large rock)
createBoulder(x, y) {
const graphics = this.scene.add.graphics();
graphics.fillStyle(0x707070);
graphics.fillCircle(x, y, 20);
graphics.fillStyle(0x505050);
graphics.fillCircle(x - 5, y - 5, 12);
graphics.fillStyle(0x909090, 0.5);
graphics.fillCircle(x + 5, y + 5, 8);
return graphics;
}
// Cactus
createCactus(x, y) {
const graphics = this.scene.add.graphics();
// Main body
graphics.fillStyle(0x4a7c59);
graphics.fillRect(x - 5, y - 15, 10, 30);
// Arms
graphics.fillRect(x - 12, y - 8, 7, 10);
graphics.fillRect(x + 5, y - 5, 7, 10);
// Highlights
graphics.fillStyle(0x5a8c69, 0.6);
graphics.fillRect(x - 3, y - 12, 3, 24);
return graphics;
}
// Mushroom
createMushroom(x, y) {
const graphics = this.scene.add.graphics();
// Stem
graphics.fillStyle(0xeeeeee);
graphics.fillRect(x - 2, y, 4, 8);
// Cap
graphics.fillStyle(0xd63031);
graphics.fillCircle(x, y, 6);
// Spots
graphics.fillStyle(0xffffff);
graphics.fillCircle(x - 3, y - 1, 1.5);
graphics.fillCircle(x + 2, y + 1, 1.2);
return graphics;
}
// Vine (swamp feature)
createVine(x, y) {
const graphics = this.scene.add.graphics();
graphics.lineStyle(2, 0x2d4a2d);
graphics.beginPath();
graphics.moveTo(x, y - 10);
graphics.bezierCurveTo(
x + 5, y - 5,
x - 5, y + 5,
x, y + 10
);
graphics.strokePath();
return graphics;
}
getTileTexture(tileType) {
const types = Map2DData.tileTypes;
switch (tileType) {
case types.GRASS: return 'tile2d_grass';
case types.GRASS_FLOWERS: return 'tile2d_grass_flowers';
case types.DIRT: return 'tile2d_dirt';
case types.DIRT_EDGE: return 'tile2d_dirt_edge';
case types.WATER: return 'tile2d_water';
case types.WATER_EDGE: return 'tile2d_water_edge';
case types.STONE: return 'tile2d_stone';
default: return 'tile2d_grass';
}
}
addDecoration(gridX, gridY, decorType) {
const size = this.tileSize;
const worldX = gridX * size + size / 2;
const worldY = gridY * size + size / 2;
const types = Map2DData.tileTypes;
let sprite;
switch (decorType) {
case types.TREE:
sprite = this.createTree(worldX, worldY);
break;
case types.FLOWER_RED:
sprite = this.createFlower(worldX, worldY, 0xff6b6b);
break;
case types.FLOWER_YELLOW:
sprite = this.createFlower(worldX, worldY, 0xffd93d);
break;
case types.FLOWER_BLUE:
sprite = this.createFlower(worldX, worldY, 0x6bcbff);
break;
case types.LILY_PAD:
sprite = this.createLilyPad(worldX, worldY);
break;
case types.BUSH:
sprite = this.createBush(worldX, worldY);
break;
case 'puddle':
sprite = this.createPuddle(worldX, worldY);
break;
}
if (sprite) {
// 🎨 2.5D DEPTH SORTING - Y position = depth!
// Objects lower on screen appear in front
sprite.setDepth(10 + worldY);
this.decorLayer.add(sprite);
}
}
createTree(x, y) {
// 🌳 TREE VARIETY SYSTEM!
const treeTypes = ['cherry', 'oak', 'pine', 'dead', 'apple'];
const randomType = Phaser.Utils.Array.GetRandom(treeTypes);
switch (randomType) {
case 'oak': return this.createOakTree(x, y);
case 'pine': return this.createPineTree(x, y);
case 'dead': return this.createDeadTree(x, y);
case 'apple': return this.createAppleTree(x, y);
default: return this.createCherryTree(x, y);
}
}
createCherryTree(x, y) {
// 🌸 PNG SPRITE!
if (this.scene.textures.exists('tree_cherry')) {
const tree = this.scene.add.image(x, y, 'tree_cherry');
const scale = Phaser.Math.FloatBetween(0.4, 0.6); // SMALLER! (was 0.8-1.2)
tree.setScale(scale);
tree.setOrigin(0.5, 0.85);
// Shadow
const shadow = this.scene.add.ellipse(x, y + 15, 30 * scale, 10, 0x000000, 0.2);
shadow.setDepth(tree.depth - 1);
return tree;
}
// FALLBACK: Procedural
return this.createProceduralCherryTree(x, y);
}
createProceduralCherryTree(x, y) {
// Old procedural code (backup)
const graphics = this.scene.add.graphics();
const growthScale = Phaser.Math.FloatBetween(0.8, 1.4); // BIGGER!
const heightOffset = -20 * growthScale;
const crownSize = 30 * growthScale; // MUCH BIGGER!
// Shadow (ellipse on ground)
graphics.fillStyle(0x000000, 0.2);
graphics.fillEllipse(x, y + 30, 40 * growthScale, 12); // Bigger shadow
// Trunk (with shading for volume)
const trunkW = 12 * growthScale; // Thicker
const trunkH = 30 * growthScale; // Taller
// Dark side (right)
graphics.fillStyle(0x6D3F1A);
graphics.fillRect(x + trunkW / 4, y + 10 + heightOffset, trunkW / 2, trunkH);
// Light side (left)
graphics.fillStyle(0x8B5A2B);
graphics.fillRect(x - trunkW / 2, y + 10 + heightOffset, trunkW / 2, trunkH);
// CROWN - Layered circles for 2.5D!
const crownY = y - 10 * growthScale + heightOffset; // Higher crown
// Dark base (shadow underneath)
graphics.fillStyle(0xE75480);
graphics.fillCircle(x + 4 * growthScale, crownY + 4 * growthScale, crownSize);
// Main pink crown
graphics.fillStyle(0xFF69B4);
graphics.fillCircle(x, crownY, crownSize);
// Top highlight (light from top-left)
graphics.fillStyle(0xFFB6C1, 0.8);
graphics.fillCircle(x - 6 * growthScale, crownY - 6 * growthScale, crownSize * 0.6);
// Bright specular spot
graphics.fillStyle(0xFFFFFF, 0.5);
graphics.fillCircle(x - 8 * growthScale, crownY - 8 * growthScale, crownSize * 0.3);
return graphics;
}
createOakTree(x, y) {
// 🌲 PNG SPRITE!
if (this.scene.textures.exists('tree_oak')) {
const tree = this.scene.add.image(x, y, 'tree_oak');
const scale = Phaser.Math.FloatBetween(0.45, 0.65); // SMALLER!
tree.setScale(scale);
tree.setOrigin(0.5, 0.85);
const shadow = this.scene.add.ellipse(x, y + 15, 35 * scale, 12, 0x000000, 0.2);
shadow.setDepth(tree.depth - 1);
return tree;
}
// FALLBACK:
const graphics = this.scene.add.graphics();
const growthScale = Phaser.Math.FloatBetween(0.8, 1.4); // BIGGER!
// Shadow
graphics.fillStyle(0x000000, 0.15);
graphics.fillEllipse(x, y + 24, 25 * growthScale, 8);
// Thick trunk
graphics.fillStyle(0x654321);
graphics.fillRect(x - 5 * growthScale, y, 10 * growthScale, 24 * growthScale);
// Large round crown
graphics.fillStyle(0x228B22); // Forest green
graphics.fillCircle(x, y - 10 * growthScale, 18 * growthScale);
// Darker outline
graphics.lineStyle(2, 0x006400);
graphics.strokeCircle(x, y - 10 * growthScale, 18 * growthScale);
// Highlight
graphics.fillStyle(0x32CD32, 0.6);
graphics.fillCircle(x - 5 * growthScale, y - 15 * growthScale, 8 * growthScale);
return graphics;
}
createPineTree(x, y) {
// 🌲 PNG SPRITE!
if (this.scene.textures.exists('tree_pine')) {
const tree = this.scene.add.image(x, y, 'tree_pine');
const scale = Phaser.Math.FloatBetween(0.45, 0.7); // SMALLER!
tree.setScale(scale);
tree.setOrigin(0.5, 0.9); // Taller
const shadow = this.scene.add.ellipse(x, y + 20, 30 * scale, 10, 0x000000, 0.2);
shadow.setDepth(tree.depth - 1);
return tree;
}
// FALLBACK:
const graphics = this.scene.add.graphics();
const growthScale = Phaser.Math.FloatBetween(0.8, 1.5); // BIGGER (Pine taller)
// Shadow
graphics.fillStyle(0x000000, 0.15);
graphics.fillEllipse(x, y + 20, 15 * growthScale, 6);
// Trunk
graphics.fillStyle(0x8B4513);
graphics.fillRect(x - 3 * growthScale, y + 5, 6 * growthScale, 15 * growthScale);
// Stacked triangles (pine shape)
graphics.fillStyle(0x2F4F2F); // Dark green
// Bottom tier
graphics.fillTriangle(
x, y - 5 * growthScale,
x - 14 * growthScale, y + 10,
x + 14 * growthScale, y + 10
);
// Middle tier
graphics.fillTriangle(
x, y - 15 * growthScale,
x - 10 * growthScale, y,
x + 10 * growthScale, y
);
// Top tier
graphics.fillTriangle(
x, y - 25 * growthScale,
x - 7 * growthScale, y - 10 * growthScale,
x + 7 * growthScale, y - 10 * growthScale
);
return graphics;
}
createDeadTree(x, y) {
// 💀 PNG SPRITE!
if (this.scene.textures.exists('tree_dead')) {
const tree = this.scene.add.image(x, y, 'tree_dead');
const scale = Phaser.Math.FloatBetween(0.35, 0.55); // SMALLER!
tree.setScale(scale);
tree.setOrigin(0.5, 0.85);
const shadow = this.scene.add.ellipse(x, y + 18, 28 * scale, 10, 0x000000, 0.3);
shadow.setDepth(tree.depth - 1);
return tree;
}
// FALLBACK:
const graphics = this.scene.add.graphics();
const growthScale = Phaser.Math.FloatBetween(0.7, 1.2); // BIGGER!
// Shadow
graphics.fillStyle(0x000000, 0.2);
graphics.fillEllipse(x, y + 20, 18 * growthScale, 6);
// Dark trunk
graphics.fillStyle(0x3D3D3D);
graphics.fillRect(x - 4 * growthScale, y, 8 * growthScale, 22 * growthScale);
// Branches (bare)
graphics.lineStyle(3 * growthScale, 0x2D2D2D);
graphics.beginPath();
graphics.moveTo(x, y - 5 * growthScale);
graphics.lineTo(x - 10 * growthScale, y - 15 * growthScale);
graphics.moveTo(x, y - 8 * growthScale);
graphics.lineTo(x + 12 * growthScale, y - 12 * growthScale);
graphics.strokePath();
return graphics;
}
createAppleTree(x, y) {
// 🍎 PNG SPRITE!
if (this.scene.textures.exists('tree_apple')) {
const tree = this.scene.add.image(x, y, 'tree_apple');
const scale = Phaser.Math.FloatBetween(0.4, 0.6); // SMALLER!
tree.setScale(scale);
tree.setOrigin(0.5, 0.85);
const shadow = this.scene.add.ellipse(x, y + 16, 32 * scale, 11, 0x000000, 0.2);
shadow.setDepth(tree.depth - 1);
return tree;
}
// FALLBACK:
const graphics = this.scene.add.graphics();
const growthScale = Phaser.Math.FloatBetween(0.8, 1.3); // BIGGER!
// Shadow
graphics.fillStyle(0x000000, 0.15);
graphics.fillEllipse(x, y + 22, 22 * growthScale, 7);
// Trunk
graphics.fillStyle(0x8B4513);
graphics.fillRect(x - 4 * growthScale, y + 2, 8 * growthScale, 20 * growthScale);
// Green crown
graphics.fillStyle(0x3CB371); // Medium sea green
graphics.fillCircle(x, y - 8 * growthScale, 16 * growthScale);
// Apples (red circles)
graphics.fillStyle(0xFF0000);
const applePositions = [
{ x: -8, y: -12 }, { x: 5, y: -10 }, { x: -3, y: -5 },
{ x: 8, y: -8 }, { x: 0, y: -15 }
];
applePositions.forEach(pos => {
graphics.fillCircle(
x + pos.x * growthScale,
y + pos.y * growthScale,
3 * growthScale
);
});
return graphics;
}
createFlower(x, y, color) {
const graphics = this.scene.add.graphics();
// Petals
graphics.fillStyle(color);
for (let i = 0; i < 5; i++) {
const angle = (Math.PI * 2 * i) / 5;
const px = x + Math.cos(angle) * 3;
const py = y + Math.sin(angle) * 3;
graphics.fillCircle(px, py, 2);
}
// Center
graphics.fillStyle(0xFFEB3B);
graphics.fillCircle(x, y, 2);
return graphics;
}
createLilyPad(x, y) {
const graphics = this.scene.add.graphics();
// Lily pad (green circle)
graphics.fillStyle(0x4a8d2f);
graphics.fillCircle(x, y, 8);
// Pink flower
graphics.fillStyle(0xFF69B4);
for (let i = 0; i < 5; i++) {
const angle = (Math.PI * 2 * i) / 5;
const px = x + Math.cos(angle) * 3;
const py = y + Math.sin(angle) * 3;
graphics.fillCircle(px, py, 2);
}
graphics.fillStyle(0xFFD700);
graphics.fillCircle(x, y, 1.5);
return graphics;
}
createBush(x, y) {
const graphics = this.scene.add.graphics();
graphics.fillStyle(0x3a6b1f, 0.9);
graphics.fillCircle(x, y, 10);
graphics.fillCircle(x - 6, y + 2, 8);
graphics.fillCircle(x + 6, y + 2, 8);
graphics.fillStyle(0x4a8d2f, 0.7);
graphics.fillCircle(x, y - 3, 6);
return graphics;
}
createPuddle(x, y) {
// Use existing puddle sprite if available
if (this.scene.textures.exists('luza_sprite')) {
const sprite = this.scene.add.image(x, y, 'luza_sprite');
sprite.setScale(0.3); // SMALL puddles!
sprite.setAlpha(0.3); // More transparent
return sprite;
}
// Fallback - small ellipse
const graphics = this.scene.add.graphics();
graphics.fillStyle(0x4488bb, 0.4);
graphics.fillEllipse(x, y, 8, 5); // Smaller!
return graphics;
}
createFallbackMap() {
// Create simple fallback if Map2DData fails
for (let y = 0; y < this.height; y++) {
this.tiles[y] = [];
for (let x = 0; x < this.width; x++) {
this.tiles[y][x] = {
base: 0, // Grass
decoration: null,
walkable: true
};
}
}
}
getTile(x, y) {
if (x < 0 || x >= this.width || y < 0 || y >= this.height) {
return null;
}
// Safety check: ensure tiles array is initialized
if (!this.tiles || !this.tiles[y]) {
return null;
}
return this.tiles[y][x];
}
isWalkable(x, y) {
const tile = this.getTile(x, y);
return tile ? tile.walkable : false;
}
update(time, delta) {
// 🌊 ANIMATED WATER - shimmer effect with tint!
if (this.waterSprites && this.waterSprites.length > 0) {
this.waterSprites.forEach(sprite => {
if (sprite && sprite.active) {
// Wave shimmer using tint color
const wave = Math.sin(time * 0.002) * 0.15 + 0.85;
const tintValue = Math.floor(wave * 255);
sprite.setTint(
0x2A7FBC | (tintValue << 16) | (tintValue << 8) | tintValue
);
// Subtle alpha pulsing
sprite.setAlpha(0.95 + Math.sin(time * 0.001) * 0.05);
}
});
}
}
destroy() {
if (this.groundLayer) this.groundLayer.destroy();
if (this.pathsLayer) this.pathsLayer.destroy();
if (this.decorLayer) this.decorLayer.destroy();
}
}