diff --git a/dev_plan.md b/dev_plan.md index 49fce8d..4d24b22 100644 --- a/dev_plan.md +++ b/dev_plan.md @@ -82,7 +82,7 @@ --- ## FAZA 3: NPC-ji in Dekoracije -**Status:** ⏸️ Čaka +**Status:** ⏳ V TEKU ### Opravila: - [ ] Dodaj NPC-je (3 na velikost 100x100) diff --git a/index.html b/index.html index 33d88e1..7201325 100644 --- a/index.html +++ b/index.html @@ -44,6 +44,7 @@ + diff --git a/src/entities/NPC.js b/src/entities/NPC.js new file mode 100644 index 0000000..aaa4280 --- /dev/null +++ b/src/entities/NPC.js @@ -0,0 +1,147 @@ +// NPC Entity +// NPC z random walk AI in isometrično podporo +class NPC { + constructor(scene, gridX, gridY, offsetX = 0, offsetY = 0, type = 'zombie') { + this.scene = scene; + this.gridX = gridX; + this.gridY = gridY; + this.type = type; + + // Terrain offset + this.offsetX = offsetX; + this.offsetY = offsetY; + + this.iso = new IsometricUtils(48, 24); + + // Random walk paramters + this.moveSpeed = 100; // px/s (počasnejše od igralca) + this.gridMoveTime = 300; // ms za premik (počasneje) + + // Stanje + this.isMoving = false; + this.pauseTime = 0; + this.maxPauseTime = 2000; // Pavza med premiki (2s) + + // Kreira sprite + this.createSprite(); + + // Začetna pozicija + this.updatePosition(); + + // Naključna začetna pavza + this.pauseTime = Math.random() * this.maxPauseTime; + } + + createSprite() { + // Generiraj NPC teksturo glede na tip + const texKey = `npc_${this.type}`; + + if (!this.scene.textures.exists(texKey)) { + TextureGenerator.createNPCSprite(this.scene, texKey, this.type); + } + + // Kreira sprite + const screenPos = this.iso.toScreen(this.gridX, this.gridY); + this.sprite = this.scene.add.sprite( + screenPos.x + this.offsetX, + screenPos.y + this.offsetY, + texKey + ); + this.sprite.setOrigin(0.5, 1); // Anchor na dnu sprite-a + + // Depth sorting + this.updateDepth(); + } + + update(delta) { + if (this.isMoving) { + return; // Že se premika + } + + // Random walk - pavza med premiki + this.pauseTime += delta; + + if (this.pauseTime >= this.maxPauseTime) { + this.performRandomWalk(); + this.pauseTime = 0; + } + } + + performRandomWalk() { + // Naključna smer (NSEW + možnost obstati) + const directions = [ + { x: -1, y: 0 }, // North-West + { x: 1, y: 0 }, // South-East + { x: 0, y: -1 }, // South-West + { x: 0, y: 1 }, // North-East + { x: 0, y: 0 } // Stay (30% možnost) + ]; + + const dir = Phaser.Math.RND.pick(directions); + const targetX = this.gridX + dir.x; + const targetY = this.gridY + dir.y; + + // Preveri kolizijo z robovi + const terrainSystem = this.scene.terrainSystem; + if (terrainSystem && this.iso.isInBounds(targetX, targetY, terrainSystem.width, terrainSystem.height)) { + // Preveri da ni ista pozicija kot igralec + if (this.scene.player) { + const playerPos = this.scene.player.getPosition(); + if (targetX === playerPos.x && targetY === playerPos.y) { + return; // Ne premakni se na igralca + } + } + + if (dir.x !== 0 || dir.y !== 0) { + this.moveToGrid(targetX, targetY); + } + } + } + + moveToGrid(targetX, targetY) { + this.isMoving = true; + this.gridX = targetX; + this.gridY = targetY; + + const targetScreen = this.iso.toScreen(targetX, targetY); + + // Tween za smooth gibanje + this.scene.tweens.add({ + targets: this.sprite, + x: targetScreen.x + this.offsetX, + y: targetScreen.y + this.offsetY, + duration: this.gridMoveTime, + ease: 'Linear', + onComplete: () => { + this.isMoving = false; + } + }); + + // Posodobi depth + this.updateDepth(); + } + + updatePosition() { + const screenPos = this.iso.toScreen(this.gridX, this.gridY); + this.sprite.setPosition( + screenPos.x + this.offsetX, + screenPos.y + this.offsetY + ); + this.updateDepth(); + } + + updateDepth() { + const depth = this.iso.getDepth(this.gridX, this.gridY); + this.sprite.setDepth(depth + 1000); // +1000 da je nad terenom + } + + getPosition() { + return { x: this.gridX, y: this.gridY }; + } + + destroy() { + if (this.sprite) { + this.sprite.destroy(); + } + } +} diff --git a/src/scenes/GameScene.js b/src/scenes/GameScene.js index 7763c81..208b314 100644 --- a/src/scenes/GameScene.js +++ b/src/scenes/GameScene.js @@ -5,6 +5,7 @@ class GameScene extends Phaser.Scene { this.terrainSystem = null; this.terrainContainer = null; this.player = null; + this.npcs = []; // Array za NPCje } create() { @@ -31,6 +32,16 @@ class GameScene extends Phaser.Scene { console.log('👤 Initializing player...'); this.player = new Player(this, 50, 50, this.terrainOffsetX, this.terrainOffsetY); + // Dodaj 3 NPCje - random pozicije + console.log('🧟 Initializing NPCs...'); + const npcTypes = ['zombie', 'villager', 'merchant']; + for (let i = 0; i < 3; i++) { + const randomX = Phaser.Math.Between(20, 80); + const randomY = Phaser.Math.Between(20, 80); + const npc = new NPC(this, randomX, randomY, this.terrainOffsetX, this.terrainOffsetY, npcTypes[i]); + this.npcs.push(npc); + } + // Kamera sledi igralcu this.cameras.main.startFollow(this.player.sprite, true, 0.1, 0.1); @@ -60,7 +71,7 @@ class GameScene extends Phaser.Scene { this.fpsText.setScrollFactor(0); this.fpsText.setDepth(1000); - console.log('✅ GameScene ready - FAZA 2!'); + console.log('✅ GameScene ready - FAZA 3!'); } setupCamera() { @@ -91,7 +102,7 @@ class GameScene extends Phaser.Scene { const width = this.cameras.main.width; // Naslov - const title = this.add.text(width / 2, 20, 'FAZA 2: Igralec in Gibanje', { + const title = this.add.text(width / 2, 20, 'FAZA 3: NPC-ji in Dekoracije', { fontFamily: 'Courier New', fontSize: '20px', fill: '#00ff41', @@ -127,6 +138,11 @@ class GameScene extends Phaser.Scene { this.player.update(delta); } + // Update NPCs + for (const npc of this.npcs) { + npc.update(delta); + } + // Update FPS if (this.fpsText) { this.fpsText.setText(`FPS: ${Math.round(this.game.loop.actualFps)}`); @@ -146,13 +162,12 @@ class GameScene extends Phaser.Scene { // Debug info update if (this.debugText && this.player) { const playerPos = this.player.getPosition(); - const screenPos = this.player.getScreenPosition(); this.debugText.setText( - `FAZA 2 - Player Movement\n` + + `FAZA 3 - NPCs & Decorations\n` + `Zoom: ${cam.zoom.toFixed(2)}\n` + - `Player Grid: (${playerPos.x}, ${playerPos.y})\n` + - `Player Screen: (${Math.round(screenPos.x)}, ${Math.round(screenPos.y)})` + `Player: (${playerPos.x}, ${playerPos.y})\n` + + `NPCs: ${this.npcs.length}` ); } } diff --git a/src/utils/TextureGenerator.js b/src/utils/TextureGenerator.js index 3bda746..8eac0de 100644 --- a/src/utils/TextureGenerator.js +++ b/src/utils/TextureGenerator.js @@ -197,4 +197,97 @@ class TextureGenerator { pixel(ox + x, oy + 16, outlineColor); } } + + // Generiraj NPC sprite (32x32px pixel art) + static createNPCSprite(scene, key = 'npc', type = 'zombie') { + const size = 32; + const canvas = scene.textures.createCanvas(key, size, size); + const ctx = canvas.getContext(); + + ctx.clearRect(0, 0, size, size); + + const pixel = (x, y, color) => { + ctx.fillStyle = color; + ctx.fillRect(x, y, 1, 1); + }; + + const ox = 12; + const oy = 4; + const outlineColor = '#000000'; + + // Različne barve glede na tip + let skinColor, shirtColor, pantsColor; + + switch (type) { + case 'zombie': + skinColor = '#9ACD32'; // Zelena koža + shirtColor = '#8B4513'; // Rjava raztrgana srajca + pantsColor = '#4A4A4A'; // Temno siva + break; + case 'villager': + skinColor = '#FFDBAC'; + shirtColor = '#4169E1'; // Modra srajca + pantsColor = '#654321'; // Rjave hlače + break; + case 'merchant': + skinColor = '#FFDBAC'; + shirtColor = '#DAA520'; // Zlata/rumena srajca + pantsColor = '#2F4F4F'; // Temno zelene hlače + break; + default: + skinColor = '#FFDBAC'; + shirtColor = '#888888'; + pantsColor = '#666666'; + } + + // Glava (brez klobuka za NPCje) + for (let y = 2; y < 6; y++) { + pixel(ox + 0, oy + y, outlineColor); + for (let x = 1; x < 7; x++) { + pixel(ox + x, oy + y, skinColor); + } + pixel(ox + 7, oy + y, outlineColor); + } + + // Oči + pixel(ox + 2, oy + 4, outlineColor); + pixel(ox + 5, oy + 4, outlineColor); + + // Telo - srajca + for (let y = 6; y < 11; y++) { + pixel(ox + 0, oy + y, outlineColor); + for (let x = 1; x < 7; x++) { + pixel(ox + x, oy + y, shirtColor); + } + pixel(ox + 7, oy + y, outlineColor); + } + + // Roke + for (let y = 7; y < 10; y++) { + pixel(ox - 1, oy + y, skinColor); + pixel(ox - 2, oy + y, outlineColor); + pixel(ox + 8, oy + y, skinColor); + pixel(ox + 9, oy + y, outlineColor); + } + + // Noge - hlače + for (let y = 11; y < 16; y++) { + pixel(ox + 0, oy + y, outlineColor); + pixel(ox + 1, oy + y, pantsColor); + pixel(ox + 2, oy + y, pantsColor); + pixel(ox + 3, oy + y, outlineColor); + pixel(ox + 4, oy + y, outlineColor); + pixel(ox + 5, oy + y, pantsColor); + pixel(ox + 6, oy + y, pantsColor); + pixel(ox + 7, oy + y, outlineColor); + } + + // Dno nog + for (let x = 0; x < 8; x++) { + pixel(ox + x, oy + 16, outlineColor); + } + + canvas.refresh(); + return canvas; + } }