Skip to content

tokyo to gamev2 #34

@baely

Description

@baely

Complete Godot Tile Mapping Documentation: From .tscn to PNG

All Edge Cases and Implementation Details


Table of Contents

  1. Overview
  2. Tile Mode Types
  3. Coordinate Encoding
  4. Autotile Edge Cases
  5. Atlas Tile Edge Cases
  6. Multi-Size Tile Edge Cases
  7. Layer System Edge Cases
  8. Extraction and Scaling Edge Cases
  9. Special Properties and Limitations
  10. 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:

  1. 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
  2. Tile 59 (fence-garden-outside): 320x192 pixels

    • Grid: 10×6 = 60 possible variants
    • Source: TemplesandShrinesv2/MainTileMap/MainTileMap.png
  3. Tile 79: Large decorative atlas

  4. Tile 89: Environmental atlas

  5. 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, y

Example:

  • 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:

  1. Position: Encoded as described above
  2. Tile ID: Index into the tileset (0-176 in tokyo_outside.tscn)
  3. 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 tile
  • 432 (binary 110110000): Right + Bottom neighbors → Top-left corner
  • 438 (binary 110110110): Right + Bottom + Top neighbors → Left edge
  • 54 (binary 000110110): Top + Right neighbors → Bottom-left corner
  • 216 (binary 011011000): All left-side neighbors → Right edge
  • 27 (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 = 30

This 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 discarded

Layer 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:

  1. MainTileMap.png (TemplesandShrinesv2) - ExtResource(1)
  2. Main_Tilemap.png (TemplesShrines_v2) - ExtResource(2)
  3. Tilemap.png (Osaka_V5) - ExtResource(3) - 1376 × 672 pixels
  4. MainTileMap.png (DotonboriCity_v3) - ExtResource(4)
  5. MainTileMap.png (OvergrownBackstreets_v1) - ExtResource(5)
  6. Tilemap.png (JapaneseCity_Complete_v7_1) - ExtResource(6)
  7. 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 only

Edge 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")
    continue

Consequences:

  • No output PNG generated
  • JSON references tile_X that 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.png

Extraction 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_id

Step 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}"] = sprite

Step 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:

  1. Autotiles lose variants when extracted (only top-left kept)
  2. Atlas tiles have 60+ variants but only icon exported
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions