From ceeb0f9de55facbf475eb8dc3e93f54d106c33d7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 9 Nov 2025 16:21:50 +0000 Subject: [PATCH] feat(npc): Complete Phase -1 prerequisites for NPC behavior system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✅ Phase -1: Critical Prerequisites COMPLETE Changes: 1. **Added walk animations** to npc-sprites.js (8 directions) - Walk animations: right, down, up, up-right, down-right - Idle animations: right, down, up, up-right, down-right - Left directions use right animations with flipX - Frame references from hacker sprite sheet 2. **Verified existing features**: - ✅ setupNPCEnvironmentCollisions exists (line 381) - ✅ Phone NPC filtering exists (rooms.js line 1899) - ✅ roomId added to NPCs (npc-lazy-loader.js line 39) - ✅ Player position access safe (game.js line 715) Animation Details: - Idle: 5 base directions + 3 mirrored = 8 total - Walk: 5 base directions + 3 mirrored = 8 total - Legacy idle animation preserved for backward compatibility - Frame rate: 8 fps for walk, 4 fps for idle Ready for Phase 0: Foundation Setup --- js/systems/npc-sprites.js | 109 ++++++++++++++++++++++++++++++++++---- 1 file changed, 100 insertions(+), 9 deletions(-) diff --git a/js/systems/npc-sprites.js b/js/systems/npc-sprites.js index babb19b..cb54145 100644 --- a/js/systems/npc-sprites.js +++ b/js/systems/npc-sprites.js @@ -119,10 +119,10 @@ export function calculateNPCWorldPosition(npc, roomData) { /** * Set up animations for an NPC sprite - * + * * Creates animation sequences based on sprite configuration. - * Supports: idle, greeting, and talking animations. - * + * Supports: idle (8 directions), walk (8 directions), greeting, and talking animations. + * * @param {Phaser.Scene} scene - Phaser scene instance * @param {Phaser.Sprite} sprite - NPC sprite * @param {string} spriteSheet - Texture key @@ -131,12 +131,56 @@ export function calculateNPCWorldPosition(npc, roomData) { */ export function setupNPCAnimations(scene, sprite, spriteSheet, config, npcId) { const animPrefix = config.animPrefix || 'idle'; - - // Idle animation (facing down by default) - // For hacker sprite: frames 20-23 = idle-down + + // ===== IDLE ANIMATIONS (8 directions) ===== + // Idle animations for 5 base directions (left uses right with flipX) + const idleAnimations = [ + { dir: 'right', frame: 0 }, + { dir: 'down', frame: 5 }, + { dir: 'up', frame: 10 }, + { dir: 'up-right', frame: 15 }, + { dir: 'down-right', frame: 20 } + ]; + + idleAnimations.forEach(anim => { + const animKey = `npc-${npcId}-idle-${anim.dir}`; + if (!scene.anims.exists(animKey)) { + scene.anims.create({ + key: animKey, + frames: [{ key: spriteSheet, frame: anim.frame }], + frameRate: config.idleFrameRate || 4, + repeat: -1 + }); + } + }); + + // Create mirrored idle animations for left directions + // These use the same animation keys but will be flipped with sprite.setFlipX(true) + const leftIdleAnimations = [ + { dir: 'left', mirrorDir: 'right' }, + { dir: 'up-left', mirrorDir: 'up-right' }, + { dir: 'down-left', mirrorDir: 'down-right' } + ]; + + leftIdleAnimations.forEach(anim => { + const animKey = `npc-${npcId}-idle-${anim.dir}`; + const mirrorKey = `npc-${npcId}-idle-${anim.mirrorDir}`; + if (!scene.anims.exists(animKey) && scene.anims.exists(mirrorKey)) { + // Create alias - left directions will use right animations with flipX + const mirrorAnim = scene.anims.get(mirrorKey); + scene.anims.create({ + key: animKey, + frames: mirrorAnim.frames, + frameRate: mirrorAnim.frameRate, + repeat: mirrorAnim.repeat + }); + } + }); + + // Legacy idle animation (default facing down) for backward compatibility const idleStart = config.idleFrameStart || 20; const idleEnd = config.idleFrameEnd || 23; - + if (!scene.anims.exists(`npc-${npcId}-idle`)) { scene.anims.create({ key: `npc-${npcId}-idle`, @@ -148,7 +192,54 @@ export function setupNPCAnimations(scene, sprite, spriteSheet, config, npcId) { repeat: -1 }); } - + + // ===== WALK ANIMATIONS (8 directions) ===== + // Walk animations for 5 base directions (left uses right with flipX) + const walkAnimations = [ + { dir: 'right', frames: [1, 2, 3, 4] }, + { dir: 'down', frames: [6, 7, 8, 9] }, + { dir: 'up', frames: [11, 12, 13, 14] }, + { dir: 'up-right', frames: [16, 17, 18, 19] }, + { dir: 'down-right', frames: [21, 22, 23, 24] } + ]; + + walkAnimations.forEach(anim => { + const animKey = `npc-${npcId}-walk-${anim.dir}`; + if (!scene.anims.exists(animKey)) { + scene.anims.create({ + key: animKey, + frames: scene.anims.generateFrameNumbers(spriteSheet, { + frames: anim.frames + }), + frameRate: 8, + repeat: -1 + }); + } + }); + + // Create mirrored walk animations for left directions + const leftWalkAnimations = [ + { dir: 'left', mirrorDir: 'right' }, + { dir: 'up-left', mirrorDir: 'up-right' }, + { dir: 'down-left', mirrorDir: 'down-right' } + ]; + + leftWalkAnimations.forEach(anim => { + const animKey = `npc-${npcId}-walk-${anim.dir}`; + const mirrorKey = `npc-${npcId}-walk-${anim.mirrorDir}`; + if (!scene.anims.exists(animKey) && scene.anims.exists(mirrorKey)) { + // Create alias - left directions will use right animations with flipX + const mirrorAnim = scene.anims.get(mirrorKey); + scene.anims.create({ + key: animKey, + frames: mirrorAnim.frames, + frameRate: mirrorAnim.frameRate, + repeat: mirrorAnim.repeat + }); + } + }); + + // ===== OPTIONAL ANIMATIONS ===== // Optional: Greeting animation (wave or nod) if (config.greetFrameStart !== undefined && config.greetFrameEnd !== undefined) { if (!scene.anims.exists(`npc-${npcId}-greet`)) { @@ -163,7 +254,7 @@ export function setupNPCAnimations(scene, sprite, spriteSheet, config, npcId) { }); } } - + // Optional: Talking animation (subtle movement) if (config.talkFrameStart !== undefined && config.talkFrameEnd !== undefined) { if (!scene.anims.exists(`npc-${npcId}-talk`)) {