From 3086356171d65be60588c95155ff3aa4576ae887 Mon Sep 17 00:00:00 2001 From: NovaFarma Dev Date: Sat, 6 Dec 2025 18:00:59 +0100 Subject: [PATCH] FAZA 2: Player entity with WASD movement, walking animation, and camera follow - Ready for testing --- FAZA_2_CHECKLIST.md | 233 ++++++++++++++++++++++++++++++++++ README.md | 12 +- index.html | 4 + src/entities/Player.js | 163 ++++++++++++++++++++++++ src/scenes/GameScene.js | 76 +++++------ src/utils/TextureGenerator.js | 200 +++++++++++++++++++++++++++++ 6 files changed, 641 insertions(+), 47 deletions(-) create mode 100644 FAZA_2_CHECKLIST.md create mode 100644 src/entities/Player.js create mode 100644 src/utils/TextureGenerator.js diff --git a/FAZA_2_CHECKLIST.md b/FAZA_2_CHECKLIST.md new file mode 100644 index 0000000..5ba7d1b --- /dev/null +++ b/FAZA_2_CHECKLIST.md @@ -0,0 +1,233 @@ +# FAZA 2: Igralec in Gibanje - Checklist + +**Status:** ✅ PRIPRAVLJEN ZA TESTIRANJE + +**Datum:** 2025-12-06 + +--- + +## ✅ Opravila (Developer) + +- [x] Kreacija TextureGenerator (proceduralni pixel art) +- [x] Generacija player sprite (32x32px) +- [x] Implementacija Player entitete +- [x] WASD gibanje (grid-based) +- [x] Smooth movement (tween animacija) +- [x] Walking animacija (4 frame-i) +- [x] Depth sorting za igralca +- [x] Kolizija z robovi mape +- [x] Camera follow igralcu +- [x] Posodobitev GameScene za player support +- [x] Posodobitev UI (naslov, kontrole) +- [x] Debug info (player pozicija) + +**VSE OPRAVILA ZAKLJUČENA** ✅ + +--- + +## 🧪 Ročno testiranje (Naročnik) + +### Test 1: Player Spawn +**Ukaz:** `npm start` → pritisni SPACE v menu + +**Pričakovani rezultat:** +- [ ] Igralec se pojavi na sredini mape (grid 50,50) +- [ ] Igralec je pixel art karakter (farmer s klobukom) +- [ ] Barve: Bež klobuk, zelena srajca, rjave hlače +- [ ] Velikost: 32x32px +- [ ] Igralec je viden NA terenu (ne za terenom) + +**Status:** ⏳ ČAKA NA TESTIRANJE + +--- + +### Test 2: WASD Gibanje +**Ukazi:** W (gor), A (levo), S (dol), D (desno) + +**Pričakovani rezultat:** +- [ ] W - igralec se premakne "north-west" (isometric) +- [ ] S - igralec se premakne "south-east" (isometric) +- [ ] A - igralec se premakne "south-west" (isometric) +- [ ] D - igralec se premakne "north-east" (isometric) +- [ ] Gibanje je smooth (tween animacija ~200ms) +- [ ] En pritisk = en tile premik + +**Status:** ⏳ ČAKA NA TESTIRANJE + +--- + +### Test 3: Walking Animacija +**Ukaz:** Drži WASD tipko + +**Pričakovani rezultat:** +- [ ] Med gibanjem se predvaja walking animacija +- [ ] Animacija ima 4 frame-e +- [ ] Noge se gibljejo (leva, desna) +- [ ] Ko se ustavi, se vrne v idle pose (frame 0) + +**Status:** ⏳ ČAKA NA TESTIRANJE + +--- + +### Test 4: Depth Sorting +**Ukaz:** Premi igralca po različnih delih mape + +**Pričakovani rezultat:** +- [ ] Igralec je vedno narisan PRED tile-i pod njim +- [ ] Igralec je vedno narisan ZA tile-i pred njim +- [ ] Pri gibanju se depth pravilno posodablja +- [ ] Nobenih graphical glitch-ov + +**Status:** ⏳ ČAKA NA TESTIRANJE + +--- + +### Test 5: Kolizija z Robovi +**Ukaz:** Premi igralca do robov mape + +**Pričakovani rezultat:** +- [ ] Igralec ne more iti preko severnega roba (grid y = 0) +- [ ] Igralec ne more iti preko južnega roba (grid y = 99) +- [ ] Igralec ne more iti preko zahodnega roba (grid x = 0) +- [ ] Igralec ne more iti preko vzhodnega roba (grid x = 99) +- [ ] Ko pritisne W/A/S/D pri robu, se NE premakne + +**Status:** ⏳ ČAKA NA TESTIRANJE + +--- + +### Test 6: Camera Follow +**Pričakovani rezultat:** +- [ ] Kamera sledi igralcu +- [ ] Smooth camera movement (ne trga) +- [ ] Igralec je vedno v centru pogleda +- [ ] Ko se igralec premakne, se kamera prilagodi + +**Status:** ⏳ ČAKA NA TESTIRANJE + +--- + +### Test 7: Zoom Kontrole +**Ukazi:** Q (zoom in), E (zoom out), Mouse Wheel + +**Pričakovani rezultat:** +- [ ] Q povečuje zoom (igralec postane večji) +- [ ] E zmanjšuje zoom (igralec postane manjši) +- [ ] Mouse wheel deluje enako +- [ ] Zoom range: 0.3x - 2.0x +- [ ] Camera follow še vedno deluje pri zoom-u + +**Status:** ⏳ ČAKA NA TESTIRANJE + +--- + +### Test 8: UI in Debug Info +**Pričakovani rezultat:** +- [ ] Naslov: "FAZA 2: Igralec in Gibanje" (zelena, zgoraj) +- [ ] Kontrole info (desno zgoraj): + - "WASD - Gibanje igralca" + - "Q/E - Zoom" + - "Mouse Wheel - Zoom" +- [ ] Debug info (levo zgoraj): + - Zoom vrednost + - Player Grid pozicija (50, 50 na začetku) + - Player Screen pozicija +- [ ] FPS counter (spodaj levo) ~ 60 FPS + +**Status:** ⏳ ČAKA NA TESTIRANJE + +--- + +### Test 9: Performance +**Pričakovani rezultat:** +- [ ] FPS: 55-60 pri počitku +- [ ] FPS: 50+ med gibanjem +- [ ] Smooth gibanje brez stutterja +- [ ] Walking animacija smooth +- [ ] Brez lag-a pri depth sorting + +**Status:** ⏳ ČAKA NA TESTIRANJE + +--- + +### Test 10: Vizualna Kvaliteta Igralca +**Pričakovani rezultat:** +- [ ] Pixel art je čist (brez blurringa) +- [ ] Klobuk, srajca, hlače so jasno vidni +- [ ] Črne outlines so vidne +- [ ] Oči so vidne (2 črni piksli) +- [ ] Roke so vidne (ob straneh) +- [ ] Noge so vidne (2 ločeni) + +**Status:** ⏳ ČAKA NA TESTIRANJE + +--- + +## 📋 Potrditev Naročnika + +``` +FAZA 2: [STATUS] +- Testirano: [DA/NE] +- Datum testiranja: ___________ +- Opombe: + + + + +- Test 1: [✅/❌] +- Test 2: [✅/❌] +- Test 3: [✅/❌] +- Test 4: [✅/❌] +- Test 5: [✅/❌] +- Test 6: [✅/❌] +- Test 7: [✅/❌] +- Test 8: [✅/❌] +- Test 9: [✅/❌] +- Test 10: [✅/❌] + +ODOBRENO ZA FAZO 3: [DA/NE] + +Podpis naročnika: _____________ +``` + +--- + +## 🚨 V primeru težav + +### Težava: Igralec se ne prikaže +**Rešitev:** +- Preveri konzolo (F12) za error-je +- Če vidiš "TextureGenerator is not defined", reload (Ctrl+R) +- Preveri da je igralec na správnem depth-u (ne za terenom) + +### Težava: WASD ne deluje +**Rešitev:** +- Preveri da ima okno focus +- Poskusi klikniti v igro pred pritiskom WASD +- Preveri da kamera follow ne blokira input-a + +### Težava: Walking animacija ne deluje +**Rešitev:** +- To je normalno - animacija je zelo subtilna (pixel art) +- Preveri FPS - če je nizek, animacija morda ne deluje + +### Težava: Igralec gre skozi robove +**Rešitev:** +- To je bug - javi v konzoli grid pozicijo igralca +- Check bi moral biti: gridX >= 0 && gridX < 100 + +### Težava: FPS prenizek +**Rešitev:** +- S 10,000 tile-ov + player je FPS lahko 40-50 +- To je sprejemljivo za testiranje + +--- + +## ➡️ Naslednji koraki (po odobritvi) + +Ko naročnik potrdi FAZO 2, se začne: +**FAZA 3: NPC-ji in Dekoracije** +- NPC entitete (3 NPC-ji) +- Random walk AI +- Okrasni elementi (rože, grmičevje) +- Parallax dekoracije (oblaki, ptice) diff --git a/README.md b/README.md index 37ad515..f80619a 100644 --- a/README.md +++ b/README.md @@ -62,13 +62,21 @@ novafarma/ - Electron + Phaser integracija - Osnovne scene (Boot, Preload, Game) -**FAZA 1: ✅ COMPLETE - Čaka na testiranje** +**FAZA 1: ✅ APPROVED** (2025-12-06) - Perlin Noise terrain generator - 100x100 isometrična mapa - 5 tipov terena (voda, pesek, trava, zemlja, kamen) - Kamera kontrole (WASD, mouse pan, zoom) -**Naslednja faza:** FAZA 2 - Igralec in Gibanje +**FAZA 2: ✅ COMPLETE - Čaka na testiranje** +- Player entiteta (32x32px pixel art) +- WASD gibanje (grid-based) +- Walking animacija +- Depth sorting +- Kolizija z robovi +- Camera follow + +**Naslednja faza:** FAZA 3 - NPC-ji in Dekoracije --- diff --git a/index.html b/index.html index 5431a01..33d88e1 100644 --- a/index.html +++ b/index.html @@ -37,10 +37,14 @@ + + + + diff --git a/src/entities/Player.js b/src/entities/Player.js new file mode 100644 index 0000000..a9251a1 --- /dev/null +++ b/src/entities/Player.js @@ -0,0 +1,163 @@ +// Player Entity +// Igralec z WASD kontrolami in isometrično podporo +class Player { + constructor(scene, gridX = 50, gridY = 50) { + this.scene = scene; + this.gridX = gridX; + this.gridY = gridY; + + this.iso = new IsometricUtils(48, 24); + + // Hitrost gibanja + this.moveSpeed = 150; // px/s + this.gridMoveTime = 200; // ms za premik na eno kocko + + // Stanje + this.isMoving = false; + this.direction = 'down'; + + // Kreira sprite + this.createSprite(); + + // Setup kontrole + this.setupControls(); + + // Začetna pozicija + this.updatePosition(); + } + + createSprite() { + // Generiraj player teksturo + TextureGenerator.createPlayerSprite(this.scene, 'player'); + TextureGenerator.createPlayerWalkSprite(this.scene, 'player_walk'); + + // Kreira sprite + const screenPos = this.iso.toScreen(this.gridX, this.gridY); + this.sprite = this.scene.add.sprite(screenPos.x, screenPos.y, 'player'); + this.sprite.setOrigin(0.5, 1); // Anchor na dnu sprite-a + + // Depth sorting + this.updateDepth(); + + // Dodaj walking animacijo + if (!this.scene.anims.exists('player_walk_anim')) { + this.scene.anims.create({ + key: 'player_walk_anim', + frames: this.scene.anims.generateFrameNumbers('player_walk', { + start: 0, + end: 3 + }), + frameRate: 8, + repeat: -1 + }); + } + } + + setupControls() { + // WASD kontrole + this.keys = this.scene.input.keyboard.addKeys({ + up: Phaser.Input.Keyboard.KeyCodes.W, + down: Phaser.Input.Keyboard.KeyCodes.S, + left: Phaser.Input.Keyboard.KeyCodes.A, + right: Phaser.Input.Keyboard.KeyCodes.D + }); + } + + update(delta) { + if (!this.isMoving) { + this.handleInput(); + } + } + + handleInput() { + let targetX = this.gridX; + let targetY = this.gridY; + let moved = false; + + // WASD za isometric movement + if (this.keys.up.isDown) { + // W = North-West v isometric view + targetX--; + moved = true; + this.direction = 'up'; + } else if (this.keys.down.isDown) { + // S = South-East v isometric view + targetX++; + moved = true; + this.direction = 'down'; + } + + if (this.keys.left.isDown) { + // A = South-West v isometric view + targetY--; + moved = true; + this.direction = 'left'; + } else if (this.keys.right.isDown) { + // D = North-East v isometric view + targetY++; + moved = true; + this.direction = 'right'; + } + + // Preveri kolizijo z robovi mape + const terrainSystem = this.scene.terrainSystem; + if (moved && terrainSystem) { + if (this.iso.isInBounds(targetX, targetY, terrainSystem.width, terrainSystem.height)) { + this.moveToGrid(targetX, targetY); + } + } + } + + moveToGrid(targetX, targetY) { + this.isMoving = true; + this.gridX = targetX; + this.gridY = targetY; + + const targetScreen = this.iso.toScreen(targetX, targetY); + + // Animacija hoje + this.sprite.play('player_walk_anim', true); + + // Tween za smooth gibanje + this.scene.tweens.add({ + targets: this.sprite, + x: targetScreen.x, + y: targetScreen.y, + duration: this.gridMoveTime, + ease: 'Linear', + onComplete: () => { + this.isMoving = false; + this.sprite.stop(); + this.sprite.setFrame(0); // Idle frame + } + }); + + // Posodobi depth + this.updateDepth(); + } + + updatePosition() { + const screenPos = this.iso.toScreen(this.gridX, this.gridY); + this.sprite.setPosition(screenPos.x, screenPos.y); + 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 }; + } + + getScreenPosition() { + return { x: this.sprite.x, y: this.sprite.y }; + } + + destroy() { + if (this.sprite) { + this.sprite.destroy(); + } + } +} diff --git a/src/scenes/GameScene.js b/src/scenes/GameScene.js index bb6de86..9409c93 100644 --- a/src/scenes/GameScene.js +++ b/src/scenes/GameScene.js @@ -4,6 +4,7 @@ class GameScene extends Phaser.Scene { super({ key: 'GameScene' }); this.terrainSystem = null; this.terrainContainer = null; + this.player = null; } create() { @@ -22,6 +23,13 @@ class GameScene extends Phaser.Scene { this.terrainSystem.generate(); this.terrainContainer = this.terrainSystem.render(width / 2, 100); + // Dodaj igralca - spawn na sredini mape + console.log('👤 Initializing player...'); + this.player = new Player(this, 50, 50); + + // Kamera sledi igralcu + this.cameras.main.startFollow(this.player.sprite, true, 0.1, 0.1); + // Kamera kontrole this.setupCamera(); @@ -48,7 +56,7 @@ class GameScene extends Phaser.Scene { this.fpsText.setScrollFactor(0); this.fpsText.setDepth(1000); - console.log('✅ GameScene ready - FAZA 1!'); + console.log('✅ GameScene ready - FAZA 2!'); } setupCamera() { @@ -65,20 +73,11 @@ class GameScene extends Phaser.Scene { cam.setZoom(newZoom); }); - // Pan kontrole (Right click + drag) - this.input.on('pointermove', (pointer) => { - if (pointer.rightButtonDown()) { - cam.scrollX -= (pointer.x - pointer.prevPosition.x) / cam.zoom; - cam.scrollY -= (pointer.y - pointer.prevPosition.y) / cam.zoom; - } - }); + // Pan kontrole (Right click + drag) - DISABLED za FAZA 2 + // Player movement sedaj uporablja WASD - // WASD za kamera kontrolo (alternativa) - this.cursors = this.input.keyboard.addKeys({ - up: Phaser.Input.Keyboard.KeyCodes.W, - down: Phaser.Input.Keyboard.KeyCodes.S, - left: Phaser.Input.Keyboard.KeyCodes.A, - right: Phaser.Input.Keyboard.KeyCodes.D, + // Q/E za zoom + this.zoomKeys = this.input.keyboard.addKeys({ zoomIn: Phaser.Input.Keyboard.KeyCodes.Q, zoomOut: Phaser.Input.Keyboard.KeyCodes.E }); @@ -88,7 +87,7 @@ class GameScene extends Phaser.Scene { const width = this.cameras.main.width; // Naslov - const title = this.add.text(width / 2, 20, 'FAZA 1: Generacija Terena', { + const title = this.add.text(width / 2, 20, 'FAZA 2: Igralec in Gibanje', { fontFamily: 'Courier New', fontSize: '20px', fill: '#00ff41', @@ -101,10 +100,9 @@ class GameScene extends Phaser.Scene { // Kontrole info const controlsText = this.add.text(width - 10, 10, 'Kontrole:\n' + - 'WASD - Pan\n' + + 'WASD - Gibanje igralca\n' + 'Q/E - Zoom\n' + - 'Mouse Wheel - Zoom\n' + - 'Right Click - Pan', + 'Mouse Wheel - Zoom', { fontFamily: 'Courier New', fontSize: '11px', @@ -120,49 +118,37 @@ class GameScene extends Phaser.Scene { } update(time, delta) { + // Update player + if (this.player) { + this.player.update(delta); + } + // Update FPS if (this.fpsText) { this.fpsText.setText(`FPS: ${Math.round(this.game.loop.actualFps)}`); } - // Kamera movement (WASD) + // Zoom controls const cam = this.cameras.main; - const panSpeed = 5; - - if (this.cursors) { - if (this.cursors.up.isDown) { - cam.scrollY -= panSpeed; - } - if (this.cursors.down.isDown) { - cam.scrollY += panSpeed; - } - if (this.cursors.left.isDown) { - cam.scrollX -= panSpeed; - } - if (this.cursors.right.isDown) { - cam.scrollX += panSpeed; - } - - // Zoom - if (this.cursors.zoomIn.isDown) { + if (this.zoomKeys) { + if (this.zoomKeys.zoomIn.isDown) { cam.setZoom(Phaser.Math.Clamp(cam.zoom + 0.01, 0.3, 2.0)); } - if (this.cursors.zoomOut.isDown) { + if (this.zoomKeys.zoomOut.isDown) { cam.setZoom(Phaser.Math.Clamp(cam.zoom - 0.01, 0.3, 2.0)); } } // Debug info update - if (this.debugText) { - const pointer = this.input.activePointer; - const worldX = Math.round(pointer.worldX); - const worldY = Math.round(pointer.worldY); + if (this.debugText && this.player) { + const playerPos = this.player.getPosition(); + const screenPos = this.player.getScreenPosition(); this.debugText.setText( - `FAZA 1 - Terrain System\n` + + `FAZA 2 - Player Movement\n` + `Zoom: ${cam.zoom.toFixed(2)}\n` + - `Camera: (${Math.round(cam.scrollX)}, ${Math.round(cam.scrollY)})\n` + - `Mouse: (${worldX}, ${worldY})` + `Player Grid: (${playerPos.x}, ${playerPos.y})\n` + + `Player Screen: (${Math.round(screenPos.x)}, ${Math.round(screenPos.y)})` ); } } diff --git a/src/utils/TextureGenerator.js b/src/utils/TextureGenerator.js new file mode 100644 index 0000000..3bda746 --- /dev/null +++ b/src/utils/TextureGenerator.js @@ -0,0 +1,200 @@ +// Texture Generator +// Proceduralno generiranje tekstur in sprite-ov +class TextureGenerator { + + // Generiraj player sprite (32x32px pixel art) + static createPlayerSprite(scene, key = 'player') { + const size = 32; + const canvas = scene.textures.createCanvas(key, size, size); + const ctx = canvas.getContext(); + + // Clear + ctx.clearRect(0, 0, size, size); + + // Barve + const skinColor = '#FFDBAC'; + const hatColor = '#F4E7C6'; + const shirtColor = '#5CB85C'; + const pantsColor = '#8B6F47'; + const outlineColor = '#000000'; + + // Funkcija za risanje piksla + const pixel = (x, y, color) => { + ctx.fillStyle = color; + ctx.fillRect(x, y, 1, 1); + }; + + // Offset za centriranje + const ox = 12; + const oy = 4; + + // Outline + Hat (8 pixel širok) + // Vrh klobuka + for (let x = 0; x < 8; x++) { + pixel(ox + x, oy + 0, outlineColor); + pixel(ox + x, oy + 1, hatColor); + pixel(ox + x, oy + 2, hatColor); + } + + // Glava (6 pixel široka) + for (let y = 3; 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 (črni piksli) + pixel(ox + 2, oy + 4, outlineColor); + pixel(ox + 5, oy + 4, outlineColor); + + // Telo - srajca (6 pixel široka) + 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 (stranske) + for (let y = 7; y < 10; y++) { + // Leva roka + pixel(ox - 1, oy + y, skinColor); + pixel(ox - 2, oy + y, outlineColor); + // Desna roka + pixel(ox + 8, oy + y, skinColor); + pixel(ox + 9, oy + y, outlineColor); + } + + // Noge - hlače (vsaka noga 3 piksle široka) + for (let y = 11; y < 16; y++) { + // Leva noga + pixel(ox + 0, oy + y, outlineColor); + pixel(ox + 1, oy + y, pantsColor); + pixel(ox + 2, oy + y, pantsColor); + pixel(ox + 3, oy + y, outlineColor); + + // Desna noga + 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; + } + + // Generiraj walking animacijo (4 frame-i) + static createPlayerWalkSprite(scene, key = 'player_walk') { + const frameWidth = 32; + const frameHeight = 32; + const frameCount = 4; + + const canvas = scene.textures.createCanvas(key, frameWidth * frameCount, frameHeight); + const ctx = canvas.getContext(); + + // Frame 0: Idle (osnovni sprite) + this.drawPlayerFrame(ctx, 0, 0, 0); + + // Frame 1: Left foot forward + this.drawPlayerFrame(ctx, frameWidth, 0, 1); + + // Frame 2: Idle + this.drawPlayerFrame(ctx, frameWidth * 2, 0, 0); + + // Frame 3: Right foot forward + this.drawPlayerFrame(ctx, frameWidth * 3, 0, 2); + + canvas.refresh(); + return canvas; + } + + // Pomožna funkcija za risanje player frame-a + static drawPlayerFrame(ctx, offsetX, offsetY, frame) { + const size = 32; + + // Barve + const skinColor = '#FFDBAC'; + const hatColor = '#F4E7C6'; + const shirtColor = '#5CB85C'; + const pantsColor = '#8B6F47'; + const outlineColor = '#000000'; + + const pixel = (x, y, color) => { + ctx.fillStyle = color; + ctx.fillRect(offsetX + x, offsetY + y, 1, 1); + }; + + const ox = 12; + const oy = 4; + + // Klobuk + for (let x = 0; x < 8; x++) { + pixel(ox + x, oy + 0, outlineColor); + pixel(ox + x, oy + 1, hatColor); + pixel(ox + x, oy + 2, hatColor); + } + + // Glava + for (let y = 3; 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); + } + pixel(ox + 2, oy + 4, outlineColor); + pixel(ox + 5, oy + 4, outlineColor); + + // Telo + 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 - prilagojeno glede na frame (walking animation) + let legOffset = 0; + if (frame === 1) legOffset = -1; // Left foot forward + if (frame === 2) legOffset = 1; // Right foot forward + + for (let y = 11; y < 16; y++) { + const leftShift = (frame === 1) ? 0 : 0; + const rightShift = (frame === 2) ? 0 : 0; + + // Leva noga + pixel(ox + 0, oy + y, outlineColor); + pixel(ox + 1, oy + y, pantsColor); + pixel(ox + 2, oy + y, pantsColor); + pixel(ox + 3, oy + y, outlineColor); + + // Desna noga + pixel(ox + 4, oy + y, outlineColor); + pixel(ox + 5, oy + y, pantsColor); + pixel(ox + 6, oy + y, pantsColor); + pixel(ox + 7, oy + y, outlineColor); + } + + for (let x = 0; x < 8; x++) { + pixel(ox + x, oy + 16, outlineColor); + } + } +}