# 🎨 SPRITE SHEET SYSTEM **Novafarma - Sprite Atlas & Animation Guide** **Date:** 10.12.2025 **Status:** In Development --- ## πŸ“Š **Current Sprite Sheets** ### **1. Player Walk Animation** ```javascript // Loaded in PreloadScene.js line 94 this.load.spritesheet('player_walk', 'assets/sprites/player_walk_strip.png', { frameWidth: 64, frameHeight: 64 }); // Animation created (line 101-106) this.anims.create({ key: 'player_walk_anim', frames: this.anims.generateFrameNumbers('player_walk', { start: 0, end: 5 }), frameRate: 12, repeat: -1 }); ``` **Specs:** - **File:** `assets/sprites/player_walk_strip.png` - **Frame Size:** 64x64 pixels - **Frames:** 6 (0-5) - **Frame Rate:** 12 FPS - **Total Width:** 384px (6 frames Γ— 64px) - **Total Height:** 64px --- ### **2. Zombie Walk Animation** ```javascript // Loaded in PreloadScene.js line 95 this.load.spritesheet('zombie_walk', 'assets/sprites/zombie_walk_strip.png', { frameWidth: 64, frameHeight: 64 }); // Animation created (line 108-113) this.anims.create({ key: 'zombie_walk_anim', frames: this.anims.generateFrameNumbers('zombie_walk', { start: 0, end: 5 }), frameRate: 8, repeat: -1 }); ``` **Specs:** - **File:** `assets/sprites/zombie_walk_strip.png` - **Frame Size:** 64x64 pixels - **Frames:** 6 (0-5) - **Frame Rate:** 8 FPS (slower than player) - **Total Width:** 384px - **Total Height:** 64px --- ## πŸ“¦ **Sprite Sheet Format** ### **Horizontal Strip (Current)** ``` [Frame 0][Frame 1][Frame 2][Frame 3][Frame 4][Frame 5] 64px 64px 64px 64px 64px 64px ``` **Advantages:** - βœ… Simple to create - βœ… Easy to visualize - βœ… Small file for few frames **Disadvantages:** - ❌ Wide files for many frames - ❌ No multi-direction support --- ### **Grid Layout (Recommended for Full Animations)** ``` [Idle 1][Idle 2][Idle 3][Idle 4] [Walk 1][Walk 2][Walk 3][Walk 4] [Run 1][Run 2][Run 3][Run 4] [Attack1][Attack2][Attack3][Attack4] ``` **Advantages:** - βœ… Organized by animation type - βœ… Square-ish texture (better for GPU) - βœ… Easy to add new animations --- ### **Directional Grid (Best for Isometric)** ``` [DOWN] [LEFT] [RIGHT] [UP] [Frame0][Frame1] [Frame0][Frame1] [Frame0][Frame1] [Frame0][Frame1] Example for Player (4 directions Γ— 4 frames = 16 total): 64px 64px 64px 64px 64px 64px 64px 64px β”Œβ”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”β”Œβ”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”β”Œβ”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”β”Œβ”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β” β”‚Down 0 β”‚Down 1 β”‚β”‚Left 0 β”‚Left 1 β”‚β”‚Right0 β”‚Right1 β”‚β”‚Up 0 β”‚Up 1 β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€ β”‚Down 2 β”‚Down 3 β”‚β”‚Left 2 β”‚Left 3 β”‚β”‚Right2 β”‚Right3 β”‚β”‚Up 2 β”‚Up 3 β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”˜ Total Size: 512px Γ— 128px (8 columns Γ— 2 rows) ``` --- ## 🎯 **Recommended Sprite Sheets** ### **Priority 1: Player Complete** (64x64) ```javascript // player_complete.png: 512x256 (8 cols Γ— 4 rows) Rows: 0: Walk Down (4 frames) 1: Walk Left (4 frames) 2: Walk Right (4 frames) 3: Walk Up (4 frames) // Total: 32 frames (8Γ—4) // File size: ~50-100 KB ``` **Loading Code:** ```javascript this.load.spritesheet('player_complete', 'assets/sprites/player_complete.png', { frameWidth: 64, frameHeight: 64 }); ``` **Animation Code:** ```javascript // Walk Down this.anims.create({ key: 'player_walk_down', frames: this.anims.generateFrameNumbers('player_complete', { start: 0, end: 3 }), frameRate: 10, repeat: -1 }); // Walk Left this.anims.create({ key: 'player_walk_left', frames: this.anims.generateFrameNumbers('player_complete', { start: 8, end: 11 }), frameRate: 10, repeat: -1 }); // Walk Right this.anims.create({ key: 'player_walk_right', frames: this.anims.generateFrameNumbers('player_complete', { start: 16, end: 19 }), frameRate: 10, repeat: -1 }); // Walk Up this.anims.create({ key: 'player_walk_up', frames: this.anims.generateFrameNumbers('player_complete', { start: 24, end: 27 }), frameRate: 10, repeat: -1 }); ``` --- ### **Priority 2: Player Actions** (64x64) ```javascript // player_actions.png: 256x128 (4 cols Γ— 2 rows) Row 0: Idle animation (4 frames) Row 1: Attack animation (4 frames) // Total: 8 frames ``` **Animations:** ```javascript // Idle this.anims.create({ key: 'player_idle', frames: this.anims.generateFrameNumbers('player_actions', { start: 0, end: 3 }), frameRate: 6, repeat: -1 }); // Attack this.anims.create({ key: 'player_attack', frames: this.anims.generateFrameNumbers('player_actions', { start: 4, end: 7 }), frameRate: 15, repeat: 0 // Play once }); ``` --- ### **Priority 3: Zombie Complete** (64x64) ```javascript // zombie_complete.png: 512x128 (8 cols Γ— 2 rows) Row 0: Walk cycle (8 frames) Row 1: Attack cycle (8 frames) ``` --- ### **Priority 4: Items Atlas** (32x32) ```javascript // items_atlas.png: 512x512 (16 cols Γ— 16 rows = 256 items) Items organized by category: Rows 0-2: Tools (axe, pickaxe, hoe, sword, etc.) Rows 3-5: Resources (wood, stone, iron_ore, gold, etc.) Rows 6-8: Seeds & Crops (wheat, carrot, potato, etc.) Rows 9-11: Food (bread, stew, apple, etc.) Rows 12-14: Buildings (fence, chest, furnace, etc.) Rows 15: Special items (key, blueprint, etc.) ``` **Loading:** ```javascript this.load.spritesheet('items_atlas', 'assets/sprites/items_atlas.png', { frameWidth: 32, frameHeight: 32 }); ``` **Usage:** ```javascript // Get specific item frame const axeFrame = scene.textures.getFrame('items_atlas', 0); // First item const pickaxeFrame = scene.textures.getFrame('items_atlas', 1); // Second item // Create sprite from atlas const axeIcon = scene.add.sprite(x, y, 'items_atlas', 0); ``` --- ## πŸ› οΈ **Creating Sprite Sheets** ### **Method 1: Piskel (Free Online)** 1. Go to [piskelapp.com](https://www.piskelapp.com/) 2. Create 64x64 canvas 3. Draw each frame 4. Export as sprite sheet 5. Configure: - Format: PNG - Layout: Horizontal strip or Grid - Spritesheet: Yes - Frame size: 64x64 --- ### **Method 2: Aseprite (Paid, Best)** 1. File β†’ New β†’ Sprite - Width: 64px - Height: 64px 2. Draw animation frames 3. File β†’ Export Sprite Sheet - Sheet Type: By rows/columns - Columns: 8 (or as needed) - Padding: 0 - Format: PNG --- ### **Method 3: Python Script (Automated)** ```python # combine_sprites.py from PIL import Image import os def create_sprite_sheet(images_folder, output_file, frame_width=64, frame_height=64, columns=8): """Combine individual PNG files into sprite sheet""" # Get all PNG files files = sorted([f for f in os.listdir(images_folder) if f.endswith('.png')]) if not files: print("No PNG files found!") return # Calculate sheet dimensions num_frames = len(files) rows = (num_frames + columns - 1) // columns sheet_width = columns * frame_width sheet_height = rows * frame_height # Create blank sheet sheet = Image.new('RGBA', (sheet_width, sheet_height), (0, 0, 0, 0)) # Paste each frame for idx, filename in enumerate(files): img = Image.open(os.path.join(images_folder, filename)) img = img.resize((frame_width, frame_height), Image.NEAREST) # Pixel art scaling col = idx % columns row = idx // columns x = col * frame_width y = row * frame_height sheet.paste(img, (x, y)) print(f"Added {filename} at ({x}, {y})") sheet.save(output_file) print(f"βœ… Sprite sheet saved: {output_file} ({sheet_width}x{sheet_height})") print(f" Frames: {num_frames}, Columns: {columns}, Rows: {rows}") # Usage create_sprite_sheet('frames/player_walk/', 'player_walk_strip.png', columns=6) create_sprite_sheet('frames/zombie_walk/', 'zombie_walk_strip.png', columns=6) create_sprite_sheet('frames/items/', 'items_atlas.png', frame_width=32, frame_height=32, columns=16) ``` --- ## πŸ“ **Sprite Sheet Calculator** ```javascript /** * Calculate sprite sheet dimensions */ function calculateSheetSize(numFrames, frameSize, columns) { const rows = Math.ceil(numFrames / columns); const width = columns * frameSize; const height = rows * frameSize; console.log(`Frames: ${numFrames}`); console.log(`Layout: ${columns} cols Γ— ${rows} rows`); console.log(`Size: ${width}Γ—${height}px`); console.log(`File: ~${Math.ceil(width * height * 4 / 1024)}KB`); return { width, height, rows }; } // Examples: calculateSheetSize(6, 64, 6); // Player walk: 384Γ—64 calculateSheetSize(32, 64, 8); // Player full: 512Γ—256 calculateSheetSize(256, 32, 16); // Items atlas: 512Γ—512 ``` --- ## 🎨 **Frame Naming Convention** ### **For Individual Frames (Before Combining):** ``` player_walk_down_0.png player_walk_down_1.png player_walk_down_2.png player_walk_down_3.png player_walk_left_0.png ... ``` ### **For Sprite Sheets:** ``` player_complete.png (all animations) player_walk.png (only walk cycle) player_actions.png (idle, attack, etc.) zombie_complete.png (all zombie animations) items_atlas.png (all items) terrain_tiles.png (all terrain) ``` --- ## πŸ”„ **Animation State Machine** ```javascript class AnimationController { constructor(sprite) { this.sprite = sprite; this.currentAnim = null; } playWalk(direction) { const animKey = `player_walk_${direction}`; if (this.currentAnim !== animKey) { this.sprite.anims.play(animKey); this.currentAnim = animKey; } } playIdle() { if (this.currentAnim !== 'player_idle') { this.sprite.anims.play('player_idle'); this.currentAnim = 'player_idle'; } } playAttack() { this.sprite.anims.play('player_attack'); this.currentAnim = 'player_attack'; // Return to idle when attack finishes this.sprite.once('animationcomplete', () => { this.playIdle(); }); } } ``` --- ## πŸ“‹ **TODO: Sprite Sheets Needed** ### **πŸ”΄ High Priority:** - [ ] `player_complete.png` (512Γ—256, 32 frames) - 4 directions - [ ] `zombie_complete.png` (512Γ—128, 16 frames) - walk + attack - [ ] `items_atlas.png` (512Γ—512, 256 items) - all game items ### **🟑 Medium Priority:** - [ ] `npc_merchant.png` (256Γ—64, 4 frames) - idle animation - [ ] `animals.png` (512Γ—128, 16 frames) - cow, chicken animations - [ ] `effects.png` (256Γ—256, 64 frames) - hit, explosion, sparkles ### **🟒 Low Priority:** - [ ] `ui_elements.png` (512Γ—512) - buttons, icons, borders - [ ] `terrain_tiles.png` (512Γ—512) - all terrain variations - [ ] `crops.png` (256Γ—128) - growth stages for all crops --- ## πŸ’Ύ **File Size Optimization** ### **PNG Optimization Tools:** ```bash # NPM package npm install -g pngquant # Optimize sprite sheet pngquant --quality=65-80 player_complete.png -o player_complete_opt.png # Batch optimization pngquant --quality=65-80 assets/sprites/*.png --ext .png --force ``` ### **Expected Sizes:** - **64x64 Γ— 32 frames** (Player): ~80-120 KB - **32x32 Γ— 256 items** (Items): ~200-300 KB - **Total assets**: ~1-2 MB (very reasonable!) --- ## 🎯 **Next Steps** 1. **Create Priority Sheets:** - Use Piskel/Aseprite to create `player_complete.png` - Generate `items_atlas.png` with Python script 2. **Update PreloadScene:** - Add new sprite sheet loads - Create animations for all directions 3. **Update Player.js:** - Use AnimationController - Switch animations based on movement 4. **Performance Test:** - Measure FPS with full animations - Optimize if needed --- **Status:** πŸ“‹ **Planning Complete - Ready for Implementation** **Tools:** Piskel (free) or Aseprite ($20) **Python Script:** Ready for batch processing