Tiled Map Setup: Ground tiles, ruins & automated asset processing

Features:
- Resized 4513 PNG assets to 40% for optimal Tiled performance
- Created comprehensive tileset library (grass, dirt, trees, flowers, ruins, walls)
- Generated 3 test maps: travnik_32x32, zapuscena_vas_48x48, travnik_s_objekti
- Added 9 different ruined building tilesets for TownRestorationSystem integration

 Tools Added:
- resize_assets_for_tiled.py: Batch resize all assets to 40%
- generate_tiled_map.py: Auto-generate maps with placed objects
- fix_tiled_map.py: Create proper tile-based maps

 Structure:
- Slike_za_Tiled/: 4513 resized assets ready for Tiled
- assets/tilesets/: 16 tileset definitions (.tsx files)
- assets/maps/: 3 ready-to-use Tiled maps (.tmx files)

 Documentation:
- docs/TILED_SETUP_GUIDE.md: Complete setup and usage guide

Ready for map design in Tiled Map Editor!
This commit is contained in:
2025-12-24 03:41:40 +01:00
parent 639dec504c
commit a4d795c561
4539 changed files with 929 additions and 0 deletions

46
tools/fix_tiled_map.py Normal file
View File

@@ -0,0 +1,46 @@
"""
Generate improved Tiled map with proper tile layers (not objects)
"""
import os
OUTPUT_MAP = r"c:\novafarma\assets\maps\travnik_s_objekti.tmx"
MAP_WIDTH = 32
MAP_HEIGHT = 32
TILE_WIDTH = 48
TILE_HEIGHT = 48
def create_simple_grass_map():
"""Create a simple map with grass ground layer"""
# Create CSV data for ground - all grass (tile ID 1)
ground_data = []
for y in range(MAP_HEIGHT):
row = ["1"] * MAP_WIDTH
ground_data.append(",".join(row))
ground_csv = "\n".join(ground_data)
xml_content = f'''<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.11.1" orientation="orthogonal" renderorder="right-down" width="{MAP_WIDTH}" height="{MAP_HEIGHT}" tilewidth="{TILE_WIDTH}" tileheight="{TILE_HEIGHT}" infinite="0" nextlayerid="2" nextobjectid="1">
<tileset firstgid="1" name="grass_single" tilewidth="48" tileheight="48" tilecount="1" columns="1">
<image source="../../tilesets/grass.png" width="48" height="48"/>
</tileset>
<layer id="1" name="Ground" width="{MAP_WIDTH}" height="{MAP_HEIGHT}">
<data encoding="csv">
{ground_csv}
</data>
</layer>
</map>'''
os.makedirs(os.path.dirname(OUTPUT_MAP), exist_ok=True)
with open(OUTPUT_MAP, 'w', encoding='utf-8') as f:
f.write(xml_content)
print(f"✅ Created simple grass map: {OUTPUT_MAP}")
print(f"📏 Size: {MAP_WIDTH}x{MAP_HEIGHT} tiles")
print(f"🟢 Ground layer: All grass")
print(f"\n🚀 Open in Tiled to see the grass!")
if __name__ == "__main__":
create_simple_grass_map()

197
tools/generate_tiled_map.py Normal file
View File

@@ -0,0 +1,197 @@
"""
Generate Tiled map with Collection of Images tileset
Automatically places dirt, trees, and Kai character
"""
import os
import random
import xml.etree.ElementTree as ET
from xml.dom import minidom
# Configurations
SLIKE_DIR = r"c:\novafarma\Slike_za_Tiled"
OUTPUT_MAP = r"c:\novafarma\assets\maps\auto_generated_map.tmx"
MAP_WIDTH = 32
MAP_HEIGHT = 32
TILE_WIDTH = 48
TILE_HEIGHT = 48
def find_assets():
"""Find required assets in Slike_za_Tiled folder"""
assets = {
'dirt': [],
'trees': [],
'kai': None
}
if not os.path.exists(SLIKE_DIR):
print(f"❌ Error: {SLIKE_DIR} does not exist!")
print(" Run resize_assets_for_tiled.py first!")
return None
for filename in os.listdir(SLIKE_DIR):
if not filename.endswith('.png'):
continue
lower_name = filename.lower()
filepath = os.path.join(SLIKE_DIR, filename)
# Find dirt/soil tiles
if 'dirt' in lower_name or 'soil' in lower_name or 'ground' in lower_name:
assets['dirt'].append(filepath)
# Find trees
if 'tree' in lower_name:
assets['trees'].append(filepath)
# Find Kai character
if 'kai' in lower_name or 'character' in lower_name or 'player' in lower_name:
if assets['kai'] is None: # Take first match
assets['kai'] = filepath
print(f"🔍 Found assets:")
print(f" - Dirt/Soil tiles: {len(assets['dirt'])}")
print(f" - Trees: {len(assets['trees'])}")
print(f" - Kai character: {'' if assets['kai'] else ''}")
return assets
def create_tiled_map():
"""Create Tiled map with auto-placed objects"""
# Find assets
assets = find_assets()
if not assets:
return
# Create map root
map_elem = ET.Element('map', {
'version': '1.10',
'tiledversion': '1.11.1',
'orientation': 'orthogonal',
'renderorder': 'right-down',
'width': str(MAP_WIDTH),
'height': str(MAP_HEIGHT),
'tilewidth': str(TILE_WIDTH),
'tileheight': str(TILE_HEIGHT),
'infinite': '0',
'nextlayerid': '3',
'nextobjectid': '20'
})
# Create object layer
objectgroup = ET.SubElement(map_elem, 'objectgroup', {
'id': '2',
'name': 'Objects'
})
obj_id = 1
# Place 8 dirt tiles in center (2x4 grid)
print("\n🌍 Placing 8 dirt tiles in center...")
center_x = MAP_WIDTH // 2 - 1
center_y = MAP_HEIGHT // 2 - 2
for i in range(8):
if assets['dirt']:
dirt_asset = random.choice(assets['dirt'])
x = (center_x + (i % 2)) * TILE_WIDTH
y = (center_y + (i // 2)) * TILE_HEIGHT
obj = ET.SubElement(objectgroup, 'object', {
'id': str(obj_id),
'name': f'dirt_{i+1}',
'x': str(x),
'y': str(y),
'width': str(TILE_WIDTH),
'height': str(TILE_HEIGHT)
})
ET.SubElement(obj, 'image', {
'source': os.path.relpath(dirt_asset, os.path.dirname(OUTPUT_MAP))
})
obj_id += 1
# Place 10 trees randomly around the dirt area
print("🌲 Placing 10 trees randomly...")
tree_positions = []
attempts = 0
max_attempts = 100
while len(tree_positions) < 10 and attempts < max_attempts:
attempts += 1
tree_x = random.randint(2, MAP_WIDTH - 3) * TILE_WIDTH
tree_y = random.randint(2, MAP_HEIGHT - 3) * TILE_HEIGHT
# Avoid center dirt area
if (center_x * TILE_WIDTH <= tree_x <= (center_x + 2) * TILE_WIDTH and
center_y * TILE_HEIGHT <= tree_y <= (center_y + 4) * TILE_HEIGHT):
continue
# Avoid placing trees too close together
too_close = False
for px, py in tree_positions:
if abs(tree_x - px) < TILE_WIDTH * 3 and abs(tree_y - py) < TILE_HEIGHT * 3:
too_close = True
break
if too_close:
continue
if assets['trees']:
tree_asset = random.choice(assets['trees'])
tree_positions.append((tree_x, tree_y))
obj = ET.SubElement(objectgroup, 'object', {
'id': str(obj_id),
'name': f'tree_{len(tree_positions)}',
'x': str(tree_x),
'y': str(tree_y),
'width': str(TILE_WIDTH * 2),
'height': str(TILE_HEIGHT * 2)
})
ET.SubElement(obj, 'image', {
'source': os.path.relpath(tree_asset, os.path.dirname(OUTPUT_MAP))
})
obj_id += 1
# Place Kai character near center
if assets['kai']:
print("🧑 Placing Kai character...")
kai_x = (center_x + 2) * TILE_WIDTH + 60
kai_y = center_y * TILE_HEIGHT + 80
obj = ET.SubElement(objectgroup, 'object', {
'id': str(obj_id),
'name': 'kai_player',
'x': str(kai_x),
'y': str(kai_y),
'width': str(TILE_WIDTH),
'height': str(TILE_HEIGHT)
})
ET.SubElement(obj, 'image', {
'source': os.path.relpath(assets['kai'], os.path.dirname(OUTPUT_MAP))
})
# Write XML to file
os.makedirs(os.path.dirname(OUTPUT_MAP), exist_ok=True)
# Pretty print XML
xml_str = ET.tostring(map_elem, encoding='unicode')
dom = minidom.parseString(xml_str)
pretty_xml = dom.toprettyxml(indent=' ', encoding='UTF-8')
with open(OUTPUT_MAP, 'wb') as f:
f.write(pretty_xml)
print(f"\n{'='*60}")
print(f"✨ TILED MAP CREATED!")
print(f"{'='*60}")
print(f"📁 Location: {OUTPUT_MAP}")
print(f"📏 Size: {MAP_WIDTH}x{MAP_HEIGHT} tiles")
print(f"🎮 Objects placed:")
print(f" - 8 dirt tiles (center)")
print(f" - {len(tree_positions)} trees (random)")
print(f" - 1 Kai character")
print(f"\n🚀 Open in Tiled Map Editor to continue editing!")
if __name__ == "__main__":
create_tiled_map()

View File

@@ -0,0 +1,82 @@
"""
Resize all PNG assets to 40% for Tiled Map Editor
Scans all subfolders in assets/ and resizes images
"""
import os
from PIL import Image
from pathlib import Path
# Configurations
SOURCE_DIR = r"c:\novafarma\assets"
OUTPUT_DIR = r"c:\novafarma\Slike_za_Tiled"
SCALE_FACTOR = 0.4 # 40% of original size
def resize_images():
"""Resize all PNG images to 40% and save to Slike_za_Tiled"""
# Create output directory
os.makedirs(OUTPUT_DIR, exist_ok=True)
# Track statistics
processed = 0
skipped = 0
errors = []
print(f"🔍 Scanning {SOURCE_DIR} for PNG files...")
print(f"📁 Output directory: {OUTPUT_DIR}")
print(f"📏 Scale factor: {SCALE_FACTOR * 100}%\n")
# Walk through all subdirectories
for root, dirs, files in os.walk(SOURCE_DIR):
for filename in files:
if filename.lower().endswith('.png'):
source_path = os.path.join(root, filename)
# Create unique output filename (flat structure)
# Use relative path to create unique name
rel_path = os.path.relpath(source_path, SOURCE_DIR)
output_filename = rel_path.replace(os.sep, '_')
output_path = os.path.join(OUTPUT_DIR, output_filename)
try:
# Open and resize image
with Image.open(source_path) as img:
original_size = img.size
new_size = (
int(img.width * SCALE_FACTOR),
int(img.height * SCALE_FACTOR)
)
# Resize with high-quality resampling
resized_img = img.resize(new_size, Image.Resampling.LANCZOS)
# Save resized image
resized_img.save(output_path, 'PNG', optimize=True)
processed += 1
if processed % 50 == 0:
print(f"✅ Processed {processed} images...")
except Exception as e:
errors.append(f"{filename}: {str(e)}")
skipped += 1
# Print summary
print(f"\n{'='*60}")
print(f"✨ RESIZE COMPLETE!")
print(f"{'='*60}")
print(f"✅ Successfully processed: {processed} images")
print(f"⚠️ Skipped (errors): {skipped} images")
print(f"📁 Output location: {OUTPUT_DIR}")
if errors:
print(f"\n❌ Errors encountered:")
for error in errors[:10]: # Show first 10 errors
print(f" - {error}")
if len(errors) > 10:
print(f" ... and {len(errors) - 10} more")
print(f"\n🎮 Ready for Tiled Map Editor!")
if __name__ == "__main__":
resize_images()