FAZA 3: Add 3 NPCs with random walk AI (zombie, villager, merchant)
This commit is contained in:
@@ -82,7 +82,7 @@
|
||||
---
|
||||
|
||||
## FAZA 3: NPC-ji in Dekoracije
|
||||
**Status:** ⏸️ Čaka
|
||||
**Status:** ⏳ V TEKU
|
||||
|
||||
### Opravila:
|
||||
- [ ] Dodaj NPC-je (3 na velikost 100x100)
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
|
||||
<!-- Entities -->
|
||||
<script src="src/entities/Player.js"></script>
|
||||
<script src="src/entities/NPC.js"></script>
|
||||
|
||||
<!-- Game Files -->
|
||||
<script src="src/scenes/BootScene.js"></script>
|
||||
|
||||
147
src/entities/NPC.js
Normal file
147
src/entities/NPC.js
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user