kockasta mapa

This commit is contained in:
2025-12-07 14:28:39 +01:00
parent 98059a2659
commit 045bf24792
11 changed files with 670 additions and 1249 deletions

View File

@@ -10,276 +10,150 @@ class TerrainSystem {
this.noise = new PerlinNoise(Date.now());
this.tiles = [];
this.decorations = []; // Array za save/load compat
this.decorationsMap = new Map(); // Fast lookup key->decor
this.cropsMap = new Map(); // Store dynamic crops separately
// Render state monitoring
this.visibleTiles = new Map(); // Key: "x,y", Value: Sprite
this.visibleDecorations = new Map(); // Key: "x,y", Value: Sprite
this.visibleCrops = new Map(); // Key: "x,y", Value: Sprite
this.decorations = [];
this.decorationsMap = new Map();
this.cropsMap = new Map();
this.visibleTiles = new Map();
this.visibleDecorations = new Map();
this.visibleCrops = new Map();
// Pools
this.tilePool = {
active: [],
inactive: [],
get: () => {
if (this.tilePool.inactive.length > 0) {
const s = this.tilePool.inactive.pop();
s.setVisible(true);
return s;
}
const s = this.scene.add.sprite(0, 0, 'dirt');
s.setOrigin(0.5, 0.5);
return s;
},
release: (sprite) => {
sprite.setVisible(false);
this.tilePool.inactive.push(sprite);
}
};
this.decorationPool = {
active: [],
inactive: [],
get: () => {
if (this.decorationPool.inactive.length > 0) {
const s = this.decorationPool.inactive.pop();
s.setVisible(true);
s.clearTint();
return s;
}
return this.scene.add.sprite(0, 0, 'tree');
},
release: (sprite) => {
sprite.setVisible(false);
this.decorationPool.inactive.push(sprite);
}
};
this.cropPool = {
active: [],
inactive: [],
get: () => {
if (this.cropPool.inactive.length > 0) {
const s = this.cropPool.inactive.pop();
s.setVisible(true);
return s;
}
return this.scene.add.sprite(0, 0, 'crop_stage_1');
},
release: (sprite) => {
sprite.setVisible(false);
this.cropPool.inactive.push(sprite);
}
};
this.terrainTypes = {
WATER: { name: 'water', height: 0, color: 0x4444ff },
SAND: { name: 'sand', height: 0.2, color: 0xdddd44 },
GRASS_FULL: { name: 'grass_full', height: 0.35, color: 0x44aa44 },
GRASS_TOP: { name: 'grass_top', height: 0.45, color: 0x66cc66 },
DIRT: { name: 'dirt', height: 0.5, color: 0x8b4513 },
STONE: { name: 'stone', height: 0.7, color: 0x888888 },
PATH: { name: 'path', height: -1, color: 0xc2b280 },
FARMLAND: { name: 'farmland', height: -1, color: 0x5c4033 }
};
this.offsetX = 0;
this.offsetY = 0;
// Culling optimization
this.lastCullX = -9999;
this.lastCullY = -9999;
// Object Pools
this.tilePool = new ObjectPool(
() => {
const sprite = this.scene.add.image(0, 0, 'tile_grass');
sprite.setOrigin(0.5, 0); // Isometrični tiles imajo origin zgoraj/center ali po potrebi
return sprite;
},
(sprite) => {
sprite.setVisible(true);
sprite.setAlpha(1);
sprite.clearTint();
}
);
this.decorationPool = new ObjectPool(
() => {
const sprite = this.scene.add.sprite(0, 0, 'flower');
sprite.setOrigin(0.5, 1);
return sprite;
},
(sprite) => {
sprite.setVisible(true);
sprite.setAlpha(1);
sprite.clearTint(); // Reset damage tint
}
);
this.cropPool = new ObjectPool(
() => {
const sprite = this.scene.add.sprite(0, 0, 'crop_stage_1'); // Default texture logic needed
sprite.setOrigin(0.5, 1);
return sprite;
},
(sprite) => {
sprite.setVisible(true);
sprite.setAlpha(1);
}
);
// Tipi terena z threshold vrednostmi + Y-LAYER STACKING
this.terrainTypes = {
WATER: { threshold: 0.3, color: 0x2166aa, name: 'water', texture: 'tile_water', yLayer: -1 },
SAND: { threshold: 0.4, color: 0xf4e7c6, name: 'sand', texture: 'tile_sand', yLayer: 0 },
// Y-LAYER GRASS VARIANTS (A, B, C systém)
GRASS_FULL: { threshold: 0.50, color: 0x5cb85c, name: 'grass_full', texture: 'tile_grass_full', yLayer: 0 }, // A: Full grass
GRASS_TOP: { threshold: 0.60, color: 0x5cb85c, name: 'grass_top', texture: 'tile_grass_top', yLayer: 1 }, // B: Grass top, dirt sides
DIRT: { threshold: 0.75, color: 0x8b6f47, name: 'dirt', texture: 'tile_dirt', yLayer: 2 }, // C: Full dirt
STONE: { threshold: 1.0, color: 0x7d7d7d, name: 'stone', texture: 'tile_stone', yLayer: 3 },
PATH: { threshold: 999, color: 0x9b7653, name: 'path', texture: 'tile_path', yLayer: 0 }, // Pot/Road
FARMLAND: { threshold: 999, color: 0x4a3c2a, name: 'farmland', texture: 'tile_farmland', yLayer: 0 }
};
}
// Helper da dobi terrain type glede na elevation (Y-layer)
getTerrainTypeByElevation(noiseValue, elevation) {
// Osnovni terrain type iz noise
let baseType = this.getTerrainType(noiseValue);
// Če je grass, določi Y-layer variant glede na elevation
if (baseType.name.includes('grass') || baseType === this.terrainTypes.GRASS_FULL ||
baseType === this.terrainTypes.GRASS_TOP) {
if (elevation > 0.7) {
return this.terrainTypes.GRASS_FULL; // A: Najvišja plast (full grass)
} else if (elevation > 0.4) {
return this.terrainTypes.GRASS_TOP; // B: Srednja (grass top, dirt sides)
} else {
return this.terrainTypes.DIRT; // C: Nizka (full dirt)
}
}
return baseType;
}
// Helper za določanje tipa terena glede na noise vrednost
getTerrainType(value) {
if (value < this.terrainTypes.WATER.threshold) return this.terrainTypes.WATER;
if (value < this.terrainTypes.SAND.threshold) return this.terrainTypes.SAND;
if (value < this.terrainTypes.GRASS_FULL.threshold) return this.terrainTypes.GRASS_FULL; // Fallback grass
if (value < this.terrainTypes.GRASS_TOP.threshold) return this.terrainTypes.GRASS_TOP;
if (value < this.terrainTypes.DIRT.threshold) return this.terrainTypes.DIRT;
return this.terrainTypes.STONE;
}
getTile(x, y) {
if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
return this.tiles[y][x];
}
return null;
}
// Generiraj teksture za tiles (da ne uporabljamo Počasnih Graphics objektov)
createTileTextures() {
console.log('🎨 Creating tile textures...');
// Flat Grid Look (No depth)
const tileWidth = 48;
const tileHeight = 24; // Just the diamond
const types = Object.values(this.terrainTypes);
for (const type of Object.values(this.terrainTypes)) {
const key = `tile_${type.name}`;
if (this.scene.textures.exists(key)) continue;
// Check for custom grass tile
if (type.name === 'grass' && this.scene.textures.exists('grass_tile')) {
this.scene.textures.addImage(key, this.scene.textures.get('grass_tile').getSourceImage());
type.texture = key;
continue; // Skip procedural generation
}
const tileW = this.iso.tileWidth;
const tileH = this.iso.tileHeight;
const thickness = 8; // Minimal thickness - nearly 2D
types.forEach((type) => {
if (this.scene.textures.exists(type.name)) return;
const graphics = this.scene.make.graphics({ x: 0, y: 0, add: false });
// Helper for colors
const baseColor = Phaser.Display.Color.IntegerToColor(type.color);
let darkColor, darkerColor;
const x = 0;
const top = 0;
const midX = 24;
const midY = 12;
const bottomY = 24;
// Y-LAYER STACKING: Different side colors based on layer
if (type.name === 'grass_full') {
// A: Full Grass Block - všechno zeleno
darkColor = 0x5cb85c; // Green (lighter)
darkerColor = 0x4a9d3f; // Green (darker)
} else if (type.name === 'grass_top') {
// B: Grass Top - zgoraj zeleno, stranice zemlja
darkColor = 0x8b6f47; // Dirt color (brown)
darkerColor = 0x5d4a2e; // Darker Dirt
} else if (type.name === 'dirt') {
// C: Full Dirt - vse rjavo
darkColor = 0x8b6f47; // Dirt
darkerColor = 0x654321; // Darker Dirt
} else {
// Standard block: Darken base color significantly
darkColor = Phaser.Display.Color.IntegerToColor(type.color).darken(30).color;
darkerColor = Phaser.Display.Color.IntegerToColor(type.color).darken(50).color;
graphics.fillStyle(type.color);
// Diamond Only
graphics.beginPath();
graphics.moveTo(midX, top);
graphics.lineTo(x + 48, midY);
graphics.lineTo(midX, bottomY);
graphics.lineTo(x, midY);
graphics.closePath();
graphics.fill();
// Grid Stroke (Black/Dark)
graphics.lineStyle(1, 0x000000, 0.3);
graphics.strokePath();
// Simple details
if (type.name.includes('grass')) {
graphics.fillStyle(0x339933);
for (let i = 0; i < 5; i++) {
const rx = x + 10 + Math.random() * 28;
const ry = 5 + Math.random() * 14;
graphics.fillRect(rx, ry, 2, 2);
}
}
// 1. Draw LEFT Side (Darker) - Minecraft volumetric effect
graphics.fillStyle(darkColor, 1);
graphics.beginPath();
graphics.moveTo(0, tileH / 2); // Left Corner
graphics.lineTo(tileW / 2, tileH); // Bottom Corner
graphics.lineTo(tileW / 2, tileH + thickness); // Bottom Corner (Lowered)
graphics.lineTo(0, tileH / 2 + thickness); // Left Corner (Lowered)
graphics.closePath();
graphics.fillPath();
// 2. Draw RIGHT Side (Darkest) - Strong shadow
graphics.fillStyle(darkerColor, 1);
graphics.beginPath();
graphics.moveTo(tileW / 2, tileH); // Bottom Corner
graphics.lineTo(tileW, tileH / 2); // Right Corner
graphics.lineTo(tileW, tileH / 2 + thickness); // Right Corner (Lowered)
graphics.lineTo(tileW / 2, tileH + thickness); // Bottom Corner (Lowered)
graphics.closePath();
graphics.fillPath();
// 3. Draw TOP Surface (bright)
graphics.fillStyle(type.color, 1);
graphics.beginPath();
graphics.moveTo(tileW / 2, 0); // Top
graphics.lineTo(tileW, tileH / 2); // Right
graphics.lineTo(tileW / 2, tileH); // Bottom
graphics.lineTo(0, tileH / 2); // Left
graphics.closePath();
graphics.fillPath();
// Generate texture
graphics.generateTexture(key, tileW, tileH + thickness);
graphics.generateTexture(type.name, tileWidth, tileHeight);
graphics.destroy();
// Update texture name in type def
type.texture = key;
}
});
}
createGravestoneSprite() {
const canvas = document.createElement('canvas');
const size = 32;
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
if (this.scene.textures.exists('objects_pack')) {
const sourceTexture = this.scene.textures.get('objects_pack');
const sourceImg = sourceTexture.getSourceImage();
const sourceX = 240;
const sourceY = 160;
ctx.drawImage(sourceImg, sourceX, sourceY, size, size, 0, 0, size, size);
this.scene.textures.addCanvas('gravestone', canvas);
console.log('✅ Gravestone sprite extracted!');
}
}
// Generiraj teren (data only)
generate() {
console.log(`🌍 Generating terrain data: ${this.width}x${this.height}...`);
// Zagotovi teksture
this.createTileTextures();
if (!this.scene.textures.exists('flower')) TextureGenerator.createFlowerSprite(this.scene, 'flower');
if (this.scene.textures.exists('stone_sprite')) {
if (!this.scene.textures.exists('bush')) this.scene.textures.addImage('bush', this.scene.textures.get('stone_sprite').getSourceImage());
} else if (!this.scene.textures.exists('bush')) {
TextureGenerator.createBushSprite(this.scene, 'bush');
}
if (this.scene.textures.exists('tree_sprite')) {
if (!this.scene.textures.exists('tree')) this.scene.textures.addImage('tree', this.scene.textures.get('tree_sprite').getSourceImage());
} else if (!this.scene.textures.exists('tree')) {
TextureGenerator.createTreeSprite(this.scene, 'tree');
}
if (this.scene.textures.exists('objects_pack') && !this.scene.textures.exists('gravestone')) {
this.createGravestoneSprite();
}
for (let i = 1; i <= 4; i++) {
if (!this.scene.textures.exists(`crop_stage_${i}`)) TextureGenerator.createCropSprite(this.scene, `crop_stage_${i}`, i);
}
this.decorationsMap.clear();
this.decorations = [];
this.cropsMap.clear();
for (let y = 0; y < this.height; y++) {
this.tiles[y] = [];
for (let x = 0; x < this.width; x++) {
const noiseValue = this.noise.getNormalized(x, y, 0.05, 4);
let elevation = this.noise.getNormalized(x, y, 0.03, 3);
let terrainType = this.getTerrainTypeByElevation(noiseValue, elevation);
const nx = x * 0.1;
const ny = y * 0.1;
const elevation = this.noise.noise(nx, ny);
const pathNoise = this.noise.getNormalized(x, y, 0.08, 2);
if (pathNoise > 0.48 && pathNoise < 0.52 && noiseValue > 0.35) {
terrainType = this.terrainTypes.PATH;
}
const edgeDistance = 2;
const isNearEdge = x < edgeDistance || x >= this.width - edgeDistance || y < edgeDistance || y >= this.height - edgeDistance;
const isEdge = x === 0 || x === this.width - 1 || y === 0 || y === this.height - 1;
if (isEdge) terrainType = this.terrainTypes.STONE;
if (isEdge) elevation = 0;
else if (isNearEdge) elevation = Math.max(0, elevation - 0.2);
let terrainType = this.terrainTypes.WATER;
if (elevation > this.terrainTypes.SAND.height) terrainType = this.terrainTypes.SAND;
if (elevation > this.terrainTypes.GRASS_FULL.height) terrainType = this.terrainTypes.GRASS_FULL;
if (elevation > this.terrainTypes.DIRT.height) terrainType = this.terrainTypes.DIRT;
if (elevation > this.terrainTypes.STONE.height) terrainType = this.terrainTypes.STONE;
this.tiles[y][x] = {
gridX: x,
gridY: y,
type: terrainType.name,
texture: terrainType.texture,
height: noiseValue,
elevation: elevation,
yLayer: terrainType.yLayer,
texture: terrainType.name,
hasDecoration: false,
hasCrop: false
};
@@ -291,24 +165,33 @@ class TerrainSystem {
if (terrainType.name.includes('grass')) {
const rand = Math.random();
if (elevation > 0.6 && rand < 0.05) {
if (elevation > 0.6 && rand < 0.1) {
decorType = 'bush';
maxHp = 5;
} else if (rand < 0.025) {
}
// Trees - Volumetric
else if (rand < 0.15) {
decorType = 'tree';
maxHp = 5;
const sizeRand = Math.random();
if (sizeRand < 0.2) scale = 0.25;
else if (sizeRand < 0.8) scale = 0.6 + Math.random() * 0.2;
else scale = 1.0;
} else if (rand < 0.03) {
if (sizeRand < 0.2) scale = 0.8;
else if (sizeRand < 0.8) scale = 1.0 + Math.random() * 0.3;
else scale = 1.3;
}
// Rocks - Volumetric
else if (rand < 0.18) {
decorType = 'rock'; // 'rock' texture from TextureGenerator
maxHp = 8;
scale = 1.5; // Big rocks
}
else if (rand < 0.19) {
decorType = 'gravestone';
maxHp = 10;
} else if (rand < 0.08) {
} else if (rand < 0.30) {
decorType = 'flower';
maxHp = 1;
}
} else if (terrainType.name === 'dirt' && Math.random() < 0.02) {
} else if (terrainType.name === 'dirt' && Math.random() < 0.05) {
decorType = 'bush';
maxHp = 3;
}
@@ -401,14 +284,12 @@ class TerrainSystem {
setTileType(x, y, typeName) {
if (!this.tiles[y] || !this.tiles[y][x]) return;
const typeDef = Object.values(this.terrainTypes).find(t => t.name === typeName);
if (!typeDef) return;
this.tiles[y][x].type = typeName;
this.tiles[y][x].texture = typeDef.texture;
const key = `${x},${y}`;
if (this.visibleTiles.has(key)) {
const sprite = this.visibleTiles.get(key);
sprite.setTexture(typeDef.texture);
sprite.setTexture(typeName);
}
}
@@ -446,8 +327,14 @@ class TerrainSystem {
this.offsetY = offsetY;
}
getTile(x, y) {
if (this.tiles[y] && this.tiles[y][x]) {
return this.tiles[y][x];
}
return null;
}
updateCulling(camera) {
// Simple Culling
const view = camera.worldView;
let buffer = 200;
if (this.scene.settings && this.scene.settings.viewDistance === 'LOW') buffer = 50;
@@ -471,25 +358,26 @@ class TerrainSystem {
const startY = Math.max(0, minGridY);
const endY = Math.min(this.height, maxGridY);
const neededKeys = new Set();
const neededTileKeys = new Set();
const neededDecorKeys = new Set();
const neededCropKeys = new Set();
for (let y = startY; y < endY; y++) {
for (let x = startX; x < endX; x++) {
// TILES
const key = `${x},${y}`;
neededKeys.add(key);
const tile = this.tiles[y][x];
if (!this.visibleTiles.has(key)) {
const tile = this.tiles[y][x];
const screenPos = this.iso.toScreen(x, y);
const sprite = this.tilePool.get();
sprite.setPosition(screenPos.x + this.offsetX, screenPos.y + this.offsetY);
sprite.setTexture(tile.texture);
sprite.setDepth(this.iso.getDepth(x, y));
this.visibleTiles.set(key, sprite);
// TILES
if (tile) {
neededTileKeys.add(key);
if (!this.visibleTiles.has(key)) {
const sprite = this.tilePool.get();
sprite.setTexture(tile.type);
const screenPos = this.iso.toScreen(x, y);
sprite.setPosition(screenPos.x + this.offsetX, screenPos.y + this.offsetY);
sprite.setDepth(this.iso.getDepth(x, y));
this.visibleTiles.set(key, sprite);
}
}
// DECORATIONS
@@ -501,23 +389,19 @@ class TerrainSystem {
const screenPos = this.iso.toScreen(x, y);
sprite.setPosition(screenPos.x + this.offsetX, screenPos.y + this.offsetY);
// Fix for house sprite
if (decor.type.includes('house_sprite') || decor.type.includes('market_sprite')) {
sprite.setTexture(decor.type);
sprite.setScale(decor.scale || 1.0);
if (decor.type.includes('house_sprite') || decor.type.includes('market') || decor.type.includes('structure')) {
sprite.setOrigin(0.5, 0.8);
} else {
sprite.setTexture(decor.type);
sprite.setScale(decor.scale || 1);
// Volumetric trees/rocks origin adjustment
// Usually volumetric needed (0.5, 1) or slightly different?
sprite.setOrigin(0.5, 0.9); // Slight tweak
}
// Origin adjusted for buildings
if (decor.type.includes('house') || decor.type.includes('market')) {
sprite.setOrigin(0.5, 0.8); // Adjusted origin for buildings
} else {
sprite.setOrigin(0.5, 1);
}
sprite.setTexture(decor.type);
sprite.setScale(decor.scale || 1.0);
sprite.setDepth(this.iso.getDepth(x, y) + 1); // Above tile
sprite.setDepth(this.iso.getDepth(x, y) + 1);
this.visibleDecorations.set(key, sprite);
}
}
@@ -538,16 +422,14 @@ class TerrainSystem {
}
}
// Cleanup tiles
// Cleanup
for (const [key, sprite] of this.visibleTiles) {
if (!neededKeys.has(key)) {
if (!neededTileKeys.has(key)) {
sprite.setVisible(false);
this.tilePool.release(sprite);
this.visibleTiles.delete(key);
}
}
// Cleanup decorations
for (const [key, sprite] of this.visibleDecorations) {
if (!neededDecorKeys.has(key)) {
sprite.setVisible(false);
@@ -555,8 +437,6 @@ class TerrainSystem {
this.visibleDecorations.delete(key);
}
}
// Cleanup crops
for (const [key, sprite] of this.visibleCrops) {
if (!neededCropKeys.has(key)) {
sprite.setVisible(false);