12 KiB
12 KiB
🎨 SPRITE SHEET SYSTEM
Novafarma - Sprite Atlas & Animation Guide
Date: 10.12.2025
Status: In Development
📊 Current Sprite Sheets
1. Player Walk Animation
// 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
// 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)
// 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:
this.load.spritesheet('player_complete', 'assets/sprites/player_complete.png', {
frameWidth: 64,
frameHeight: 64
});
Animation Code:
// 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)
// player_actions.png: 256x128 (4 cols × 2 rows)
Row 0: Idle animation (4 frames)
Row 1: Attack animation (4 frames)
// Total: 8 frames
Animations:
// 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)
// 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)
// 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:
this.load.spritesheet('items_atlas', 'assets/sprites/items_atlas.png', {
frameWidth: 32,
frameHeight: 32
});
Usage:
// 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)
- Go to piskelapp.com
- Create 64x64 canvas
- Draw each frame
- Export as sprite sheet
- Configure:
- Format: PNG
- Layout: Horizontal strip or Grid
- Spritesheet: Yes
- Frame size: 64x64
Method 2: Aseprite (Paid, Best)
- File → New → Sprite
- Width: 64px
- Height: 64px
- Draw animation frames
- File → Export Sprite Sheet
- Sheet Type: By rows/columns
- Columns: 8 (or as needed)
- Padding: 0
- Format: PNG
Method 3: Python Script (Automated)
# 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
/**
* 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
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 directionszombie_complete.png(512×128, 16 frames) - walk + attackitems_atlas.png(512×512, 256 items) - all game items
🟡 Medium Priority:
npc_merchant.png(256×64, 4 frames) - idle animationanimals.png(512×128, 16 frames) - cow, chicken animationseffects.png(256×256, 64 frames) - hit, explosion, sparkles
🟢 Low Priority:
ui_elements.png(512×512) - buttons, icons, bordersterrain_tiles.png(512×512) - all terrain variationscrops.png(256×128) - growth stages for all crops
💾 File Size Optimization
PNG Optimization Tools:
# 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
-
Create Priority Sheets:
- Use Piskel/Aseprite to create
player_complete.png - Generate
items_atlas.pngwith Python script
- Use Piskel/Aseprite to create
-
Update PreloadScene:
- Add new sprite sheet loads
- Create animations for all directions
-
Update Player.js:
- Use AnimationController
- Switch animations based on movement
-
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