🎬 Complete animation system - manifest (492 total animations) + generator script for Priority 1 (120 PNG core movement)

This commit is contained in:
2025-12-31 11:18:47 +01:00
parent f1c4051c3e
commit 4f873f585c
2 changed files with 424 additions and 0 deletions

View File

@@ -0,0 +1,184 @@
# 🎬 COMPLETE ANIMATION MANIFEST
**Main Characters**: Kai, Ana, Gronk
**Styles**: A (cartoon) & B (noir)
**Target**: Full game-ready animation sets
---
## 📊 ANIMATION BREAKDOWN
### **PRIORITY 1: CORE MOVEMENT** (Essential for gameplay)
#### **Walk Cycle** (4 directions × 4 frames = 16 frames)
- `walk_north_1.png` through `walk_north_4.png`
- `walk_south_1.png` through `walk_south_4.png`
- `walk_east_1.png` through `walk_east_4.png`
- `walk_west_1.png` through `walk_west_4.png`
#### **Idle Stance** (4 directions × 1 frame = 4 frames)
- `idle_north.png`
- `idle_south.png`
- `idle_east.png`
- `idle_west.png`
**Subtotal Priority 1**: 20 frames × 2 styles = **40 PNG per character**
**Total Priority 1**: 40 × 3 characters = **120 PNG**
---
### **PRIORITY 2: FARMING ACTIONS** (Core gameplay)
#### **Hoeing** (4 directions × 2 frames = 8 frames)
- `hoe_north_1.png`, `hoe_north_2.png`
- `hoe_south_1.png`, `hoe_south_2.png`
- `hoe_east_1.png`, `hoe_east_2.png`
- `hoe_west_1.png`, `hoe_west_2.png`
#### **Watering** (4 directions × 2 frames = 8 frames)
- `water_north_1.png`, `water_north_2.png`
- `water_south_1.png`, `water_south_2.png`
- `water_east_1.png`, `water_east_2.png`
- `water_west_1.png`, `water_west_2.png`
#### **Planting/Picking** (4 directions × 2 frames = 8 frames)
- `plant_north_1.png`, `plant_north_2.png`
- `plant_south_1.png`, `plant_south_2.png`
- `plant_east_1.png`, `plant_east_2.png`
- `plant_west_1.png`, `plant_west_2.png`
**Subtotal Priority 2**: 24 frames × 2 styles = **48 PNG per character**
**Total Priority 2**: 48 × 3 characters = **144 PNG**
---
### **PRIORITY 3: COMBAT** (Action gameplay)
#### **Melee Attack** (4 directions × 3 frames = 12 frames)
- `attack_north_1.png` through `attack_north_3.png`
- `attack_south_1.png` through `attack_south_3.png`
- `attack_east_1.png` through `attack_east_3.png`
- `attack_west_1.png` through `attack_west_3.png`
#### **Hit/Hurt** (1 universal frame)
- `hurt.png`
#### **Death** (1 frame)
- `death.png`
**Subtotal Priority 3**: 14 frames × 2 styles = **28 PNG per character**
**Total Priority 3**: 28 × 3 characters = **84 PNG**
---
### **PRIORITY 4: INTERACTIONS** (Polish)
#### **Interact/Pick Up** (4 directions × 1 frame = 4 frames)
- `interact_north.png`
- `interact_south.png`
- `interact_east.png`
- `interact_west.png`
#### **Carry Item** (4 directions × 1 frame = 4 frames)
- `carry_north.png`
- `carry_south.png`
- `carry_east.png`
- `carry_west.png`
**Subtotal Priority 4**: 8 frames × 2 styles = **16 PNG per character**
**Total Priority 4**: 16 × 3 characters = **48 PNG**
---
### **PRIORITY 5: RUNNING** (Optional enhancement)
#### **Run Cycle** (4 directions × 4 frames = 16 frames)
- `run_north_1.png` through `run_north_4.png`
- `run_south_1.png` through `run_south_4.png`
- `run_east_1.png` through `run_east_4.png`
- `run_west_1.png` through `run_west_4.png`
**Subtotal Priority 5**: 16 frames × 2 styles = **32 PNG per character**
**Total Priority 5**: 32 × 3 characters = **96 PNG**
---
## 📈 GRAND TOTAL
| Priority | Description | Frames/Char | PNG/Char | Total PNG |
|:---------|:------------|------------:|---------:|----------:|
| **P1** | Core Movement | 20 | 40 | 120 |
| **P2** | Farming Actions | 24 | 48 | 144 |
| **P3** | Combat | 14 | 28 | 84 |
| **P4** | Interactions | 8 | 16 | 48 |
| **P5** | Running | 16 | 32 | 96 |
| **TOTAL** | **All Animations** | **82** | **164** | **492** |
---
## 🎯 GENERATION STRATEGY
### **Phase 1** (Essential - Day 1):
- Priority 1 (Core Movement): **120 PNG**
- Priority 2 (Farming): **144 PNG**
**= 264 PNG** (Playable demo ready!)
### **Phase 2** (Combat - Day 2):
- Priority 3 (Combat): **84 PNG**
### **Phase 3** (Polish - Day 3):
- Priority 4 (Interactions): **48 PNG**
- Priority 5 (Running): **96 PNG**
---
## 📋 NAMING CONVENTION
Format: `{character}_{action}_{direction}_{frame}_{style}.png`
**Examples**:
- `kai_walk_north_1_stylea.png`
- `ana_hoe_south_2_styleb.png`
- `gronk_attack_east_3_stylea.png`
---
## 🔧 GENERATION PROMPTS
**Base prompt structure**:
```
Character: [Kai/Ana/Gronk] from reference image
Action: [walking/hoeing/attacking] [direction]
Frame: [1/2/3/4] of animation cycle
Style: [Style A cartoon vector / Style B noir gritty]
Maintain exact character consistency with master reference
Full body, centered, isolated white background, game sprite asset
```
---
## ⏱️ TIME ESTIMATES
**With API quota** (5 req/min = 60/hour):
- Phase 1 (264 PNG): ~4.5 hours
- Phase 2 (84 PNG): ~1.5 hours
- Phase 3 (144 PNG): ~2.5 hours
**Total**: ~8.5 hours for complete animation sets!
---
## ✅ DELIVERABLES
**Per character** (Kai, Ana, Gronk):
- ✅ 82 unique animation frames
- ✅ 2 styles each (Style A & B)
-**164 PNG files per character**
- ✅ Complete animation set ready for Phaser/Tiled integration
**All 3 characters**: **492 PNG total** 🎮
---
**Created**: 31.12.2025
**Ready for**: 01.01.2026 (quota reset)
**Status**: ⏰ Awaiting generation start

View File

@@ -0,0 +1,240 @@
#!/usr/bin/env python3
"""
🎬 CHARACTER ANIMATION GENERATOR
Generates complete animation sets for Kai, Ana, Gronk
Based on CHARACTER_ANIMATION_MANIFEST.md
"""
import os
import sys
import time
from pathlib import Path
from PIL import Image
try:
import google.generativeai as genai
except ImportError:
print("❌ google-generativeai not installed!")
sys.exit(1)
# Configuration
REPO = Path("/Users/davidkotnik/repos/novafarma")
LIKI = REPO / "assets/slike/liki"
# API Setup
api_key = os.environ.get("GEMINI_API_KEY")
if not api_key:
print("❌ GEMINI_API_KEY not set!")
sys.exit(1)
genai.configure(api_key=api_key)
# Characters and their master references
CHARACTERS = {
"kai": {
"styleA_ref": "kai_master_styleA_reference.png",
"styleB_ref": "kai_master_styleB_reference.png",
"description": "young male adventurer with green hair and backpack"
},
"ana": {
"styleA_ref": "ana_master_styleA_reference.png",
"styleB_ref": "ana_master_styleB_reference.png",
"description": "young girl explorer with pink dreadlocks, green vest, cargo shorts"
},
"gronk": {
"styleA_ref": "gronk_master_styleA_reference.png",
"styleB_ref": "gronk_master_styleB_reference.png",
"description": "large friendly orc with green skin, pink dreadlocks, pink hoodie, vape pen"
}
}
# Animation definitions - PRIORITY 1: CORE MOVEMENT
ANIMATIONS_P1 = {
"walk": {
"frames": 4,
"directions": ["north", "south", "east", "west"],
"prompts": {
"north": "walking away from camera, back view, {frame} of 4 walk cycle",
"south": "walking toward camera, front view, {frame} of 4 walk cycle",
"east": "walking to the right, side view facing right, {frame} of 4 walk cycle",
"west": "walking to the left, side view facing left, {frame} of 4 walk cycle"
}
},
"idle": {
"frames": 1,
"directions": ["north", "south", "east", "west"],
"prompts": {
"north": "standing idle, back view",
"south": "standing idle, front view",
"east": "standing idle, side view facing right",
"west": "standing idle, side view facing left"
}
}
}
STYLE_PROMPTS = {
"stylea": "2D game character sprite, cartoon vector art style with bold black outlines, flat colors, cute playful aesthetic, isolated on pure white background",
"styleb": "2D game character sprite, dark hand-drawn gritty noir style with dramatic shadows, high contrast, sketchy atmospheric lines, isolated on pure white background"
}
def create_preview(image_path: Path, size=256):
"""Create preview version"""
try:
img = Image.open(image_path)
preview = img.resize((size, size), Image.Resampling.LANCZOS)
preview_path = image_path.parent / f"{image_path.stem}_preview_{size}x{size}.png"
preview.save(preview_path, 'PNG', optimize=True)
return preview_path
except Exception as e:
print(f" ⚠️ Preview failed: {e}")
return None
def generate_animation_frame(character, action, direction, frame, style, log_file):
"""Generate single animation frame"""
char_data = CHARACTERS[character]
style_suffix = style
# Build filename
if frame > 0:
filename = f"{character}_{action}_{direction}_{frame}_{style_suffix}.png"
else:
filename = f"{character}_{action}_{direction}_{style_suffix}.png"
output_path = LIKI / character / filename
# Skip if exists
if output_path.exists():
print(f" ⏭️ {filename} exists")
return True
# Build prompt
anim_data = ANIMATIONS_P1[action]
action_prompt = anim_data["prompts"][direction]
if "{frame}" in action_prompt:
action_prompt = action_prompt.format(frame=frame)
style_prompt = STYLE_PROMPTS[style_suffix]
full_prompt = f"""{style_prompt}
Character: {char_data['description']}
EXACTLY match the visual style and appearance from the master reference image.
Action: {action_prompt}
Full body visible, centered composition, game asset sprite.
Maintain complete visual consistency with reference - same colors, proportions, clothing details.
"""
start = time.time()
try:
print(f" 🎨 Generating: {filename}")
log_file.write(f"{time.strftime('%H:%M:%S')} - {filename}\n")
log_file.flush()
model = genai.GenerativeModel("gemini-2.5-flash")
response = model.generate_content([full_prompt])
if hasattr(response, '_result') and response._result.candidates:
image_data = response._result.candidates[0].content.parts[0].inline_data.data
# Save
output_path.parent.mkdir(parents=True, exist_ok=True)
with open(output_path, 'wb') as f:
f.write(image_data)
# Preview
create_preview(output_path)
elapsed = time.time() - start
print(f" ✅ Saved ({elapsed:.1f}s)")
log_file.write(f"{time.strftime('%H:%M:%S')} - SUCCESS ({elapsed:.1f}s)\n")
log_file.flush()
return True
else:
print(f" ❌ No image data")
log_file.write(f"{time.strftime('%H:%M:%S')} - FAILED - No data\n")
log_file.flush()
return False
except Exception as e:
print(f" ❌ Error: {e}")
log_file.write(f"{time.strftime('%H:%M:%S')} - ERROR: {e}\n")
log_file.flush()
return False
def main():
# Create log
log_dir = REPO / "logs"
log_dir.mkdir(exist_ok=True)
log_file = open(log_dir / f"character_animations_{time.strftime('%Y%m%d_%H%M%S')}.log", 'w')
print("="*70)
print("🎬 CHARACTER ANIMATION GENERATOR - PRIORITY 1")
print("="*70)
print("\n3 Characters × 20 frames × 2 styles = 120 PNG\n")
log_file.write(f"CHARACTER ANIMATION GENERATION - {time.strftime('%Y-%m-%d %H:%M:%S')}\n")
log_file.write("="*70 + "\n\n")
log_file.flush()
stats = {'total': 0, 'success': 0, 'failed': 0}
try:
for character in CHARACTERS.keys():
print(f"\n{'='*70}")
print(f"👤 {character.upper()}")
print(f"{'='*70}")
for style in ["stylea", "styleb"]:
print(f"\n🎨 Style: {style}")
for action, anim_data in ANIMATIONS_P1.items():
print(f"\n 📹 {action.upper()}")
for direction in anim_data["directions"]:
frames = anim_data["frames"]
if frames == 1:
stats['total'] += 1
if generate_animation_frame(character, action, direction, 0, style, log_file):
stats['success'] += 1
else:
stats['failed'] += 1
time.sleep(15) # Rate limiting
else:
for frame in range(1, frames + 1):
stats['total'] += 1
if generate_animation_frame(character, action, direction, frame, style, log_file):
stats['success'] += 1
else:
stats['failed'] += 1
time.sleep(15) # Rate limiting
# Progress
progress = (stats['total'] / 120) * 100
print(f"\n📊 Overall Progress: {progress:.1f}% | ✅ {stats['success']} | ❌ {stats['failed']}")
# Final summary
print("\n" + "="*70)
print("🎉 GENERATION COMPLETE!")
print("="*70)
print(f"✅ Success: {stats['success']}/120")
print(f"❌ Failed: {stats['failed']}/120")
print(f"📊 Success rate: {(stats['success']/120)*100:.1f}%")
log_file.write(f"\n{'='*70}\n")
log_file.write(f"COMPLETE - Success: {stats['success']}, Failed: {stats['failed']}\n")
except KeyboardInterrupt:
print(f"\n\n⚠️ INTERRUPTED at {stats['total']}/120")
log_file.write(f"\n\nINTERRUPTED at {stats['total']}/120\n")
finally:
log_file.close()
if __name__ == "__main__":
main()