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
{
"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 asTilemapData.parsed.metaandtile_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 intoTilemapData.background_layer(BackgroundLayer); additional image layers remain indata.parsed.layersfor manual loading. - Object animation:
objects[].animationwith requiredframe_count+frame_duration_msand optionalspeed,loop,animation_mode,random_phase,frames. Parsed asObjectAnimation; access viaobj.animation(raw dataclass) ordata.get_object_animation(obj)which returns normalizedAnimDatadict withframes(list of surfaces),frame_w/h,frame_duration_ms,loop,animation_mode, andproperties. When per-objectanimationisNone, the effective animation falls back to the object's tilesetParsedTileset.animation(per-tileset strip shared by all instances — e.g., all coins) viaget_tileset_animation(obj.ttype);get_object_animationhandles this fallback automatically. Playback (speed, loop,random_start_timeshash(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
{
"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-idtileswithshapes[]. - Each shape:
type: "polygon",vertices(tile-local, y-down),one_way. - Tiles missing from
tilesare walkable. Tile8above is a one-way platform (top 16px solid). - Parser:
parse_tileset_collision/load_tileset_collision/CollisionCache.get_tileset_collision→TilesetCollision.
CHARACTER COLLISION
{
"name": "hero",
"shape": {
"type": "rectangle",
"width": 24.0,
"height": 32.0,
"offset": [4.0, 0.0]
},
"properties": {
"collision_layer": 1,
"collision_mask": 65535
}
}- One
shapeper character:rectangle|circle|capsule|polygon, withoffset. properties.collision_layer/collision_maskdefault to 1 / all.- Parser:
parse_character_collision/load_character_collision→CharacterCollision. Apply itsshapeto your sprite at spawn. Both acceptrender_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 fromSpriteAnimationSet.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
{
"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 avariant_id+duration_ms;loopandfpsmetadata.- Runtime:
SpriteAnimationSet.load(...)+AnimationPlayer.update(dt_ms); passrender_scale=toload()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.
{
"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
}
]
}