#!/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()