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:
@@ -38,13 +38,20 @@ const POND_RADIUS = 4; // Radij ribnika (8x8)
|
||||
|
||||
// Terrain Generator System
|
||||
class TerrainSystem {
|
||||
constructor(scene, width = 100, height = 100) {
|
||||
constructor(scene, width = 100, height = 100, seed = null) {
|
||||
this.scene = scene;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
||||
// 🎲 SEED-BASED GENERATION
|
||||
this.seed = seed || Date.now().toString();
|
||||
console.log(`🌍 TerrainSystem initialized with seed: ${this.seed}`);
|
||||
|
||||
// Seeded RNG - vedno isti rezultati za isti seed!
|
||||
this.rng = this.createSeededRNG(this.seed);
|
||||
|
||||
this.iso = new IsometricUtils(48, 24);
|
||||
this.noise = new PerlinNoise(Date.now());
|
||||
this.noise = new PerlinNoise(this.hashCode(this.seed));
|
||||
|
||||
this.tiles = [];
|
||||
this.decorations = [];
|
||||
@@ -151,6 +158,33 @@ class TerrainSystem {
|
||||
this.tiles = Array.from({ length: this.height }, () => Array(this.width).fill(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create seeded random number generator
|
||||
* Uses Mulberry32 algorithm for consistent randomness
|
||||
*/
|
||||
createSeededRNG(seed) {
|
||||
let h = this.hashCode(seed);
|
||||
return function () {
|
||||
h = Math.imul(h ^ (h >>> 16), 2246822507);
|
||||
h = Math.imul(h ^ (h >>> 13), 3266489909);
|
||||
h = (h ^= h >>> 16) >>> 0;
|
||||
return h / 4294967296; // Return 0-1
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert string seed to numeric hash
|
||||
*/
|
||||
hashCode(str) {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash; // Convert to 32bit integer
|
||||
}
|
||||
return Math.abs(hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preveri, ali je nova lokacija (newX, newY) dovolj oddaljena od vseh
|
||||
* že postavljenih dreves. Uporablja kvadrat razdalje za hitrejšo optimizacijo.
|
||||
@@ -175,34 +209,41 @@ class TerrainSystem {
|
||||
createTileTextures() {
|
||||
const tileWidth = 48;
|
||||
const tileHeight = 60;
|
||||
const P = 2; // PADDING (Margin) za preprečevanje črt
|
||||
const P = 0; // NO PADDING - seamless tiles!
|
||||
|
||||
const types = Object.values(this.terrainTypes);
|
||||
|
||||
// POSEBNA OBDELAVA ZA VODO - 2D Stardew Valley Style!
|
||||
// POSEBNA OBDELAVA ZA VODO - 2D Smooth Pond/Lake Style!
|
||||
if (!this.scene.textures.exists('water')) {
|
||||
const waterGraphics = this.scene.make.graphics({ x: 0, y: 0, add: false });
|
||||
|
||||
// TEMNA MODRA VODA - dobro vidna!
|
||||
// RICH BLUE WATER - Stardew Valley style gradient
|
||||
waterGraphics.fillGradientStyle(
|
||||
0x0a3d62, 0x0a3d62, // Temno modra (zgoraj)
|
||||
0x1e5f8c, 0x1e5f8c // Srednje modra (spodaj)
|
||||
0x1a5f7a, 0x1a5f7a, // Deep blue (top)
|
||||
0x2a7fbc, 0x2a7fbc // Medium blue (bottom)
|
||||
);
|
||||
waterGraphics.fillRect(0, 0, 48, 48);
|
||||
|
||||
// Svetli highlights za valovanje
|
||||
waterGraphics.fillStyle(0x3a8fc2, 0.5);
|
||||
waterGraphics.fillCircle(12, 12, 10);
|
||||
waterGraphics.fillCircle(36, 28, 8);
|
||||
waterGraphics.fillCircle(24, 38, 6);
|
||||
// SMOOTH WAVE HIGHLIGHTS (organic circles)
|
||||
waterGraphics.fillStyle(0x4aa3d0, 0.4);
|
||||
waterGraphics.fillCircle(10, 10, 12);
|
||||
waterGraphics.fillCircle(35, 25, 10);
|
||||
waterGraphics.fillCircle(20, 38, 8);
|
||||
|
||||
// Temnejši border za kontrast
|
||||
waterGraphics.lineStyle(2, 0x062a40, 1);
|
||||
waterGraphics.strokeRect(0, 0, 48, 48);
|
||||
// Soft shimmer spots
|
||||
waterGraphics.fillStyle(0x7fc9e8, 0.3);
|
||||
waterGraphics.fillCircle(15, 20, 6);
|
||||
waterGraphics.fillCircle(38, 12, 5);
|
||||
|
||||
// Bright reflection highlights
|
||||
waterGraphics.fillStyle(0xffffff, 0.2);
|
||||
waterGraphics.fillCircle(12, 15, 4);
|
||||
waterGraphics.fillCircle(32, 30, 3);
|
||||
|
||||
// NO BORDER - seamless tiles!
|
||||
waterGraphics.generateTexture('water', 48, 48);
|
||||
waterGraphics.destroy();
|
||||
console.log('🌊 2D Water texture created (Stardew Valley style)!');
|
||||
console.log('🌊 Smooth pond water texture created (Stardew Valley style)!');
|
||||
}
|
||||
|
||||
types.forEach((type) => {
|
||||
@@ -231,8 +272,8 @@ class TerrainSystem {
|
||||
graphics.lineTo(xs, midY);
|
||||
graphics.closePath();
|
||||
graphics.fill();
|
||||
graphics.lineStyle(2, cLeft); // Overdraw
|
||||
graphics.strokePath();
|
||||
// graphics.lineStyle(2, cLeft); // NO STROKE!
|
||||
// graphics.strokePath();
|
||||
|
||||
// Right Face
|
||||
const cRight = 0x6B3410; // RJAVA DIRT - Right face (temnejša)
|
||||
@@ -244,8 +285,8 @@ class TerrainSystem {
|
||||
graphics.lineTo(midX, bottomY);
|
||||
graphics.closePath();
|
||||
graphics.fill();
|
||||
graphics.lineStyle(2, cRight);
|
||||
graphics.strokePath();
|
||||
// graphics.lineStyle(2, cRight); // NO STROKE!
|
||||
// graphics.strokePath();
|
||||
|
||||
// 2. ZGORNJA PLOSKEV (Top Face)
|
||||
graphics.fillStyle(type.color);
|
||||
@@ -256,32 +297,107 @@ class TerrainSystem {
|
||||
graphics.lineTo(midX, bottomY);
|
||||
graphics.closePath();
|
||||
graphics.fill();
|
||||
graphics.lineStyle(2, type.color); // Overdraw
|
||||
graphics.strokePath();
|
||||
// graphics.lineStyle(2, type.color); // NO STROKE!
|
||||
// graphics.strokePath();
|
||||
|
||||
// Highlight
|
||||
graphics.lineStyle(1, 0xffffff, 0.15);
|
||||
graphics.beginPath();
|
||||
graphics.moveTo(xs, midY);
|
||||
graphics.lineTo(midX, topY);
|
||||
graphics.lineTo(xe, midY);
|
||||
graphics.strokePath();
|
||||
// Highlight - REMOVED for seamless tiles
|
||||
// graphics.lineStyle(1, 0xffffff, 0.15);
|
||||
// graphics.beginPath();
|
||||
// graphics.moveTo(xs, midY);
|
||||
// graphics.lineTo(midX, topY);
|
||||
// graphics.lineTo(xe, midY);
|
||||
// graphics.strokePath();
|
||||
|
||||
// 3. DETAJLI
|
||||
// 3. DETAJLI - ENHANCED!
|
||||
if (type.name.includes('grass')) {
|
||||
graphics.fillStyle(Phaser.Display.Color.IntegerToColor(type.color).lighten(10).color);
|
||||
// Darker grass spots (rich texture)
|
||||
graphics.fillStyle(Phaser.Display.Color.IntegerToColor(type.color).darken(15).color, 0.4);
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const rx = xs + 8 + Math.random() * 32;
|
||||
const ry = topY + 3 + Math.random() * 18;
|
||||
const size = 1 + Math.random() * 2;
|
||||
graphics.fillCircle(rx, ry, size);
|
||||
}
|
||||
|
||||
// Lighter grass highlights (freshness)
|
||||
graphics.fillStyle(Phaser.Display.Color.IntegerToColor(type.color).lighten(20).color, 0.5);
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const rx = xs + 10 + Math.random() * 28;
|
||||
const ry = topY + 4 + Math.random() * 16;
|
||||
graphics.fillCircle(rx, ry, 1);
|
||||
}
|
||||
|
||||
// Medium grass blades
|
||||
graphics.fillStyle(Phaser.Display.Color.IntegerToColor(type.color).lighten(10).color, 0.6);
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const rx = xs + 12 + Math.random() * 24;
|
||||
const ry = topY + 5 + Math.random() * 14;
|
||||
graphics.fillRect(rx, ry, 1, 2);
|
||||
}
|
||||
}
|
||||
|
||||
// DIRT texture - Enhanced!
|
||||
if (type.name.includes('dirt') || type.name === 'DIRT') {
|
||||
// Darker dirt clumps
|
||||
graphics.fillStyle(Phaser.Display.Color.IntegerToColor(type.color).darken(20).color, 0.5);
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const rx = xs + 8 + Math.random() * 32;
|
||||
const ry = topY + 3 + Math.random() * 18;
|
||||
const size = 2 + Math.random() * 3;
|
||||
graphics.fillCircle(rx, ry, size);
|
||||
}
|
||||
|
||||
// Lighter dirt spots
|
||||
graphics.fillStyle(Phaser.Display.Color.IntegerToColor(type.color).lighten(15).color, 0.4);
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const rx = xs + 10 + Math.random() * 28;
|
||||
const ry = topY + 4 + Math.random() * 16;
|
||||
graphics.fillCircle(rx, ry, 1.5);
|
||||
}
|
||||
|
||||
// Small stones in dirt
|
||||
graphics.fillStyle(0x9b8f77, 0.6);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const rx = xs + 12 + Math.random() * 24;
|
||||
const ry = topY + 5 + Math.random() * 14;
|
||||
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 = xs + 8 + Math.random() * 30;
|
||||
// Dark stone spots
|
||||
graphics.fillStyle(0x404040, 0.6);
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const rx = xs + 6 + Math.random() * 36;
|
||||
const ry = topY + 3 + Math.random() * 18;
|
||||
const size = 2 + Math.random() * 4;
|
||||
graphics.fillCircle(rx, ry, size);
|
||||
}
|
||||
|
||||
// Medium gray spots
|
||||
graphics.fillStyle(0x606060, 0.5);
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const rx = xs + 8 + Math.random() * 32;
|
||||
const ry = topY + 4 + Math.random() * 16;
|
||||
graphics.fillRect(rx, ry, 3, 3);
|
||||
const size = 1.5 + Math.random() * 3;
|
||||
graphics.fillCircle(rx, ry, size);
|
||||
}
|
||||
|
||||
// Lighter highlights
|
||||
graphics.fillStyle(0xa0a0a0, 0.4);
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const rx = xs + 10 + Math.random() * 28;
|
||||
const ry = topY + 5 + Math.random() * 14;
|
||||
graphics.fillCircle(rx, ry, 1);
|
||||
}
|
||||
|
||||
// Crack lines
|
||||
graphics.lineStyle(1, 0x303030, 0.3);
|
||||
for (let i = 0; i < 3; i++) {
|
||||
graphics.beginPath();
|
||||
graphics.moveTo(xs + Math.random() * 48, topY + Math.random() * 20);
|
||||
graphics.lineTo(xs + Math.random() * 48, topY + Math.random() * 20);
|
||||
graphics.strokePath();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,90 +420,43 @@ class TerrainSystem {
|
||||
createWaterFrames() {
|
||||
const tileWidth = 48;
|
||||
const tileHeight = 48;
|
||||
const P = 2;
|
||||
|
||||
// Generiraj 4 frame-e za water animacijo
|
||||
// 🌊 SMOOTH ANIMATED POND WATER (Stardew Valley style)
|
||||
for (let frame = 0; frame < 4; frame++) {
|
||||
const graphics = this.scene.make.graphics({ x: 0, y: 0, add: false });
|
||||
|
||||
const xs = P;
|
||||
const xe = 48 + P;
|
||||
const midX = 24 + P;
|
||||
const topY = P;
|
||||
const midY = 12 + P;
|
||||
const bottomY = 24 + P;
|
||||
const depth = 14;
|
||||
// 1. BASE WATER - Rich gradient
|
||||
graphics.fillGradientStyle(
|
||||
0x1a5f7a, 0x1a5f7a, // Deep blue
|
||||
0x2a7fbc, 0x2a7fbc // Medium blue
|
||||
);
|
||||
graphics.fillRect(0, 0, tileWidth, tileHeight);
|
||||
|
||||
// 1. STRANICE (DARK BLUE - kot na sliki!)
|
||||
// Left Face - temno modra
|
||||
const cLeft = 0x0066aa;
|
||||
graphics.fillStyle(cLeft);
|
||||
graphics.beginPath();
|
||||
graphics.moveTo(midX, bottomY);
|
||||
graphics.lineTo(midX, bottomY + depth);
|
||||
graphics.lineTo(xs, midY + depth);
|
||||
graphics.lineTo(xs, midY);
|
||||
graphics.closePath();
|
||||
graphics.fill();
|
||||
// 2. ANIMATED WAVE CIRCLES (moving highlights)
|
||||
const waveOffset = frame * 3;
|
||||
|
||||
// Right Face - še temnejša modra
|
||||
const cRight = 0x004488;
|
||||
graphics.fillStyle(cRight);
|
||||
graphics.beginPath();
|
||||
graphics.moveTo(xe, midY);
|
||||
graphics.lineTo(xe, midY + depth);
|
||||
graphics.lineTo(midX, bottomY + depth);
|
||||
graphics.lineTo(midX, bottomY);
|
||||
graphics.closePath();
|
||||
graphics.fill();
|
||||
// Primary wave circles
|
||||
graphics.fillStyle(0x4aa3d0, 0.35);
|
||||
graphics.fillCircle(10 + waveOffset, 10, 12 - frame);
|
||||
graphics.fillCircle(35 - waveOffset, 25, 10 + frame * 0.5);
|
||||
graphics.fillCircle(20, 38 + (frame % 2), 8);
|
||||
|
||||
// 2. TOP SURFACE - SVETLO CYAN (kot na sliki!)
|
||||
const waterColor = 0x33ccff;
|
||||
graphics.fillStyle(waterColor);
|
||||
graphics.beginPath();
|
||||
graphics.moveTo(xs, midY);
|
||||
graphics.lineTo(midX, topY);
|
||||
graphics.lineTo(xe, midY);
|
||||
graphics.lineTo(midX, bottomY);
|
||||
graphics.closePath();
|
||||
graphics.fill();
|
||||
// Secondary shimmer
|
||||
graphics.fillStyle(0x7fc9e8, 0.25);
|
||||
graphics.fillCircle(15 + (frame * 2), 20, 6);
|
||||
graphics.fillCircle(38 - frame, 12, 5);
|
||||
graphics.fillCircle(8, 32 + frame, 4);
|
||||
|
||||
// 3. WAVE PATTERN
|
||||
const offset = frame * 3;
|
||||
graphics.lineStyle(1, 0x66ddff, 0.3);
|
||||
// 3. BRIGHT REFLECTION SPOTS (twinkling)
|
||||
graphics.fillStyle(0xffffff, 0.15 + (frame % 2) * 0.1);
|
||||
graphics.fillCircle(12, 15, 3 + (frame % 2));
|
||||
graphics.fillCircle(32 + (frame % 3), 30, 2);
|
||||
graphics.fillCircle(25, 8, 2);
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
graphics.beginPath();
|
||||
const baseY = topY + 6 + i * 5;
|
||||
for (let px = xs; px <= xe; px += 2) {
|
||||
const relativeX = px - xs;
|
||||
const waveOffset = Math.sin((relativeX + offset + i * 10) * 0.15) * 1.5;
|
||||
const py = baseY + waveOffset;
|
||||
if (px === xs) graphics.moveTo(px, py);
|
||||
else graphics.lineTo(px, py);
|
||||
}
|
||||
graphics.strokePath();
|
||||
}
|
||||
|
||||
// 4. SPARKLE POINTS
|
||||
graphics.fillStyle(0xffffff);
|
||||
const sparkles = [
|
||||
{ x: midX - 10 + (frame * 2) % 20, y: midY + 3 },
|
||||
{ x: midX + 8 - (frame * 3) % 16, y: midY + 8 },
|
||||
{ x: midX - 4 + Math.floor(frame * 1.5) % 8, y: midY + 13 }
|
||||
];
|
||||
sparkles.forEach(s => {
|
||||
graphics.fillRect(s.x, s.y, 1, 1);
|
||||
graphics.fillRect(s.x - 2, s.y, 1, 1);
|
||||
graphics.fillRect(s.x + 2, s.y, 1, 1);
|
||||
graphics.fillRect(s.x, s.y - 2, 1, 1);
|
||||
graphics.fillRect(s.x, s.y + 2, 1, 1);
|
||||
});
|
||||
|
||||
graphics.generateTexture(`water_frame_${frame}`, tileWidth + P * 2, tileHeight + P * 2);
|
||||
graphics.generateTexture(`water_frame_${frame}`, tileWidth, tileHeight);
|
||||
graphics.destroy();
|
||||
}
|
||||
console.log('🌊 Water frames created!');
|
||||
console.log('🌊 Smooth animated pond water created!');
|
||||
}
|
||||
|
||||
generate() {
|
||||
@@ -468,6 +537,12 @@ class TerrainSystem {
|
||||
terrainType = this.terrainTypes.WATER; // Voda!
|
||||
}
|
||||
|
||||
// 🏔️ HEIGHT GENERATION (2.5D Elevation)
|
||||
// Using second Perlin noise layer for smooth hills
|
||||
const heightNoise = this.noise.noise(x * 0.05, y * 0.05); // Low frequency = smooth hills
|
||||
const rawHeight = (heightNoise + 1) * 2.5; // Convert -1..1 to 0..5 range
|
||||
const elevationHeight = Math.floor(rawHeight); // Discrete levels (0-5)
|
||||
|
||||
// Create Tile Data
|
||||
this.tiles[y][x] = {
|
||||
type: terrainType.name,
|
||||
@@ -475,7 +550,8 @@ class TerrainSystem {
|
||||
hasDecoration: false,
|
||||
hasCrop: false,
|
||||
solid: terrainType.solid || false,
|
||||
isHouse: isHouse
|
||||
isHouse: isHouse,
|
||||
height: elevationHeight // 🏔️ NEW: Elevation data (0-5)
|
||||
};
|
||||
|
||||
// Track valid positions for decorations (TREES!)
|
||||
@@ -736,7 +812,7 @@ class TerrainSystem {
|
||||
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');
|
||||
// TEMP DISABLED: if (Math.random() > 0.6) this.addDecoration(gridX + x, gridY + y, 'fence');
|
||||
this.setTile(gridX + x, gridY + y, 'stone');
|
||||
}
|
||||
}
|
||||
@@ -751,7 +827,7 @@ class TerrainSystem {
|
||||
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');
|
||||
// TEMP DISABLED: this.addDecoration(tx, ty, 'fence');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -770,7 +846,7 @@ class TerrainSystem {
|
||||
const isCenter = (x === 2 || y === 2);
|
||||
if (isCenter && Math.random() > 0.5) continue;
|
||||
if (Math.random() > 0.3) {
|
||||
this.addDecoration(tx, ty, 'fence');
|
||||
// TEMP DISABLED: this.addDecoration(tx, ty, 'fence');
|
||||
} else {
|
||||
// User rocks in ruins
|
||||
if (Math.random() > 0.5) {
|
||||
@@ -970,25 +1046,48 @@ class TerrainSystem {
|
||||
if (!this.visibleTiles.has(key)) {
|
||||
const sprite = this.tilePool.get();
|
||||
|
||||
// Use water texture with animation support
|
||||
// Use ANIMATED water frames (not static bubble texture!)
|
||||
if (tile.type === 'water') {
|
||||
sprite.setTexture('water');
|
||||
sprite.setTexture('water_frame_0'); // Start with frame 0
|
||||
sprite.isWater = true; // Mark for animation
|
||||
|
||||
// ANIMACIJA: Dodaj alpha tween za valovanje
|
||||
this.scene.tweens.add({
|
||||
targets: sprite,
|
||||
alpha: 0.7,
|
||||
duration: 1000,
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
ease: 'Sine.easeInOut'
|
||||
});
|
||||
// NO alpha tween - animation handles it
|
||||
} else {
|
||||
sprite.setTexture(tile.type);
|
||||
}
|
||||
|
||||
const screenPos = this.iso.toScreen(x, y);
|
||||
sprite.setPosition(Math.round(screenPos.x + this.offsetX), Math.round(screenPos.y + this.offsetY));
|
||||
|
||||
// 🌊 SKIP HEIGHT EFFECTS FOR WATER (prevents grid lines!)
|
||||
if (tile.type === 'water') {
|
||||
sprite.setPosition(
|
||||
Math.round(screenPos.x + this.offsetX),
|
||||
Math.round(screenPos.y + this.offsetY)
|
||||
);
|
||||
sprite.setScale(1.0);
|
||||
sprite.clearTint();
|
||||
} else {
|
||||
// 🏔️ HEIGHT VISUALIZATION (2.5D Effect) - EXTREME!
|
||||
const height = tile.height || 0;
|
||||
|
||||
// 1. Tint Effect (EXTREME CONTRAST - black valleys, white peaks)
|
||||
// Height 0 = 0x666666 (dark gray), Height 5 = 0xffffff (pure white)
|
||||
const tintValue = 0x666666 + (height * 0x333333);
|
||||
sprite.setTint(tintValue);
|
||||
|
||||
// 2. Scale Variation (MASSIVE - 50% size increase!)
|
||||
const scaleBonus = 1.0 + (height * 0.1); // Max +50% at height 5
|
||||
sprite.setScale(scaleBonus);
|
||||
|
||||
// 3. Y-Offset (HUGE elevation - mountains!)
|
||||
const elevationOffset = -(height * 15); // Each height level = 15px up!
|
||||
|
||||
sprite.setPosition(
|
||||
Math.round(screenPos.x + this.offsetX),
|
||||
Math.round(screenPos.y + this.offsetY + elevationOffset)
|
||||
);
|
||||
}
|
||||
|
||||
sprite.setDepth(this.iso.getDepth(x, y, this.iso.LAYER_FLOOR)); // Tiles = Floor
|
||||
this.visibleTiles.set(key, sprite);
|
||||
}
|
||||
@@ -1019,6 +1118,38 @@ class TerrainSystem {
|
||||
|
||||
// Layer Objects
|
||||
sprite.setDepth(this.iso.getDepth(x, y, this.iso.LAYER_OBJECTS));
|
||||
|
||||
// 🎯 HYBRID POINTER EVENTS - Click-to-collect system
|
||||
// Only for collectible resources (trees, rocks, etc.)
|
||||
const isCollectible = decor.type.includes('tree') ||
|
||||
decor.type.includes('rock') ||
|
||||
decor.type.includes('bush') ||
|
||||
decor.type.includes('flower');
|
||||
|
||||
if (isCollectible) {
|
||||
// Make interactive with hand cursor
|
||||
sprite.setInteractive({ useHandCursor: true });
|
||||
|
||||
// Store grid position for later use
|
||||
sprite.setData('gridX', x);
|
||||
sprite.setData('gridY', y);
|
||||
sprite.setData('decorType', decor.type);
|
||||
|
||||
// HOVER EVENT - Yellow highlight
|
||||
sprite.on('pointerover', () => {
|
||||
sprite.setTint(0xffff00); // Yellow highlight
|
||||
});
|
||||
|
||||
sprite.on('pointerout', () => {
|
||||
sprite.clearTint(); // Remove highlight
|
||||
});
|
||||
|
||||
// CLICK EVENT - Collect resource (with proximity check)
|
||||
sprite.on('pointerdown', () => {
|
||||
this.handleResourceClick(x, y, decor.type, sprite);
|
||||
});
|
||||
}
|
||||
|
||||
this.visibleDecorations.set(key, sprite);
|
||||
}
|
||||
}
|
||||
@@ -1166,11 +1297,136 @@ class TerrainSystem {
|
||||
}
|
||||
}
|
||||
|
||||
isSolid(x, y) {
|
||||
if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
|
||||
return this.tiles[y][x].solid || false;
|
||||
// 🏔️ HEIGHT-AWARE COLLISION
|
||||
// Returns true if tile is unwalkable (solid OR cliff)
|
||||
isSolid(x, y, fromX = null, fromY = null) {
|
||||
// Out of bounds = solid
|
||||
if (x < 0 || x >= this.width || y < 0 || y >= this.height) {
|
||||
return true;
|
||||
}
|
||||
return true; // Out of bounds = solid
|
||||
|
||||
const tile = this.tiles[y][x];
|
||||
|
||||
// 1. Check if tile itself is solid (walls, etc.)
|
||||
if (tile.solid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. Check height difference (cliff detection)
|
||||
if (fromX !== null && fromY !== null) {
|
||||
const fromTile = this.getTile(fromX, fromY);
|
||||
if (fromTile) {
|
||||
const fromHeight = fromTile.height || 0;
|
||||
const toHeight = tile.height || 0;
|
||||
const heightDiff = Math.abs(toHeight - fromHeight);
|
||||
|
||||
// Can't walk over height difference > 1 (cliffs!)
|
||||
if (heightDiff > 1) {
|
||||
console.log(`🏔️ Blocked by cliff! Height diff: ${heightDiff} (from ${fromHeight} to ${toHeight})`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false; // Walkable
|
||||
}
|
||||
|
||||
/**
|
||||
* 🎯 HYBRID RESOURCE CLICK HANDLER
|
||||
* Handles click-to-collect with proximity check
|
||||
* @param {number} x - Grid X position
|
||||
* @param {number} y - Grid Y position
|
||||
* @param {string} decorType - Type of decoration (tree, rock, etc.)
|
||||
* @param {Phaser.GameObjects.Sprite} sprite - The clicked sprite
|
||||
*/
|
||||
handleResourceClick(x, y, decorType, sprite) {
|
||||
// 1. Get player position
|
||||
if (!this.scene.player) {
|
||||
console.warn('⚠️ Player not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const playerPos = this.scene.player.getPosition();
|
||||
const playerX = playerPos.x;
|
||||
const playerY = playerPos.y;
|
||||
|
||||
// 2. PROXIMITY CHECK - Player must be within 3 tiles
|
||||
const distance = Phaser.Math.Distance.Between(playerX, playerY, x, y);
|
||||
const MAX_DISTANCE = 3; // 3 tiles
|
||||
|
||||
if (distance > MAX_DISTANCE) {
|
||||
// Too far - show warning
|
||||
console.log(`⚠️ Too far! Distance: ${distance.toFixed(1)} tiles`);
|
||||
|
||||
// Visual feedback - shake sprite
|
||||
this.scene.tweens.add({
|
||||
targets: sprite,
|
||||
x: sprite.x + 5,
|
||||
duration: 50,
|
||||
yoyo: true,
|
||||
repeat: 2
|
||||
});
|
||||
|
||||
// Floating text
|
||||
if (this.scene.events) {
|
||||
this.scene.events.emit('show-floating-text', {
|
||||
x: sprite.x,
|
||||
y: sprite.y - 50,
|
||||
text: 'Preblizu!',
|
||||
color: '#ff4444'
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. TOOL CHECK - Player needs correct tool
|
||||
const requiredTool = this.getRequiredTool(decorType);
|
||||
const hasTool = this.scene.player.hasToolEquipped(requiredTool);
|
||||
|
||||
if (!hasTool && requiredTool) {
|
||||
console.log(`⚠️ Need tool: ${requiredTool}`);
|
||||
|
||||
// Floating text
|
||||
if (this.scene.events) {
|
||||
this.scene.events.emit('show-floating-text', {
|
||||
x: sprite.x,
|
||||
y: sprite.y - 50,
|
||||
text: `Potrebuješ: ${requiredTool}`,
|
||||
color: '#ff4444'
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. COLLECT - Damage decoration (uses existing HP system)
|
||||
console.log(`✅ Collecting ${decorType} at (${x}, ${y})`);
|
||||
|
||||
// Use existing damage system (maintains HP logic)
|
||||
const result = this.damageDecoration(x, y, 1); // 1 hit per click
|
||||
|
||||
// Optional: Instant collect mode (if you want 1-click collect)
|
||||
// this.damageDecoration(x, y, 999);
|
||||
|
||||
// Sound effect
|
||||
if (this.scene.soundManager) {
|
||||
if (decorType.includes('tree')) {
|
||||
this.scene.soundManager.playChopSound();
|
||||
} else if (decorType.includes('rock')) {
|
||||
this.scene.soundManager.playMineSound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get required tool for decoration type
|
||||
*/
|
||||
getRequiredTool(decorType) {
|
||||
if (decorType.includes('tree')) return 'axe';
|
||||
if (decorType.includes('rock')) return 'pickaxe';
|
||||
if (decorType.includes('bush')) return 'axe';
|
||||
return null; // No tool required (flowers, etc.)
|
||||
}
|
||||
|
||||
// Water Animation Update - DISABLED (using tweens now)
|
||||
|
||||
Reference in New Issue
Block a user