tilemap-parserv5.2.0GITHUB

Map Parsing & Rendering

Loading a map is one call; rendering is one call per frame. load_map() parses the tilemap-editor JSON into a TilemapData, and TileLayerRenderer draws it with chunked culling.

LOADING MAPS

load_map.pyPYTHON
from tilemap_parser import load_map

game_data = load_map("data/map.json")

print(f"Map size: {game_data.map_size}")     # (cols, rows)
print(f"Tile size: {game_data.tile_size}")   # (w, h), effective px
print(f"Render scale: {game_data.render_scale}")

# Layers, in draw order, filtered by type if you want:
for layer in game_data.get_layers(layer_type="tile"):
    print(layer.name, layer.z_index)

The raw parsed structure lives on game_data.parsed (see the API reference's parsed data classes). For collision you usually want the union grid: game_data.build_tile_map() stacks every tile layer Godot-style into {(col, row): ((gid, flipbits), ...)}, which is what PhysicsWorld holds. Overlapping layers never overwrite each other — a deco tile on top can't erase solid ground below, and each entry keeps its own flip flags.

TILE RENDERING

TileLayerRenderer draws only the visible chunks, respects layer z_index and y_sort, and reports what it did. The camera offset is passed per call; nothing is stored:

game loopPYTHON
from tilemap_parser import TileLayerRenderer

renderer = TileLayerRenderer(game_data)

# In your game loop:
stats = renderer.render(screen, camera.offset)
# stats: LayerRenderStats: drawn_tiles, skipped_tiles, visible_layers
  • render(target, camera_xy, viewport_size=None, *, extra_objects=None, current_time_ms=None): current_time_ms drives tileset animations.
  • extra_objects lets you blit sprites in the same pass (any objects with surface, x, y), after the tile layers.
  • warm_cache() pre-bakes every tile variant up front, then frees the source data.

RENDERING & COLLISION OVERLAYS

The renderer is collision-blind: it draws tile textures from each layer's ttype / variant and has no idea whether a tile is solid, one-way, or air. Collision facts live in the world — world.tile_map plus the collision tileset. The split is on purpose: render everything, collide with a subset (exclude_layers only affects the world).

Want debug overlays — say, dashed edges on one-way platforms? Ask the world, not the renderer:

overlay.pyPYTHON
from tilemap_parser import iter_cell_entries

for entry in iter_cell_entries(world.tile_map.get((tx, ty))):
    tile_data = world.resolve_stack_entry(entry)  # GID-routed + flipped
    if tile_data is None:
        continue
    if any(s.one_way for s in tile_data.shapes):
        # dashed top edge at (tx * world.tile_size[0], ty * world.tile_size[1])

one_way is authored per polygon in the collision JSON — never in the map JSON — and platformer movement already handles it (blocks from above, passes from below), so the query is for visuals only. Two id-space caveats:

  • The renderer keys tiles by layer ttype; the world keys by stacked (gid, flipbits) entries. Same map, same gids — until use_gids=True makes the world's ids global and the two spaces diverge. Always query through world.tile_map with iter_cell_entries / world.resolve_stack_entry, never through the renderer.
  • Tiles with no collision entry draw fine but collide as air; layers in exclude_layers or with collision_enabled=False draw fine but don't exist for physics.

EXTRACTING OBJECTS

Object layers become MapObjects: pre-scaled surfaces, positions and collision shapes, ready to feed an ObjectCollisionManager. load_map_objects takes the map and a directory containing the matching .object_collision.json files:

objects.pyPYTHON
from tilemap_parser import load_map_objects, ObjectCollisionManager

objects = load_map_objects(game_data, "data/object_collision")

manager = ObjectCollisionManager()
for obj in objects:
    manager.add_object(obj)

player_start = next((o for o in objects if o.name == "PlayerStart"), None)
if player_start is not None:
    player.x, player.y = player_start.x, player_start.y
  • Every object layer is iterated; there is no per-layer filter. The first region's layer/mask are adopted by the object.
  • require_collision=False (the default) also returns visual-only objects (with empty shapes — gate on has_collision before adding to the collision manager); pass True to return only objects that have matching collision regions.
  • All spatial data is pre-scaled by the map's render_scale; no scaling on your side.

BACKGROUND (IMAGE) LAYERS

Image layers hold a single external image — a parallax sky, backdrop, or full-screen art. They carry no tiles or objects, only image_path and image_rect. The parser parses all image-layer metadata as ParsedLayer with layer_type == "image" (aliases "background" / "background_layer" are also accepted) but TilemapData.load eagerly loads only the first visible image layer into TilemapData.background_layer; any image layer can be loaded on demand with get_image_layer_surface / get_image_layer_surfaces, which resolve ../../assets-style paths and cache results.

background.pyPYTHON
data = load_map("data/map.json")

bg = data.background_layer      # BackgroundLayer | None
if bg is not None and bg.surface is not None:
    # bg.image_path, bg.image_rect (x,y,w,h), bg.surface
    pos = (bg.image_rect[0], bg.image_rect[1]) if bg.image_rect else (0, 0)
    screen.blit(bg.surface, pos)

# Or query any image layer directly:
for layer in data.get_layers(layer_type="image"):
    print(layer.name, layer.image_path, layer.image_rect)

# Lazy surface loading (visible layers by default):
surf = data.get_image_layer_surface("sky")  # by name, id, or ParsedLayer
all_bgs = data.get_image_layer_surfaces()   # [(layer, surface)] in z-order
hidden_too = data.get_image_layer_surfaces(include_hidden=True)

# One image, placed N times (editor duplicates): single composited surface
composed = data.get_placed_image_layer_surface("sky", render_scale=data.render_scale)
  • image_path: project-relative path; resolved against the map directory (and extra_search_base if given). Missing files produce a warning and surface == None only when skip_missing_images is enabled (default true); when skip_missing_images=False, loading raises MapParseError.
  • image_rect: pixel rect (x, y, w, h) where the image is drawn. None when not authored.
  • Only the first visible image/background layer is exposed as background_layer; all are still in data.parsed.layers. Use get_image_layer_surface(layer) / get_image_layer_surfaces() to load any image layer on demand — hidden layers are skipped unless you pass include_hidden=True. Layers with editor duplicates expose image_placements; get_placed_image_layer_surface() composites base + copies into one surface (no manual tiling loops).