Skip to content

feat: Support infinite maps in flame_tiled - #4074

Merged
spydon merged 3 commits into
mainfrom
feat/flame-tiled-infinite-maps
Sep 30, 2026
Merged

spydon merged 3 commits into
mainfrom
feat/flame-tiled-infinite-maps

Conversation

@ufrshubham

@ufrshubham ufrshubham commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Description

Stacked on #4073, which upgrades flame_tiled to tiled 0.12.0.

Adds support for infinite maps from the Tiled editor.

Until now, loading an infinite map crashed with a null check error. Infinite maps don't have a
single tileData grid. Tiled stores their tiles in chunks, usually 16×16, placed at any tile
coordinate, including negative ones when you paint left of or above the origin. flame_tiled
assumed a width × height grid starting at (0, 0) everywhere: cacheTiles() started with
layer.tileData!, and cached transforms were indexed as transforms[x][y].

This PR builds on the new TileLayer tile access APIs from flame-engine/tiled.dart#94
(contentBounds, tileAt, setTileAt, forEachTile), which work the same way for finite and
infinite layers.

Tile layers (FlameTileLayer and the four orientation layers):

  • The transform cache now covers the layer's contentBounds: (0, 0, width, height) for finite
    layers, and the area covered by all chunks for infinite ones. Transforms are stored relative to
    that area's top-left corner (originX/originY), so negative coordinates work. transformAt
    and storeTransform do the offset, so nothing indexes the cache directly anymore.
  • Tiles are painted in row order: rows sorted by y, each row sorted by x, through
    tilesByWorldRow(). This matters because infinite layers store tiles chunk by chunk. Painting
    them in that order would draw all of one chunk before the first row of the chunk next to it.
    Tiles that overlap their neighbours (isometric, staggered, hexagonal and oversized tiles) would
    then stack wrongly along chunk borders. Finite maps are visited in the same order as before, so
    they render exactly the same.

RenderableTiledMap:

  • getTileData/setTileData (and the ByLayerIndex variants) use TileLayer.tileAt/
    setTileAt. They take the tile coordinates shown in the Tiled editor, so negative coordinates
    work on infinite maps. For cells outside the map (or outside every chunk), getTileData returns
    null and setTileData does nothing. Before, both threw a RangeError for cells outside a
    finite map.
  • tileStack uses transformAt, so it also works at negative coordinates.

Current limitations (also documented in tiled.md):

  • All chunks in the file are cached at load time. Streaming chunks based on the camera isn't
    implemented yet.
  • TiledComponent's size still comes from the map's width and height, so tiles at negative
    coordinates render outside the component's bounds.
  • setTileData can't create new chunks.

Tests:

  • Loading an empty infinite map, and reading, writing and tileStack at negative coordinates.
  • Three goldens, rendered with a new renderMapRegionToPng helper that can capture negative
    coordinates:
    • infinite_map.png: a whole infinite map, with two layers across six chunks.
    • infinite_oversized_tiles_orthogonal.png: a dungeon room across the chunk border at x = 0,
      with a 32×32 door standing in front of the top wall, so its frame overlaps the walls of both
      chunks. I checked that this golden fails if tiles are painted chunk by chunk: the wall of the
      right chunk then covers half the door.
    • infinite_oversized_tiles_isometric.png: an isometric scene across the four chunks around the
      origin.

Checklist

  • I have followed the Contributor Guide when preparing my PR.
  • I have updated/added tests for ALL new/updated/fixed functionality.
  • I have updated/added relevant documentation in docs and added dartdoc comments with ///.
  • [-] I have updated/added relevant examples in examples or docs.

Breaking Change?

  • Yes, this PR is a breaking change.
  • No, this PR is not a breaking change.

Related Issues

Closes #2855
Closes #3932

tiled 0.12.0 resolves external tilesets and templates through
ParserProvider and replaces TsxProvider, so flame_tiled is updated to
the new API.

- Load external files through the async TiledMap.fromString loader,
  which is given the path of each referenced file. Object templates are
  resolved through it as well now, not only tilesets.
- FlameTsxProvider implements ParserProvider instead of the removed
  TsxProvider, so it can still be passed to TiledMap.parseTmx and
  TiledMap.parseJson for a preloaded tileset.
- Drop the direct xml dependency from flame_tiled, it is not used
  anymore.
- Allow xml 7 in flame_kenney_xml, since tiled 0.12 requires it and the
  workspace resolves a single version.

BREAKING CHANGE: flame_tiled re-exports tiled 0.12.0, which replaces
TsxProvider with ParserProvider. FlameTsxProvider no longer implements
TsxProvider; it implements ParserProvider instead.
Use the TileLayer access APIs from tiled 0.12.0 (contentBounds, tileAt,
setTileAt, forEachTile) so that infinite maps, whose tiles live in
chunks at possibly negative coordinates instead of in tileData, can be
loaded, rendered and edited.

Tile layers:
- Size the cached transforms from the layer's contentBounds and store
  them relative to an origin, so tiles at negative Tiled coordinates are
  supported. Add transformAt/storeTransform to access them.
- Iterate tiles through tilesByWorldRow(), which groups the non-empty
  tiles by row, sorted by y and then x. Infinite layers store tiles chunk
  by chunk, and painting them in that order would draw overlapping tiles
  (isometric, staggered, hexagonal and oversized tiles) in the wrong
  order at chunk borders. Finite maps render exactly as before.

RenderableTiledMap:
- getTileData/setTileData use TileLayer.tileAt/setTileAt and return
  null / do nothing for cells outside of the layer instead of throwing.
- tileStack uses transformAt, so it works with negative coordinates.

Tests and docs:
- Add infinite map fixtures and tests for loading, tile access and
  editing at negative coordinates.
- Add golden tests rendering a whole infinite map, an isometric map
  across the four chunks around the origin, and an orthogonal room with
  an oversized door tile overlapping walls of two chunks, which fails if
  tiles are painted chunk by chunk.
- Document infinite map support and its current limitations.
spydon pushed a commit that referenced this pull request Sep 30, 2026
Upgrades `flame_tiled` from `tiled` 0.11.0 to 0.12.0.

0.12.0 has the new `TileLayer` tile access APIs (`tileAt`, `setTileAt`,
`forEachTile`,
`contentBounds`), which infinite map support needs. That support is in
#4074, which is stacked on
this PR. This PR only does the upgrade, so the breaking part can be
reviewed on its own.

The breaking change in 0.12.0 comes from flame-engine/tiled.dart#72:
external tilesets and
templates are now resolved through `ParserProvider` instead of
`TsxProvider`, and
`TileMapParser` is gone. `flame_tiled` re-exports
`package:tiled/tiled.dart`, so this reaches
Flame users directly.

Changes:

- **Loading external files:** `RenderableTiledMap.fromString` passes a
loader to the new async
`TiledMap.fromString`. tiled calls it with the path of every external
file the map references,
relative to the map, and the loader reads it from the asset bundle.
Before, `flame_tiled` had to
create a `TsxProvider` per tileset. External object templates (`.tx`)
now load through the same
  path, not only tilesets.
- **`FlameTsxProvider`:** now implements `ParserProvider` instead of the
removed `TsxProvider`, so
it can still be passed to `TiledMap.parseTmx`/`parseJson` for a
preloaded tileset. `parse`,
  `filename`, `data`, `getSource` and `getCachedSource` are still there.
- **Dependencies:**
- `flame_tiled` no longer depends on `xml` directly. It was only used to
build an `XmlParser`,
    and `Parser.fromString` from `tiled` does that now.
- `flame_kenney_xml` now allows `xml` 7 (`>=6.5.0 <8.0.0`). tiled 0.12
requires `xml: ^7.0.0`,
and the workspace resolves a single version, so without this the
workspace doesn't resolve.
`flame_kenney_xml` doesn't depend on `tiled`, and its tests pass on xml
7.0.1.

`TiledComponent.load` and `RenderableTiledMap.fromFile`/`fromString` are
used exactly as before.
The existing tests for external tilesets and `FlameTsxProvider` pass
unchanged.
Base automatically changed from feat/flame-tiled-tiled-0.12 to main September 30, 2026 19:48

@spydon spydon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, lgtm!
It was surprisingly little code to solve that.

@spydon
spydon merged commit 72901b4 into main Sep 30, 2026
8 checks passed
@spydon
spydon deleted the feat/flame-tiled-infinite-maps branch September 30, 2026 19:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tiled's infinite mode will report an error [flame_tiled] Null check on tileData fails for infinite maps

2 participants