📚 Documentation + scripts from today's exploration

- ASSET_COUNT_STATUS_01_01_2026.md - asset tracking
- CHARACTER_PRODUCTION_PLAN.md - character animation plan
- CHARACTER_GENERATION_FINAL_PLAN.md - API alternatives research
- COMFYUI_SETUP_TODAY.md - ComfyUI setup guide
- TASKS_01_01_2026.md - consolidated task list
- FULL_STORY_OVERVIEW.md - game narrative summary
- preview_animations.html - animation preview gallery
- Test scripts for API exploration (test_minimal.py, test_imagen.py)
- Character generation scripts (generate_all_characters_complete.py, generate_characters_working.py)

These were created during API troubleshooting and production planning.
This commit is contained in:
2026-01-01 18:29:50 +01:00
parent fbe6d8cb90
commit a101d49f2e
11 changed files with 2714 additions and 0 deletions

View File

@@ -0,0 +1,393 @@
#!/usr/bin/env python3
"""
COMPLETE CHARACTER ANIMATION GENERATOR
Generates ALL animations for Kai, Ana, and Gronk (435 PNG total)
Date: 1.1.2026
"""
import os
import time
from pathlib import Path
import google.generativeai as genai
from PIL import Image
import io
# ==================== CONFIGURATION ====================
# Try both environment variable names
API_KEY = os.getenv('GEMINI_API_KEY') or os.getenv('GOOGLE_API_KEY')
if not API_KEY:
print("❌ ERROR: API key not found!")
print("Please set one of:")
print(" export GEMINI_API_KEY='your-key-here'")
print(" export GOOGLE_API_KEY='your-key-here'")
raise ValueError("API key not set!")
# NOTE: Using deprecated google.generativeai for compatibility
# TODO: Migrate to google.genai in future
import warnings
warnings.filterwarnings('ignore', category=FutureWarning)
genai.configure(api_key=API_KEY)
print(f"✅ API Key configured (first 10 chars): {API_KEY[:10]}...")
OUTPUT_DIR = Path("assets/slike")
DELAY_BETWEEN_CALLS = 15 # seconds (safe rate limiting)
# Art Style Constants
STYLE_BASE = """
Dark Hand-Drawn 2D Stylized Indie Game Art.
CRITICAL REQUIREMENTS:
- THICK bold black outlines (3-4px)
- Exaggerated proportions, warped perspective
- High-contrast noir elements
- Centered subject with 10px transparent margin
- 32-bit PNG with alpha channel
- Clean background (pure transparency)
"""
# ==================== CHARACTER DEFINITIONS ====================
KAI_BASE = """
Character: KAI MARKOVIĆ (Age 17, Male Protagonist)
- Green & Pink dreadlocks (distinctive!)
- Tactical survivor outfit (torn, weathered)
- Lean athletic build
- Determined expression
- REFERENCE: Check konsistentno/kai_master_styleA_reference.png
"""
ANA_BASE = """
Character: ANA MARKOVIĆ (Age 17, Female Twin)
- Pink dreadlocks (matches Kai's style but all pink)
- Explorer vest, cargo pants
- Slender athletic build
- Intelligent, caring expression
- REFERENCE: Check konsistentno/ana_master_styleA_reference.png
"""
GRONK_BASE = """
Character: GRONK (Zen Troll Companion)
- Large green troll, 7ft tall
- Pink mohawk hair
- Multiple piercings (ears, nose)
- Vape pen accessory (signature item!)
- Baggy pants, no shirt
- Chill, relaxed expression
- REFERENCE: Check konsistentno/gronk_master_styleA_reference.png
"""
# ==================== ANIMATION DEFINITIONS ====================
KAI_ANIMATIONS = {
# PHASE 1: Core Gameplay (72 PNG)
"walk_north": {"frames": 4, "desc": "walking away from camera, back view"},
"walk_northeast": {"frames": 4, "desc": "walking diagonal up-right"},
"walk_east": {"frames": 4, "desc": "walking right, side view (right profile)"},
"walk_southeast": {"frames": 4, "desc": "walking diagonal down-right"},
"walk_south": {"frames": 4, "desc": "walking toward camera, front view"},
"walk_southwest": {"frames": 4, "desc": "walking diagonal down-left"},
"walk_west": {"frames": 4, "desc": "walking left, side view (left profile)"},
"walk_northwest": {"frames": 4, "desc": "walking diagonal up-left"},
"idle_front": {"frames": 4, "desc": "idle standing, front view, breathing animation"},
"idle_side": {"frames": 4, "desc": "idle standing, side profile, breathing animation"},
"attack_north": {"frames": 4, "desc": "sword swing upward (attacking north)"},
"attack_east": {"frames": 4, "desc": "sword swing right (attacking east)"},
"attack_south": {"frames": 4, "desc": "sword swing downward (attacking south)"},
"attack_west": {"frames": 4, "desc": "sword swing left (attacking west)"},
"dig_north": {"frames": 4, "desc": "digging with shovel upward"},
"dig_east": {"frames": 4, "desc": "digging with shovel to the right"},
"dig_south": {"frames": 4, "desc": "digging with shovel downward"},
"dig_west": {"frames": 4, "desc": "digging with shovel to the left"},
# PHASE 2: Expanded (47 PNG)
"run_north": {"frames": 4, "desc": "running away, back view, faster pace"},
"run_northeast": {"frames": 4, "desc": "running diagonal up-right"},
"run_east": {"frames": 4, "desc": "running right, side view"},
"run_southeast": {"frames": 4, "desc": "running diagonal down-right"},
"run_south": {"frames": 4, "desc": "running toward camera, front view"},
"run_southwest": {"frames": 4, "desc": "running diagonal down-left"},
"run_west": {"frames": 4, "desc": "running left, side view"},
"run_northwest": {"frames": 4, "desc": "running diagonal up-left"},
"plant_front": {"frames": 3, "desc": "planting seeds, kneeling, front view"},
"plant_side": {"frames": 3, "desc": "planting seeds, kneeling, side view"},
"harvest_front": {"frames": 3, "desc": "harvesting crops, bending down, front view"},
"harvest_side": {"frames": 3, "desc": "harvesting crops, bending down, side view"},
"hurt": {"frames": 3, "desc": "taking damage, recoiling, pain expression"},
# PHASE 3: Polish (27 PNG)
"sad": {"frames": 4, "desc": "crying, hands on face, emotional"},
"happy": {"frames": 4, "desc": "celebrating, arms raised, joyful"},
"thinking": {"frames": 3, "desc": "hand on chin, pondering"},
"shocked": {"frames": 2, "desc": "surprised, eyes wide, mouth open"},
"use_item_front": {"frames": 3, "desc": "using item from inventory, front view"},
"use_item_side": {"frames": 3, "desc": "using item from inventory, side view"},
"eat": {"frames": 3, "desc": "eating food, bringing to mouth"},
"death": {"frames": 5, "desc": "death animation, collapsing to ground"},
# PHASE 4: Advanced (10 PNG)
"zombie_command": {"frames": 4, "desc": "telepathic gesture, hand raised, concentration"},
"telepathy_effect": {"frames": 6, "desc": "psychic energy emanating, glowing effect"},
}
ANA_ANIMATIONS = {
# PHASE 1: Core (56 PNG)
"walk_north": {"frames": 4, "desc": "walking away, back view"},
"walk_northeast": {"frames": 4, "desc": "walking diagonal up-right"},
"walk_east": {"frames": 4, "desc": "walking right, side view"},
"walk_southeast": {"frames": 4, "desc": "walking diagonal down-right"},
"walk_south": {"frames": 4, "desc": "walking toward camera, front view"},
"walk_southwest": {"frames": 4, "desc": "walking diagonal down-left"},
"walk_west": {"frames": 4, "desc": "walking left, side view"},
"walk_northwest": {"frames": 4, "desc": "walking diagonal up-left"},
"idle_front": {"frames": 4, "desc": "idle standing, front view"},
"idle_side": {"frames": 4, "desc": "idle standing, side profile"},
"research_front": {"frames": 4, "desc": "writing in notebook, studying"},
"research_side": {"frames": 4, "desc": "writing in notebook, side view"},
"heal_front": {"frames": 4, "desc": "applying bandage, medical care"},
"heal_side": {"frames": 4, "desc": "applying bandage, side view"},
# PHASE 2: Expanded (46 PNG)
"run_north": {"frames": 4, "desc": "running away, back view"},
"run_northeast": {"frames": 4, "desc": "running diagonal up-right"},
"run_east": {"frames": 4, "desc": "running right"},
"run_southeast": {"frames": 4, "desc": "running diagonal down-right"},
"run_south": {"frames": 4, "desc": "running toward camera"},
"run_southwest": {"frames": 4, "desc": "running diagonal down-left"},
"run_west": {"frames": 4, "desc": "running left"},
"run_northwest": {"frames": 4, "desc": "running diagonal up-left"},
"examine_front": {"frames": 3, "desc": "examining with magnifying glass"},
"examine_side": {"frames": 3, "desc": "examining, side view"},
"mix_potion": {"frames": 4, "desc": "mixing ingredients in flask"},
"worried": {"frames": 4, "desc": "worried expression, hand to head"},
# PHASE 3: Polish (18 PNG)
"relief": {"frames": 4, "desc": "relief, exhaling, relaxed"},
"twin_bond_glow": {"frames": 6, "desc": "glowing psychic connection effect"},
"flashback_pose": {"frames": 3, "desc": "memory pose, ethereal"},
"death": {"frames": 5, "desc": "death animation, collapsing"},
# PHASE 4: Advanced (18 PNG)
"collect_sample_front": {"frames": 3, "desc": "collecting sample with vial"},
"collect_sample_side": {"frames": 3, "desc": "collecting sample, side view"},
"defend": {"frames": 4, "desc": "defensive stance with staff"},
"cure_cast": {"frames": 5, "desc": "casting cure spell, magical gesture"},
"hurt": {"frames": 3, "desc": "taking damage, recoiling"},
}
GRONK_ANIMATIONS = {
# PHASE 1: Core (72 PNG)
"walk_north": {"frames": 4, "desc": "heavy lumbering walk, back view"},
"walk_northeast": {"frames": 4, "desc": "heavy walk diagonal up-right"},
"walk_east": {"frames": 4, "desc": "heavy walk right, side view"},
"walk_southeast": {"frames": 4, "desc": "heavy walk diagonal down-right"},
"walk_south": {"frames": 4, "desc": "heavy walk toward camera, front view"},
"walk_southwest": {"frames": 4, "desc": "heavy walk diagonal down-left"},
"walk_west": {"frames": 4, "desc": "heavy walk left, side view"},
"walk_northwest": {"frames": 4, "desc": "heavy walk diagonal up-left"},
"idle_front": {"frames": 4, "desc": "idle, chill stance, front view"},
"idle_side": {"frames": 4, "desc": "idle, chill stance, side view"},
"vape_front": {"frames": 6, "desc": "vaping, exhaling smoke cloud, front view"},
"vape_side": {"frames": 6, "desc": "vaping, exhaling smoke, side view"},
"smash_north": {"frames": 5, "desc": "club smash upward"},
"smash_east": {"frames": 5, "desc": "club smash right"},
"smash_south": {"frames": 5, "desc": "club smash downward"},
"smash_west": {"frames": 5, "desc": "club smash left"},
# PHASE 2: Expanded (47 PNG)
"run_north": {"frames": 4, "desc": "slow lumbering run, back view"},
"run_northeast": {"frames": 4, "desc": "lumbering run diagonal up-right"},
"run_east": {"frames": 4, "desc": "lumbering run right"},
"run_southeast": {"frames": 4, "desc": "lumbering run diagonal down-right"},
"run_south": {"frames": 4, "desc": "lumbering run toward camera"},
"run_southwest": {"frames": 4, "desc": "lumbering run diagonal down-left"},
"run_west": {"frames": 4, "desc": "lumbering run left"},
"run_northwest": {"frames": 4, "desc": "lumbering run diagonal up-left"},
"lift_heavy_front": {"frames": 4, "desc": "lifting heavy object, straining"},
"lift_heavy_side": {"frames": 4, "desc": "lifting heavy object, side view"},
"laugh": {"frames": 4, "desc": "laughing, belly laugh, jovial"},
"hurt": {"frames": 3, "desc": "taking damage, grimacing"},
# PHASE 3: Polish (16 PNG)
"meditate": {"frames": 4, "desc": "zen meditation pose, sitting, serene"},
"confused": {"frames": 3, "desc": "confused, scratching head"},
"chill": {"frames": 3, "desc": "ultra relaxed, peace sign"},
"death": {"frames": 5, "desc": "death animation, collapsing heavily"},
# PHASE 4: Advanced (6 PNG)
"block": {"frames": 3, "desc": "blocking with raised arms"},
"taunt": {"frames": 4, "desc": "taunting enemies, beckoning"},
}
# ==================== GENERATION FUNCTIONS ====================
def generate_animation_frame(character_name, character_base, animation_name, frame_num, animation_desc, style=STYLE_BASE):
"""Generate a single animation frame"""
prompt = f"""
{style}
{character_base}
ANIMATION: {animation_name} - Frame {frame_num}
Description: {animation_desc}
Frame {frame_num} timing notes:
- Frame 1: Start pose
- Frame 2: Mid-motion
- Frame 3: Peak/impact
- Frame 4: Follow-through/return
Generate this specific frame showing clear progression in the animation cycle.
Maintain character consistency (check reference in konsistentno/ folder).
"""
try:
print(f" 🎨 Generating {character_name} - {animation_name} - Frame {frame_num}...")
model = genai.GenerativeModel('gemini-2.0-flash-exp')
response = model.generate_content([prompt])
if response.parts and len(response.parts) > 0:
image_data = response.parts[0].inline_data.data
image = Image.open(io.BytesIO(image_data))
return image
else:
print(f" ❌ No image generated for {animation_name} frame {frame_num}")
return None
except Exception as e:
print(f" ❌ Error generating {animation_name} frame {frame_num}: {e}")
return None
def generate_character_animations(character_name, character_base, animations_dict):
"""Generate all animations for a character"""
char_dir = OUTPUT_DIR / character_name.lower()
char_dir.mkdir(parents=True, exist_ok=True)
total_frames = sum(anim["frames"] for anim in animations_dict.values())
current_frame = 0
print(f"\n{'='*60}")
print(f"🎬 GENERATING {character_name.upper()} ANIMATIONS")
print(f"{'='*60}")
print(f"Total animations: {len(animations_dict)}")
print(f"Total frames: {total_frames}")
print(f"Output directory: {char_dir}")
print()
for anim_name, anim_data in animations_dict.items():
frames_count = anim_data["frames"]
desc = anim_data["desc"]
print(f"\n📹 Animation: {anim_name} ({frames_count} frames)")
for frame_num in range(1, frames_count + 1):
current_frame += 1
# Generate frame
image = generate_animation_frame(
character_name,
character_base,
anim_name,
frame_num,
desc
)
if image:
# Save original
filename = f"{character_name.lower()}_{anim_name}_frame{frame_num}_1024x1024.png"
filepath = char_dir / filename
image.save(filepath, "PNG")
print(f" ✅ Saved: {filename}")
# Create preview (256x256)
preview = image.resize((256, 256), Image.Resampling.LANCZOS)
preview_filename = f"{character_name.lower()}_{anim_name}_frame{frame_num}_preview_256x256.png"
preview_filepath = char_dir / preview_filename
preview.save(preview_filepath, "PNG")
print(f" ✅ Saved preview: {preview_filename}")
# Progress
progress = (current_frame / total_frames) * 100
print(f" 📊 Progress: {current_frame}/{total_frames} ({progress:.1f}%)")
# Rate limiting
print(f" ⏳ Waiting {DELAY_BETWEEN_CALLS}s...")
time.sleep(DELAY_BETWEEN_CALLS)
print(f"\n{character_name.upper()} COMPLETE! Generated {total_frames} frames ({total_frames * 2} files with previews)")
return total_frames
# ==================== MAIN EXECUTION ====================
def main():
"""Generate all character animations"""
print("=" * 80)
print("🎮 DOLINASMRTI - COMPLETE CHARACTER ANIMATION GENERATOR")
print("=" * 80)
print(f"Date: 2026-01-01")
print(f"Target: 435 PNG (all 3 characters, all animations)")
print(f"API Delay: {DELAY_BETWEEN_CALLS}s between calls")
print()
start_time = time.time()
total_generated = 0
# Generate Kai
kai_frames = generate_character_animations("Kai", KAI_BASE, KAI_ANIMATIONS)
total_generated += kai_frames
# Generate Ana
ana_frames = generate_character_animations("Ana", ANA_BASE, ANA_ANIMATIONS)
total_generated += ana_frames
# Generate Gronk
gronk_frames = generate_character_animations("Gronk", GRONK_BASE, GRONK_ANIMATIONS)
total_generated += gronk_frames
# Summary
elapsed = time.time() - start_time
hours = int(elapsed // 3600)
minutes = int((elapsed % 3600) // 60)
print("\n" + "=" * 80)
print("🎆 GENERATION COMPLETE!")
print("=" * 80)
print(f"Total frames generated: {total_generated}")
print(f"Total files created: {total_generated * 2} (with previews)")
print(f"Time elapsed: {hours}h {minutes}min")
print(f"\nCharacter breakdown:")
print(f" - Kai: {kai_frames} frames")
print(f" - Ana: {ana_frames} frames")
print(f" - Gronk: {gronk_frames} frames")
print("\n✅ All character animations ready for game integration!")
if __name__ == "__main__":
main()