tilemap-parserv5.2.0GITHUB

JSON Formats

Everything below is real data from the examples and fixtures, nothing invented. Each format shows the file and what the parser exposes from it. Files are authored in tilemap-editor; hand-writing them is possible but the editor keeps them consistent.

TILEMAP MAP

map.jsonJSON
{
  "meta": {
    "tile_size": "32;32",
    "map_size": "30;20",
    "zoom_level": 1.0,
    "render_scale": 1.0,
    "version": "1.1"
  },
  "resources": { "tilesets": [{ "path": "assets/hero.png", "type": "object" }] },
  "project_state": { "rules": [], "groups": [], "automap_rules": [] },
  "data": {
    "layers": [
      {
        "name": "Background",
        "type": "image",
        "visible": true, "locked": false, "opacity": 1.0, "z_index": -1,
        "tiles": {},
        "image_path": "assets/sky.png",
        "image_rect": {"x": 0, "y": 0, "w": 960, "h": 640}
      },
      {
        "name": "Layer 1",
        "type": "tile",
        "visible": true, "locked": false, "opacity": 1.0, "z_index": 0,
        "tiles": {}, "properties": {}
      },
      {
        "name": "Objects",
        "type": "object",
        "visible": true, "locked": false, "opacity": 1.0, "z_index": 1,
        "tiles": {},
        "objects": {
          "1": {
            "area": {"x": 64, "y": 128, "w": 32, "h": 32},
            "ttype": 0, "tileset_type": "object", "variant": 0,
            "animation": {
              "frame_count": 4, "frame_duration_ms": 120,
              "speed": 1.0, "loop": true, "animation_mode": "default",
              "frames": [0, 1, 2, 3]
            }
          }
        }
      }
    ]
  }
}
  • meta: tile_size (semicolon-delimited), map_size, render_scale, version. Exposed as TilemapData.parsed.meta and tile_size/render_scale.
  • resources.tilesets: tileset references, incl. animation metadata for animated tiles.
  • data.layers: tile layers with visibility, z_index, opacity, per-tile entries. Types: "tile", "object", "image" (aliases "background", "background_layer").
  • Image layers: image_path + image_rect (pixel rect {x,y,w,h}). Parses all image-layer metadata but eagerly loads only the first visible image layer into TilemapData.background_layer (BackgroundLayer); load any image layer on demand with get_image_layer_surface / get_image_layer_surfaces (include_hidden=True to include hidden layers).
  • Object animation: objects[].animation with required frame_count + frame_duration_ms and optional speed, loop, animation_mode, random_phase, frames. Parsed as ObjectAnimation; access via obj.animation (raw dataclass) or data.get_object_animation(obj) which returns normalized AnimData dict with frames (list of surfaces), frame_w/h, frame_duration_ms, loop, animation_mode, and properties. When per-object animation is None, the effective animation falls back to the object's tileset ParsedTileset.animation (per-tileset strip shared by all instances — e.g., all coins) via get_tileset_animation(obj.ttype); get_object_animation handles this fallback automatically. Playback (speed, loop, random_start_times hash (x*73856093 ^ y*19349663 ^ ttype*83492791) % count) is user-side.
  • Parser entry: load_map(path) → TilemapData; build_tile_map() unions layers into stacked (gid, flipbits) entries per cell (Godot-style — overlaps never overwrite).

TILESET COLLISION

terrain.collision.jsonJSON
{
  "tileset_name": "Terrain (32x32)",
  "tile_size": [32, 32],
  "tiles": {
    "8": {
      "tile_id": 8,
      "shapes": [
        { "type": "polygon", "vertices": [[0.0, 16.0], [32.0, 16.0], [32.0, 32.0], [0.0, 32.0]], "one_way": true }
      ]
    },
    "26": {
      "tile_id": 26,
      "shapes": [
        { "type": "polygon", "vertices": [[0.0, 0.0], [32.0, 0.0], [32.0, 32.0], [0.0, 32.0]], "one_way": false }
      ]
    }
  }
}
  • Top-level tileset_name + tile_size; per-id tiles with shapes[].
  • Each shape: type: "polygon", vertices (tile-local, y-down), one_way.
  • Tiles missing from tiles are walkable. Tile 8 above is a one-way platform (top 16px solid).
  • Per-tile properties.collision_layer / collision_mask (defaults 1 / all) filter which sprites the tile blocks — both sides must agree, same rule as bodies. Movement checks follow it; flip flags (flip_h / flip_v / flip_d on the placed tile) mirror the shapes.
  • Parser: parse_tileset_collision / load_tileset_collision / CollisionCache.get_tileset_collision → TilesetCollision.

CHARACTER COLLISION

hero.collision.jsonJSON
{
  "name": "hero",
  "shape": {
    "type": "rectangle",
    "width": 24.0,
    "height": 32.0,
    "offset": [4.0, 0.0]
  },
  "properties": {
    "collision_layer": 1,
    "collision_mask": 65535
  }
}
  • One shape per character: rectangle | circle | capsule | polygon, with offset.
  • properties.collision_layer / collision_mask default to 1 / all.
  • Parser: parse_character_collision / load_character_collision → CharacterCollision. Apply its shape to your sprite at spawn. Both accept render_scale= to scale the shape's dimensions and offsets. By design this scales collision data only — no image is touched — so the sprite paired with the shape must already be at the target resolution (e.g. frames from SpriteAnimationSet.load(render_scale=...)).

OBJECT COLLISION

Region-based polygon paint: tileset_name, regions: { id: { name, region_rect, shapes[], properties } }. Parsed by parse_object_collision → ObjectCollisionData with get_region(region_id). Each region carries its own layer/mask.

ANIMATION

player.anim.jsonJSON
{
  "spritesheet_path": "../../assets/player_spritesheet.png",
  "tile_size": [128, 96],
  "grid_offset": [0, 0],
  "animations": {
    "idle": {
      "name": "idle",
      "frames": [
        { "variant_id": 0, "duration_ms": 100.0 },
        { "variant_id": 1, "duration_ms": 100.0 }
      ],
      "loop": true,
      "fps": 60.0
    },
    "jump": {
      "name": "jump",
      "frames": [ { "variant_id": 30, "duration_ms": 100.0 } ],
      "loop": false,
      "fps": 60.0
    }
  }
}
  • spritesheet_path, tile_size, grid_offset: how to cut the sheet.
  • animations: named clips; each frame is a variant_id + duration_ms; loop and fps metadata.
  • Runtime: SpriteAnimationSet.load(...) + AnimationPlayer.update(dt_ms); pass render_scale= to load() to scale the sheet and its atlas grid together.

PARTICLE CONFIGS

Emitters placed in the editor land as nodes. The parser exposes them via parse_nodes_file (ParsedNode); parse_particle_dict/parse_particle_file parse config dicts. The same fields construct ParticleSystemConfig in code.

map.nodes.json: a particle emitter nodeJSON
{
  "version": 1,
  "groups": [],
  "nodes": [
    {
      "node_id": "fc5eae05-0f8b-42f2-9b5e-9f1d0fa1752d",
      "name": "Emitter 1",
      "node_type": "particle_emitter",
      "area": { "x": 90, "y": 5, "w": 485, "h": 314 },
      "layer_name": "decoration",
      "properties": {
        "emission_shape": "rect",
        "particle_shape": "circle",
        "particle_size_min": 1,
        "particle_size_max": 3,
        "spawn_rate": 60,
        "max_particles": 180,
        "lifetime_min": 1.0,
        "lifetime_max": 3.0,
        "speed_min": 50,
        "speed_max": 120,
        "direction": 0,
        "spread": 15,
        "gravity_x": 80,
        "gravity_y": 5,
        "start_color_r": 200, "start_color_g": 180, "start_color_b": 140, "start_color_a": 180,
        "end_color_r": 160, "end_color_g": 140, "end_color_b": 100, "end_color_a": 20,
        "start_scale": 1.0,
        "end_scale": 0.5,
        "rotation_speed": 5,
        "alpha_fade": "fade_out"
      },
      "group": null
    }
  ]
}