feat: Complete 2D Visual Overhaul - Isometric to Flat Top-Down
- NEW: Flat2DTerrainSystem.js (375 lines) - NEW: map2d_data.js procedural map (221 lines) - MODIFIED: GameScene async create, 2D terrain integration - MODIFIED: Player.js flat 2D positioning - MODIFIED: game.js disabled pixelArt for smooth rendering - FIXED: 15+ bugs (updateCulling, isometric conversions, grid lines) - ADDED: Phase 28 to TASKS.md - DOCS: DNEVNIK.md session summary Result: Working flat 2D game with Stardew Valley style! Time: 5.5 hours
This commit is contained in:
386
src/systems/Flat2DTerrainSystem.js
Normal file
386
src/systems/Flat2DTerrainSystem.js
Normal file
@@ -0,0 +1,386 @@
|
||||
// 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 = 100;
|
||||
this.height = 100;
|
||||
|
||||
// Tile map data
|
||||
this.tiles = [];
|
||||
|
||||
// Rendering containers
|
||||
this.groundLayer = null;
|
||||
this.pathsLayer = null;
|
||||
this.decorLayer = null;
|
||||
|
||||
// Textures ready flag
|
||||
this.texturesReady = false;
|
||||
|
||||
console.log('🎨 Flat2DTerrainSystem initialized');
|
||||
}
|
||||
|
||||
async generate() {
|
||||
console.log('🗺️ Generating flat 2D map...');
|
||||
|
||||
// Create textures first
|
||||
this.createTileTextures();
|
||||
|
||||
// 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 map
|
||||
this.renderMap();
|
||||
|
||||
console.log('✅ Flat 2D map ready!');
|
||||
}
|
||||
|
||||
createTileTextures() {
|
||||
const graphics = this.scene.make.graphics({ x: 0, y: 0, add: false });
|
||||
const size = this.tileSize;
|
||||
|
||||
// GRASS - VIBRANT RICH GREEN! 🌿
|
||||
graphics.clear();
|
||||
graphics.fillStyle(0x59b36a); // BRIGHT rich green!
|
||||
graphics.fillRect(0, 0, size, size);
|
||||
|
||||
// Add grass texture - DARKER spots
|
||||
for (let i = 0; i < 15; i++) {
|
||||
const x = Math.random() * size;
|
||||
const y = Math.random() * size;
|
||||
graphics.fillStyle(0x3a8d4f, 0.5);
|
||||
graphics.fillCircle(x, y, 2 + Math.random() * 3);
|
||||
}
|
||||
// LIGHTER highlights
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const x = Math.random() * size;
|
||||
const y = Math.random() * size;
|
||||
graphics.fillStyle(0x7ad389, 0.6);
|
||||
graphics.fillCircle(x, y, 1.5);
|
||||
}
|
||||
graphics.generateTexture('tile2d_grass', size, size);
|
||||
|
||||
// GRASS WITH FLOWERS
|
||||
graphics.clear();
|
||||
graphics.fillStyle(0x4a9d5f);
|
||||
graphics.fillRect(0, 0, size, size);
|
||||
|
||||
// Grass texture
|
||||
for (let i = 0; i < 10; i++) {
|
||||
graphics.fillStyle(0x3a8d4f, 0.4);
|
||||
graphics.fillCircle(Math.random() * size, Math.random() * size, 1.5);
|
||||
}
|
||||
|
||||
// Small flowers
|
||||
const flowerColors = [0xff6b6b, 0xffd93d, 0x6bcbff];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
graphics.fillStyle(flowerColors[Math.floor(Math.random() * 3)]);
|
||||
graphics.fillCircle(Math.random() * size, Math.random() * size, 2);
|
||||
}
|
||||
graphics.generateTexture('tile2d_grass_flowers', size, size);
|
||||
|
||||
// DIRT - VIBRANT BROWN! 🟤
|
||||
graphics.clear();
|
||||
graphics.fillStyle(0xa87f5a); // BRIGHT brown!
|
||||
graphics.fillRect(0, 0, size, size);
|
||||
|
||||
// Dirt texture - darker clumps
|
||||
for (let i = 0; i < 20; i++) {
|
||||
graphics.fillStyle(0x7a5f3a, 0.6);
|
||||
graphics.fillCircle(Math.random() * size, Math.random() * size, 3 + Math.random() * 4);
|
||||
}
|
||||
// Lighter spots
|
||||
for (let i = 0; i < 12; i++) {
|
||||
graphics.fillStyle(0xc89f6f, 0.5);
|
||||
graphics.fillCircle(Math.random() * size, Math.random() * size, 2);
|
||||
}
|
||||
graphics.generateTexture('tile2d_dirt', size, size);
|
||||
|
||||
// DIRT EDGE - Transition to grass
|
||||
graphics.clear();
|
||||
graphics.fillGradientStyle(0x8b6f47, 0x8b6f47, 0x6a9d5f, 0x6a9d5f, 1);
|
||||
graphics.fillRect(0, 0, size, size);
|
||||
graphics.generateTexture('tile2d_dirt_edge', size, size);
|
||||
|
||||
// WATER - BRIGHT BLUE! 💧
|
||||
graphics.clear();
|
||||
graphics.fillStyle(0x3498db); // VIBRANT blue!
|
||||
graphics.fillRect(0, 0, size, size);
|
||||
|
||||
// Water highlights - darker depth
|
||||
for (let i = 0; i < 8; i++) {
|
||||
graphics.fillStyle(0x2078ab, 0.4);
|
||||
graphics.fillCircle(Math.random() * size, Math.random() * size, 4 + Math.random() * 6);
|
||||
}
|
||||
// Light reflections
|
||||
for (let i = 0; i < 12; i++) {
|
||||
graphics.fillStyle(0x5dade2, 0.5);
|
||||
graphics.fillCircle(Math.random() * size, Math.random() * size, 2);
|
||||
}
|
||||
// White sparkles
|
||||
for (let i = 0; i < 10; i++) {
|
||||
graphics.fillStyle(0xffffff, 0.6);
|
||||
graphics.fillCircle(Math.random() * size, Math.random() * size, 1);
|
||||
}
|
||||
graphics.generateTexture('tile2d_water', size, size);
|
||||
|
||||
// WATER EDGE - Lighter border
|
||||
graphics.clear();
|
||||
graphics.fillGradientStyle(0x4aacdc, 0x4aacdc, 0x1a5f7a, 0x1a5f7a, 0.7);
|
||||
graphics.fillRect(0, 0, size, size);
|
||||
graphics.generateTexture('tile2d_water_edge', size, size);
|
||||
|
||||
// STONE - Gray
|
||||
graphics.clear();
|
||||
graphics.fillStyle(0x808080);
|
||||
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');
|
||||
}
|
||||
|
||||
renderMap() {
|
||||
// Create layer containers
|
||||
this.groundLayer = this.scene.add.container(0, 0);
|
||||
this.pathsLayer = this.scene.add.container(0, 0);
|
||||
this.decorLayer = this.scene.add.container(0, 0);
|
||||
|
||||
// Set depths
|
||||
this.groundLayer.setDepth(1);
|
||||
this.pathsLayer.setDepth(2);
|
||||
this.decorLayer.setDepth(3);
|
||||
|
||||
const size = this.tileSize;
|
||||
|
||||
// Render all tiles
|
||||
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;
|
||||
|
||||
// Get texture key from tile type
|
||||
const textureKey = this.getTileTexture(tile.base);
|
||||
|
||||
// Create tile sprite
|
||||
const tileSprite = this.scene.add.image(worldX, worldY, textureKey);
|
||||
tileSprite.setOrigin(0, 0);
|
||||
tileSprite.setDisplaySize(size, size);
|
||||
|
||||
// Add to appropriate layer
|
||||
if (tile.base <= 1) {
|
||||
this.groundLayer.add(tileSprite);
|
||||
} else {
|
||||
this.pathsLayer.add(tileSprite);
|
||||
}
|
||||
|
||||
// Add decoration if exists
|
||||
if (tile.decoration) {
|
||||
this.addDecoration(x, y, tile.decoration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✅ Map rendered: 3 layers created');
|
||||
}
|
||||
|
||||
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) {
|
||||
this.decorLayer.add(sprite);
|
||||
}
|
||||
}
|
||||
|
||||
createTree(x, y) {
|
||||
const graphics = this.scene.add.graphics();
|
||||
|
||||
// Trunk
|
||||
graphics.fillStyle(0x8B4513);
|
||||
graphics.fillRect(x - 6, y, 12, 20);
|
||||
|
||||
// Crown (round)
|
||||
graphics.fillStyle(0x2d5016, 0.9);
|
||||
graphics.fillCircle(x, y - 10, 18);
|
||||
|
||||
graphics.fillStyle(0x3a6b1f, 0.8);
|
||||
graphics.fillCircle(x - 5, y - 12, 14);
|
||||
graphics.fillCircle(x + 5, y - 8, 12);
|
||||
|
||||
graphics.fillStyle(0x4a8d2f, 0.7);
|
||||
graphics.fillCircle(x, y - 15, 10);
|
||||
|
||||
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.8);
|
||||
sprite.setAlpha(0.4);
|
||||
return sprite;
|
||||
}
|
||||
|
||||
// Fallback
|
||||
const graphics = this.scene.add.graphics();
|
||||
graphics.fillStyle(0x4488bb, 0.5);
|
||||
graphics.fillEllipse(x, y, 12, 8);
|
||||
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) {
|
||||
// Reserved for animations (water waves, etc)
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.groundLayer) this.groundLayer.destroy();
|
||||
if (this.pathsLayer) this.pathsLayer.destroy();
|
||||
if (this.decorLayer) this.decorLayer.destroy();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user