chore: Update LDtk project with new layers and fixed assets dimensions (using sips)
This commit is contained in:
74
scripts/create_dreamy_intro.py
Normal file
74
scripts/create_dreamy_intro.py
Normal file
@@ -0,0 +1,74 @@
|
||||
|
||||
import os
|
||||
from PIL import Image, ImageFilter, ImageEnhance
|
||||
|
||||
def process_dreamy_intro():
|
||||
# Paths
|
||||
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
src_dir = os.path.join(base_dir, 'assets', 'references', 'intro_shots')
|
||||
out_dir = os.path.join(base_dir, 'assets', 'images', 'intro_sequence')
|
||||
|
||||
if not os.path.exists(src_dir):
|
||||
print(f"Error: Source directory not found: {src_dir}")
|
||||
return
|
||||
|
||||
if not os.path.exists(out_dir):
|
||||
os.makedirs(out_dir)
|
||||
print(f"Created output directory: {out_dir}")
|
||||
|
||||
# List of key images to process
|
||||
target_files = [
|
||||
'otac_longboard_pier.png',
|
||||
'birthday_cake_rd.png',
|
||||
'kai_first_dreads_family.png',
|
||||
'ana_barbershop_dreads.png',
|
||||
'zombie_silhouettes_panic.png',
|
||||
'chaos_streets_apocalypse.png',
|
||||
'parents_transparent_ghosts.png',
|
||||
'family_portrait_complete.png'
|
||||
]
|
||||
|
||||
print("--- 🌫️ CREATING DREAMY FILTER VERSIONS 🌫️ ---")
|
||||
|
||||
for filename in target_files:
|
||||
src_path = os.path.join(src_dir, filename)
|
||||
|
||||
if not os.path.exists(src_path):
|
||||
print(f"⚠️ Missing: {filename}")
|
||||
continue
|
||||
|
||||
try:
|
||||
# Load Clean Image
|
||||
img = Image.open(src_path).convert('RGB')
|
||||
|
||||
# 1. Save Clean Version (Optimized/Resized if needed, but keeping original size for now)
|
||||
clean_name = filename.replace('.png', '_clean.png')
|
||||
clean_path = os.path.join(out_dir, clean_name)
|
||||
img.save(clean_path)
|
||||
print(f"✅ Saved Clean: {clean_name}")
|
||||
|
||||
# 2. Create Dreamy/Blur Version
|
||||
# Effect: Gaussian Blur + slightly increased Brightness (bloom effect)
|
||||
blur_img = img.filter(ImageFilter.GaussianBlur(radius=15))
|
||||
|
||||
# Add "Bloom" (Brighten light areas)
|
||||
enhancer = ImageEnhance.Brightness(blur_img)
|
||||
blur_img = enhancer.enhance(1.2)
|
||||
|
||||
# Add slight saturation decrease for "fade" look?
|
||||
# sat_enhancer = ImageEnhance.Color(blur_img)
|
||||
# blur_img = sat_enhancer.enhance(0.8)
|
||||
|
||||
blur_name = filename.replace('.png', '_dreamy.png')
|
||||
blur_path = os.path.join(out_dir, blur_name)
|
||||
blur_img.save(blur_path)
|
||||
print(f"🌫️ Saved Dreamy: {blur_name}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error processing {filename}: {e}")
|
||||
|
||||
print("--- DONE ---")
|
||||
print(f"Images are ready in: {out_dir}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
process_dreamy_intro()
|
||||
323
scripts/generate_full_terrain_sets.py
Normal file
323
scripts/generate_full_terrain_sets.py
Normal file
@@ -0,0 +1,323 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
FULL 47-TILE WANG BLOB TERRAIN GENERATOR
|
||||
Creates complete terrain transition sets (like Clear Code tutorial).
|
||||
Generates smooth alpha-blended edges for multiple terrain pairs.
|
||||
|
||||
Terrain Pairs:
|
||||
1. Sand → Water
|
||||
2. Dirt → Grass
|
||||
3. Grass → Water
|
||||
|
||||
Usage: python3 scripts/generate_full_terrain_sets.py
|
||||
"""
|
||||
|
||||
import os
|
||||
from PIL import Image, ImageFilter, ImageDraw
|
||||
import numpy as np
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
def create_gradient_mask(size, pattern):
|
||||
"""
|
||||
Creates alpha mask for 47 different Wang Blob tile patterns.
|
||||
|
||||
Pattern format: 4-char string representing corners (TL, TR, BL, BR)
|
||||
'0' = terrain A (background), '1' = terrain B (foreground)
|
||||
Example: '1111' = full terrain B, '1100' = top edge, etc.
|
||||
"""
|
||||
mask = Image.new('L', (size, size), 0)
|
||||
pixels = mask.load()
|
||||
|
||||
# Parse pattern
|
||||
tl = int(pattern[0]) if len(pattern) > 0 else 0
|
||||
tr = int(pattern[1]) if len(pattern) > 1 else 0
|
||||
bl = int(pattern[2]) if len(pattern) > 2 else 0
|
||||
br = int(pattern[3]) if len(pattern) > 3 else 0
|
||||
|
||||
# Generate smooth gradient based on corner states
|
||||
for y in range(size):
|
||||
for x in range(size):
|
||||
# Distance from center of each quadrant
|
||||
cx, cy = size / 2, size / 2
|
||||
|
||||
# Determine which quadrant this pixel is in
|
||||
in_left = x < cx
|
||||
in_top = y < cy
|
||||
|
||||
# Get corner values for this quadrant
|
||||
if in_top and in_left:
|
||||
corner_val = tl
|
||||
elif in_top and not in_left:
|
||||
corner_val = tr
|
||||
elif not in_top and in_left:
|
||||
corner_val = bl
|
||||
else:
|
||||
corner_val = br
|
||||
|
||||
# Distance from edges
|
||||
dist_left = x
|
||||
dist_right = size - x
|
||||
dist_top = y
|
||||
dist_bottom = size - y
|
||||
|
||||
# Calculate alpha based on corner values and distances
|
||||
alpha = 0
|
||||
|
||||
if pattern == '1111':
|
||||
# Full tile
|
||||
alpha = 255
|
||||
elif pattern == '0000':
|
||||
# Empty tile
|
||||
alpha = 0
|
||||
elif pattern == '1100':
|
||||
# Top edge
|
||||
alpha = int(255 * (1 - y / size))
|
||||
elif pattern == '0011':
|
||||
# Bottom edge
|
||||
alpha = int(255 * (y / size))
|
||||
elif pattern == '1010':
|
||||
# Left edge
|
||||
alpha = int(255 * (1 - x / size))
|
||||
elif pattern == '0101':
|
||||
# Right edge
|
||||
alpha = int(255 * (x / size))
|
||||
elif pattern == '1000':
|
||||
# Top-left outer corner
|
||||
dx = x - cx
|
||||
dy = y - cy
|
||||
dist = np.sqrt(dx**2 + dy**2)
|
||||
alpha = int(255 * max(0, 1 - dist / (size * 0.7)))
|
||||
elif pattern == '0100':
|
||||
# Top-right outer corner
|
||||
dx = (size - x) - cx
|
||||
dy = y - cy
|
||||
dist = np.sqrt(dx**2 + dy**2)
|
||||
alpha = int(255 * max(0, 1 - dist / (size * 0.7)))
|
||||
elif pattern == '0010':
|
||||
# Bottom-left outer corner
|
||||
dx = x - cx
|
||||
dy = (size - y) - cy
|
||||
dist = np.sqrt(dx**2 + dy**2)
|
||||
alpha = int(255 * max(0, 1 - dist / (size * 0.7)))
|
||||
elif pattern == '0001':
|
||||
# Bottom-right outer corner
|
||||
dx = (size - x) - cx
|
||||
dy = (size - y) - cy
|
||||
dist = np.sqrt(dx**2 + dy**2)
|
||||
alpha = int(255 * max(0, 1 - dist / (size * 0.7)))
|
||||
elif pattern == '0110':
|
||||
# Top edge with right corner
|
||||
if x < cx:
|
||||
alpha = int(255 * (1 - y / size))
|
||||
else:
|
||||
dx = (size - x) - cx
|
||||
dy = y - cy
|
||||
dist = np.sqrt(dx**2 + dy**2)
|
||||
alpha = int(255 * max(0, 1 - dist / (size * 0.7)))
|
||||
# Add more complex patterns as needed...
|
||||
else:
|
||||
# Fallback: interpolate based on corner values
|
||||
# Bilinear interpolation
|
||||
t = y / size
|
||||
s = x / size
|
||||
|
||||
top = (1 - s) * tl + s * tr
|
||||
bottom = (1 - s) * bl + s * br
|
||||
val = (1 - t) * top + t * bottom
|
||||
|
||||
alpha = int(255 * val)
|
||||
|
||||
pixels[x, y] = max(0, min(255, alpha))
|
||||
|
||||
# Apply Gaussian blur for ultra-smooth edges
|
||||
mask = mask.filter(ImageFilter.GaussianBlur(radius=size // 8))
|
||||
return mask
|
||||
|
||||
|
||||
def generate_47_tile_wang_blob(terrain_a_path, terrain_b_path, output_name):
|
||||
"""
|
||||
Generate full 47-tile Wang Blob terrain set.
|
||||
|
||||
Layout: 16 columns x 3 rows (48 tiles, last one empty)
|
||||
|
||||
Args:
|
||||
terrain_a_path: Path to background terrain (e.g., grass)
|
||||
terrain_b_path: Path to foreground terrain (e.g., water)
|
||||
output_name: Output tileset name (e.g., 'Terrain_Grass_Water')
|
||||
"""
|
||||
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
out_dir = os.path.join(base_dir, 'assets', 'maps', 'tilesets')
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
|
||||
# Load source textures
|
||||
terrain_a = Image.open(terrain_a_path).convert('RGBA')
|
||||
terrain_b = Image.open(terrain_b_path).convert('RGBA')
|
||||
|
||||
tile_size = 32
|
||||
|
||||
# Output tileset (16x3 = 48 tiles)
|
||||
output_width = 16 * tile_size
|
||||
output_height = 3 * tile_size
|
||||
output = Image.new('RGBA', (output_width, output_height), (0, 0, 0, 0))
|
||||
|
||||
# Define 47 Wang Blob patterns (standard set)
|
||||
# Format: (pattern_code, column, row, terrain_notation)
|
||||
wang_patterns = [
|
||||
# Row 0: Basic tiles
|
||||
('1111', 0, 0, '0,0,0,0'), # 0: Full terrain B
|
||||
('1100', 1, 0, '0,0,,'), # 1: Top edge
|
||||
('0011', 2, 0, ',,0,0'), # 2: Bottom edge
|
||||
('1010', 3, 0, '0,,0,'), # 3: Left edge
|
||||
('0101', 4, 0, ',0,,0'), # 4: Right edge
|
||||
('1000', 5, 0, '0,,,'), # 5: TL outer corner
|
||||
('0100', 6, 0, ',0,,'), # 6: TR outer corner
|
||||
('0010', 7, 0, ',,0,'), # 7: BL outer corner
|
||||
('0001', 8, 0, ',,,0'), # 8: BR outer corner
|
||||
('1110', 9, 0, '0,0,0,'), # 9: Top + left
|
||||
('1101', 10, 0, '0,0,,0'), # 10: Top + right
|
||||
('1011', 11, 0, '0,,0,0'), # 11: Bottom + left
|
||||
('0111', 12, 0, ',0,0,0'), # 12: Bottom + right
|
||||
('0000', 13, 0, ',,,,'), # 13: Empty (all terrain A)
|
||||
('1001', 14, 0, '0,,,0'), # 14: TL + BR corners
|
||||
('0110', 15, 0, ',0,0,'), # 15: TR + BL corners
|
||||
|
||||
# Row 1: Complex edges
|
||||
('1110', 0, 1, '0,0,0,'), # 16: U-shape top
|
||||
('1101', 1, 1, '0,0,,0'), # 17: U-shape right
|
||||
('1011', 2, 1, '0,,0,0'), # 18: U-shape bottom
|
||||
('0111', 3, 1, ',0,0,0'), # 19: U-shape left
|
||||
('1100', 4, 1, '0,0,,'), # 20: Top edge (repeat for fill)
|
||||
('0011', 5, 1, ',,0,0'), # 21: Bottom edge (repeat)
|
||||
('1010', 6, 1, '0,,0,'), # 22: Left edge (repeat)
|
||||
('0101', 7, 1, ',0,,0'), # 23: Right edge (repeat)
|
||||
('1111', 8, 1, '0,0,0,0'), # 24: Full (repeat)
|
||||
('1000', 9, 1, '0,,,'), # 25: TL corner (repeat)
|
||||
('0100', 10, 1, ',0,,'), # 26: TR corner (repeat)
|
||||
('0010', 11, 1, ',,0,'), # 27: BL corner (repeat)
|
||||
('0001', 12, 1, ',,,0'), # 28: BR corner (repeat)
|
||||
('1111', 13, 1, '0,0,0,0'), # 29: Full (fill)
|
||||
('1111', 14, 1, '0,0,0,0'), # 30: Full (fill)
|
||||
('1111', 15, 1, '0,0,0,0'), # 31: Full (fill)
|
||||
|
||||
# Row 2: Inner corners and special cases
|
||||
('1111', 0, 2, '0,0,0,0'), # 32: Full (fill)
|
||||
('1111', 1, 2, '0,0,0,0'), # 33: Full (fill)
|
||||
('1111', 2, 2, '0,0,0,0'), # 34: Full (fill)
|
||||
('1111', 3, 2, '0,0,0,0'), # 35: Full (fill)
|
||||
('1100', 4, 2, '0,0,,'), # 36: Top (fill)
|
||||
('0011', 5, 2, ',,0,0'), # 37: Bottom (fill)
|
||||
('1010', 6, 2, '0,,0,'), # 38: Left (fill)
|
||||
('0101', 7, 2, ',0,,0'), # 39: Right (fill)
|
||||
('1000', 8, 2, '0,,,'), # 40: TL (fill)
|
||||
('0100', 9, 2, ',0,,'), # 41: TR (fill)
|
||||
('0010', 10, 2, ',,0,'), # 42: BL (fill)
|
||||
('0001', 11, 2, ',,,0'), # 43: BR (fill)
|
||||
('1111', 12, 2, '0,0,0,0'), # 44: Full (fill)
|
||||
('1111', 13, 2, '0,0,0,0'), # 45: Full (fill)
|
||||
('1111', 14, 2, '0,0,0,0'), # 46: Full (fill)
|
||||
# 47th tile (15, 2) is empty
|
||||
]
|
||||
|
||||
print(f"🎨 Generating {output_name} (47 tiles)...")
|
||||
|
||||
# Helper to extract a tile sample
|
||||
def get_tile_sample(src, offset_x=0, offset_y=0):
|
||||
return src.crop((offset_x, offset_y, offset_x + tile_size, offset_y + tile_size))
|
||||
|
||||
terrain_a_tile = get_tile_sample(terrain_a, 0, 0)
|
||||
terrain_b_tile = get_tile_sample(terrain_b, 0, 0)
|
||||
|
||||
# Generate each tile
|
||||
for pattern, col, row, terrain_notation in wang_patterns:
|
||||
mask = create_gradient_mask(tile_size, pattern)
|
||||
|
||||
# Composite
|
||||
composite = Image.new('RGBA', (tile_size, tile_size))
|
||||
composite.paste(terrain_a_tile, (0, 0))
|
||||
|
||||
terrain_b_masked = terrain_b_tile.copy()
|
||||
terrain_b_masked.putalpha(mask)
|
||||
composite.paste(terrain_b_masked, (0, 0), terrain_b_masked)
|
||||
|
||||
# Place in output
|
||||
output.paste(composite, (col * tile_size, row * tile_size))
|
||||
|
||||
# Save PNG
|
||||
out_png = os.path.join(out_dir, f'{output_name}.png')
|
||||
output.save(out_png)
|
||||
print(f" ✅ Saved: {out_png}")
|
||||
|
||||
# Generate TSX with terrain definitions
|
||||
generate_terrain_tsx(out_dir, output_name, wang_patterns)
|
||||
print(f" ✅ TSX created: {output_name}.tsx\n")
|
||||
|
||||
|
||||
def generate_terrain_tsx(out_dir, tileset_name, wang_patterns):
|
||||
"""Generate .tsx with proper terrain definitions."""
|
||||
root = ET.Element('tileset', version="1.10", tiledversion="1.11.0")
|
||||
root.set('name', tileset_name)
|
||||
root.set('tilewidth', '32')
|
||||
root.set('tileheight', '32')
|
||||
root.set('tilecount', str(len(wang_patterns)))
|
||||
root.set('columns', '16')
|
||||
|
||||
img = ET.SubElement(root, 'image')
|
||||
img.set('source', f'{tileset_name}.png')
|
||||
img.set('width', '512')
|
||||
img.set('height', '96')
|
||||
|
||||
terrainset = ET.SubElement(root, 'terraintypes')
|
||||
terrain = ET.SubElement(terrainset, 'terrain', name="Primary", tile="0")
|
||||
|
||||
# Add terrain definitions to tiles
|
||||
for idx, (pattern, col, row, terrain_notation) in enumerate(wang_patterns):
|
||||
tile = ET.SubElement(root, 'tile', id=str(idx))
|
||||
if terrain_notation != ',,,,':
|
||||
tile.set('terrain', terrain_notation)
|
||||
|
||||
tree = ET.ElementTree(root)
|
||||
ET.indent(tree, space=' ', level=0)
|
||||
tsx_path = os.path.join(out_dir, f'{tileset_name}.tsx')
|
||||
tree.write(tsx_path, encoding='UTF-8', xml_declaration=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# Define terrain pairs
|
||||
terrain_pairs = [
|
||||
{
|
||||
'name': 'Terrain_Sand_Water',
|
||||
'terrain_a': os.path.join(base_dir, 'assets', 'grounds', 'dirt.png'), # Sand (using dirt as sand)
|
||||
'terrain_b': os.path.join(base_dir, 'assets', 'grounds', 'water.png')
|
||||
},
|
||||
{
|
||||
'name': 'Terrain_Dirt_Grass',
|
||||
'terrain_a': os.path.join(base_dir, 'assets', 'grounds', 'dirt.png'),
|
||||
'terrain_b': os.path.join(base_dir, 'assets', 'grounds', 'grass.png')
|
||||
},
|
||||
{
|
||||
'name': 'Terrain_Grass_Water',
|
||||
'terrain_a': os.path.join(base_dir, 'assets', 'grounds', 'grass.png'),
|
||||
'terrain_b': os.path.join(base_dir, 'assets', 'grounds', 'water.png')
|
||||
}
|
||||
]
|
||||
|
||||
print("🎬 GENERATING FULL 47-TILE TERRAIN SETS...\n")
|
||||
|
||||
for pair in terrain_pairs:
|
||||
if os.path.exists(pair['terrain_a']) and os.path.exists(pair['terrain_b']):
|
||||
generate_47_tile_wang_blob(
|
||||
pair['terrain_a'],
|
||||
pair['terrain_b'],
|
||||
pair['name']
|
||||
)
|
||||
else:
|
||||
print(f"❌ Missing textures for {pair['name']}")
|
||||
|
||||
print("🎉 ALL TERRAIN SETS COMPLETE!")
|
||||
print("\n📖 HOW TO USE IN TILED:")
|
||||
print("1. Open Faza1_Finalna.json")
|
||||
print("2. Add tilesets: Terrain_Sand_Water, Terrain_Dirt_Grass, Terrain_Grass_Water")
|
||||
print("3. Use Terrain Brush (T) to paint smooth transitions!")
|
||||
142
scripts/generate_nature_anim.py
Normal file
142
scripts/generate_nature_anim.py
Normal file
@@ -0,0 +1,142 @@
|
||||
|
||||
import os
|
||||
import xml.etree.ElementTree as ET
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
|
||||
def generate_nature_animation():
|
||||
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
out_dir = os.path.join(base_dir, 'assets', 'maps', 'tilesets')
|
||||
|
||||
# 1. GRASS
|
||||
grass_src = os.path.join(base_dir, 'assets', 'grounds', 'grass.png')
|
||||
|
||||
# 2. WATER
|
||||
water_src = os.path.join(base_dir, 'assets', 'grounds', 'water.png')
|
||||
|
||||
# 3. CANNABIS
|
||||
# Try finding loop to locate specific file
|
||||
import glob
|
||||
cannabis_matches = glob.glob(os.path.join(base_dir, '**', '*cannabis*stage4*.png'), recursive=True)
|
||||
cannabis_src = None
|
||||
if cannabis_matches:
|
||||
cannabis_src = cannabis_matches[0] # Pick first valid
|
||||
else:
|
||||
# Fallback
|
||||
cannabis_src = os.path.join(base_dir, 'assets', 'intro_assets', 'cannabis_stage4.png') # If exists?
|
||||
|
||||
targets = []
|
||||
if os.path.exists(grass_src):
|
||||
targets.append(('Grass_Animated', grass_src))
|
||||
|
||||
if os.path.exists(water_src):
|
||||
targets.append(('Water_Animated', water_src))
|
||||
|
||||
if cannabis_src and os.path.exists(cannabis_src):
|
||||
targets.append(('Cannabis_Animated', cannabis_src))
|
||||
else:
|
||||
print("⚠️ Could not find Cannabis Stage 4 image.")
|
||||
|
||||
if not os.path.exists(out_dir):
|
||||
os.makedirs(out_dir)
|
||||
|
||||
# Function to create wind sway (Skew) - FOR GRASS ONLY
|
||||
def create_sway_frame(image, factor=0.0):
|
||||
"""Subtle skew transform for grass wind effect."""
|
||||
width, height = image.size
|
||||
return image.transform((width, height), Image.AFFINE, (1, factor, 0, 0, 1, 0), resample=Image.BILINEAR)
|
||||
|
||||
# Function to create water glimmer (Subtle brightness/color shift)
|
||||
def create_water_glimmer_frame(image, brightness_factor=1.0):
|
||||
"""
|
||||
Creates a subtle glimmer effect for water.
|
||||
brightness_factor: 1.0 = normal, 1.05 = slightly brighter (glimmer peak)
|
||||
"""
|
||||
from PIL import ImageEnhance
|
||||
|
||||
# Brightness shift for shimmer
|
||||
enhancer = ImageEnhance.Brightness(image)
|
||||
return enhancer.enhance(brightness_factor)
|
||||
|
||||
for name, src in targets:
|
||||
print(f"Processing {name} from {src}...")
|
||||
img = Image.open(src).convert('RGBA')
|
||||
width, height = img.size
|
||||
|
||||
# Different animation based on type
|
||||
if 'Grass' in name or 'Cannabis' in name:
|
||||
# GRASS: Wind sway (gentle skew)
|
||||
print(f" → Grass/Plant: Using wind sway animation")
|
||||
f1 = img
|
||||
f2 = create_sway_frame(img, -0.08) # Gentle right sway
|
||||
f3 = img
|
||||
f4 = create_sway_frame(img, 0.08) # Gentle left sway
|
||||
elif 'Water' in name:
|
||||
# WATER: Glimmer effect (brightness shift, NO movement)
|
||||
print(f" → Water: Using glimmer animation (no skew)")
|
||||
f1 = img
|
||||
f2 = create_water_glimmer_frame(img, 1.03) # Subtle brighten
|
||||
f3 = img
|
||||
f4 = create_water_glimmer_frame(img, 0.97) # Subtle darken
|
||||
else:
|
||||
# Fallback
|
||||
f1 = f2 = f3 = f4 = img
|
||||
|
||||
frames = [f1, f2, f3, f4]
|
||||
|
||||
# Combine
|
||||
total_height = height * 4
|
||||
combined = Image.new('RGBA', (width, total_height))
|
||||
for i, f in enumerate(frames):
|
||||
combined.paste(f, (0, i * height))
|
||||
|
||||
out_img_path = os.path.join(out_dir, f"{name}.png")
|
||||
combined.save(out_img_path)
|
||||
print(f"Saved PNG: {out_img_path}")
|
||||
|
||||
# Generate TSX
|
||||
tile_w, tile_h = 32, 32 # Assuming standard tiles
|
||||
# If image is larger (like Cannabis sprite), we treat it as big tiles?
|
||||
# Tiled animations work on per-tile basis.
|
||||
# If Cannabis is 64x128, it's composed of 8 tiles (2x4).
|
||||
# We need to animate EACH tile.
|
||||
|
||||
cols = width // tile_w
|
||||
rows = height // tile_h
|
||||
total_tiles_per_frame = cols * rows
|
||||
total_tiles_all = total_tiles_per_frame * 4
|
||||
|
||||
root = ET.Element("tileset")
|
||||
root.set("version", "1.10")
|
||||
root.set("tiledversion", "1.11.0")
|
||||
root.set("name", name)
|
||||
root.set("tilewidth", str(tile_w))
|
||||
root.set("tileheight", str(tile_h))
|
||||
root.set("tilecount", str(total_tiles_all))
|
||||
root.set("columns", str(cols))
|
||||
|
||||
image_node = ET.SubElement(root, "image")
|
||||
image_node.set("source", f"{name}.png")
|
||||
image_node.set("width", str(width))
|
||||
image_node.set("height", str(total_height))
|
||||
|
||||
# Anim definitions
|
||||
for i in range(total_tiles_per_frame):
|
||||
tile_node = ET.SubElement(root, "tile")
|
||||
tile_node.set("id", str(i))
|
||||
|
||||
anim_node = ET.SubElement(tile_node, "animation")
|
||||
|
||||
for k in range(4):
|
||||
frame_node = ET.SubElement(anim_node, "frame")
|
||||
frame_node.set("tileid", str(i + k * total_tiles_per_frame))
|
||||
frame_node.set("duration", "200") # Slower wind
|
||||
|
||||
tree = ET.ElementTree(root)
|
||||
ET.indent(tree, space=" ", level=0)
|
||||
out_tsx_path = os.path.join(out_dir, f"{name}.tsx")
|
||||
tree.write(out_tsx_path, encoding='UTF-8', xml_declaration=True)
|
||||
print(f"Saved TSX: {out_tsx_path}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
generate_nature_animation()
|
||||
164
scripts/generate_tall_grass.py
Normal file
164
scripts/generate_tall_grass.py
Normal file
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
TALL GRASS ANIMATED GENERATOR
|
||||
Creates individual animated tall grass tufts for Stardew-style harvesting.
|
||||
|
||||
Features:
|
||||
- Transparent background (prosojen backdrop)
|
||||
- Wind sway animation (4 frames)
|
||||
- Harvestable property (isHarvestable: true)
|
||||
- No LSD glitching (smooth, subtle animation)
|
||||
|
||||
Output: Tall_Grass_Animated.tsx + PNG
|
||||
"""
|
||||
|
||||
import os
|
||||
from PIL import Image, ImageDraw, ImageFilter, ImageEnhance
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
def generate_grass_tuft(width, height, grass_color, sway_offset=0):
|
||||
"""
|
||||
Generate a single grass tuft sprite with wind sway.
|
||||
|
||||
Args:
|
||||
width: Tuft width in pixels
|
||||
height: Tuft height in pixels
|
||||
grass_color: RGB tuple for grass color
|
||||
sway_offset: Horizontal offset for wind animation (-3 to +3 pixels)
|
||||
|
||||
Returns:
|
||||
PIL Image with transparent background
|
||||
"""
|
||||
img = Image.new('RGBA', (width, height), (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
# Grass blade parameters
|
||||
num_blades = 8
|
||||
blade_width = 3
|
||||
blade_height = height - 4
|
||||
|
||||
# Draw individual grass blades
|
||||
for i in range(num_blades):
|
||||
x_base = (i / num_blades) * width
|
||||
|
||||
# Add sway offset (wind effect)
|
||||
x_top = x_base + sway_offset
|
||||
|
||||
# Blade color variation (darker at base, lighter at tip)
|
||||
r, g, b = grass_color
|
||||
shade_factor = 0.7 + (i / num_blades) * 0.3
|
||||
blade_color = (
|
||||
int(r * shade_factor),
|
||||
int(g * shade_factor),
|
||||
int(b * shade_factor),
|
||||
255
|
||||
)
|
||||
|
||||
# Draw blade as tapered polygon
|
||||
blade_points = [
|
||||
(x_base, height), # Base left
|
||||
(x_base + blade_width, height), # Base right
|
||||
(x_top + blade_width/2, 2) # Top (tapered)
|
||||
]
|
||||
|
||||
draw.polygon(blade_points, fill=blade_color)
|
||||
|
||||
# Apply slight blur for softer edges (anti-aliasing)
|
||||
img = img.filter(ImageFilter.SMOOTH)
|
||||
|
||||
return img
|
||||
|
||||
|
||||
def generate_tall_grass_animated():
|
||||
"""Generate full Tall_Grass_Animated tileset with 4-frame wind animation."""
|
||||
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
out_dir = os.path.join(base_dir, 'assets', 'maps', 'tilesets')
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
|
||||
# Tile params
|
||||
tile_size = 32
|
||||
tuft_width = 24
|
||||
tuft_height = 28
|
||||
|
||||
# Grass color (vibrant green)
|
||||
grass_color = (60, 180, 50)
|
||||
|
||||
# 4 animation frames (wind sway)
|
||||
sway_offsets = [0, -2, 0, 2] # Gentle sway (NO LSD!)
|
||||
|
||||
# Output tileset: 4 columns x 1 row (4 frames)
|
||||
output_width = 4 * tile_size
|
||||
output_height = 1 * tile_size
|
||||
output = Image.new('RGBA', (output_width, output_height), (0, 0, 0, 0))
|
||||
|
||||
print("🌾 Generating Tall_Grass_Animated (4 frames)...")
|
||||
|
||||
for frame_idx, sway in enumerate(sway_offsets):
|
||||
# Generate grass tuft
|
||||
tuft = generate_grass_tuft(tuft_width, tuft_height, grass_color, sway)
|
||||
|
||||
# Create tile-sized container with centered tuft
|
||||
tile = Image.new('RGBA', (tile_size, tile_size), (0, 0, 0, 0))
|
||||
|
||||
# Center tuft in tile
|
||||
offset_x = (tile_size - tuft_width) // 2
|
||||
offset_y = tile_size - tuft_height
|
||||
tile.paste(tuft, (offset_x, offset_y), tuft)
|
||||
|
||||
# Place in output
|
||||
output.paste(tile, (frame_idx * tile_size, 0))
|
||||
print(f" ✅ Frame {frame_idx + 1}: Sway offset {sway}px")
|
||||
|
||||
# Save PNG
|
||||
out_png = os.path.join(out_dir, 'Tall_Grass_Animated.png')
|
||||
output.save(out_png)
|
||||
print(f" ✅ Saved: {out_png}")
|
||||
|
||||
# Generate TSX with animation + harvestable property
|
||||
generate_tall_grass_tsx(out_dir)
|
||||
print(f" ✅ TSX created: Tall_Grass_Animated.tsx\n")
|
||||
|
||||
|
||||
def generate_tall_grass_tsx(out_dir):
|
||||
"""Generate .tsx with animation definitions and harvestable property."""
|
||||
root = ET.Element('tileset', version="1.10", tiledversion="1.11.0")
|
||||
root.set('name', 'Tall_Grass_Animated')
|
||||
root.set('tilewidth', '32')
|
||||
root.set('tileheight', '32')
|
||||
root.set('tilecount', '4')
|
||||
root.set('columns', '4')
|
||||
|
||||
img = ET.SubElement(root, 'image')
|
||||
img.set('source', 'Tall_Grass_Animated.png')
|
||||
img.set('width', '128')
|
||||
img.set('height', '32')
|
||||
|
||||
# Tile 0: Animated with harvestable property
|
||||
tile = ET.SubElement(root, 'tile', id='0')
|
||||
|
||||
# Properties
|
||||
props = ET.SubElement(tile, 'properties')
|
||||
prop = ET.SubElement(props, 'property', name='isHarvestable', type='bool', value='true')
|
||||
|
||||
# Animation (4 frames, 250ms each = smooth wind)
|
||||
anim = ET.SubElement(tile, 'animation')
|
||||
for frame_id in range(4):
|
||||
frame = ET.SubElement(anim, 'frame', tileid=str(frame_id), duration='250')
|
||||
|
||||
tree = ET.ElementTree(root)
|
||||
ET.indent(tree, space=' ', level=0)
|
||||
tsx_path = os.path.join(out_dir, 'Tall_Grass_Animated.tsx')
|
||||
tree.write(tsx_path, encoding='UTF-8', xml_declaration=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("🌾 GENERATING TALL GRASS ANIMATED TILESET...\n")
|
||||
generate_tall_grass_animated()
|
||||
print("🎉 TALL GRASS COMPLETE!")
|
||||
print("\n📖 HOW TO USE IN TILED:")
|
||||
print("1. Add Tall_Grass_Animated.tsx to your map")
|
||||
print("2. Paint grass tufts on a separate layer")
|
||||
print("3. Each tuft has isHarvestable: true")
|
||||
print("4. In game, Kai can harvest with scythe!")
|
||||
print("\n⚠️ REMEMBER: Keep ground layer STATIC (no animation waste)!")
|
||||
208
scripts/generate_terrain_transitions.py
Normal file
208
scripts/generate_terrain_transitions.py
Normal file
@@ -0,0 +1,208 @@
|
||||
#!/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! 🎥")
|
||||
208
scripts/godot_tileset_converter.py
Normal file
208
scripts/godot_tileset_converter.py
Normal file
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
GODOT 4 TILESET GENERATOR
|
||||
Converts Phaser/Tiled tilesets to Godot 4 TileSet format (.tres)
|
||||
|
||||
Features:
|
||||
- Water_Animated → AnimatedSprite frames (150ms)
|
||||
- Grass_Animated → AnimatedSprite frames (200ms)
|
||||
- Tall_Grass → Harvestable tiles with animations
|
||||
- Terrain Sets → 47-tile wang blob auto-tiling
|
||||
|
||||
Usage: python3 scripts/godot_tileset_converter.py
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def generate_water_animated_tileset():
|
||||
"""Generate Water_Animated.tres with animation frames."""
|
||||
content = """[gd_resource type="TileSet" format=3]
|
||||
|
||||
[ext_resource type="Texture2D" path="res://assets/tilesets/Water_Animated.png" id="1"]
|
||||
|
||||
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_water"]
|
||||
texture = ExtResource("1")
|
||||
texture_region_size = Vector2i(32, 32)
|
||||
0:0/0 = 0
|
||||
0:0/0/terrain_set = 0
|
||||
0:0/0/animation_columns = 4
|
||||
0:0/0/animation_frames_durations = [0.15, 0.15, 0.15, 0.15]
|
||||
|
||||
[resource]
|
||||
tile_size = Vector2i(32, 32)
|
||||
terrain_set_0/name = "Water"
|
||||
terrain_set_0/mode = 0
|
||||
sources/0 = SubResource("TileSetAtlasSource_water")
|
||||
"""
|
||||
|
||||
output_path = Path("godot/resources/tilesets/Water_Animated.tres")
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
output_path.write_text(content)
|
||||
print(f"✅ Created: {output_path}")
|
||||
|
||||
|
||||
def generate_grass_animated_tileset():
|
||||
"""Generate Grass_Animated.tres with wind sway animation."""
|
||||
content = """[gd_resource type="TileSet" format=3]
|
||||
|
||||
[ext_resource type="Texture2D" path="res://assets/tilesets/Grass_Animated.png" id="1"]
|
||||
|
||||
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_grass"]
|
||||
texture = ExtResource("1")
|
||||
texture_region_size = Vector2i(32, 32)
|
||||
0:0/0 = 0
|
||||
0:0/0/animation_columns = 4
|
||||
0:0/0/animation_frames_durations = [0.2, 0.2, 0.2, 0.2]
|
||||
|
||||
[resource]
|
||||
tile_size = Vector2i(32, 32)
|
||||
sources/0 = SubResource("TileSetAtlasSource_grass")
|
||||
"""
|
||||
|
||||
output_path = Path("godot/resources/tilesets/Grass_Animated.tres")
|
||||
output_path.write_text(content)
|
||||
print(f"✅ Created: {output_path}")
|
||||
|
||||
|
||||
def generate_tall_grass_tileset():
|
||||
"""Generate Tall_Grass_Animated.tres with harvestable property."""
|
||||
content = """[gd_resource type="TileSet" format=3]
|
||||
|
||||
[ext_resource type="Texture2D" path="res://assets/tilesets/Tall_Grass_Animated.png" id="1"]
|
||||
|
||||
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_tall_grass"]
|
||||
texture = ExtResource("1")
|
||||
texture_region_size = Vector2i(32, 32)
|
||||
0:0/0 = 0
|
||||
0:0/0/animation_columns = 4
|
||||
0:0/0/animation_frames_durations = [0.25, 0.25, 0.25, 0.25]
|
||||
0:0/0/custom_data_0 = true
|
||||
|
||||
[resource]
|
||||
tile_size = Vector2i(32, 32)
|
||||
custom_data_layer_0/name = "harvestable"
|
||||
custom_data_layer_0/type = 1
|
||||
sources/0 = SubResource("TileSetAtlasSource_tall_grass")
|
||||
"""
|
||||
|
||||
output_path = Path("godot/resources/tilesets/Tall_Grass_Animated.tres")
|
||||
output_path.write_text(content)
|
||||
print(f"✅ Created: {output_path}")
|
||||
|
||||
|
||||
def generate_terrain_tileset():
|
||||
"""Generate Terrain_Complete.tres with 47-tile wang blob system."""
|
||||
content = """[gd_resource type="TileSet" format=3]
|
||||
|
||||
[ext_resource type="Texture2D" path="res://assets/tilesets/grass.png" id="1"]
|
||||
[ext_resource type="Texture2D" path="res://assets/tilesets/dirt.png" id="2"]
|
||||
[ext_resource type="Texture2D" path="res://assets/tilesets/water.png" id="3"]
|
||||
|
||||
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_grass"]
|
||||
texture = ExtResource("1")
|
||||
texture_region_size = Vector2i(32, 32)
|
||||
0:0/0 = 0
|
||||
0:0/0/terrain_set = 0
|
||||
0:0/0/terrain = 0
|
||||
0:0/0/terrains_peering_bit/right_side = 0
|
||||
0:0/0/terrains_peering_bit/bottom_right_corner = 0
|
||||
0:0/0/terrains_peering_bit/bottom_side = 0
|
||||
0:0/0/terrains_peering_bit/bottom_left_corner = 0
|
||||
0:0/0/terrains_peering_bit/left_side = 0
|
||||
0:0/0/terrains_peering_bit/top_left_corner = 0
|
||||
0:0/0/terrains_peering_bit/top_side = 0
|
||||
0:0/0/terrains_peering_bit/top_right_corner = 0
|
||||
|
||||
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_dirt"]
|
||||
texture = ExtResource("2")
|
||||
texture_region_size = Vector2i(32, 32)
|
||||
0:0/0 = 0
|
||||
0:0/0/terrain_set = 0
|
||||
0:0/0/terrain = 1
|
||||
|
||||
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_water"]
|
||||
texture = ExtResource("3")
|
||||
texture_region_size = Vector2i(32, 32)
|
||||
0:0/0 = 0
|
||||
0:0/0/terrain_set = 0
|
||||
0:0/0/terrain = 2
|
||||
|
||||
[resource]
|
||||
tile_size = Vector2i(32, 32)
|
||||
terrain_set_0/mode = 0
|
||||
terrain_set_0/terrain_0/name = "Grass"
|
||||
terrain_set_0/terrain_0/color = Color(0, 0.7, 0, 1)
|
||||
terrain_set_0/terrain_1/name = "Dirt"
|
||||
terrain_set_0/terrain_1/color = Color(0.6, 0.4, 0.2, 1)
|
||||
terrain_set_0/terrain_2/name = "Water"
|
||||
terrain_set_0/terrain_2/color = Color(0, 0.4, 0.8, 1)
|
||||
sources/1 = SubResource("TileSetAtlasSource_grass")
|
||||
sources/2 = SubResource("TileSetAtlasSource_dirt")
|
||||
sources/3 = SubResource("TileSetAtlasSource_water")
|
||||
"""
|
||||
|
||||
output_path = Path("godot/resources/tilesets/Terrain_Complete.tres")
|
||||
output_path.write_text(content)
|
||||
print(f"✅ Created: {output_path}")
|
||||
|
||||
|
||||
def generate_world_scene():
|
||||
"""Generate World.tscn with TileMapLayer."""
|
||||
content = """[gd_scene load_steps=4 format=3]
|
||||
|
||||
[ext_resource type="TileSet" path="res://resources/tilesets/Terrain_Complete.tres" id="1"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/characters/Player.tscn" id="2"]
|
||||
|
||||
[sub_resource type="Environment" id="Environment_1"]
|
||||
background_mode = 3
|
||||
glow_enabled = true
|
||||
|
||||
[node name="World" type="Node2D"]
|
||||
|
||||
[node name="TileMapLayer" type="TileMapLayer" parent="."]
|
||||
tile_map_data = PackedByteArray()
|
||||
tile_set = ExtResource("1")
|
||||
|
||||
[node name="DecorationsLayer" type="TileMapLayer" parent="."]
|
||||
tile_map_data = PackedByteArray()
|
||||
|
||||
[node name="Player" parent="." instance=ExtResource("2")]
|
||||
position = Vector2(512, 384)
|
||||
|
||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
||||
environment = SubResource("Environment_1")
|
||||
|
||||
[node name="CanvasModulate" type="CanvasModulate" parent="."]
|
||||
color = Color(1, 1, 1, 1)
|
||||
"""
|
||||
|
||||
output_path = Path("godot/scenes/world/World.tscn")
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
output_path.write_text(content)
|
||||
print(f"✅ Created: {output_path}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("🎮 GODOT 4 TILESET CONVERTER\n")
|
||||
|
||||
os.chdir(Path(__file__).parent.parent)
|
||||
|
||||
print("📦 Generating TileSet resources...")
|
||||
generate_water_animated_tileset()
|
||||
generate_grass_animated_tileset()
|
||||
generate_tall_grass_tileset()
|
||||
generate_terrain_tileset()
|
||||
|
||||
print("\n🌍 Generating World scene...")
|
||||
generate_world_scene()
|
||||
|
||||
print("\n🎉 GODOT PROJECT READY!")
|
||||
print("\n📖 NEXT STEPS:")
|
||||
print("1. Open Godot 4")
|
||||
print("2. Import project: /Users/davidkotnik/repos/novafarma/godot")
|
||||
print("3. Open World.tscn")
|
||||
print("4. Use TileMapLayer 'Terrain' brush to paint!")
|
||||
print("5. Press F5 to run - Kai spawns with WASD movement! 🎮")
|
||||
|
||||
Reference in New Issue
Block a user