From 49f389a743d000fc1ceafe1182436243dc8c5ea0 Mon Sep 17 00:00:00 2001 From: DevKage Date: Wed, 30 Sep 2026 23:15:14 +0530 Subject: [PATCH 1/2] feat!: Upgrade flame_tiled to tiled 0.12.0 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. --- packages/flame_kenney_xml/pubspec.yaml | 2 +- .../lib/src/flame_tsx_provider.dart | 26 +++++++++---------- .../lib/src/renderable_tile_map.dart | 15 ++++++----- packages/flame_tiled/pubspec.yaml | 3 +-- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/flame_kenney_xml/pubspec.yaml b/packages/flame_kenney_xml/pubspec.yaml index b4a523c26b2..c553ec5963d 100644 --- a/packages/flame_kenney_xml/pubspec.yaml +++ b/packages/flame_kenney_xml/pubspec.yaml @@ -21,7 +21,7 @@ dependencies: flame: ^1.38.0 flutter: sdk: flutter - xml: ^6.5.0 + xml: ">=6.5.0 <8.0.0" dev_dependencies: build_runner: ^2.4.11 diff --git a/packages/flame_tiled/lib/src/flame_tsx_provider.dart b/packages/flame_tiled/lib/src/flame_tsx_provider.dart index a84571ec255..012b3bb631f 100644 --- a/packages/flame_tiled/lib/src/flame_tsx_provider.dart +++ b/packages/flame_tiled/lib/src/flame_tsx_provider.dart @@ -1,36 +1,34 @@ import 'package:flame/flame.dart'; import 'package:flutter/services.dart'; import 'package:tiled/tiled.dart'; -import 'package:xml/xml.dart'; -/// A implementation of [TsxProvider] use by RenderableTileMap. +/// A [ParserProvider] for a single external tileset file loaded from +/// [Flame.bundle] or a custom asset bundle. /// -/// It uses [Flame.bundle] or a custom asset bundle -/// and has a built-in cache for the file read. -class FlameTsxProvider implements TsxProvider { +/// `RenderableTiledMap` resolves external tilesets on its own, this is only +/// needed when parsing a map manually, for example through +/// [TiledMap.parseTmx]. +class FlameTsxProvider implements ParserProvider { /// Parsed data for this tsx file. final String data; /// Stored filename for corresponding tsx file. - final String _filename; + final String filename; - FlameTsxProvider._(this.data, this._filename); + FlameTsxProvider._(this.data, this.filename); @override - String get filename => _filename; + bool canProvide(String path) => path == filename; @override - Parser getSource(String key) { - final node = XmlDocument.parse(data).rootElement; - return XmlParser(node); - } + Parser getSource(String path) => Parser.fromString(data); - @override + /// Parsed tileset source, or `null` when [data] is empty. Parser? getCachedSource() { if (data.isEmpty) { return null; } - return getSource(''); + return getSource(filename); } /// Parses a file returning a [FlameTsxProvider]. diff --git a/packages/flame_tiled/lib/src/renderable_tile_map.dart b/packages/flame_tiled/lib/src/renderable_tile_map.dart index d0a21e77e93..453d519e8b7 100644 --- a/packages/flame_tiled/lib/src/renderable_tile_map.dart +++ b/packages/flame_tiled/lib/src/renderable_tile_map.dart @@ -7,7 +7,6 @@ import 'package:flame/extensions.dart'; import 'package:flame/flame.dart'; import 'package:flame/rendering.dart'; import 'package:flame_tiled/src/extensions.dart'; -import 'package:flame_tiled/src/flame_tsx_provider.dart'; import 'package:flame_tiled/src/renderable_layers/group_layer.dart'; import 'package:flame_tiled/src/renderable_layers/renderable_layer.dart'; import 'package:flame_tiled/src/renderable_layers/tile_layers/tile_layer.dart'; @@ -252,8 +251,9 @@ class RenderableTiledMap { /// /// {@template renderable_tile_map_path} /// The [fileName] is the full path of the map, as declared in the - /// `pubspec.yaml`, for example `assets/tiles/map.tmx`. Any external `.tsx` - /// tileset the map references is resolved relative to that path. + /// `pubspec.yaml`, for example `assets/tiles/map.tmx`. External tilesets + /// (`.tsx`) and object templates (`.tx`) that the map references are + /// resolved relative to that path. /// {@endtemplate} /// /// {@template tiled_images_directory} @@ -306,8 +306,8 @@ class RenderableTiledMap { /// Parses a string returning a [RenderableTiledMap]. /// - /// External `.tsx` tilesets the map references are resolved against - /// [tsxDirectory]. + /// External tilesets (`.tsx`) and object templates (`.tx`) that the map + /// references are resolved against [tsxDirectory]. /// /// {@macro tiled_images_directory} /// @@ -330,9 +330,12 @@ class RenderableTiledMap { double atlasPackingSpacingY = 0, String? package, }) async { + // tiled calls this once for every external file the map references, + // including files referenced from other external files, always with a + // path relative to the map. final map = await TiledMap.fromString( contents, - (key) => FlameTsxProvider.parse(key, bundle, tsxDirectory), + (path) => (bundle ?? Flame.bundle).loadString('$tsxDirectory$path'), ); return fromTiledMap( map, diff --git a/packages/flame_tiled/pubspec.yaml b/packages/flame_tiled/pubspec.yaml index 2f25e110756..ae9b0f906b4 100644 --- a/packages/flame_tiled/pubspec.yaml +++ b/packages/flame_tiled/pubspec.yaml @@ -22,8 +22,7 @@ dependencies: flutter: sdk: flutter meta: ^1.12.0 - tiled: ^0.11.0 - xml: ^6.3.0 + tiled: ^0.12.0 dev_dependencies: dartdoc: ^9.0.0 From 333ae955297987c170d18eb51153031e7367eaed Mon Sep 17 00:00:00 2001 From: DevKage Date: Wed, 30 Sep 2026 23:16:07 +0530 Subject: [PATCH 2/2] feat: Support infinite maps in flame_tiled 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. --- doc/bridge_packages/flame_tiled/tiled.md | 9 + .../tile_layers/hexagonal_tile_layer.dart | 15 +- .../tile_layers/isometric_tile_layer.dart | 17 +- .../tile_layers/orthogonal_tile_layer.dart | 17 +- .../tile_layers/staggered_tile_layer.dart | 15 +- .../tile_layers/tile_layer.dart | 77 ++++++++- .../lib/src/renderable_tile_map.dart | 51 +++--- .../test/assets/empty-infinite-map.tmx | 6 + .../flame_tiled/test/assets/infinite-map.tmx | 158 ++++++++++++++++++ .../infinite_oversized_tiles_isometric.tmx | 158 ++++++++++++++++++ .../infinite_oversized_tiles_orthogonal.tmx | 89 ++++++++++ .../flame_tiled/test/goldens/infinite_map.png | Bin 0 -> 19588 bytes .../infinite_oversized_tiles_isometric.png | Bin 0 -> 25723 bytes .../infinite_oversized_tiles_orthogonal.png | Bin 0 -> 4533 bytes .../flame_tiled/test/test_image_utils.dart | 21 +++ packages/flame_tiled/test/tiled_test.dart | 144 ++++++++++++++++ 16 files changed, 705 insertions(+), 72 deletions(-) create mode 100644 packages/flame_tiled/test/assets/empty-infinite-map.tmx create mode 100644 packages/flame_tiled/test/assets/infinite-map.tmx create mode 100644 packages/flame_tiled/test/assets/infinite_oversized_tiles_isometric.tmx create mode 100644 packages/flame_tiled/test/assets/infinite_oversized_tiles_orthogonal.tmx create mode 100644 packages/flame_tiled/test/goldens/infinite_map.png create mode 100644 packages/flame_tiled/test/goldens/infinite_oversized_tiles_isometric.png create mode 100644 packages/flame_tiled/test/goldens/infinite_oversized_tiles_orthogonal.png diff --git a/doc/bridge_packages/flame_tiled/tiled.md b/doc/bridge_packages/flame_tiled/tiled.md index 4a5b7cd3394..0a465023b29 100644 --- a/doc/bridge_packages/flame_tiled/tiled.md +++ b/doc/bridge_packages/flame_tiled/tiled.md @@ -26,6 +26,15 @@ The [dart] package provides a simple `Tiled` class and [flame_tiled] provides a `TiledComponent`, for the map rendering, which renders the tiles on the screen and supports rotations and flips. +Infinite maps from the Tiled editor are supported. Tile positions, `getTileData`, and object +coordinates match the editor (including negative tile indices if you painted left or above the +origin). All chunks present in the file are cached at load time; very large infinite maps may +therefore be expensive. Chunk streaming based on the camera is not implemented yet. + +Keep in mind that the size of a `TiledComponent` still comes from the map's `width` and `height`, +so tiles outside of that area (for example at negative indices) are rendered outside of the +component's bounds. `setTileData` only changes cells inside chunks that already exist in the map. + ## Tiled Editor diff --git a/packages/flame_tiled/lib/src/renderable_layers/tile_layers/hexagonal_tile_layer.dart b/packages/flame_tiled/lib/src/renderable_layers/tile_layers/hexagonal_tile_layer.dart index 04726a45ca2..6cd0f4b8ced 100644 --- a/packages/flame_tiled/lib/src/renderable_layers/tile_layers/hexagonal_tile_layer.dart +++ b/packages/flame_tiled/lib/src/renderable_layers/tile_layers/hexagonal_tile_layer.dart @@ -24,7 +24,6 @@ class HexagonalTileLayer extends FlameTileLayer { @override void cacheTiles() { - final tileData = layer.tileData!; final halfDestinationTile = destTileSize / 2; final size = destTileSize; final halfMapTile = Vector2(map.tileWidth / 2, map.tileHeight / 2); @@ -43,9 +42,7 @@ class HexagonalTileLayer extends FlameTileLayer { staggerX = size.x * 0.75; } - for (var ty = 0; ty < tileData.length; ty++) { - final tileRow = tileData[ty]; - + for (final MapEntry(key: ty, value: tileRow) in tilesByWorldRow().entries) { // Hexagonal Pointy Tiles shift left and right depending on the row if (map.staggerAxis == StaggerAxis.y) { if ((ty.isOdd && map.staggerIndex == StaggerIndex.odd) || @@ -61,12 +58,7 @@ class HexagonalTileLayer extends FlameTileLayer { // painted over. See the second pass loop after tx. final xSecondPass = []; - for (var tx = 0; tx < tileRow.length; tx++) { - final tileGid = tileRow[tx]; - if (tileGid.tile == 0) { - continue; - } - + for (final (tx, tileGid) in tileRow) { final tile = map.tileByGid(tileGid.tile)!; final tileset = map.tilesetByTileGId(tileGid.tile); final img = tile.image ?? tileset.image; @@ -125,7 +117,7 @@ class HexagonalTileLayer extends FlameTileLayer { final scos = flips.cos * scale; final ssin = flips.sin * scale; - final transform = transforms[tx][ty] = MutableRSTransform( + final transform = MutableRSTransform( scos, ssin, offsetX, @@ -133,6 +125,7 @@ class HexagonalTileLayer extends FlameTileLayer { -scos * anchorX + ssin * anchorY, -ssin * anchorX - scos * anchorY, ); + storeTransform(tx, ty, transform); // A second pass is only needed in the case of staggery. if (map.staggerAxis == StaggerAxis.x && staggerY > 0) { xSecondPass.add(TileTransform(src, transform, flips, batch)); diff --git a/packages/flame_tiled/lib/src/renderable_layers/tile_layers/isometric_tile_layer.dart b/packages/flame_tiled/lib/src/renderable_layers/tile_layers/isometric_tile_layer.dart index 781c5929ece..addc692ffcd 100644 --- a/packages/flame_tiled/lib/src/renderable_layers/tile_layers/isometric_tile_layer.dart +++ b/packages/flame_tiled/lib/src/renderable_layers/tile_layers/isometric_tile_layer.dart @@ -23,7 +23,6 @@ class IsometricTileLayer extends FlameTileLayer { @override void cacheTiles() { - final tileData = layer.tileData!; final halfDestinationTile = destTileSize / 2; final size = destTileSize; final isometricXShift = map.height * halfDestinationTile.x; @@ -34,15 +33,8 @@ class IsometricTileLayer extends FlameTileLayer { return; } - for (var ty = 0; ty < tileData.length; ty++) { - final tileRow = tileData[ty]; - - for (var tx = 0; tx < tileRow.length; tx++) { - final tileGid = tileRow[tx]; - if (tileGid.tile == 0) { - continue; - } - + for (final MapEntry(key: ty, value: tileRow) in tilesByWorldRow().entries) { + for (final (tx, tileGid) in tileRow) { final tile = map.tileByGid(tileGid.tile)!; final tileset = map.tilesetByTileGId(tileGid.tile); final img = tile.image ?? tileset.image; @@ -78,7 +70,7 @@ class IsometricTileLayer extends FlameTileLayer { final scos = flips.cos * scale; final ssin = flips.sin * scale; - transforms[tx][ty] = MutableRSTransform( + final transform = MutableRSTransform( scos, ssin, offsetX, @@ -86,10 +78,11 @@ class IsometricTileLayer extends FlameTileLayer { -scos * anchorX + ssin * anchorY, -ssin * anchorX - scos * anchorY, ); + storeTransform(tx, ty, transform); batch.addTransform( source: src, - transform: transforms[tx][ty], + transform: transform, flip: shouldFlip(flips), ); diff --git a/packages/flame_tiled/lib/src/renderable_layers/tile_layers/orthogonal_tile_layer.dart b/packages/flame_tiled/lib/src/renderable_layers/tile_layers/orthogonal_tile_layer.dart index 39d5f714592..9d81370428b 100644 --- a/packages/flame_tiled/lib/src/renderable_layers/tile_layers/orthogonal_tile_layer.dart +++ b/packages/flame_tiled/lib/src/renderable_layers/tile_layers/orthogonal_tile_layer.dart @@ -23,7 +23,6 @@ class OrthogonalTileLayer extends FlameTileLayer { @override void cacheTiles() { - final tileData = layer.tileData!; final size = destTileSize; final halfMapTile = Vector2(map.tileWidth / 2, map.tileHeight / 2); final batch = tiledAtlas.batch; @@ -31,15 +30,8 @@ class OrthogonalTileLayer extends FlameTileLayer { return; } - for (var ty = 0; ty < tileData.length; ty++) { - final tileRow = tileData[ty]; - - for (var tx = 0; tx < tileRow.length; tx++) { - final tileGid = tileRow[tx]; - if (tileGid.tile == 0) { - continue; - } - + for (final MapEntry(key: ty, value: tileRow) in tilesByWorldRow().entries) { + for (final (tx, tileGid) in tileRow) { final tile = map.tileByGid(tileGid.tile)!; final tileset = map.tilesetByTileGId(tileGid.tile); final img = tile.image ?? tileset.image; @@ -83,7 +75,7 @@ class OrthogonalTileLayer extends FlameTileLayer { final scos = flips.cos * scale; final ssin = flips.sin * scale; - transforms[tx][ty] = MutableRSTransform( + final transform = MutableRSTransform( scos, ssin, offsetX, @@ -91,10 +83,11 @@ class OrthogonalTileLayer extends FlameTileLayer { -scos * anchorX + ssin * anchorY, -ssin * anchorX - scos * anchorY, ); + storeTransform(tx, ty, transform); batch.addTransform( source: src, - transform: transforms[tx][ty], + transform: transform, flip: shouldFlip(flips), ); diff --git a/packages/flame_tiled/lib/src/renderable_layers/tile_layers/staggered_tile_layer.dart b/packages/flame_tiled/lib/src/renderable_layers/tile_layers/staggered_tile_layer.dart index 0d693808a69..a869a77f9eb 100644 --- a/packages/flame_tiled/lib/src/renderable_layers/tile_layers/staggered_tile_layer.dart +++ b/packages/flame_tiled/lib/src/renderable_layers/tile_layers/staggered_tile_layer.dart @@ -23,7 +23,6 @@ class StaggeredTileLayer extends FlameTileLayer { @override void cacheTiles() { - final tileData = layer.tileData!; final halfDestinationTile = destTileSize / 2; final size = destTileSize; final halfMapTile = Vector2(map.tileWidth / 2, map.tileHeight / 2); @@ -43,9 +42,7 @@ class StaggeredTileLayer extends FlameTileLayer { staggerX = size.x * 0.5; } - for (var ty = 0; ty < tileData.length; ty++) { - final tileRow = tileData[ty]; - + for (final MapEntry(key: ty, value: tileRow) in tilesByWorldRow().entries) { // Isometric staggered tiles shift left and right depending on the row if (map.staggerAxis == StaggerAxis.y) { if ((ty.isOdd && map.staggerIndex == StaggerIndex.odd) || @@ -61,12 +58,7 @@ class StaggeredTileLayer extends FlameTileLayer { // painted over. See the second pass loop after tx. final xSecondPass = []; - for (var tx = 0; tx < tileRow.length; tx++) { - final tileGid = tileRow[tx]; - if (tileGid.tile == 0) { - continue; - } - + for (final (tx, tileGid) in tileRow) { final tile = map.tileByGid(tileGid.tile)!; final tileset = map.tilesetByTileGId(tileGid.tile); final img = tile.image ?? tileset.image; @@ -125,7 +117,7 @@ class StaggeredTileLayer extends FlameTileLayer { final scos = flips.cos * scale; final ssin = flips.sin * scale; - final transform = transforms[tx][ty] = MutableRSTransform( + final transform = MutableRSTransform( scos, ssin, offsetX, @@ -133,6 +125,7 @@ class StaggeredTileLayer extends FlameTileLayer { -scos * anchorX + ssin * anchorY, -ssin * anchorX - scos * anchorY, ); + storeTransform(tx, ty, transform); // A second pass is only needed in the case of staggery. if (map.staggerAxis == StaggerAxis.x && staggerY > 0) { diff --git a/packages/flame_tiled/lib/src/renderable_layers/tile_layers/tile_layer.dart b/packages/flame_tiled/lib/src/renderable_layers/tile_layers/tile_layer.dart index f43d342266f..4de03129c70 100644 --- a/packages/flame_tiled/lib/src/renderable_layers/tile_layers/tile_layer.dart +++ b/packages/flame_tiled/lib/src/renderable_layers/tile_layers/tile_layer.dart @@ -1,3 +1,5 @@ +import 'dart:collection'; + import 'package:flame/components.dart'; import 'package:flame/extensions.dart'; import 'package:flame/rendering.dart'; @@ -35,7 +37,20 @@ import 'package:meta/meta.dart'; abstract class FlameTileLayer extends RenderableLayer { late Paint _layerPaint; final TiledAtlas tiledAtlas; + + /// Cached transform of every tile, indexed as + /// `transforms[x - originX][y - originY]` for the Tiled tile `(x, y)`. + /// + /// It covers the layer's [TileLayer.contentBounds]. Use [transformAt] and + /// [storeTransform] instead of indexing it directly. late List> transforms; + + /// The Tiled coordinates of the tile stored at `transforms[0][0]`. + /// + /// This is `(0, 0)` for finite maps. Infinite maps can have tiles at + /// negative coordinates, in which case the origin is negative too. + int originX = 0; + int originY = 0; final animations = []; final Map animationFrames; final bool ignoreFlip; @@ -179,16 +194,70 @@ abstract class FlameTileLayer extends RenderableLayer { @override void refreshCache() { animations.clear(); - transforms = List.generate( - layer.width, - (index) => List.filled(layer.height, null), - ); + // The area that has tiles: `(0, 0, width, height)` for finite layers, the + // area covered by all chunks for infinite layers, and `null` for layers + // without any tile data. + final bounds = layer.contentBounds; + if (bounds == null) { + originX = 0; + originY = 0; + transforms = >[]; + } else { + originX = bounds.left; + originY = bounds.top; + transforms = List.generate( + bounds.width, + (_) => List.filled(bounds.height, null), + ); + } tiledAtlas.batch?.clear(); cacheTiles(); } + /// Transform for Tiled tile `(x, y)`, or `null` if that cell is empty or + /// outside this layer's cached bounds. + MutableRSTransform? transformAt(int x, int y) { + final ix = x - originX; + final iy = y - originY; + if (ix < 0 || + iy < 0 || + ix >= transforms.length || + iy >= transforms[ix].length) { + return null; + } + return transforms[ix][iy]; + } + + /// Stores the [transform] of the Tiled tile `(tx, ty)` in [transforms]. + @protected + void storeTransform(int tx, int ty, MutableRSTransform transform) { + transforms[tx - originX][ty - originY] = transform; + } + + /// Non-empty tiles of this layer grouped by Tiled row, with rows sorted by + /// `y` and each row sorted by `x`. + /// + /// Infinite layers store tiles chunk by chunk, so iterating the layer + /// directly does not visit tiles in row-major order. Overlapping tiles + /// (isometric, staggered, hexagonal and oversized tiles) need row-major + /// order to be painted like Tiled does. + @protected + SplayTreeMap> tilesByWorldRow() { + final rows = SplayTreeMap>(); + layer.forEachTile((x, y, gid) { + if (gid.tile == 0) { + return; + } + rows.putIfAbsent(y, () => []).add((x, gid)); + }); + for (final row in rows.values) { + row.sort((a, b) => a.$1.compareTo(b.$1)); + } + return rows; + } + /// We need to know the following information for each tile to render a layer /// correctly. /// diff --git a/packages/flame_tiled/lib/src/renderable_tile_map.dart b/packages/flame_tiled/lib/src/renderable_tile_map.dart index 453d519e8b7..2228ad5cb69 100644 --- a/packages/flame_tiled/lib/src/renderable_tile_map.dart +++ b/packages/flame_tiled/lib/src/renderable_tile_map.dart @@ -108,6 +108,13 @@ class RenderableTiledMap { /// Changes the Gid of the corresponding layer at the given layerId, /// if different + /// + /// {@template renderable_tile_map_set_tile} + /// [x] and [y] are the tile coordinates shown in the Tiled editor, which + /// can be negative on infinite maps. Only cells that exist in the map can + /// be changed: nothing happens for cells outside of a finite map, or + /// outside of the chunks of an infinite map. + /// {@endtemplate} void setTileData({ required int layerId, required int x, @@ -116,21 +123,16 @@ class RenderableTiledMap { }) { final layer = map.layers.firstWhereOrNull((layer) => layer.id == layerId); if (layer is TileLayer) { - final td = layer.tileData; - if (td != null) { - if (td[y][x].tile != gid.tile || - td[y][x].flips.horizontally != gid.flips.horizontally || - td[y][x].flips.vertically != gid.flips.vertically || - td[y][x].flips.diagonally != gid.flips.diagonally) { - td[y][x] = gid; - _refreshCache(); - } + if (layer.setTileAt(x, y, gid)) { + _refreshCache(); } } } /// Changes the Gid of the corresponding layer at the given position, /// if different + /// + /// {@macro renderable_tile_map_set_tile} void setTileDataByLayerIndex({ required int layerIndex, required int x, @@ -139,20 +141,19 @@ class RenderableTiledMap { }) { final layer = map.layers[layerIndex]; if (layer is TileLayer) { - final td = layer.tileData; - if (td != null) { - if (td[y][x].tile != gid.tile || - td[y][x].flips.horizontally != gid.flips.horizontally || - td[y][x].flips.vertically != gid.flips.vertically || - td[y][x].flips.diagonally != gid.flips.diagonally) { - td[y][x] = gid; - _refreshCache(); - } + if (layer.setTileAt(x, y, gid)) { + _refreshCache(); } } } /// Gets the Gid of the corresponding layer at the given layerId + /// + /// {@template renderable_tile_map_get_tile} + /// [x] and [y] are the tile coordinates shown in the Tiled editor, which + /// can be negative on infinite maps. Returns `null` for cells outside of a + /// finite map, or outside of the chunks of an infinite map. + /// {@endtemplate} Gid? getTileData({ required int layerId, required int x, @@ -160,12 +161,14 @@ class RenderableTiledMap { }) { final layer = map.layers.firstWhereOrNull((layer) => layer.id == layerId); if (layer is TileLayer) { - return layer.tileData?[y][x]; + return layer.tileAt(x, y); } return null; } /// Gets the Gid of the corresponding layer at the given position + /// + /// {@macro renderable_tile_map_get_tile} Gid? getTileDataByLayerIndex({ required int layerIndex, required int x, @@ -173,13 +176,16 @@ class RenderableTiledMap { }) { final layer = map.layers[layerIndex]; if (layer is TileLayer) { - return layer.tileData?[y][x]; + return layer.tileAt(x, y); } return null; } /// Select a group of tiles from the coordinates [x] and [y]. /// + /// [x] and [y] are the tile coordinates shown in the Tiled editor, which + /// can be negative on infinite maps. + /// /// If [all] is set to true, every renderable tile from the map is collected. /// /// If the [named] or [ids] sets are not empty, any layer with matching @@ -239,8 +245,9 @@ class RenderableTiledMap { continue; } - if (layer.transforms[x][y] != null) { - tiles.add(layer.transforms[x][y]!); + final transform = layer.transformAt(x, y); + if (transform != null) { + tiles.add(transform); } } } diff --git a/packages/flame_tiled/test/assets/empty-infinite-map.tmx b/packages/flame_tiled/test/assets/empty-infinite-map.tmx new file mode 100644 index 00000000000..1861cbf1fea --- /dev/null +++ b/packages/flame_tiled/test/assets/empty-infinite-map.tmx @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/flame_tiled/test/assets/infinite-map.tmx b/packages/flame_tiled/test/assets/infinite-map.tmx new file mode 100644 index 00000000000..9a4279ebf1b --- /dev/null +++ b/packages/flame_tiled/test/assets/infinite-map.tmx @@ -0,0 +1,158 @@ + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,163, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,163, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,163, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132 + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +132,163,130,131,131,132,130,131,131,130,163,132,132,131,130,132, +163,131,131,131,131,131,131,131,130,163,131,163,131,130,131,163, +130,163,132,130,132,130,163,131,132,163,132,130,130,132,130,132, +130,132,163,163,130,130,132,130,131,131,131,163,132,130,130,131, +131,163,130,131,131,132,130,163,131,131,130,131,163,130,131,131, +163,132,132,130,163,163,163,130,132,163,132,132,131,131,132,163, +130,163,131,130,163,130,163,132,132,163,132,131,163,163,131,132 + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +132,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +163,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +132,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +130,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +130,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +130,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +130,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,163, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,163, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,163, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,163, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,131, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,163, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,163, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,163, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,130, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,131, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +163,130,131,163,130,132,130,131,132,131,163,163,163,163,130,131, +130,132,132,163,130,132,131,130,131,163,132,130,163,132,130,131, +132,131,130,132,131,130,163,130,130,131,131,130,163,130,130,131, +132,163,132,132,163,132,131,131,163,130,131,163,163,163,131,132, +130,131,132,132,130,131,132,132,132,163,130,132,130,130,132,132, +130,132,132,131,130,131,132,131,131,132,131,131,163,131,130,130, +132,163,132,132,130,130,163,131,130,163,163,131,131,130,163,131, +130,131,163,132,163,163,131,130,131,130,131,132,131,163,130,132, +163,163,130,163,163,132,132,163,163,132,163,132,131,163,131,131, +163,163,131,132,130,131,130,163,131,131,163,131,163,130,132,132, +163,130,163,132,132,131,132,131,130,132,131,163,163,130,163,131, +132,163,163,163,131,131,131,130,131,130,132,163,130,132,130,132, +132,132,131,131,130,132,130,131,131,132,130,132,163,131,132,132, +130,163,132,163,130,130,130,163,163,163,132,130,130,163,130,132, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +130,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +131,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +131,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +163,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +132,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +130,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +131,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +130,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +131,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +130,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +132,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +163,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +163,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +163,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +227,2,3,4,2,3,4,2,3,4,2,3,4,2,3,228, +259,34,35,35,35,35,35,35,35,35,101,35,35,35,35,260, +258,0,0,0,0,0,0,0,0,0,133,0,0,0,0,257, +258,0,0,0,0,0,0,0,0,0,0,0,0,0,0,257, +258,0,0,0,0,0,0,0,0,0,0,0,0,0,0,257, +258,0,0,0,0,0,0,0,0,0,0,0,0,0,0,257 + + +258,0,0,0,0,0,0,0,0,0,0,0,0,0,0,257, +258,0,0,0,0,0,0,0,0,0,0,0,0,0,0,257, +258,0,0,0,0,0,0,0,0,0,0,0,0,0,0,257, +258,0,0,0,0,0,0,0,0,0,0,0,0,0,0,257, +258,0,0,0,0,0,0,0,0,0,0,0,0,0,0,257, +258,0,0,0,0,0,0,225,3,3,3,3,226,0,0,257, +258,0,0,0,0,0,0,257,35,35,35,35,258,0,0,257, +258,0,418,419,420,421,0,257,0,0,0,0,258,0,0,257, +291,3,450,451,452,453,3,292,0,0,0,0,258,0,0,257, +323,35,67,483,484,67,35,324,0,0,0,0,258,0,0,257, +0,0,0,0,0,0,0,0,0,0,0,0,258,0,0,257, +0,0,0,0,0,0,0,0,0,0,0,0,262,3,4,261, +0,0,0,0,0,0,0,0,0,0,0,0,323,35,35,324, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + diff --git a/packages/flame_tiled/test/assets/infinite_oversized_tiles_isometric.tmx b/packages/flame_tiled/test/assets/infinite_oversized_tiles_isometric.tmx new file mode 100644 index 00000000000..57a8e726d11 --- /dev/null +++ b/packages/flame_tiled/test/assets/infinite_oversized_tiles_isometric.tmx @@ -0,0 +1,158 @@ + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,2, +0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,2, +0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,2 + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2, +0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,2, +0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,2, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + diff --git a/packages/flame_tiled/test/assets/infinite_oversized_tiles_orthogonal.tmx b/packages/flame_tiled/test/assets/infinite_oversized_tiles_orthogonal.tmx new file mode 100644 index 00000000000..d999412a6cd --- /dev/null +++ b/packages/flame_tiled/test/assets/infinite_oversized_tiles_orthogonal.tmx @@ -0,0 +1,89 @@ + + + + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,131,131,131,131,131,131,131,131, +0,0,0,0,0,0,0,0,132,163,130,131,132,163,130,131, +0,0,0,0,0,0,0,0,163,131,163,131,163,131,163,131, +0,0,0,0,0,0,0,0,130,163,132,131,130,163,132,131, +0,0,0,0,0,0,0,0,131,131,131,131,131,131,131,131, +0,0,0,0,0,0,0,0,132,163,130,131,132,163,130,131, +0,0,0,0,0,0,0,0,163,131,163,131,163,131,163,131, +0,0,0,0,0,0,0,0,130,163,132,131,130,163,132,131, +0,0,0,0,0,0,0,0,131,131,131,131,131,131,131,131, +0,0,0,0,0,0,0,0,132,163,130,131,132,163,130,131, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +131,131,131,131,131,131,131,131,0,0,0,0,0,0,0,0, +132,163,130,131,132,163,130,131,0,0,0,0,0,0,0,0, +163,131,163,131,163,131,163,131,0,0,0,0,0,0,0,0, +130,163,132,131,130,163,132,131,0,0,0,0,0,0,0,0, +131,131,131,131,131,131,131,131,0,0,0,0,0,0,0,0, +132,163,130,131,132,163,130,131,0,0,0,0,0,0,0,0, +163,131,163,131,163,131,163,131,0,0,0,0,0,0,0,0, +130,163,132,131,130,163,132,131,0,0,0,0,0,0,0,0, +131,131,131,131,131,131,131,131,0,0,0,0,0,0,0,0, +132,163,130,131,132,163,130,131,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + + + +0,0,0,0,0,0,0,0,227,2,3,4,2,3,4,2, +0,0,0,0,0,0,0,0,259,35,35,35,35,35,35,35, +0,0,0,0,0,0,0,0,258,0,0,0,0,0,0,1138, +0,0,0,0,0,0,0,0,258,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,258,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,258,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,258,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,258,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,258,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,291,3,3,3,3,3,3,3, +0,0,0,0,0,0,0,0,323,35,35,35,35,35,35,35, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +3,4,2,3,4,2,3,228,0,0,0,0,0,0,0,0, +35,35,35,35,35,35,35,260,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,257,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,257,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,257,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,257,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,257,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,257,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,257,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,292,0,0,0,0,0,0,0,0, +35,35,35,35,35,35,35,324,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + diff --git a/packages/flame_tiled/test/goldens/infinite_map.png b/packages/flame_tiled/test/goldens/infinite_map.png new file mode 100644 index 0000000000000000000000000000000000000000..13d0943a38eaec3da219ef508bc89b201fbbe67f GIT binary patch literal 19588 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%0Avsy+88|O=2dO< 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 RZOE^W9JD=9WPbMA{{pa4*P#Fa literal 0 HcmV?d00001 diff --git a/packages/flame_tiled/test/goldens/infinite_oversized_tiles_isometric.png b/packages/flame_tiled/test/goldens/infinite_oversized_tiles_isometric.png new file mode 100644 index 0000000000000000000000000000000000000000..0b7c6505a75473bf970b54c8a4a6703bfd2567e8 GIT binary patch literal 25723 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 literal 0 HcmV?d00001 diff --git a/packages/flame_tiled/test/goldens/infinite_oversized_tiles_orthogonal.png b/packages/flame_tiled/test/goldens/infinite_oversized_tiles_orthogonal.png new file mode 100644 index 0000000000000000000000000000000000000000..23b19bd96f219ff584e6bd125f4c8a699cb68b77 GIT binary patch literal 4533 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 jPYmKA5}{GV-`OA zCnpe59mnoAxfPq8Y7kjdmVk-@^zETh-+#(TUt~h`k2*Zy@h7oBqfh-CvK_Tb@PW-D z7LB#|NYx)NV8KsPlSNJ&Ss^&{NsJ$Ez-GKTzt|6X(FbP2nQ-9n;`dr-MG-Nw2kKvB zRDal<%HP|68X$-DLIM<@#D4GXN4Jm<9`ZgvuYa^bq^&o?N4`h^fBEJe!@M6JxD^|g zR-Q6gfO4n{g=X(HE%|%cEzj&CDUD*sJ&;rAg88TjWR%L{x3^OKQifaJL#<6ohz);r zw)g$IqU6PIB^X6Qgzd?FXUfyqv>x?dkz&A z>a#sKuwVB*%3TLOqDV=;P8vt0eT1@fsoS_aR2fIu?(=Dr(ac{oQf)^aV45>GhcZ E0Zmb9ApigX literal 0 HcmV?d00001 diff --git a/packages/flame_tiled/test/test_image_utils.dart b/packages/flame_tiled/test/test_image_utils.dart index 3f89f655b26..98093472ef7 100644 --- a/packages/flame_tiled/test/test_image_utils.dart +++ b/packages/flame_tiled/test/test_image_utils.dart @@ -19,5 +19,26 @@ Future renderMapToPng( return imageToPng(image); } +/// Renders the part of [map] inside [region], which is given in map pixels. +/// +/// Unlike [renderMapToPng] this can capture content at negative coordinates, +/// which infinite maps have when tiles are placed left of or above the origin. +Future renderMapRegionToPng( + RenderableTiledMap map, + Rect region, +) async { + final canvasRecorder = PictureRecorder(); + final canvas = Canvas(canvasRecorder); + canvas.translate(-region.left, -region.top); + map.render(canvas); + final picture = canvasRecorder.endRecording(); + + final image = await picture.toImageSafe( + region.width.toInt(), + region.height.toInt(), + ); + return imageToPng(image); +} + Future imageToPng(Image image) async => (await image.toByteData(format: ImageByteFormat.png))!.buffer.asUint8List(); diff --git a/packages/flame_tiled/test/tiled_test.dart b/packages/flame_tiled/test/tiled_test.dart index 1cd47514ea2..ad97fe4479c 100644 --- a/packages/flame_tiled/test/tiled_test.dart +++ b/packages/flame_tiled/test/tiled_test.dart @@ -1230,4 +1230,148 @@ void main() { expect(renderableTiledMap.getLayerOpacity(3), equals(0.2)); }); }); + + group('infinite maps', () { + test('empty infinite map loads without tiles', () async { + final bundle = TestAssetBundle( + imageNames: const [], + stringNames: ['empty-infinite-map.tmx'], + ); + final component = await TiledComponent.load( + 'assets/tiles/empty-infinite-map.tmx', + Vector2.all(16), + bundle: bundle, + images: Images(bundle: bundle), + ); + + expect(component.tileMap.map.infinite, isTrue); + expect(component.size, Vector2(120 * 16, 68 * 16)); + expect( + component.tileMap.getTileData(layerId: 1, x: 0, y: 0), + isNull, + ); + }); + + test('loads chunks and reads Tiled world coordinates', () async { + final bundle = TestAssetBundle( + imageNames: ['0x72_DungeonTilesetII_v1.4.png'], + stringNames: ['infinite-map.tmx'], + ); + final map = await RenderableTiledMap.fromFile( + 'assets/tiles/infinite-map.tmx', + Vector2.all(16), + bundle: bundle, + images: Images(bundle: bundle), + ); + + expect(map.map.infinite, isTrue); + + final floorTile = map.getTileData(layerId: 2, x: -16, y: -7); + expect(floorTile, isNotNull); + expect(floorTile!.tile, 132); + + expect(map.getTileData(layerId: 2, x: 1000, y: 1000), isNull); + + map.setTileData( + layerId: 2, + x: -16, + y: -7, + gid: const Gid(1, Flips.defaults()), + ); + expect(map.getTileData(layerId: 2, x: -16, y: -7)!.tile, 1); + + final stack = map.tileStack(-16, -7, all: true); + expect(stack.length, greaterThan(0)); + // Tiles at negative indices are placed at negative world positions, + // like in the Tiled editor. + expect(stack.position, Vector2((-16 + 0.5) * 16, (-7 + 0.5) * 16)); + }); + + test('TiledComponent size uses map width and height', () async { + final bundle = TestAssetBundle( + imageNames: ['0x72_DungeonTilesetII_v1.4.png'], + stringNames: ['infinite-map.tmx'], + ); + final component = await TiledComponent.load( + 'assets/tiles/infinite-map.tmx', + Vector2.all(16), + bundle: bundle, + images: Images(bundle: bundle), + ); + + expect(component.size, Vector2(120 * 16, 68 * 16)); + }); + + test('renders all chunks', () async { + final bundle = TestAssetBundle( + imageNames: ['0x72_DungeonTilesetII_v1.4.png'], + stringNames: ['infinite-map.tmx'], + ); + final map = await RenderableTiledMap.fromFile( + 'assets/tiles/infinite-map.tmx', + Vector2.all(16), + bundle: bundle, + images: Images(bundle: bundle), + ); + + // The chunks cover tiles (-32, -16) to (16, 16). + final pngData = await renderMapRegionToPng( + map, + const Rect.fromLTWH(-32 * 16, -16 * 16, 48 * 16, 32 * 16), + ); + await expectLater( + pngData, + matchesGoldenFile('goldens/infinite_map.png'), + ); + }); + + // The room straddles the border between two chunks. Its door is a 32x32 + // tile standing on the floor in front of the top wall, so it overlaps wall + // tiles of both chunks. Painting chunk by chunk instead of row by row + // would draw the wall of the right chunk over the door. + test('paints oversized tiles across chunks in row order', () async { + final bundle = TestAssetBundle( + imageNames: ['0x72_DungeonTilesetII_v1.4.png'], + stringNames: ['infinite_oversized_tiles_orthogonal.tmx'], + ); + final map = await RenderableTiledMap.fromFile( + 'assets/tiles/infinite_oversized_tiles_orthogonal.tmx', + Vector2.all(16), + bundle: bundle, + images: Images(bundle: bundle), + ); + + // The room covers tiles (-8, 0) to (7, 10). + final pngData = await renderMapRegionToPng( + map, + const Rect.fromLTWH(-8 * 16, 0, 16 * 16, 11 * 16), + ); + await expectLater( + pngData, + matchesGoldenFile('goldens/infinite_oversized_tiles_orthogonal.png'), + ); + }); + + test('renders isometric chunks around the origin', () async { + final bundle = TestAssetBundle( + imageNames: ['isometric_spritesheet.png'], + stringNames: ['infinite_oversized_tiles_isometric.tmx'], + ); + final map = await RenderableTiledMap.fromFile( + 'assets/tiles/infinite_oversized_tiles_isometric.tmx', + Vector2(64, 32), + bundle: bundle, + images: Images(bundle: bundle), + ); + + final pngData = await renderMapRegionToPng( + map, + const Rect.fromLTWH(0, -144, 384, 240), + ); + await expectLater( + pngData, + matchesGoldenFile('goldens/infinite_oversized_tiles_isometric.png'), + ); + }); + }); }