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 image layer into TilemapData.background_layer (BackgroundLayer); additional image layers remain in data.parsed.layers for manual loading.
  • 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() flattens layers into the collision dict.

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).
  • 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
    }
  ]
}