-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Complete Godot Tile Mapping Documentation: From .tscn to PNG
All Edge Cases and Implementation Details
Table of Contents
- Overview
- Tile Mode Types
- Coordinate Encoding
- Autotile Edge Cases
- Atlas Tile Edge Cases
- Multi-Size Tile Edge Cases
- Layer System Edge Cases
- Extraction and Scaling Edge Cases
- Special Properties and Limitations
- Complete Data Flow
1. Overview
The Tokyo scene uses 169 unique tile IDs across 2 tilesets with 3 different tile modes:
- GuttyKreum.tres: 122 single tiles, 14 autotiles, 33 atlas tiles
- julia_tiles.tres: 7 single tiles (all placeholder landmarks)
File Locations
- Scene:
/Scenes/tokyo/tokyo_outside.tscn - Main tileset:
/Assets/Tilemap/GuttyKreum.tres - Secondary tileset:
/Assets/Tilemap/julia_tiles.tres - Source PNGs: 7 different tilemap images in
/Assets/GuttyKreum/subdirectories
2. Tile Mode Types
Mode 0: Single Tiles (122 instances)
- Definition: One tile ID maps to exactly one image region
- Example: Tile 8 (block) →
Rect2(128, 224, 32, 32)from Osaka_V5/Tilemap.png - Edge Case: None - simplest tile type
- Extraction: Direct crop from source PNG at specified region
Example Definition:
8/name = "block"
8/texture = ExtResource( 3 )
8/tex_offset = Vector2( 0, 0 )
8/modulate = Color( 1, 1, 1, 1 )
8/region = Rect2( 128, 224, 32, 32 )
8/tile_mode = 0
Mode 1: Autotiles with Bitmask (14 instances)
- Definition: One tile ID contains multiple variants selected automatically by neighbor context
- Standard 3x3 Grid (96x96 pixels containing 9 variants):
[Corner TL] [Edge Top] [Corner TR] [Edge Left] [Center] [Edge Right] [Corner BL] [Edge Bot] [Corner BR] - Bitmask Mode: Always mode 2 (3x3 full bitmask) in this project
- Tile Size: Vector2(32, 32) - each variant is 32x32
- Spacing: Always 0 (no gaps between variants)
Bitmask Values:
Vector2(0, 0), 432 → Top-left corner (neighbors on right and bottom)
Vector2(1, 1), 511 → Center piece (neighbors on all 8 sides)
Vector2(2, 2), 27 → Bottom-right corner (neighbors on top and left)
Examples of Autotiles:
- Tile 0: road-edges-1
- Tile 10: (another road variant)
- Tile 38: (decorative edges)
- Tile 58: shrub-edges
- Tile 61: (path variant)
- Tile 65: (wall/fence variant)
Edge Case: When extracted for web export, only the top-left variant (0,0) is used, losing the automatic neighbor-based selection.
Mode 2: Atlas Tiles (33 instances)
- Definition: Large regions containing multiple tile variations WITHOUT automatic bitmask logic
- Manual Selection: Game designer must manually choose which variant to use
- Icon Coordinate: Specifies which sub-tile represents the entire atlas in the editor
Examples:
-
Tile 57 (sand-garden): 192x128 pixels
- Grid: 6×4 = 24 possible variants
- Each variant: 32×32 pixels
- Source: TemplesShrines_v2/Non-RPGMaker_Tilemap/Main_Tilemap.png
-
Tile 59 (fence-garden-outside): 320x192 pixels
- Grid: 10×6 = 60 possible variants
- Source: TemplesandShrinesv2/MainTileMap/MainTileMap.png
-
Tile 79: Large decorative atlas
-
Tile 89: Environmental atlas
-
Tile 90: Structural atlas
Edge Case: Atlas may define 60 tiles but the scene only uses specific ones. Extraction tools only process the icon coordinate tile, not all variants.
3. Coordinate Encoding
Godot's Position Encoding System
Godot encodes 2D tile positions as a single 32-bit integer:
position = y * 65536 + x
Range:
- Both x and y are signed 16-bit integers
- Valid range: -32768 to 32767 for each axis
- Total addressable space: 65536 × 65536 tiles
Edge Case: Negative coordinates require two's complement conversion
Decoding Algorithm
def decode_godot_coordinate(godot_pos):
"""
Convert Godot's tile coordinate to x,y position.
Godot uses: y * 65536 + x for encoding positions (signed 16-bit values).
"""
# Extract y (high 16 bits) and x (low 16 bits)
y = godot_pos >> 16 # Arithmetic right shift preserves sign
x = godot_pos & 0xFFFF # Get lower 16 bits
# Convert x to signed 16-bit
if x >= 32768:
x -= 65536
return x, yExample:
- Encoded:
-3997756 - Decoded:
x = -60, y = -61 - World position:
(-1920, -1952)pixels (multiply by cell_size of 32)
PoolIntArray Storage Format
Tile data is stored as a flat array of integers in triplet format:
[position1, tile_id1, flags1, position2, tile_id2, flags2, ...]
Triplet Components:
- Position: Encoded as described above
- Tile ID: Index into the tileset (0-176 in tokyo_outside.tscn)
- Flags: Bit flags for transformations (usually 0)
Example from tokyo_outside.tscn:
tile_data = PoolIntArray( -3997756, 8, 0, -3997755, 8, 0, -3997754, 9, 0, ... )
- Position -3997756 has tile 8, flags 0
- Position -3997755 has tile 8, flags 0
- Position -3997754 has tile 9, flags 0
Edge Case: Incomplete triplets at array end are ignored by the parser.
4. Autotile Edge Cases
Bitmask Mode Types
Godot supports three bitmask modes:
- Mode 0: 2×2 minimal bitmask (not used in this project)
- Mode 1: 3×3 minimal bitmask (not used in this project)
- Mode 2: 3×3 full bitmask (used by all autotiles in this project)
Neighbor Detection Algorithm
Each autotile checks its 8 neighboring positions and builds a bitmask:
Bit Position | Direction | Value
-------------|----------------|-------
0 | Top-left | 1
1 | Top | 2
2 | Top-right | 4
3 | Left | 8
4 | Center | 16 (always set)
5 | Right | 32
6 | Bottom-left | 64
7 | Bottom | 128
8 | Bottom-right | 256
Common Bitmask Values:
511(binary 111111111): All neighbors present → Use center tile432(binary 110110000): Right + Bottom neighbors → Top-left corner438(binary 110110110): Right + Bottom + Top neighbors → Left edge54(binary 000110110): Top + Right neighbors → Bottom-left corner216(binary 011011000): All left-side neighbors → Right edge27(binary 000011011): Left + Top neighbors → Bottom-right corner
Autotile Variant Layout
The 3×3 grid in the PNG corresponds to different connection patterns:
Position in PNG | Bitmask | Typical Use
-------------------|---------|-------------
[0,0] (0, 0) | 432 | Top-left corner
[1,0] (32, 0) | 438 | Top edge
[2,0] (64, 0) | 54 | Top-right corner
[0,1] (0, 32) | 504 | Left edge
[1,1] (32, 32) | 511 | Center (all neighbors)
[2,1] (64, 32) | 63 | Right edge
[0,2] (0, 64) | 216 | Bottom-left corner
[1,2] (32, 64) | 219 | Bottom edge
[2,2] (64, 64) | 27 | Bottom-right corner
Common Autotile Patterns in Project
- Road edges (tiles 0, 10): Create seamless roads with proper corners
- Garden shrubs (tile 58): Natural organic edges for gardens
- Wall systems (tiles 100, 101): Complex corner and edge handling for structures
Edge Case: Fallback Mode
- fallback_mode = 0: If no bitmask matches, use the first tile (top-left)
- All autotiles in project: Use default fallback behavior
- Rarely triggers: Bitmask definitions usually cover all cases
Edge Case: Web Export Limitation
When extracting autotiles for web export, the extraction script only takes the top-left variant:
if w > 32 or h > 32:
# This is an autotile - extract just the first 32x32 cell
tile = source.crop((x, y, x + 32, y + 32))This means the web version loses automatic tile selection and all tiles appear as the same variant.
5. Atlas Tile Edge Cases
Large Atlas Examples
1. Tile 59 (fence-garden-outside): 320×192 pixels
- Source: MainTileMap.png at position (2720, 544)
- Grid dimensions: 10 tiles wide × 6 tiles tall = 60 possible variants
- Each variant: 32×32 pixels
- No automatic selection logic
- Manual placement required for variety
2. Tile 57 (sand-garden): 192×128 pixels
- Source: Main_Tilemap.png at position (0, 384)
- Grid dimensions: 6 tiles wide × 4 tiles tall = 24 variations
- Used for decorative garden patterns
- Provides visual variety without bitmask complexity
3. Other Notable Atlases:
- Tile 110: 192×96 (6×3 grid)
- Tile 115: 192×96 (6×3 grid)
- Tile 118: 192×96 (6×3 grid)
- Tile 145: 192×96 (6×3 grid)
- Tile 158: 192×96 (6×3 grid)
- Tile 160: 192×96 (6×3 grid)
Atlas Properties
57/tile_mode = 2
57/autotile/icon_coordinate = Vector2( 0, 0 ) # Which variant represents the atlas
57/autotile/tile_size = Vector2( 32, 32 ) # Size of each sub-tile
57/autotile/spacing = 0 # No gaps between tiles
Key Differences from Autotiles:
- No bitmask_flags: Atlas tiles don't use neighbor detection
- Manual selection: Designer chooses which variant to place
- Icon coordinate: Defines which sub-tile appears in editor palette
Edge Case: Partial Atlas Usage
- An atlas may define 60 possible tiles
- The scene might only use 3-4 specific variants
- Extraction tools only export the icon coordinate tile
- Other variants are not extracted to save space
Edge Case: Atlas vs Autotile Confusion
Both use autotile/ properties but behave differently:
- Mode 1 (autotile): Has
bitmask_flags, automatic selection - Mode 2 (atlas): No
bitmask_flags, manual selection only
6. Multi-Size Tile Edge Cases
Non-Standard Tile Sizes Found in Project
1. Standard 32×32: Majority of tiles (100+ tiles)
- Clean 1:1 grid mapping
- Most common for floors, walls, small decorations
2. Wide 64×32 Tiles: Horizontal elements
- Example: Tile 12 at
Rect2(1792, 288, 64, 32) - Use case: Wide doors, signs, horizontal decorations
- Spans: 2 cells wide × 1 cell tall
3. Tall 32×64 Tiles: Vertical elements
- Example: Tile 14 at
Rect2(1760, 224, 32, 64) - Use case: Trees, poles, tall decorations
- Spans: 1 cell wide × 2 cells tall
4. 96×96 Autotiles: 3×3 grids
- Contains 9 variants of 32×32 each
- 14 tiles in project use this format
5. Large 128×64 Tiles:
- Example: Tile 50 at
Rect2(2336, 256, 128, 64) - Spans: 4 cells wide × 2 cells tall
6. Huge 160×160 Decorative Elements:
- Example: Tile 18 at
Rect2(1888, 96, 160, 160) - Spans: 5 cells wide × 5 cells tall
- Use case: Large decorative structures
7. Massive Landmark Tiles (julia_tiles.tres):
- Tokyo Station:
Rect2(32, 32, 1312, 256)= 1312×256 pixels- Spans: 41 cells wide × 8 cells tall
- Tokyo Tower:
Rect2(32, 0, 256, 480)= 256×480 pixels- Spans: 8 cells wide × 15 cells tall
Rendering Multi-Size Tiles
Cell Size: Always 32×32 in TileMap node
cell_size = Vector2( 32, 32 )
Automatic Spanning: Godot automatically spans larger tiles across multiple grid cells:
- A 64×32 tile placed at position (5, 10) will occupy cells (5,10) and (6,10)
- A 160×160 tile spans 25 grid cells (5×5 area)
Overlap Edge Case:
- Larger tiles can visually overlap adjacent cells
- Z-ordering determines which tile appears on top
- Collision is still based on cell placement, not visual bounds
Extraction Edge Cases for Multi-Size Tiles
Standard Approach (extract_from_guttykreum.py):
if w > 32 or h > 32:
# Extract just the top-left 32x32 cell for consistency
tile = source.crop((x, y, x + 32, y + 32))
else:
# Regular tile - extract the full region
tile = source.crop((x, y, x + w, y + h))
# Always scale to 64x64 for gamev2
tile = tile.resize((64, 64), Image.NEAREST)Consequences:
- Large decorative tiles are cropped to just top-left corner
- Visual information is lost
- Web version may look incomplete for large structures
7. Layer System Edge Cases
tokyo_outside.tscn Layer Structure
The scene contains 8 TileMap layers with different purposes:
Layer Name | Z-Order | Collision | Purpose
----------------------------|---------|-----------|---------------------------
dimensions | N/A | N/A | Skip - boundary metadata
zone-allocation | N/A | N/A | Skip - non-visual data
floor | 0 | false | Base ground tiles
overfloor | 1 | false | Ground decorations
julia-tmp | 3 | false | Temporary decorations
overworld-noncollidables | 4 | false | Walk-through objects
overworld-collidables | 5 | true | Blocking objects (walls, etc.)
other-tmp | 6 | false | Top layer elements
Layer Configuration Code
From parse_tokyo_multilayer.py:
LAYER_CONFIG = {
"dimensions": {"skip": True},
"zone-allocation": {"skip": True},
"floor": {"z": 0, "collision": False, "name": "Floor"},
"overfloor": {"z": 1, "collision": False, "name": "Over Floor"},
"overworld-collidables": {"z": 5, "collision": True, "name": "Collidables"},
"julia-tmp": {"z": 3, "collision": False, "name": "Decorations"},
"overworld-noncollidables": {"z": 4, "collision": False, "name": "Non-Collidables"},
"other-tmp": {"z": 6, "collision": False, "name": "Other"}
}Z-Order Rendering
- Lower z-values render first (behind)
- Higher z-values render last (on top)
- Same z-value: Rendered in scene order
- Range: 0 to 6 in this project
Edge Case: Skip Layers
dimensions and zone-allocation layers:
- Contain actual tile data in the .tscn file
- Used for editor boundaries and zone planning
- Not rendered in-game
- Must be explicitly skipped during export
Edge Case: Collision Layer Ambiguity
Only one layer has collision enabled:
overworld-collidables(z=5) blocks player movement- Other layers allow walking through (even at higher z-orders)
- Visual appearance doesn't indicate collision
Coordinate Normalization Across Layers
Problem: Each layer may have different tile coordinate ranges:
- Floor layer might span x=-60 to x=80
- Decorations layer might only have tiles at x=10 to x=30
Solution: All layers normalized to the same crop region:
crop_start_x = 9
crop_start_y = 9
max_width = 40
max_height = 30This creates a viewport from (9,9) to (48,38) in grid coordinates.
Edge Case: Tiles outside the crop region are silently discarded:
if crop_min_x <= pos[0] <= crop_max_x and crop_min_y <= pos[1] <= crop_max_y:
tiles[pos] = tid # Keep tile
# Otherwise discardedLayer Tile Count Variation
Different layers have vastly different tile counts:
- floor: 1000+ tiles (dense coverage)
- overworld-collidables: 200-300 tiles (sparse walls/obstacles)
- julia-tmp: ~20 tiles (few decorations)
- dimensions: Full map coverage but skipped
Edge Case: Empty Layer Cells
- Layers with sparse coverage leave gaps
- No "default" tile fills empty cells
- Lower layers show through empty cells in higher layers
- Creates layered visual depth
8. Extraction and Scaling Edge Cases
Source PNG Files
Total tilemap images: 7 different PNG files
Sources in GuttyKreum.tres:
MainTileMap.png(TemplesandShrinesv2) - ExtResource(1)Main_Tilemap.png(TemplesShrines_v2) - ExtResource(2)Tilemap.png(Osaka_V5) - ExtResource(3) - 1376 × 672 pixelsMainTileMap.png(DotonboriCity_v3) - ExtResource(4)MainTileMap.png(OvergrownBackstreets_v1) - ExtResource(5)Tilemap.png(JapaneseCity_Complete_v7_1) - ExtResource(6)Maintilemap.png(Train_Stationv8) - ExtResource(7)
All PNG Properties:
- Format: 8-bit/color RGBA
- Non-interlaced
- Vary in size from hundreds to thousands of pixels
Scaling Methods and Rationale
Target Size: All tiles scaled to 64×64 for gamev2 web version
Why 64×64?
- Original Godot tiles: 32×32 pixels
- Web display needs larger tiles for visibility
- 2× scaling maintains pixel art aesthetic
- Clean integer multiplier prevents artifacts
Scaling Algorithm:
tile = tile.resize((64, 64), Image.NEAREST)Why NEAREST filter?
- Preserves sharp pixel art edges
- No blur or anti-aliasing
- Exact 2× scaling (32→64) looks perfect
- Each source pixel becomes a 2×2 block
Alternative filters NOT used:
- BILINEAR: Would blur pixel art
- BICUBIC: Would create soft edges
- LANCZOS: Overkill for exact 2× scaling
Autotile Extraction Edge Case
Problem: Autotiles contain 9 variants in a 96×96 region
Solution: Extract only top-left variant
if w > 32 or h > 32:
# This is an autotile - extract just the first 32x32 cell
tile = source.crop((x, y, x + 32, y + 32))Consequences:
- Loses automatic corner/edge selection
- All autotile instances look identical
- Roads lose seamless connections
- Gardens lose organic edges
Why not extract all 9?
- Web renderer doesn't implement bitmask logic
- Would need neighbor detection algorithm in JavaScript
- Simpler to use single variant
- Future enhancement possible
Atlas Extraction Edge Case
Problem: Atlas tiles can contain 60+ variants
Current behavior: Only icon coordinate extracted
# Takes the region's top-left corner
tile = source.crop((x, y, x + 32, y + 32))Missing variants:
- Tile 59 has 60 variants, only 1 extracted
- Tile 57 has 24 variants, only 1 extracted
Future improvement: Could extract all variants with naming scheme:
tile_59_0_0.png,tile_59_0_1.png, etc.- Requires scene parser to track which variants are used
Non-Square Tile Handling
Current approach: Crop to square
# For a 64x32 tile:
tile = source.crop((x, y, x + 32, y + 32)) # Takes left half onlyEdge case: Wide and tall tiles lose information
- 64×32 tiles: Right half discarded
- 32×64 tiles: Bottom half discarded
- 160×160 tiles: Cropped to top-left 32×32
Better approach (not implemented):
- Extract full region
- Scale with aspect ratio preserved
- Pad to 64×64 with transparency
- Would require renderer changes
Missing Tile Handling
Detection:
if tile_id not in tile_mappings:
print(f"Warning: Tile {tile_id} not found in GuttyKreum.tres")
continueConsequences:
- No output PNG generated
- JSON references
tile_Xthat doesn't exist - Web renderer shows gap or error
Fallback in JSON:
sprite_name = TILE_MAP.get(tile_id, f"tile_{tile_id}_unknown")No default placeholder image - missing tiles appear as broken references
Extraction Success Tracking
The extraction script reports:
success_count = 0
for tile_id in USED_TILE_IDS:
if extract_tile(...):
success_count += 1
print(f"✓ Successfully extracted {success_count}/{len(USED_TILE_IDS)} tiles")Failure causes:
- Source PNG not found
- Invalid region coordinates
- File permission issues
- Disk space issues
9. Special Properties and Limitations
Properties NOT Implemented in This Project
1. Animated Tiles
Search for "animation_" in tilesets: No results found
- No tile animations used
- All tiles are static
- animation_frames property never set
- animation_speed never configured
2. Collision Shapes
All tiles have: shapes = [ ]
- No per-tile collision polygons
- Layer-level collision only (overworld-collidables)
- Simple grid-based collision
- No complex shapes or slopes
3. Navigation Meshes
All tiles have: navpoly_map = [ ]
- No pathfinding navigation data
- No agent-based pathfinding
- No navigation regions
4. Occluders
All tiles have: occluder_map = [ ]
- No light occlusion
- No 2D lighting system used
- No shadow casting
5. Tile Priority
All tiles have: priority_map = [ ]
- No tile rendering priority overrides
- Default z-order from layers only
6. Z-Index Per Tile
All tiles have: z_index_map = [ ]
- No individual tile z-ordering
- Z-order set at layer level only
- All tiles in a layer render at same depth
7. One-Way Collision
All tiles have:
shape_one_way = false
shape_one_way_margin = 0.0
- No platform-style collision
- No jump-through surfaces
8. Tile Transformations in Scene
Flags in PoolIntArray always = 0
- No rotated tiles
- No flipped tiles
- All tiles placed in default orientation
Texture Properties (Always Default Values)
1. Texture Offset
tex_offset = Vector2( 0, 0 )
- Never used for pixel-perfect adjustments
- All tiles aligned to grid
2. Modulate Color
modulate = Color( 1, 1, 1, 1 )
- No color tinting applied
- All tiles use original colors
- Full opacity (alpha = 1)
3. Shape Transform
shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 )
- Identity matrix (no transformation)
- No rotation or skewing
- No scaling offset
4. Navigation Offset
navigation_offset = Vector2( 0, 0 )
- Default position
- Not used (no navigation meshes)
Cell Properties (Fixed Values)
1. Cell Size
cell_size = Vector2( 32, 32 )
- Locked at 32×32 pixels
- Never varies across project
- All tilemaps use same grid size
2. Format
format = 1
- Compatibility mode
- Standard PoolIntArray format
- Not using compressed format
3. Cell Transformations
- No rotation/flip in scene data
- Flags always 0
- Could support it but unused
Tileset Limitations
1. No Material Override
- All tiles use default material
- No shader effects
- No custom rendering
2. No Custom Data Layers
- Godot 4.x feature not available
- Using Godot 3.6
- No custom metadata per tile
3. No Tile Proxies
- No tile ID remapping
- Direct ID references only
4. Fixed Tile Count
- 169 tiles defined
- Can't dynamically add tiles
- Scene references fixed IDs
10. Complete Data Flow
Full Pipeline: Godot Scene → Web Renderer
Phase 1: Godot Scene Loading (Godot Engine)
Step 1: Parse .tscn File
tokyo_outside.tscn → Godot Parser → Scene Tree
- Reads external resources (tileset references)
- Builds node hierarchy
- Loads TileMap nodes
Step 2: Load Tileset Resources
GuttyKreum.tres → Parse → 169 tile definitions
julia_tiles.tres → Parse → 7 tile definitions
- Maps tile IDs to textures and regions
- Loads autotile bitmask configurations
- Loads atlas configurations
Step 3: Load Texture Resources
ExtResource(1) → MainTileMap.png → Texture object
ExtResource(2) → Main_Tilemap.png → Texture object
... (7 total PNG files)
- Loads each tilemap PNG into memory
- Creates GPU textures
- Ready for region extraction
Step 4: Process TileMap Layers
For each TileMap node:
1. Read tile_data PoolIntArray
2. Parse triplets (position, tile_id, flags)
3. Decode positions to grid coordinates
4. Build tile grid data structure
Phase 2: Tile Rendering (Godot Engine)
Step 5: For Each Tile Position
Grid position (-60, -61) → World position (-1920, -1952)
- Multiply by cell_size (32×32)
Step 6: Tile Lookup
Tile ID 8 → GuttyKreum.tres → Find definition
Step 7: Determine Tile Mode
If Mode 0 (Single Tile):
Direct region lookup: Rect2(128, 224, 32, 32)
Extract from texture → Render at position
If Mode 1 (Autotile):
1. Check 8 neighboring positions
2. For each neighbor:
- Is same tile ID? → Add to bitmask
3. Calculate final bitmask value
4. Look up bitmask in bitmask_flags
5. Find matching Vector2 position in 3x3 grid
6. Calculate region: base_x + (grid_x * 32), base_y + (grid_y * 32)
7. Extract 32x32 region → Render at position
Example autotile selection:
Neighbors: Top=yes, Right=yes, Bottom=no, Left=no
Bitmask: 2 + 32 = 34
Match: Vector2(2,0) → Top-right corner variant
Region: Rect2(1248 + 64, 288 + 0, 32, 32) = (1312, 288, 32, 32)
If Mode 2 (Atlas):
Use icon_coordinate to select variant
Calculate region from atlas grid
Extract 32x32 region → Render at position
Step 8: Layer Compositing
Render layers in z-order:
z=0 (floor) → Bottom layer
z=1 (overfloor)
z=3 (julia-tmp)
z=4 (noncollidables)
z=5 (collidables)
z=6 (other-tmp) → Top layer
Phase 3: Web Export Pipeline (Python Scripts)
Step 9: Parse Tileset Definitions (extract_from_guttykreum.py)
parse_guttykreum_tres(tres_path) → Returns:
{
8: ("/path/to/Osaka_V5/Tilemap.png", (128, 224, 32, 32)),
9: ("/path/to/Osaka_V5/Tilemap.png", (32, 224, 32, 32)),
... 167 more tiles
}Step 10: Extract Individual Tiles
For each tile ID in USED_TILE_IDS (140 tiles):
1. Open source PNG with PIL
2. Get region (x, y, w, h)
3. Handle special cases:
- If autotile (w>32 or h>32):
→ Crop to top-left 32x32 only
- If regular tile:
→ Crop exact region
4. Scale to 64x64 with NEAREST filter
5. Save to gamev2/assets/sprites/tile_X.pngExtraction code:
source = Image.open(source_image_path)
if w > 32 or h > 32:
tile = source.crop((x, y, x + 32, y + 32)) # Autotile: top-left only
else:
tile = source.crop((x, y, x + w, y + h)) # Regular: exact region
tile = tile.resize((64, 64), Image.NEAREST) # Scale to web size
tile.save(output_path)Step 11: Parse Scene File (parse_godot_scene.py)
parse_tscn_file("tokyo_outside.tscn", multi_layer=True) → Returns:
{
"floor": {(-60, -61): 8, (-60, -60): 8, ...},
"overfloor": {(10, 15): 45, ...},
"overworld-collidables": {(20, 20): 100, ...},
...
}Parsing process:
# Read PoolIntArray
values = [int(x) for x in array_str.split(',')]
# Parse triplets
for i in range(0, len(values), 3):
position = values[i]
tile_id = values[i + 1]
flags = values[i + 2]
x, y = decode_godot_coordinate(position)
tiles[(x, y)] = tile_idStep 12: Apply Crop and Normalization
# Define viewport
crop_start_x = 9
crop_start_y = 9
max_width = 40
max_height = 30
# Filter tiles to viewport
for (x, y), tile_id in tiles.items():
if 9 <= x <= 48 and 9 <= y <= 38:
# Normalize to origin
norm_x = x - 9 # Range: 0-39
norm_y = y - 9 # Range: 0-29
# Map to sprite name
sprite = TILE_MAP.get(tile_id, f"tile_{tile_id}")
# Store in JSON format
floor[f"{norm_x},{norm_y}"] = spriteStep 13: Generate Multi-Layer JSON (parse_tokyo_multilayer.py)
gamev2_layers = [
{
"name": "Floor",
"z": 0,
"collision": false,
"tiles": {"0,0": "tile_8", "0,1": "tile_8", ...}
},
{
"name": "Collidables",
"z": 5,
"collision": true,
"tiles": {"11,11": "tile_100", ...}
},
...
]Step 14: Write to config.json
{
"rooms": {
"Tokyo": {
"layers": [
{"name": "Floor", "z": 0, "collision": false, "tiles": {...}},
{"name": "Over Floor", "z": 1, "collision": false, "tiles": {...}},
...
]
}
}
}Phase 4: Web Rendering (JavaScript/gamev2)
Step 15: Load Room Configuration
fetch('config.json')
.then(config => config.rooms.Tokyo)
.then(room => renderRoom(room))Step 16: Pre-load Tile Sprites
const sprites = {}
for (let layer of room.layers) {
for (let [coord, spriteName] of Object.entries(layer.tiles)) {
if (!sprites[spriteName]) {
sprites[spriteName] = new Image()
sprites[spriteName].src = `assets/sprites/${spriteName}.png`
}
}
}Step 17: Render Layers by Z-Order
// Sort layers by z-order
layers.sort((a, b) => a.z - b.z)
// Render each layer
for (let layer of layers) {
for (let [coord, spriteName] of Object.entries(layer.tiles)) {
const [x, y] = coord.split(',').map(Number)
const sprite = sprites[spriteName]
// Calculate screen position
const screenX = x * 64 // 64px tile size for web
const screenY = y * 64
// Draw to canvas
ctx.drawImage(sprite, screenX, screenY, 64, 64)
}
}Step 18: Handle Collision
// Only check tiles in collision layers
for (let layer of layers.filter(l => l.collision)) {
for (let [coord, spriteName] of Object.entries(layer.tiles)) {
const [x, y] = coord.split(',').map(Number)
if (playerX === x && playerY === y) {
// Collision detected - block movement
return false
}
}
}Complete Flow Diagram
┌─────────────────────────────────────────────────────────────┐
│ GODOT ENGINE (Design Time) │
├─────────────────────────────────────────────────────────────┤
│ 1. tokyo_outside.tscn → TileMap nodes with PoolIntArrays │
│ 2. GuttyKreum.tres → 169 tile definitions │
│ 3. 7 × PNG tilesets → Source images │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ PYTHON EXTRACTION (Build Time) │
├─────────────────────────────────────────────────────────────┤
│ 4. parse_guttykreum_tres.py → Tile definitions map │
│ 5. extract_from_guttykreum.py → 140 × PNG sprites (64×64) │
│ 6. parse_godot_scene.py → Parse PoolIntArrays │
│ 7. parse_tokyo_multilayer.py → Multi-layer JSON │
│ 8. OUTPUT: config.json + tile sprites │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ WEB RENDERER (Run Time) │
├─────────────────────────────────────────────────────────────┤
│ 9. Load config.json → Room layer structure │
│ 10. Load tile_X.png sprites → Image cache │
│ 11. Render layers by z-order → Canvas drawing │
│ 12. Check collision layer → Movement blocking │
└─────────────────────────────────────────────────────────────┘
Data Transformation Summary
Godot Format:
Position: -3997756 (encoded)
Tile ID: 8
Flags: 0
Decoded:
Grid: x=-60, y=-61
Tile: "block"
Source: Osaka_V5/Tilemap.png
Region: (128, 224, 32, 32)
Normalized:
Viewport: x=0, y=0 (after crop and normalize)
JSON Output:
{
"tiles": {
"0,0": "tile_8"
}
}Web Render:
drawImage(tile_8_sprite, 0, 0, 64, 64)This complete pipeline transforms Godot's optimized tilemap format into a web-friendly sprite-based rendering system, handling all edge cases along the way.
Summary of All Edge Cases
By Category
Tile Mode Edge Cases:
- Autotiles lose variants when extracted (only top-left kept)
- Atlas tiles have 60+ variants but only icon exported
- Single tiles are straightforward with no edge cases
Coordinate Edge Cases:
4. Negative coordinates require two's complement conversion
5. 32-bit encoding limits map size to ±32k tiles
6. PoolIntArray uses triplets (incomplete data ignored)
Autotile Edge Cases:
7. Bitmask mode 2 with 512 possible values
8. 3×3 grid provides 9 tile variants
9. Fallback to first tile if no bitmask match
10. Neighbor detection checks all 8 adjacent cells
Atlas Edge Cases:
11. No automatic selection (manual placement only)
12. Up to 10×6 grids (60 variants)
13. Icon coordinate determines editor representation
Multi-Size Edge Cases:
14. Tiles range from 32×32 to 1312×256 pixels
15. Large tiles span multiple grid cells
16. Extraction crops large tiles to 32×32 (data loss)
Layer Edge Cases:
17. 8 layers with 2 non-visual (skipped)
18. Z-ordering from 0 to 6
19. Only 1 layer has collision enabled
20. Coordinate normalization crop (9,9) to (48,38)
21. Tiles outside crop region discarded
Extraction Edge Cases:
22. NEAREST scaling preserves pixel art
23. 32×32 → 64×64 for web display
24. Autotiles: top-left variant only
25. Atlas: icon coordinate only
26. Missing tiles logged but no placeholder
Limitation Edge Cases:
27. No animations (all static)
28. No collision shapes (grid-based only)
29. No navigation meshes
30. No light occluders
31. No tile priorities or z-overrides
32. No tile transformations (flip/rotate)
33. Fixed cell size (32×32)
34. No color modulation used
This comprehensive documentation covers every aspect of the Godot tile mapping system in your project, from the low-level encoding to the high-level rendering pipeline.