tilemap-parserv5.2.0GITHUB

Tile Clips: sidecar animations for tiles

Stride animation moves a whole tileset (variant + frame × stride). Tile clips animate individual cells: the editor authors named, ordered, multi-sheet clips into *.tanim.json sidecars, assigns them per variant with the anim_clip tile property, and TileLayerRenderer plays them. Stride behavior is unchanged.

SIDECAR FORMAT

water.tanim.jsonPYTHON
{"version": 1, "tileset": "tiles/water.png",
 "clips": {"waves": {"loop": true, "mode": "default",
   "frames": [{"sheet": "tiles/water.png", "variant": 5, "duration_ms": 120.0}]}}}
  • Frames are ordered with per-frame duration_ms; sheets are path refs resolved against loaded tilesets (exact path first, unique basename/stem fallback).
  • Assignment lives in the map JSON (tile_properties[variant]["anim_clip"] = "waves"); the sidecar holds reusable definitions only.
  • Malformed leaves are skipped; bad files load as empty. A file can never break rendering.

LOADING

clips.pyPYTHON
from tilemap_parser import TileLayerRenderer, load_tile_anim_file, find_tileanims

renderer = TileLayerRenderer(data, tile_clips="data/tileanims/water.tanim.json")
renderer = TileLayerRenderer(data, tile_clips=["a.tanim.json", "b.tanim.json"])
renderer = TileLayerRenderer(data, tile_clips={"waves": clip})  # pre-parsed
renderer = TileLayerRenderer(data)  # legacy: stride/static, byte-identical

auto = find_tileanims("data/tileanims")  # sorted sidecar paths

clip_property (default "anim_clip") names the tile property key; effective properties merge tileset tile_properties[variant] under per-placement props. load_optional_anim="anim" restricts stride animation to flagged tiles. Duplicate clip names: first loaded wins, with a warning on renderer.warnings.

PRECEDENCE PER CELL

clip > flagged stride > tileset stride > static. An explicit clip assignment always beats the blanket; nothing assigned behaves exactly as before.

FALLBACK BEHAVIOR

Animation only ever replaces the blit. Whatever was plotted always renders:

  • Unknown clip name, empty clip, unresolvable sheets → base variant.
  • Out-of-range frame → base variant for that frame, never a hole.
  • Non-looping clip past its end → holds the last frame.

TIMING AND PHASE

Frame lookup walks cumulative durations (same rule as the editor's clip_frame_at). mode: "random_start_times" rotates the frame order per cell with the standard hash ((x * 73856093 ^ y * 19349663 ^ ttype * 83492791) % len(frames)), so identical cells don't tick in sync.

timing.pyPYTHON
from tilemap_parser import clip_frame_at

frame = clip_frame_at(clip, pygame.time.get_ticks())  # pure function, no renderer needed