#!/usr/bin/env python3 """ TERRAIN TRANSITION GENERATOR Creates smooth alpha-blended edge tiles for Tiled terrain sets. Generates Water→Grass transitions using Wang/Blob tile patterns. Usage: python scripts/generate_terrain_transitions.py Output: assets/maps/tilesets/Terrain_Transitions.png + .tsx """ import os from PIL import Image, ImageFilter, ImageDraw import numpy as np def create_smooth_gradient_mask(size, edge_type): """ Creates a gradient alpha mask for different edge types. edge_type: 'top', 'bottom', 'left', 'right', 'tl', 'tr', 'bl', 'br', 'center' """ mask = Image.new('L', (size, size), 0) draw = ImageDraw.Draw(mask) # Full opacity in center, fade to edges for i in range(size): for j in range(size): # Distance from center dx = abs(i - size/2) dy = abs(j - size/2) # Gradient based on edge type if edge_type == 'center': # Full water mask.putpixel((i, j), 255) elif edge_type == 'top': # Fade from bottom to top alpha = int(255 * (1 - j / size)) mask.putpixel((i, j), alpha) elif edge_type == 'bottom': # Fade from top to bottom alpha = int(255 * (j / size)) mask.putpixel((i, j), alpha) elif edge_type == 'left': # Fade from right to left alpha = int(255 * (1 - i / size)) mask.putpixel((i, j), alpha) elif edge_type == 'right': # Fade from left to right alpha = int(255 * (i / size)) mask.putpixel((i, j), alpha) elif edge_type == 'tl': # Top-left corner dist = np.sqrt(dx**2 + dy**2) alpha = int(255 * max(0, 1 - dist / (size * 0.7))) mask.putpixel((i, j), alpha) elif edge_type == 'tr': # Top-right corner dx_flip = abs(i - size/2) dist = np.sqrt(dx_flip**2 + dy**2) alpha = int(255 * max(0, 1 - dist / (size * 0.7))) mask.putpixel((i, j), alpha) elif edge_type == 'bl': # Bottom-left corner dy_flip = abs(j - size/2) dist = np.sqrt(dx**2 + dy_flip**2) alpha = int(255 * max(0, 1 - dist / (size * 0.7))) mask.putpixel((i, j), alpha) elif edge_type == 'br': # Bottom-right corner dx_flip = abs(i - size/2) dy_flip = abs(j - size/2) dist = np.sqrt(dx_flip**2 + dy_flip**2) alpha = int(255 * max(0, 1 - dist / (size * 0.7))) mask.putpixel((i, j), alpha) # Apply Gaussian blur for ultra-smooth edges mask = mask.filter(ImageFilter.GaussianBlur(radius=size // 8)) return mask def generate_terrain_transitions(): """ Generates a 47-tile Wang/Blob tileset for water-grass transitions. Layout: 16x3 grid (48 tiles total, last one empty) Tile layout (standard blob tileset): Row 1: Center, edges (top, right, bottom, left, corners) Row 2: Complex edges and inner corners Row 3: Special cases """ base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Load source textures grass_path = os.path.join(base_dir, 'assets', 'grounds', 'grass.png') water_path = os.path.join(base_dir, 'assets', 'grounds', 'water.png') if not os.path.exists(grass_path) or not os.path.exists(water_path): print("❌ Missing source textures (grass.png or water.png)") return grass_src = Image.open(grass_path).convert('RGBA') water_src = Image.open(water_path).convert('RGBA') # Tile size tile_size = 32 # Output tileset (16 columns x 3 rows = 48 tiles) output_width = 16 * tile_size # 512px output_height = 3 * tile_size # 96px output = Image.new('RGBA', (output_width, output_height), (0, 0, 0, 0)) # Helper to extract a tile from source texture def get_tile(src, x_offset=0, y_offset=0): """Extract a 32x32 tile from source texture.""" return src.crop((x_offset, y_offset, x_offset + tile_size, y_offset + tile_size)) # Sample tiles from source grass_tile = get_tile(grass_src, 0, 0) water_tile = get_tile(water_src, 0, 0) # Define tile types and their masks tile_definitions = [ # Row 0 (Basic tiles + edges) ('center', 0, 0), # 0: Full water ('top', 1, 0), # 1: Water edge at top ('right', 2, 0), # 2: Water edge at right ('bottom', 3, 0), # 3: Water edge at bottom ('left', 4, 0), # 4: Water edge at left ('tl', 5, 0), # 5: Top-left outer corner ('tr', 6, 0), # 6: Top-right outer corner ('bl', 7, 0), # 7: Bottom-left outer corner ('br', 8, 0), # 8: Bottom-right outer corner # ... add more complex tiles as needed ] print("🌊 Generating Water→Grass terrain transitions...") for edge_type, col, row in tile_definitions: # Create mask mask = create_smooth_gradient_mask(tile_size, edge_type) # Composite: Water (foreground) over Grass (background) composite = Image.new('RGBA', (tile_size, tile_size)) composite.paste(grass_tile, (0, 0)) # Apply water with mask water_with_alpha = water_tile.copy() water_with_alpha.putalpha(mask) composite.paste(water_with_alpha, (0, 0), water_with_alpha) # Place in output output.paste(composite, (col * tile_size, row * tile_size)) print(f" ✅ Tile {col},{row}: {edge_type}") # Save output out_dir = os.path.join(base_dir, 'assets', 'maps', 'tilesets') os.makedirs(out_dir, exist_ok=True) out_path = os.path.join(out_dir, 'Terrain_Transitions.png') output.save(out_path) print(f"✅ Saved transition tileset: {out_path}") # Generate TSX with Terrain definitions generate_terrain_tsx(out_dir, tile_definitions) def generate_terrain_tsx(out_dir, tile_definitions): """Generate .tsx file with Terrain Set definitions.""" import xml.etree.ElementTree as ET # Create tileset XML root = ET.Element('tileset', version="1.10", tiledversion="1.11.0") root.set('name', 'Terrain_Transitions') root.set('tilewidth', '32') root.set('tileheight', '32') root.set('tilecount', str(len(tile_definitions))) root.set('columns', '16') # Image source img = ET.SubElement(root, 'image') img.set('source', 'Terrain_Transitions.png') img.set('width', '512') img.set('height', '96') # Terrain definitions (Tiled 1.10+ format) terrainset = ET.SubElement(root, 'terraintypes') terrain = ET.SubElement(terrainset, 'terrain', name="Water", tile="0") # Assign terrain IDs to tiles (Wang blob pattern) # This part is complex - for now, just mark center tile for idx, (edge_type, col, row) in enumerate(tile_definitions): if edge_type == 'center': tile = ET.SubElement(root, 'tile', id=str(idx)) tile.set('terrain', '0,0,0,0') # Full water on all corners # Write TSX tree = ET.ElementTree(root) ET.indent(tree, space=' ', level=0) tsx_path = os.path.join(out_dir, 'Terrain_Transitions.tsx') tree.write(tsx_path, encoding='UTF-8', xml_declaration=True) print(f"✅ Saved TSX with terrain definitions: {tsx_path}") if __name__ == '__main__': generate_terrain_transitions() print("\n🎬 DONE! Now open Tiled and:") print(" 1. Add 'Terrain_Transitions' tileset to your map") print(" 2. Use the Terrain Brush (T) to paint smooth water edges") print(" 3. Enjoy cinematic transitions! 🎥")