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:
46
tools/fix_tiled_map.py
Normal file
46
tools/fix_tiled_map.py
Normal 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
197
tools/generate_tiled_map.py
Normal 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()
|
||||
82
tools/resize_assets_for_tiled.py
Normal file
82
tools/resize_assets_for_tiled.py
Normal 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()
|
||||
Reference in New Issue
Block a user