shrani
This commit is contained in:
200
scripts/create_full_structure.sh
Executable file
200
scripts/create_full_structure.sh
Executable file
@@ -0,0 +1,200 @@
|
||||
#!/bin/bash
|
||||
# Complete folder structure creator for DolinaSmrti
|
||||
# Creates ~150+ organized folders for all game assets
|
||||
|
||||
cd /Users/davidkotnik/repos/novafarma/assets/images
|
||||
|
||||
echo "🗂️ Creating complete asset structure..."
|
||||
|
||||
# ========================================
|
||||
# BIOMES (18 total)
|
||||
# ========================================
|
||||
|
||||
# 01. Dolina Farm (Starting)
|
||||
mkdir -p biomes/01_dolina_farm/{terrain,buildings,props,crops,npcs,enemies}
|
||||
|
||||
# 02. Dark Forest
|
||||
mkdir -p biomes/02_dark_forest/{terrain,trees,buildings,props,npcs,enemies}
|
||||
|
||||
# 03. Abandoned Town
|
||||
mkdir -p biomes/03_abandoned_town/{terrain,buildings,streets,props,npcs,enemies}
|
||||
|
||||
# 04. River Valley
|
||||
mkdir -p biomes/04_river_valley/{terrain,water,buildings,props,npcs,enemies}
|
||||
|
||||
# 05. Mountain Pass
|
||||
mkdir -p biomes/05_mountain_pass/{terrain,rocks,buildings,props,npcs,enemies}
|
||||
|
||||
# 06. Swamp
|
||||
mkdir -p biomes/06_swamp/{terrain,water,buildings,props,plants,npcs,enemies}
|
||||
|
||||
# 07. Desert
|
||||
mkdir -p biomes/07_desert/{terrain,buildings,props,npcs,enemies}
|
||||
|
||||
# 08. Snow Zone
|
||||
mkdir -p biomes/08_snow_zone/{terrain,ice,buildings,props,npcs,enemies}
|
||||
|
||||
# 09. Underground
|
||||
mkdir -p biomes/09_underground/{terrain,crystals,buildings,props,npcs,enemies}
|
||||
|
||||
# 10. Magical Grove
|
||||
mkdir -p biomes/10_magical_grove/{terrain,magical_trees,buildings,props,plants,npcs,enemies}
|
||||
|
||||
# 11. Ancient Ruins
|
||||
mkdir -p biomes/11_ancient_ruins/{terrain,structures,buildings,props,npcs,enemies}
|
||||
|
||||
# 12. Coastal Area
|
||||
mkdir -p biomes/12_coastal_area/{terrain,water,buildings,props,npcs,enemies}
|
||||
|
||||
# 13. Volcano
|
||||
mkdir -p biomes/13_volcano/{terrain,lava,buildings,props,npcs,enemies}
|
||||
|
||||
# 14. Crystal Caves
|
||||
mkdir -p biomes/14_crystal_caves/{terrain,crystals,buildings,props,npcs,enemies}
|
||||
|
||||
# 15. Floating Islands
|
||||
mkdir -p biomes/15_floating_islands/{terrain,sky,buildings,props,npcs,enemies}
|
||||
|
||||
# 16. Corrupted Lands
|
||||
mkdir -p biomes/16_corrupted_lands/{terrain,corruption,buildings,props,npcs,enemies}
|
||||
|
||||
# 17. Spirit Realm
|
||||
mkdir -p biomes/17_spirit_realm/{terrain,ethereal,buildings,props,npcs,enemies}
|
||||
|
||||
# 18. Final Zone
|
||||
mkdir -p biomes/18_final_zone/{terrain,epic,buildings,props,npcs,enemies}
|
||||
|
||||
# ========================================
|
||||
# CHARACTERS (Main)
|
||||
# ========================================
|
||||
|
||||
mkdir -p characters/kai/{idle,walk,run,actions,combat,portraits,emotions}
|
||||
mkdir -p characters/gronk/{idle,walk,run,actions,combat,portraits,emotions}
|
||||
mkdir -p characters/grok/{idle,walk,run,actions,combat,portraits,emotions}
|
||||
mkdir -p characters/ana/{idle,walk,run,actions,portraits,emotions,cutscenes}
|
||||
mkdir -p characters/susi_dog/{idle,walk,run,actions}
|
||||
|
||||
# ========================================
|
||||
# NPCS (Generic categories)
|
||||
# ========================================
|
||||
|
||||
mkdir -p npcs/farmers/{idle,walk,work,portraits}
|
||||
mkdir -p npcs/merchants/{idle,walk,work,portraits}
|
||||
mkdir -p npcs/guards/{idle,walk,patrol,portraits}
|
||||
mkdir -p npcs/civilians/{idle,walk,work,portraits}
|
||||
mkdir -p npcs/special_characters/{idle,walk,unique,portraits}
|
||||
mkdir -p npcs/children/{idle,walk,play,portraits}
|
||||
mkdir -p npcs/elderly/{idle,walk,sit,portraits}
|
||||
|
||||
# ========================================
|
||||
# ENEMIES (All types)
|
||||
# ========================================
|
||||
|
||||
mkdir -p enemies/zombies/{common,soldier,mutant,boss}
|
||||
mkdir -p enemies/animals/{wolves,bears,deer,birds}
|
||||
mkdir -p enemies/mutants/{small,medium,large,boss}
|
||||
mkdir -p enemies/magical_creatures/{sprites,elementals,wraiths,boss}
|
||||
mkdir -p enemies/corrupted/{plants,animals,humanoids,boss}
|
||||
mkdir -p enemies/bosses/{forest,town,mountain,desert,final}
|
||||
|
||||
# ========================================
|
||||
# ITEMS (All categories)
|
||||
# ========================================
|
||||
|
||||
# Tools
|
||||
mkdir -p items/tools/{farming,combat,magic,fishing,mining}
|
||||
|
||||
# Seeds
|
||||
mkdir -p items/seeds/{crops,flowers,trees,magical}
|
||||
|
||||
# Food
|
||||
mkdir -p items/food/{raw,cooked,baked,preserved,special}
|
||||
|
||||
# Resources
|
||||
mkdir -p items/resources/{wood,stone,ore,gems,magical}
|
||||
|
||||
# Equipment
|
||||
mkdir -p items/equipment/{weapons,armor,accessories,special}
|
||||
|
||||
# Magical
|
||||
mkdir -p items/magical/{spells,scrolls,potions,artifacts,crystals}
|
||||
|
||||
# Crafting
|
||||
mkdir -p items/crafting/{materials,components,reagents}
|
||||
|
||||
# ========================================
|
||||
# UI (Interface elements)
|
||||
# ========================================
|
||||
|
||||
# HUD
|
||||
mkdir -p ui/hud/{health,stamina,mana,buffs,debuffs}
|
||||
|
||||
# Menus
|
||||
mkdir -p ui/menus/{main,inventory,crafting,skills,quests,map,settings}
|
||||
|
||||
# Dialogue
|
||||
mkdir -p ui/dialogue/{boxes,portraits,buttons,backgrounds}
|
||||
|
||||
# Icons
|
||||
mkdir -p ui/icons/{items,skills,status,achievements,quest_markers}
|
||||
|
||||
# Panels
|
||||
mkdir -p ui/panels/{stats,equipment,social,achievements}
|
||||
|
||||
# Buttons
|
||||
mkdir -p ui/buttons/{normal,hover,pressed,disabled}
|
||||
|
||||
# ========================================
|
||||
# EFFECTS (Visual effects)
|
||||
# ========================================
|
||||
|
||||
# Magic
|
||||
mkdir -p effects/magic/{fire,water,earth,air,dark,light,combo}
|
||||
|
||||
# Weather
|
||||
mkdir -p effects/weather/{rain,snow,fog,storm,wind}
|
||||
|
||||
# Particles
|
||||
mkdir -p effects/particles/{sparkles,smoke,dust,blood,energy}
|
||||
|
||||
# Animations
|
||||
mkdir -p effects/animations/{explosions,impacts,transitions,auras,shields}
|
||||
|
||||
# Environmental
|
||||
mkdir -p effects/environmental/{day_night,seasons,lighting}
|
||||
|
||||
# ========================================
|
||||
# BUILDINGS (For all biomes)
|
||||
# ========================================
|
||||
|
||||
mkdir -p buildings/residential/{houses,apartments,mansions}
|
||||
mkdir -p buildings/commercial/{shops,markets,taverns}
|
||||
mkdir -p buildings/industrial/{barns,mills,workshops}
|
||||
mkdir -p buildings/special/{churches,schools,hospitals}
|
||||
mkdir -p buildings/ruins/{damaged,destroyed,ancient}
|
||||
|
||||
# ========================================
|
||||
# PROPS (Environmental objects)
|
||||
# ========================================
|
||||
|
||||
mkdir -p props/natural/{rocks,trees,plants,water_features}
|
||||
mkdir -p props/furniture/{indoor,outdoor}
|
||||
mkdir -p props/decorative/{statues,signs,paintings}
|
||||
mkdir -p props/interactive/{doors,chests,switches,levers}
|
||||
|
||||
# ========================================
|
||||
# CUTSCENES
|
||||
# ========================================
|
||||
|
||||
mkdir -p cutscenes/opening/{frames,backgrounds}
|
||||
mkdir -p cutscenes/story/{act1,act2,act3}
|
||||
mkdir -p cutscenes/ending/{frames,backgrounds}
|
||||
|
||||
echo ""
|
||||
echo "✅ COMPLETE! Structure created:"
|
||||
echo ""
|
||||
find . -type d -maxdepth 1 | wc -l | xargs echo " Main categories:"
|
||||
find . -type d -maxdepth 2 | wc -l | xargs echo " Subcategories:"
|
||||
find . -type d -maxdepth 3 | wc -l | xargs echo " Total folders:"
|
||||
echo ""
|
||||
echo "🎨 READY FOR MASS GENERATION!"
|
||||
File diff suppressed because one or more lines are too long
146
scripts/reorganize_assets.py
Normal file
146
scripts/reorganize_assets.py
Normal file
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Reorganize assets with proper structure:
|
||||
- Each asset gets its own subfolder
|
||||
- Original 1024x1024 file: assetname_1024x1024.png
|
||||
- Resized file: assetname_32x32.png (or other size)
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
|
||||
# Target sizes for each category
|
||||
RESIZE_CONFIG = {
|
||||
'terrain': 32,
|
||||
'crops': 32,
|
||||
'buildings': 32,
|
||||
'items': 16,
|
||||
'ui': None, # Keep original
|
||||
'effects': 32,
|
||||
'environment': {
|
||||
'campfire': 32,
|
||||
'dead_tree': (32, 64),
|
||||
'rock': 32
|
||||
},
|
||||
'characters': 32,
|
||||
'enemies': 32
|
||||
}
|
||||
|
||||
def get_target_size(category: str, filename: str):
|
||||
"""Get target resize dimensions"""
|
||||
config = RESIZE_CONFIG.get(category)
|
||||
|
||||
if config is None:
|
||||
return None
|
||||
|
||||
if isinstance(config, dict):
|
||||
for key, size in config.items():
|
||||
if key in filename:
|
||||
return size
|
||||
return 32
|
||||
|
||||
return config
|
||||
|
||||
def reorganize_asset(asset_path: Path, category_dir: Path, target_size):
|
||||
"""
|
||||
Reorganize single asset:
|
||||
- Create subfolder with asset name
|
||||
- Save original as assetname_1024x1024.png
|
||||
- Save resized as assetname_32x32.png
|
||||
"""
|
||||
|
||||
try:
|
||||
# Get asset base name (without extension)
|
||||
asset_name = asset_path.stem
|
||||
|
||||
# Create subfolder
|
||||
asset_folder = category_dir / asset_name
|
||||
asset_folder.mkdir(exist_ok=True)
|
||||
|
||||
# Load original
|
||||
img = Image.open(asset_path)
|
||||
orig_width, orig_height = img.size
|
||||
|
||||
# Save original with dimensions in name
|
||||
original_filename = f"{asset_name}_{orig_width}x{orig_height}.png"
|
||||
original_path = asset_folder / original_filename
|
||||
img.save(original_path, 'PNG')
|
||||
|
||||
print(f" 📁 {asset_name}/")
|
||||
print(f" ✅ Original: {original_filename} ({orig_width}×{orig_height})")
|
||||
|
||||
# Create resized version if needed
|
||||
if target_size is not None:
|
||||
if isinstance(target_size, tuple):
|
||||
resized = img.resize(target_size, Image.Resampling.LANCZOS)
|
||||
new_width, new_height = target_size
|
||||
else:
|
||||
resized = img.resize((target_size, target_size), Image.Resampling.LANCZOS)
|
||||
new_width, new_height = target_size, target_size
|
||||
|
||||
resized_filename = f"{asset_name}_{new_width}x{new_height}.png"
|
||||
resized_path = asset_folder / resized_filename
|
||||
resized.save(resized_path, 'PNG', optimize=True)
|
||||
|
||||
print(f" ✅ Resized: {resized_filename} ({new_width}×{new_height})")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ Error with {asset_path.name}: {e}")
|
||||
return False
|
||||
|
||||
def process_category(base_dir: Path, category: str):
|
||||
"""Process all assets in category"""
|
||||
|
||||
category_path = base_dir / category
|
||||
if not category_path.exists():
|
||||
return 0
|
||||
|
||||
print(f"\n{'='*70}")
|
||||
print(f"📂 CATEGORY: {category}/")
|
||||
print(f"{'='*70}")
|
||||
|
||||
# Get all PNG files (not in subfolders)
|
||||
png_files = [f for f in category_path.glob('*.png') if f.is_file()]
|
||||
|
||||
if not png_files:
|
||||
print(" No PNG files found in root")
|
||||
return 0
|
||||
|
||||
count = 0
|
||||
for png_file in png_files:
|
||||
target_size = get_target_size(category, png_file.stem)
|
||||
if reorganize_asset(png_file, category_path, target_size):
|
||||
# Remove original file after successful reorganization
|
||||
png_file.unlink()
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
def main():
|
||||
print("=" * 70)
|
||||
print("🗂️ ASSET REORGANIZATION: SUBFOLDERS WITH ORIGINALS + RESIZED")
|
||||
print("=" * 70)
|
||||
|
||||
demo_dir = Path('assets/images/demo')
|
||||
print(f"\n📦 Processing: {demo_dir}")
|
||||
|
||||
total = 0
|
||||
for category in RESIZE_CONFIG.keys():
|
||||
if category != 'environment':
|
||||
total += process_category(demo_dir, category)
|
||||
|
||||
total += process_category(demo_dir, 'environment')
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print(f"✅ COMPLETE! Reorganized {total} assets")
|
||||
print("=" * 70)
|
||||
print("\n📁 Structure: category/assetname/")
|
||||
print(" - assetname_1024x1024.png (original)")
|
||||
print(" - assetname_32x32.png (resized)")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
129
scripts/resize_for_tiled.py
Normal file
129
scripts/resize_for_tiled.py
Normal file
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Resize all demo assets to proper tile sizes for Tiled editor
|
||||
Converts 1024x1024 generated images to game-appropriate sizes
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
|
||||
# Resize configurations
|
||||
RESIZE_CONFIG = {
|
||||
'terrain': 64, # 64x64 tiles
|
||||
'crops': 64, # 64x64 tiles
|
||||
'buildings': 64, # 64x64 (tent is square)
|
||||
'items': 32, # 32x32 for inventory items
|
||||
'ui': None, # Keep original size for UI
|
||||
'effects': 48, # 48x48 for effects
|
||||
'environment': { # Variable sizes
|
||||
'campfire': 64,
|
||||
'dead_tree': (64, 96), # Tall sprite
|
||||
'rock': (48, 32) # Wide sprite
|
||||
},
|
||||
'characters': 64, # 64x64 for character sprites
|
||||
'enemies': 64 # 64x64 for enemies
|
||||
}
|
||||
|
||||
def resize_image(input_path: Path, output_path: Path, size):
|
||||
"""Resize image maintaining aspect ratio or forcing size"""
|
||||
|
||||
try:
|
||||
img = Image.open(input_path)
|
||||
|
||||
if size is None:
|
||||
# Keep original
|
||||
if input_path != output_path:
|
||||
img.save(output_path, 'PNG')
|
||||
return True
|
||||
|
||||
if isinstance(size, tuple):
|
||||
# Force exact size (width, height)
|
||||
resized = img.resize(size, Image.Resampling.LANCZOS)
|
||||
else:
|
||||
# Square resize
|
||||
resized = img.resize((size, size), Image.Resampling.LANCZOS)
|
||||
|
||||
resized.save(output_path, 'PNG', optimize=True)
|
||||
print(f" ✅ {output_path.name}: {img.size} → {resized.size}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ Error with {input_path.name}: {e}")
|
||||
return False
|
||||
|
||||
def get_target_size(category: str, filename: str):
|
||||
"""Determine target size based on category and filename"""
|
||||
|
||||
config = RESIZE_CONFIG.get(category)
|
||||
|
||||
if config is None:
|
||||
return None
|
||||
|
||||
if isinstance(config, dict):
|
||||
# Environment has variable sizes
|
||||
for key, size in config.items():
|
||||
if key in filename:
|
||||
return size
|
||||
return 64 # Default for environment
|
||||
|
||||
return config
|
||||
|
||||
def resize_category(base_dir: Path, category: str):
|
||||
"""Resize all images in a category folder"""
|
||||
|
||||
category_path = base_dir / category
|
||||
if not category_path.exists():
|
||||
print(f"⚠️ Skipping {category} (not found)")
|
||||
return 0
|
||||
|
||||
print(f"\n📁 Processing: {category}/")
|
||||
|
||||
png_files = list(category_path.glob('*.png'))
|
||||
if not png_files:
|
||||
print(f" No PNG files found")
|
||||
return 0
|
||||
|
||||
count = 0
|
||||
for png_file in png_files:
|
||||
size = get_target_size(category, png_file.stem)
|
||||
if resize_image(png_file, png_file, size):
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("🖼️ RESIZING DEMO ASSETS FOR TILED")
|
||||
print("=" * 60)
|
||||
|
||||
# Resize transparent assets
|
||||
demo_dir = Path('assets/images/demo')
|
||||
print(f"\n📦 TRANSPARENT ASSETS: {demo_dir}")
|
||||
|
||||
total = 0
|
||||
for category in RESIZE_CONFIG.keys():
|
||||
if category != 'environment': # Handle environment specially
|
||||
total += resize_category(demo_dir, category)
|
||||
|
||||
# Environment with variable sizes
|
||||
total += resize_category(demo_dir, 'environment')
|
||||
|
||||
# Resize white background originals
|
||||
orig_dir = Path('assets/images/demo_originals_with_white_bg')
|
||||
if orig_dir.exists():
|
||||
print(f"\n📦 WHITE BG ORIGINALS: {orig_dir}")
|
||||
|
||||
for category in RESIZE_CONFIG.keys():
|
||||
if category != 'environment':
|
||||
total += resize_category(orig_dir, category)
|
||||
|
||||
total += resize_category(orig_dir, 'environment')
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print(f"✅ COMPLETE! Resized {total} images")
|
||||
print("=" * 60)
|
||||
print("\n🗺️ Ready for Tiled! All assets now proper game sizes.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
119
scripts/resize_to_32px.py
Normal file
119
scripts/resize_to_32px.py
Normal file
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Resize to PROPER INDIE GAME sizes - 32x32 standard!
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
|
||||
# CORRECT sizes for indie games
|
||||
RESIZE_CONFIG = {
|
||||
'terrain': 32, # 32x32 tiles (Stardew Valley standard!)
|
||||
'crops': 32, # 32x32 tiles
|
||||
'buildings': 32, # 32x32
|
||||
'items': 16, # 16x16 for inventory items (smaller!)
|
||||
'ui': None, # Keep original
|
||||
'effects': 24, # 24x24 for effects
|
||||
'environment': { # Variable sizes
|
||||
'campfire': 32,
|
||||
'dead_tree': (32, 48), # Tall sprite
|
||||
'rock': (24, 16) # Wide sprite
|
||||
},
|
||||
'characters': 32, # 32x32 for characters
|
||||
'enemies': 32 # 32x32 for enemies
|
||||
}
|
||||
|
||||
def resize_image(input_path: Path, output_path: Path, size):
|
||||
"""Resize image maintaining quality"""
|
||||
|
||||
try:
|
||||
img = Image.open(input_path)
|
||||
|
||||
if size is None:
|
||||
if input_path != output_path:
|
||||
img.save(output_path, 'PNG')
|
||||
return True
|
||||
|
||||
if isinstance(size, tuple):
|
||||
resized = img.resize(size, Image.Resampling.LANCZOS)
|
||||
else:
|
||||
resized = img.resize((size, size), Image.Resampling.LANCZOS)
|
||||
|
||||
resized.save(output_path, 'PNG', optimize=True)
|
||||
print(f" ✅ {output_path.name}: {img.size} → {resized.size}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ {input_path.name}: {e}")
|
||||
return False
|
||||
|
||||
def get_target_size(category: str, filename: str):
|
||||
"""Determine target size"""
|
||||
|
||||
config = RESIZE_CONFIG.get(category)
|
||||
|
||||
if config is None:
|
||||
return None
|
||||
|
||||
if isinstance(config, dict):
|
||||
for key, size in config.items():
|
||||
if key in filename:
|
||||
return size
|
||||
return 32 # Default
|
||||
|
||||
return config
|
||||
|
||||
def resize_category(base_dir: Path, category: str):
|
||||
"""Resize all images in category"""
|
||||
|
||||
category_path = base_dir / category
|
||||
if not category_path.exists():
|
||||
return 0
|
||||
|
||||
print(f"\n📁 {category}/")
|
||||
|
||||
png_files = list(category_path.glob('*.png'))
|
||||
if not png_files:
|
||||
return 0
|
||||
|
||||
count = 0
|
||||
for png_file in png_files:
|
||||
size = get_target_size(category, png_file.stem)
|
||||
if resize_image(png_file, png_file, size):
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("🎮 RESIZING TO PROPER INDIE GAME SIZES (32×32 STANDARD)")
|
||||
print("=" * 60)
|
||||
|
||||
demo_dir = Path('assets/images/demo')
|
||||
print(f"\n📦 Transparent assets: {demo_dir}")
|
||||
|
||||
total = 0
|
||||
for category in RESIZE_CONFIG.keys():
|
||||
if category != 'environment':
|
||||
total += resize_category(demo_dir, category)
|
||||
|
||||
total += resize_category(demo_dir, 'environment')
|
||||
|
||||
orig_dir = Path('assets/images/demo_originals_with_white_bg')
|
||||
if orig_dir.exists():
|
||||
print(f"\n📦 White BG originals: {orig_dir}")
|
||||
|
||||
for category in RESIZE_CONFIG.keys():
|
||||
if category != 'environment':
|
||||
total += resize_category(orig_dir, category)
|
||||
|
||||
total += resize_category(orig_dir, 'environment')
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print(f"✅ DONE! Resized {total} images to 32×32 standard!")
|
||||
print("=" * 60)
|
||||
print("\n🎮 Perfect for indie games like Stardew Valley!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
163
scripts/tile_align_and_rename.py
Normal file
163
scripts/tile_align_and_rename.py
Normal file
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Resize all assets to EXACT multiples of 32px tile size
|
||||
Rename files to include dimensions: filename_WxH.png
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
import shutil
|
||||
|
||||
# Exact tile-aligned sizes (multiples of 32)
|
||||
TILE_SIZE = 32
|
||||
|
||||
RESIZE_CONFIG = {
|
||||
'terrain': 32, # 1×1 tile
|
||||
'crops': 32, # 1×1 tile
|
||||
'buildings': 32, # 1×1 tile (tent)
|
||||
'items': 16, # 0.5×0.5 tile (inventory items)
|
||||
'ui': None, # Keep original
|
||||
'effects': 32, # 1×1 tile (changed from 24)
|
||||
'environment': {
|
||||
'campfire': 32, # 1×1 tile
|
||||
'dead_tree': (32, 64), # 1×2 tiles (changed from 32×48)
|
||||
'rock': 32 # 1×1 tile (changed from 24×16)
|
||||
},
|
||||
'characters': 32, # 1×1 tile
|
||||
'enemies': 32 # 1×1 tile
|
||||
}
|
||||
|
||||
def resize_and_rename(input_path: Path, output_dir: Path, size, backup=True):
|
||||
"""Resize image and rename with dimensions"""
|
||||
|
||||
try:
|
||||
img = Image.open(input_path)
|
||||
|
||||
# Backup original if requested
|
||||
if backup and input_path.parent == output_dir:
|
||||
backup_path = input_path.parent / "originals" / input_path.name
|
||||
backup_path.parent.mkdir(exist_ok=True)
|
||||
if not backup_path.exists():
|
||||
shutil.copy2(input_path, backup_path)
|
||||
|
||||
# Keep original if size is None
|
||||
if size is None:
|
||||
return True
|
||||
|
||||
# Resize
|
||||
if isinstance(size, tuple):
|
||||
resized = img.resize(size, Image.Resampling.LANCZOS)
|
||||
new_width, new_height = size
|
||||
else:
|
||||
resized = img.resize((size, size), Image.Resampling.LANCZOS)
|
||||
new_width, new_height = size, size
|
||||
|
||||
# Generate new filename with dimensions
|
||||
stem = input_path.stem
|
||||
|
||||
# Remove old dimensions if present
|
||||
if '_' in stem:
|
||||
parts = stem.rsplit('_', 1)
|
||||
if 'x' in parts[-1] or parts[-1].replace('x', '').replace('style', '').isdigit():
|
||||
# Keep it as is for style suffixes
|
||||
base_name = stem
|
||||
else:
|
||||
base_name = stem
|
||||
else:
|
||||
base_name = stem
|
||||
|
||||
# New filename: originalname_WxH.png
|
||||
new_filename = f"{base_name}_{new_width}x{new_height}.png"
|
||||
output_path = output_dir / new_filename
|
||||
|
||||
# Save
|
||||
resized.save(output_path, 'PNG', optimize=True)
|
||||
|
||||
# Delete old file if different name
|
||||
if output_path != input_path and input_path.exists():
|
||||
input_path.unlink()
|
||||
|
||||
print(f" ✅ {input_path.name} → {new_filename} ({img.size} → {resized.size})")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ {input_path.name}: {e}")
|
||||
return False
|
||||
|
||||
def get_target_size(category: str, filename: str):
|
||||
"""Get target size for category"""
|
||||
|
||||
config = RESIZE_CONFIG.get(category)
|
||||
|
||||
if config is None:
|
||||
return None
|
||||
|
||||
if isinstance(config, dict):
|
||||
for key, size in config.items():
|
||||
if key in filename:
|
||||
return size
|
||||
return 32 # Default
|
||||
|
||||
return config
|
||||
|
||||
def process_category(base_dir: Path, category: str):
|
||||
"""Process all images in category"""
|
||||
|
||||
category_path = base_dir / category
|
||||
if not category_path.exists():
|
||||
return 0
|
||||
|
||||
print(f"\n📁 {category}/")
|
||||
|
||||
png_files = list(category_path.glob('*.png'))
|
||||
if not png_files:
|
||||
return 0
|
||||
|
||||
count = 0
|
||||
for png_file in png_files:
|
||||
# Skip already processed files with dimensions
|
||||
if any(x in png_file.stem for x in ['_32x32', '_16x16', '_32x64', '_24x24']):
|
||||
continue
|
||||
|
||||
size = get_target_size(category, png_file.stem)
|
||||
if resize_and_rename(png_file, category_path, size):
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
def main():
|
||||
print("=" * 70)
|
||||
print("📏 TILE-ALIGNED RESIZE & RENAME (Multiples of 32px)")
|
||||
print("=" * 70)
|
||||
|
||||
demo_dir = Path('assets/images/demo')
|
||||
print(f"\n📦 Processing: {demo_dir}")
|
||||
|
||||
total = 0
|
||||
for category in RESIZE_CONFIG.keys():
|
||||
if category != 'environment':
|
||||
total += process_category(demo_dir, category)
|
||||
|
||||
total += process_category(demo_dir, 'environment')
|
||||
|
||||
# White BG originals
|
||||
orig_dir = Path('assets/images/demo_originals_with_white_bg')
|
||||
if orig_dir.exists():
|
||||
print(f"\n📦 Processing: {orig_dir}")
|
||||
|
||||
for category in RESIZE_CONFIG.keys():
|
||||
if category != 'environment':
|
||||
total += process_category(orig_dir, category)
|
||||
|
||||
total += process_category(orig_dir, 'environment')
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print(f"✅ COMPLETE! Processed {total} images")
|
||||
print("=" * 70)
|
||||
print("\n📐 All sizes now multiples of 32px!")
|
||||
print("📝 All files renamed with dimensions!")
|
||||
print("\n🎮 Perfect for Tiled!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user