This commit is contained in:
2025-12-07 21:31:44 +01:00
parent 4a0ca267ea
commit 974141c08c
52 changed files with 2485 additions and 397 deletions

View File

@@ -1,5 +1,15 @@
// ========================================================
// NOVE GLOBALNE KONSTANTE ZA LOKACIJE
// ========================================================
const FARM_SIZE = 8;
const FARM_CENTER_X = 20; // Lokacija farme na X osi
const FARM_CENTER_Y = 20; // Lokacija farme na Y osi
const CITY_SIZE = 15;
const CITY_START_X = 65; // Desni del mape (npr. med 65 in 80)
const CITY_START_Y = 65;
// Terrain Generator System
// Generira proceduralni isometrični teren in skrbi za optimizacijo (Tilemap + Culling)
class TerrainSystem {
constructor(scene, width = 100, height = 100) {
this.scene = scene;
@@ -14,10 +24,29 @@ class TerrainSystem {
this.decorationsMap = new Map();
this.cropsMap = new Map();
this.visibleTiles = new Map();
this.visibleDecorations = new Map();
this.visibleCrops = new Map();
// Pool for Decorations (Trees, Rocks, etc.)
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: [],
@@ -36,7 +65,6 @@ class TerrainSystem {
}
};
// Pool for Crops
this.cropPool = {
active: [],
inactive: [],
@@ -55,14 +83,16 @@ class TerrainSystem {
};
this.terrainTypes = {
WATER: { name: 'water', height: 0, color: 0x4444ff, index: 0 },
SAND: { name: 'sand', height: 0.2, color: 0xdddd44, index: 1 },
GRASS_FULL: { name: 'grass_full', height: 0.35, color: 0x44aa44, index: 2 },
GRASS_TOP: { name: 'grass_top', height: 0.45, color: 0x66cc66, index: 3 },
DIRT: { name: 'dirt', height: 0.5, color: 0x8b4513, index: 4 },
STONE: { name: 'stone', height: 0.7, color: 0x888888, index: 5 },
PATH: { name: 'path', height: -1, color: 0xc2b280, index: 6 },
FARMLAND: { name: 'farmland', height: -1, color: 0x5c4033, index: 7 }
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 },
PAVEMENT: { name: 'pavement', height: 0.6, color: 0x777777 },
RUINS: { name: 'ruins', height: 0.6, color: 0x555555 },
PATH: { name: 'path', height: -1, color: 0xc2b280 },
FARMLAND: { name: 'farmland', height: -1, color: 0x5c4033 }
};
this.offsetX = 0;
@@ -70,53 +100,21 @@ class TerrainSystem {
}
createTileTextures() {
// Create a single spritesheet for tiles (Tilemap Optimization)
const graphics = this.scene.make.graphics({ x: 0, y: 0, add: false });
const tileWidth = 48;
const tileHeight = 32; // 24 for iso + 8 depth
const tileHeight = 60;
const types = Object.values(this.terrainTypes);
// Draw all tiles horizontally
types.forEach((type, index) => {
// Update index just in case
type.index = index;
types.forEach((type) => {
if (this.scene.textures.exists(type.name)) return;
const x = index * tileWidth;
graphics.fillStyle(type.color);
// Draw Isometic Tile (Diamond + Thickness)
const top = 0;
const midX = x + 24;
const graphics = this.scene.make.graphics({ x: 0, y: 0, add: false });
const x = 0;
const midX = 24;
const midY = 12;
const bottomY = 24;
const depth = 8;
const depth = 20;
// Top Face
graphics.beginPath();
graphics.moveTo(midX, top);
graphics.lineTo(x + 48, midY);
graphics.lineTo(midX, bottomY);
graphics.lineTo(x, midY);
graphics.closePath();
graphics.fill();
// Add stroke to prevent seams/gaps (Robust Fix)
graphics.lineStyle(2, type.color);
graphics.strokePath();
// Thickness (Right)
graphics.fillStyle(Phaser.Display.Color.IntegerToColor(type.color).darken(20).color);
graphics.beginPath();
graphics.moveTo(x + 48, midY);
graphics.lineTo(x + 48, midY + depth);
graphics.lineTo(midX, bottomY + depth);
graphics.lineTo(midX, bottomY);
graphics.closePath();
graphics.fill();
// Thickness (Left)
graphics.fillStyle(Phaser.Display.Color.IntegerToColor(type.color).darken(40).color);
graphics.fillStyle(0x8B4513);
graphics.beginPath();
graphics.moveTo(midX, bottomY);
graphics.lineTo(midX, bottomY + depth);
@@ -125,28 +123,59 @@ class TerrainSystem {
graphics.closePath();
graphics.fill();
// Detail (Grass)
if (type.name.includes('grass')) {
graphics.fillStyle(0x339933); // Darker green blades
for (let i = 0; i < 15; i++) {
const rx = x + 8 + Math.random() * 32;
const ry = 4 + Math.random() * 16;
graphics.fillRect(rx, ry, 2, 2);
}
}
// Detail (Dirt)
if (type.name.includes('dirt')) {
graphics.fillStyle(0x5c4033);
for (let i = 0; i < 8; i++) {
const rx = x + 8 + Math.random() * 32;
const ry = 4 + Math.random() * 16;
graphics.fillRect(rx, ry, 2, 2);
}
}
});
graphics.fillStyle(0x6B3410);
graphics.beginPath();
graphics.moveTo(x + 48, midY);
graphics.lineTo(x + 48, midY + depth);
graphics.lineTo(midX, bottomY + depth);
graphics.lineTo(midX, bottomY);
graphics.closePath();
graphics.fill();
graphics.generateTexture('terrain_tileset', tileWidth * types.length, tileHeight);
graphics.destroy();
graphics.fillStyle(type.color);
graphics.beginPath();
graphics.moveTo(midX, 0);
graphics.lineTo(x + 48, midY);
graphics.lineTo(midX, bottomY);
graphics.lineTo(x, midY);
graphics.closePath();
graphics.fill();
graphics.lineStyle(1, 0xffffff, 0.15);
graphics.beginPath();
graphics.moveTo(x, midY);
graphics.lineTo(midX, 0);
graphics.lineTo(x + 48, midY);
graphics.strokePath();
if (type.name.includes('grass')) {
graphics.fillStyle(Phaser.Display.Color.IntegerToColor(type.color).lighten(10).color);
for (let i = 0; i < 8; i++) {
const rx = x + 10 + Math.random() * 28;
const ry = 4 + Math.random() * 16;
graphics.fillRect(rx, ry, 2, 2);
}
}
if (type.name.includes('stone') || type.name.includes('ruins')) {
graphics.fillStyle(0x444444);
for (let i = 0; i < 6; i++) {
const rx = x + 8 + Math.random() * 30;
const ry = 4 + Math.random() * 16;
graphics.fillRect(rx, ry, 3, 3);
}
}
if (type.name.includes('pavement')) {
graphics.lineStyle(1, 0x555555, 0.5);
graphics.beginPath();
graphics.moveTo(x + 12, midY + 6);
graphics.lineTo(x + 36, midY - 6);
graphics.strokePath();
}
graphics.generateTexture(type.name, tileWidth, tileHeight);
graphics.destroy();
});
}
generate() {
@@ -159,11 +188,29 @@ class TerrainSystem {
const ny = y * 0.1;
const elevation = this.noise.noise(nx, ny);
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;
let terrainType = this.terrainTypes.GRASS_FULL;
if (x < 3 || x >= this.width - 3 || y < 3 || y >= this.height - 3) {
terrainType = this.terrainTypes.GRASS_FULL;
} else {
if (elevation < -0.6) terrainType = this.terrainTypes.WATER;
else if (elevation > 0.1) terrainType = this.terrainTypes.SAND;
else if (elevation > 0.2) terrainType = this.terrainTypes.GRASS_FULL;
else if (elevation > 0.3) terrainType = this.terrainTypes.GRASS_TOP;
else if (elevation > 0.7) terrainType = this.terrainTypes.DIRT;
else if (elevation > 0.85) terrainType = this.terrainTypes.STONE;
}
if (Math.abs(x - FARM_CENTER_X) <= FARM_SIZE / 2 && Math.abs(y - FARM_CENTER_Y) <= FARM_SIZE / 2) {
terrainType = this.terrainTypes.DIRT;
}
if (x >= CITY_START_X && x < CITY_START_X + CITY_SIZE &&
y >= CITY_START_Y && y < CITY_START_Y + CITY_SIZE) {
terrainType = this.terrainTypes.PAVEMENT;
if (Math.random() < 0.2) {
terrainType = this.terrainTypes.RUINS;
}
}
this.tiles[y][x] = {
type: terrainType.name,
@@ -171,86 +218,85 @@ class TerrainSystem {
hasDecoration: false,
hasCrop: false
};
}
}
// Vegetation logic (Rich World)
if (x > 5 && x < this.width - 5 && y > 5 && y < this.height - 5) {
let decorType = null;
let maxHp = 1;
let scale = 1.0;
let treeCount = 0;
let rockCount = 0;
let flowerCount = 0;
if (terrainType.name.includes('grass')) {
const rand = Math.random();
if (elevation > 0.6 && rand < 0.1) {
decorType = 'bush';
maxHp = 5;
} else if (rand < 0.15) { // Common trees
decorType = 'tree';
maxHp = 5;
const sizeRand = Math.random();
if (sizeRand < 0.2) scale = 0.8;
else if (sizeRand < 0.8) scale = 1.0 + Math.random() * 0.3;
else scale = 1.3;
} else if (rand < 0.18) { // Rocks
decorType = 'rock';
maxHp = 8;
scale = 1.2 + Math.random() * 0.5;
} else if (rand < 0.19) {
decorType = 'gravestone';
maxHp = 10;
} else if (rand < 0.30) {
decorType = 'flower';
maxHp = 1;
}
} else if (terrainType.name === 'dirt' && Math.random() < 0.05) {
decorType = 'bush';
maxHp = 3;
}
const validPositions = [];
const isFarm = (x, y) => Math.abs(x - FARM_CENTER_X) <= (FARM_SIZE / 2 + 2) && Math.abs(y - FARM_CENTER_Y) <= (FARM_SIZE / 2 + 2);
const isCity = (x, y) => x >= CITY_START_X - 2 && x < CITY_START_X + CITY_SIZE + 2 && y >= CITY_START_Y - 2 && y < CITY_START_Y + CITY_SIZE + 2;
if (decorType) {
const key = `${x},${y}`;
const decorData = {
gridX: x,
gridY: y,
type: decorType,
id: key,
maxHp: maxHp,
hp: maxHp,
scale: scale
};
this.decorations.push(decorData);
this.decorationsMap.set(key, decorData);
this.tiles[y][x].hasDecoration = true;
}
for (let y = 5; y < this.height - 5; y++) {
for (let x = 5; x < this.width - 5; x++) {
if (isFarm(x, y) || isCity(x, y)) continue;
const tile = this.tiles[y][x];
if (tile.type !== 'water' && tile.type !== 'sand' && tile.type !== 'stone') {
validPositions.push({ x, y });
}
}
}
console.log('✅ Terrain and decorations generated!');
for (let i = validPositions.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[validPositions[i], validPositions[j]] = [validPositions[j], validPositions[i]];
}
// --- TILEMAP IMPLEMENTATION (Performance) ---
if (this.map) this.map.destroy();
this.map = this.scene.make.tilemap({
tileWidth: this.iso.tileWidth, // 48
tileHeight: this.iso.tileHeight, // 24
width: this.width,
height: this.height,
orientation: Phaser.Tilemaps.Orientation.ISOMETRIC
});
for (let i = 0; i < Math.min(25, validPositions.length); i++) {
const pos = validPositions[i];
let treeType = 'tree_green_new';
const rand = Math.random();
if (rand < 0.15) treeType = 'tree_blue_new';
else if (rand < 0.25) treeType = 'tree_dead_new';
// 48x32 tileset
const tileset = this.map.addTilesetImage('terrain_tileset', 'terrain_tileset', 48, 32);
this.layer = this.map.createBlankLayer('Ground', tileset, this.offsetX, this.offsetY);
this.addDecoration(pos.x, pos.y, treeType);
treeCount++;
}
for (let i = 25; i < Math.min(50, validPositions.length); i++) {
const pos = validPositions[i];
// Uporabi uporabnikove kamne
const rockType = Math.random() > 0.5 ? 'rock_1' : 'rock_2';
this.addDecoration(pos.x, pos.y, rockType);
rockCount++;
}
for (let y = 0; y < this.height; y++) {
for (let x = 0; x < this.width; x++) {
const t = this.tiles[y][x];
const typeDef = Object.values(this.terrainTypes).find(tt => tt.name === t.type);
if (typeDef) {
this.layer.putTileAt(typeDef.index, x, y);
const flowerNoise = new PerlinNoise(Date.now() + 3000);
for (let y = 5; y < this.height - 5; y++) {
for (let x = 5; x < this.width - 5; x++) {
if (isFarm(x, y) || isCity(x, y)) continue;
const tile = this.tiles[y][x];
const val = flowerNoise.noise(x * 0.12, y * 0.12);
if (val > 0.85 && tile.type.includes('grass')) {
this.addDecoration(x, y, 'flowers_new');
flowerCount++;
}
}
}
this.layer.setDepth(0); // Ground level
const roomSize = 5;
const roomsAcross = Math.floor(CITY_SIZE / roomSize);
for (let ry = 0; ry < roomsAcross; ry++) {
for (let rx = 0; rx < roomsAcross; rx++) {
if (Math.random() < 0.75) {
const gx = CITY_START_X + rx * roomSize;
const gy = CITY_START_Y + ry * roomSize;
this.placeStructure(gx, gy, 'ruin_room');
} else {
const gx = CITY_START_X + rx * roomSize + 2;
const gy = CITY_START_Y + ry * roomSize + 2;
const rockType = Math.random() > 0.5 ? 'rock_1' : 'rock_2';
this.addDecoration(gx, gy, rockType);
}
}
}
console.log(`✅ Teren generiran: ${treeCount} dreves, ${rockCount} kamnov.`);
}
damageDecoration(x, y, amount) {
@@ -299,33 +345,115 @@ class TerrainSystem {
return decor.type;
}
placeStructure(x, y, structureType) {
if (this.decorationsMap.has(`${x},${y}`)) return false;
init(offsetX, offsetY) {
this.offsetX = offsetX;
this.offsetY = offsetY;
}
setTile(x, y, type) {
if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
this.tiles[y][x].type = type;
}
}
placeStructure(gridX, gridY, type) {
if (type === 'ruin') {
for (let y = 0; y < 6; y++) {
for (let x = 0; x < 6; x++) {
if (Math.random() > 0.6) this.addDecoration(gridX + x, gridY + y, 'fence');
this.setTile(gridX + x, gridY + y, 'stone');
}
}
}
if (type === 'arena') {
const size = 12;
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const tx = gridX + x;
const ty = gridY + y;
this.setTile(tx, ty, 'stone');
if (x === 0 || x === size - 1 || y === 0 || y === size - 1) {
if (!(x === Math.floor(size / 2) && y === size - 1)) {
this.addDecoration(tx, ty, 'fence');
}
}
}
}
this.addDecoration(gridX + 6, gridY + 6, 'gravestone');
}
if (type === 'ruin_room') {
for (let y = 0; y < 5; y++) {
for (let x = 0; x < 5; x++) {
const tx = gridX + x;
const ty = gridY + y;
if (x > 0 && x < 4 && y > 0 && y < 4) {
this.setTile(tx, ty, 'ruins');
}
if (x === 0 || x === 4 || y === 0 || y === 4) {
const isCenter = (x === 2 || y === 2);
if (isCenter && Math.random() > 0.5) continue;
if (Math.random() > 0.3) {
this.addDecoration(tx, ty, 'fence');
} else {
// User rocks in ruins
if (Math.random() > 0.5) {
const rType = Math.random() > 0.5 ? 'rock_1' : 'rock_2';
this.addDecoration(tx, ty, rType);
}
}
}
}
}
}
}
addDecoration(gridX, gridY, type) {
if (gridX < 0 || gridX >= this.width || gridY < 0 || gridY >= this.height) return;
const key = `${gridX},${gridY}`;
if (this.decorationsMap.has(key)) return;
let scale = 1.0;
if (type === 'rock_1' || type === 'rock_2') scale = 1.5; // Povečano (bilo 0.5)
else if (type === 'tree_green_new' || type === 'tree_blue_new' || type === 'tree_dead_new') scale = 0.04;
else if (type === 'flowers_new') scale = 0.02;
else if (type === 'fence') scale = 0.025;
else if (type === 'gravestone') scale = 0.03;
else if (type === 'hill_sprite') scale = 0.025;
else {
// Old Assets (Low Res)
if (type.includes('tree')) scale = 1.2 + Math.random() * 0.4;
else if (type.includes('rock')) scale = 0.8;
else scale = 1.0;
}
const decorData = {
gridX: x,
gridY: y,
type: structureType,
id: `${x},${y}`,
maxHp: 5,
hp: 5
gridX: gridX,
gridY: gridY,
type: type,
id: key,
maxHp: 10,
hp: 10,
scale: scale
};
this.decorations.push(decorData);
this.decorationsMap.set(decorData.id, decorData);
const tile = this.getTile(x, y);
if (tile) tile.hasDecoration = true;
this.lastCullX = -9999;
return true;
this.decorationsMap.set(key, decorData);
if (this.tiles[gridY] && this.tiles[gridY][gridX]) {
this.tiles[gridY][gridX].hasDecoration = true;
}
}
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;
// Tilemap update
if (this.layer) {
this.layer.putTileAt(typeDef.index, x, y);
const key = `${x},${y}`;
if (this.visibleTiles.has(key)) {
const sprite = this.visibleTiles.get(key);
sprite.setTexture(typeName);
}
}
@@ -333,7 +461,6 @@ class TerrainSystem {
const key = `${x},${y}`;
this.cropsMap.set(key, cropData);
this.tiles[y][x].hasCrop = true;
this.lastCullX = -9999;
}
removeCrop(x, y) {
@@ -358,11 +485,6 @@ class TerrainSystem {
}
}
init(offsetX, offsetY) {
this.offsetX = offsetX;
this.offsetY = offsetY;
}
getTile(x, y) {
if (this.tiles[y] && this.tiles[y][x]) {
return this.tiles[y][x];
@@ -371,10 +493,10 @@ class TerrainSystem {
}
updateCulling(camera) {
// Culling for Decorations & Crops (Tiles controlled by Tilemap)
const view = camera.worldView;
let buffer = 200;
if (this.scene.settings && this.scene.settings.viewDistance === 'LOW') buffer = 50;
const left = view.x - buffer - this.offsetX;
const top = view.y - buffer - this.offsetY;
const right = view.x + view.width + buffer - this.offsetX;
@@ -395,14 +517,29 @@ class TerrainSystem {
const startY = Math.max(0, minGridY);
const endY = Math.min(this.height, maxGridY);
const neededTileKeys = new Set();
const neededDecorKeys = new Set();
const neededCropKeys = new Set();
const voxelOffset = 12;
for (let y = startY; y < endY; y++) {
for (let x = startX; x < endX; x++) {
const key = `${x},${y}`;
const tile = this.tiles[y][x];
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
const decor = this.decorationsMap.get(key);
if (decor) {
neededDecorKeys.add(key);
@@ -410,37 +547,35 @@ class TerrainSystem {
const sprite = this.decorationPool.get();
const screenPos = this.iso.toScreen(x, y);
sprite.setPosition(screenPos.x + this.offsetX, screenPos.y + this.offsetY);
sprite.setPosition(screenPos.x + this.offsetX, screenPos.y + this.offsetY - voxelOffset);
// Origin adjusted for volumetric sprites
// Trees/Rocks usually look best with origin (0.5, 0.9) to sit on the ground
if (decor.type.includes('house') || decor.type.includes('market') || decor.type.includes('structure')) {
sprite.setOrigin(0.5, 0.8);
} else {
sprite.setOrigin(0.5, 0.9);
sprite.setOrigin(0.5, 1.0);
}
// Texture & Scale
sprite.setTexture(decor.type);
sprite.setScale(decor.scale || 1.0);
if (decor.alpha !== undefined) {
sprite.setAlpha(decor.alpha);
}
sprite.setDepth(this.iso.getDepth(x, y) + 1);
this.visibleDecorations.set(key, sprite);
}
}
// CROPS
const crop = this.cropsMap.get(key);
if (crop) {
neededCropKeys.add(key);
if (!this.visibleCrops.has(key)) {
const sprite = this.cropPool.get();
const screenPos = this.iso.toScreen(x, y);
sprite.setPosition(screenPos.x + this.offsetX, screenPos.y + this.offsetY);
sprite.setPosition(screenPos.x + this.offsetX, screenPos.y + this.offsetY - voxelOffset);
sprite.setTexture(`crop_stage_${crop.stage}`);
// Crop origin
sprite.setOrigin(0.5, 1);
// Crop depth
sprite.setDepth(this.iso.getDepth(x, y) + 0.5);
this.visibleCrops.set(key, sprite);
}
@@ -448,7 +583,13 @@ class TerrainSystem {
}
}
// Cleanup
for (const [key, sprite] of this.visibleTiles) {
if (!neededTileKeys.has(key)) {
sprite.setVisible(false);
this.tilePool.release(sprite);
this.visibleTiles.delete(key);
}
}
for (const [key, sprite] of this.visibleDecorations) {
if (!neededDecorKeys.has(key)) {
sprite.setVisible(false);