- Updated the game to support new character sprite atlases for both male and female characters, allowing for a wider variety of NPC designs. - Improved player sprite initialization to dynamically select between atlas-based and legacy sprites, enhancing flexibility in character representation. - Refined collision box settings based on sprite type, ensuring accurate physics interactions for both atlas (80x80) and legacy (64x64) sprites. - Enhanced NPC behavior to utilize atlas animations, allowing for more fluid and diverse animations based on available frames. Files modified: - game.js: Added new character atlases and updated sprite loading logic. - player.js: Improved player sprite handling and collision box adjustments. - npc-behavior.js: Updated animation handling for NPCs to support atlas-based animations. - npc-sprites.js: Enhanced NPC sprite creation to accommodate atlas detection and initial frame selection. - scenario.json.erb: Updated player and NPC configurations to utilize new sprite sheets and animation settings. - m01_npc_sarah.ink: Revised dialogue options to include new interactions related to NPCs.
5.0 KiB
Frame Number Fix - Atlas vs Legacy Sprites
Problem
Error when creating NPCs with atlas sprites:
Texture "male_spy" has no frame "20"
Root Cause
The NPC sprite creation was using a hardcoded frame number (20) which works for legacy 64x64 sprites but doesn't exist in atlas sprites.
Legacy Sprites (64x64)
- Use numbered frames: 0, 1, 2, 3, ..., 20, 21, etc.
- Frame 20 is the idle down-right frame
- Frames are generated from a regular grid layout
Atlas Sprites (80x80)
- Use named frames:
"breathing-idle_south_frame_000","walk_east_frame_001", etc. - Frame numbers don't exist - only frame names
- Frames are defined in the JSON atlas
Solution
Detect sprite type and use appropriate initial frame:
Implementation
// Check if this is an atlas sprite
const isAtlas = scene.cache.json.exists(spriteSheet);
// Determine initial frame
let initialFrame;
if (isAtlas) {
// Atlas sprite - use first frame from breathing-idle_south animation
const atlasData = scene.cache.json.get(spriteSheet);
if (atlasData?.animations?.['breathing-idle_south']) {
initialFrame = atlasData.animations['breathing-idle_south'][0];
} else {
// Fallback to first frame in atlas
initialFrame = 0;
}
} else {
// Legacy sprite - use configured frame or default to 20
initialFrame = config.idleFrame || 20;
}
// Create sprite with correct frame
const sprite = scene.add.sprite(worldPos.x, worldPos.y, spriteSheet, initialFrame);
Frame Selection Logic
For Atlas Sprites
-
First choice: First frame of
breathing-idle_southanimation (facing down)- Example:
"breathing-idle_south_frame_000" - This ensures the character starts in a natural idle pose facing downward
- Example:
-
Fallback: Frame 0 (first frame in the atlas)
- Used if breathing-idle animation doesn't exist
For Legacy Sprites
- First choice:
config.idleFrame(if specified in scenario) - Fallback: Frame 20 (down-right idle frame)
Why Frame 20 for Legacy?
Legacy sprites use this frame layout:
Row 0 (frames 0-4): Right walk
Row 1 (frames 5-9): Down walk
Row 2 (frames 10-14): Up walk
Row 3 (frames 15-19): Up-right walk
Row 4 (frames 20-24): Down-right walk ← Frame 20 is first frame of this row
Frame 20 is the idle down-right pose, which is a good default starting position.
Why breathing-idle_south for Atlas?
Atlas sprites have structured animation names:
breathing-idle_south → Idle breathing facing down
breathing-idle_east → Idle breathing facing right
walk_north → Walking upward
breathing-idle_south (down) is the most natural default direction for a character to face when first appearing.
Files Modified
File: public/break_escape/js/systems/npc-sprites.js
Function: createNPCSprite()
Lines: 25-60
Testing
Verified with:
- ✅ Atlas sprites (female_hacker_hood, male_spy, etc.) - No frame errors
- ✅ Legacy sprites (hacker, hacker-red) - Still works as before
- ✅ NPCs spawn with correct initial pose
- ✅ Animations play correctly after spawn
- ✅ Console logging shows correct frame selection
Console Output
🎭 NPC briefing_cutscene created with atlas sprite (male_spy), initial frame: breathing-idle_south_frame_000
🎭 NPC sarah_martinez created with atlas sprite (female_office_worker), initial frame: breathing-idle_south_frame_000
🎭 NPC old_npc created with legacy sprite (hacker), initial frame: 20
Error Prevention
Before Fix
const idleFrame = config.idleFrame || 20; // ❌ Always uses number
const sprite = scene.add.sprite(x, y, spriteSheet, idleFrame);
// ERROR: Texture "male_spy" has no frame "20"
After Fix
const isAtlas = scene.cache.json.exists(spriteSheet);
let initialFrame;
if (isAtlas) {
// Use named frame from atlas
initialFrame = atlasData.animations['breathing-idle_south'][0];
} else {
// Use numbered frame
initialFrame = config.idleFrame || 20;
}
const sprite = scene.add.sprite(x, y, spriteSheet, initialFrame);
// ✅ Works for both atlas and legacy sprites
Related Issues
This is part of a series of fixes for atlas sprite support:
- ✅ 8-directional animation support
- ✅ Collision box adjustment for 80x80 sprites
- ✅ NPC animation stuck on single frame
- ✅ Initial frame selection (this fix)
Future Improvements
- Allow specifying initial direction in scenario config
- Support custom initial frames per NPC
- Add visual indicator of sprite type in debug mode
- Validate frame exists before creating sprite
Backward Compatibility
✅ Fully backward compatible
- Legacy sprites continue to use frame 20 (or configured frame)
- Atlas sprites use appropriate named frames
- No changes needed to existing scenarios
- Automatic detection ensures correct behavior
Performance
- No performance impact: Frame selection happens once at sprite creation
- Minimal overhead: Single JSON cache check to determine sprite type
- Efficient: Uses first frame from animation data without searching