diff --git a/packages/flame_tiled/lib/src/renderable_tile_map.dart b/packages/flame_tiled/lib/src/renderable_tile_map.dart index 880310a3914..549f9c41d59 100644 --- a/packages/flame_tiled/lib/src/renderable_tile_map.dart +++ b/packages/flame_tiled/lib/src/renderable_tile_map.dart @@ -261,12 +261,20 @@ class _RenderableTileLayer extends _RenderableLayer { } void _cacheLayerTiles() { - if (_map.orientation == MapOrientation.isometric) { - _cacheIsometricTiles(); - } else if (_map.orientation == MapOrientation.hexagonal) { - _cacheHexagonalTiles(); - } else { - _cacheOrthogonalLayerTiles(); + switch (_map.orientation) { + case MapOrientation.isometric: + _cacheIsometricTiles(); + break; + case MapOrientation.staggered: + _cacheIsometricStaggeredTiles(); + break; + case MapOrientation.hexagonal: + _cacheHexagonalTiles(); + break; + case MapOrientation.orthogonal: + default: + _cacheOrthogonalLayerTiles(); + break; } } @@ -381,7 +389,7 @@ class _RenderableTileLayer extends _RenderableLayer { } } - void _cacheHexagonalTiles() { + void _cacheIsometricStaggeredTiles() { final tileData = layer.tileData!; final batchMap = _cachedSpriteBatches; final halfDestinationTile = _destTileSize / 2; @@ -390,16 +398,114 @@ class _RenderableTileLayer extends _RenderableLayer { var staggerY = 0.0; var staggerX = 0.0; - if (_map.orientation == MapOrientation.hexagonal) { - // Hexagonal Ponity Tiles move down by a fractional amount. + // Hexagonal Ponity Tiles move down by a fractional amount. + if (_map.staggerAxis == StaggerAxis.y) { + staggerY = size.y * 0.5; + } else + // Hexagonal Flat Tiles move right by a fractional amount. + if (_map.staggerAxis == StaggerAxis.x) { + staggerX = size.x * 0.5; + } + + for (var ty = 0; ty < tileData.length; ty++) { + final tileRow = tileData[ty]; + + // Hexagonal Pointy Tiles shift left and right depending on the row if (_map.staggerAxis == StaggerAxis.y) { - staggerY = _map.tileHeight * 0.75; + if ((ty.isOdd && _map.staggerIndex == StaggerIndex.odd) || + (ty.isEven && _map.staggerIndex == StaggerIndex.even)) { + staggerX = halfDestinationTile.x; + } else { + staggerX = 0.0; + } } - // Hexagonal Flat Tiles move right by a fractional amount. - if (_map.staggerAxis == StaggerAxis.x) { - staggerX = _map.tileWidth * 0.75; + + for (var tx = 0; tx < tileRow.length; tx++) { + final tileGid = tileRow[tx]; + if (tileGid.tile == 0) { + continue; + } + + final tile = _map.tileByGid(tileGid.tile); + final tileset = _map.tilesetByTileGId(tileGid.tile); + final img = tile.image ?? tileset.image; + if (img == null) { + continue; + } + + final batch = batchMap[img.source]; + if (batch == null) { + continue; + } + + // Hexagonal Flat tiles shift up and down as we move across the row. + if (_map.staggerAxis == StaggerAxis.x) { + if ((tx.isOdd && _map.staggerIndex == StaggerIndex.odd) || + (tx.isEven && _map.staggerIndex == StaggerIndex.even)) { + staggerY = halfDestinationTile.y; + } else { + staggerY = 0.0; + } + } + + final src = tileset.computeDrawRect(tile).toRect(); + final flips = SimpleFlips.fromFlips(tileGid.flips); + final scale = size.x / src.width; + final anchorX = src.width - halfMapTile.x; + final anchorY = src.height - halfMapTile.y; + + late double offsetX; + late double offsetY; + + // halfTile.x: shfits the map half a tile forward rather than + // lining up on at the center. + // halfTile.y: shfits the map half a tile down rather than + // lining up on at the center. + // StaggerX/Y: Moves the tile forward/down depending on orientation. + // * stagger: Isometric tiles move down or right by only a fraction, + // specifically 1/2 the width or height, for packing. + if (_map.staggerAxis == StaggerAxis.y) { + offsetX = tx * size.x + staggerX + halfDestinationTile.x; + offsetY = ty * staggerY + halfDestinationTile.y; + } else { + offsetX = tx * staggerX + halfDestinationTile.x; + offsetY = ty * size.y + staggerY + halfDestinationTile.y; + } + + final scos = flips.cos * scale; + final ssin = flips.sin * scale; + + batch.addTransform( + source: src, + transform: ui.RSTransform( + scos, + ssin, + offsetX + -scos * anchorX + ssin * anchorY, + offsetY + -ssin * anchorX - scos * anchorY, + ), + flip: flips.flip, + ); } } + } + + void _cacheHexagonalTiles() { + final tileData = layer.tileData!; + final batchMap = _cachedSpriteBatches; + final halfDestinationTile = _destTileSize / 2; + final size = _destTileSize; + final halfMapTile = Vector2(_map.tileWidth / 2, _map.tileHeight / 2); + + var staggerY = 0.0; + var staggerX = 0.0; + // Hexagonal Ponity Tiles move down by a fractional amount. + if (_map.staggerAxis == StaggerAxis.y) { + staggerY = size.y * 0.75; + } else + // Hexagonal Flat Tiles move right by a fractional amount. + if (_map.staggerAxis == StaggerAxis.x) { + staggerX = size.x * 0.75; + } for (var ty = 0; ty < tileData.length; ty++) { final tileRow = tileData[ty]; @@ -459,11 +565,11 @@ class _RenderableTileLayer extends _RenderableLayer { // * stagger: Hexagonal tiles move down or right by only a fraction, // specifically 3/4 the width or height, for packing. if (_map.staggerAxis == StaggerAxis.y) { - offsetX = tx * _map.tileWidth + staggerX + halfDestinationTile.x; + offsetX = tx * size.x + staggerX + halfDestinationTile.x; offsetY = ty * staggerY + halfDestinationTile.y; } else { offsetX = tx * staggerX + halfDestinationTile.x; - offsetY = ty * _map.tileHeight + staggerY + halfDestinationTile.y; + offsetY = ty * size.y + staggerY + halfDestinationTile.y; } final scos = flips.cos * scale; diff --git a/packages/flame_tiled/lib/src/tiled_component.dart b/packages/flame_tiled/lib/src/tiled_component.dart index 859bba94741..6dfc287478c 100644 --- a/packages/flame_tiled/lib/src/tiled_component.dart +++ b/packages/flame_tiled/lib/src/tiled_component.dart @@ -63,35 +63,41 @@ class TiledComponent extends Component with HasGameRef { final xScale = tileMap.destTileSize.x / tMap.tileWidth; final yScale = tileMap.destTileSize.y / tMap.tileHeight; - late Vector2 size; - final tileScaled = Vector2( tileMap.map.tileWidth * xScale, tileMap.map.tileHeight * yScale, ); - if (tMap.orientation == MapOrientation.hexagonal) { - if (tMap.staggerAxis == StaggerAxis.y) { - size = Vector2( + switch (tMap.orientation) { + case MapOrientation.staggered: + return tMap.staggerAxis == StaggerAxis.y + ? Vector2( + tileScaled.x * tileMap.map.width + tileScaled.x / 2, + tileScaled.y + ((tileMap.map.height - 1) * tileScaled.y / 2), + ) + : Vector2( + tileScaled.x + ((tileMap.map.width - 1) * tileScaled.x / 2), + tileScaled.y * tileMap.map.height + tileScaled.y / 2, + ); + + case MapOrientation.hexagonal: + return tMap.staggerAxis == StaggerAxis.y + ? Vector2( + tileMap.map.width * tileScaled.x + tileScaled.x / 2, + tileScaled.y + ((tileMap.map.height - 1) * tileScaled.y * 0.75), + ) + : Vector2( + tileScaled.x + ((tileMap.map.width - 1) * tileScaled.x * 0.75), + (tileMap.map.height * tileScaled.y) + tileScaled.y / 2, + ); + + case MapOrientation.isometric: + case MapOrientation.orthogonal: + default: + return Vector2( tileMap.map.width * tileScaled.x, - tileScaled.y + ((tileMap.map.height - 1) * tileScaled.y * 0.75), + tileMap.map.height * tileScaled.y, ); - } else { - size = Vector2( - tileScaled.x + ((tileMap.map.width - 1) * tileScaled.x * 0.75), - (tileMap.map.height * tileScaled.y) + tileScaled.y / 2, - ); - } - } else { - size = Vector2( - tileMap.map.width * tileScaled.x, - tileMap.map.height * tileScaled.y, - ); - } - - if (tMap.staggerAxis == StaggerAxis.y) { - size.x += tMap.tileWidth / 2; } - return size; } } diff --git a/packages/flame_tiled/test/assets/iso_stag.png b/packages/flame_tiled/test/assets/iso_stag.png new file mode 100644 index 00000000000..7dcf40140d3 Binary files /dev/null and b/packages/flame_tiled/test/assets/iso_stag.png differ diff --git a/packages/flame_tiled/test/assets/iso_stag_x_even.tmx b/packages/flame_tiled/test/assets/iso_stag_x_even.tmx new file mode 100644 index 00000000000..b4f1d9ce73b --- /dev/null +++ b/packages/flame_tiled/test/assets/iso_stag_x_even.tmx @@ -0,0 +1,16 @@ + + + + + + + + eJxjZWBgYEXC7EDMCaVhYpxIbA4ozQjFrGgYABL4AGw= + + + + + eJxjYkAAbiBmYWBoYIHQDjBxIPsAjM2FRsPYbFAMAFTMAcg= + + + diff --git a/packages/flame_tiled/test/assets/iso_stag_x_odd.tmx b/packages/flame_tiled/test/assets/iso_stag_x_odd.tmx new file mode 100644 index 00000000000..3a996e038aa --- /dev/null +++ b/packages/flame_tiled/test/assets/iso_stag_x_odd.tmx @@ -0,0 +1,16 @@ + + + + + + + + eJxjZWBgYEXDnEhsdigfRDNgUYuOARJUAGw= + + + + + eJxjYGBg4GKAACYozcKAAEB2AxAfAGIHBhwApI8bygYARcABqg== + + + diff --git a/packages/flame_tiled/test/assets/iso_stag_y_even.tmx b/packages/flame_tiled/test/assets/iso_stag_y_even.tmx new file mode 100644 index 00000000000..1bb3432acbb --- /dev/null +++ b/packages/flame_tiled/test/assets/iso_stag_y_even.tmx @@ -0,0 +1,16 @@ + + + + + + + + eJxjZGBgYIRiDijNjsTnBGJWJBokzoykBx0DAAyAAEM= + + + + + eJxjYkAAbgZUAOMzMTA0sDFgByxIbC4gBgAcSACv + + + diff --git a/packages/flame_tiled/test/assets/iso_stag_y_odd.tmx b/packages/flame_tiled/test/assets/iso_stag_y_odd.tmx new file mode 100644 index 00000000000..4f68dd4eb19 --- /dev/null +++ b/packages/flame_tiled/test/assets/iso_stag_y_odd.tmx @@ -0,0 +1,16 @@ + + + + + + + + eJxjZGBgYIRiDijNjsTnBGJWJBokzoykBx0DAAyAAEM= + + + + + eJxjYkAAbgZUAOMzMTA0sDFgByxIbC4gBgAcSACv + + + diff --git a/packages/flame_tiled/test/goldens/iso_stag_x_even.png b/packages/flame_tiled/test/goldens/iso_stag_x_even.png new file mode 100644 index 00000000000..6f89b92f93c Binary files /dev/null and b/packages/flame_tiled/test/goldens/iso_stag_x_even.png differ diff --git a/packages/flame_tiled/test/goldens/iso_stag_x_odd.png b/packages/flame_tiled/test/goldens/iso_stag_x_odd.png new file mode 100644 index 00000000000..bc5ed59a7aa Binary files /dev/null and b/packages/flame_tiled/test/goldens/iso_stag_x_odd.png differ diff --git a/packages/flame_tiled/test/goldens/iso_stag_y_even.png b/packages/flame_tiled/test/goldens/iso_stag_y_even.png new file mode 100644 index 00000000000..4c2e1a2adf9 Binary files /dev/null and b/packages/flame_tiled/test/goldens/iso_stag_y_even.png differ diff --git a/packages/flame_tiled/test/goldens/iso_stag_y_odd.png b/packages/flame_tiled/test/goldens/iso_stag_y_odd.png new file mode 100644 index 00000000000..b0c6890ec16 Binary files /dev/null and b/packages/flame_tiled/test/goldens/iso_stag_y_odd.png differ diff --git a/packages/flame_tiled/test/tiled_test.dart b/packages/flame_tiled/test/tiled_test.dart index fa0a0031132..19cad67c1b4 100644 --- a/packages/flame_tiled/test/tiled_test.dart +++ b/packages/flame_tiled/test/tiled_test.dart @@ -413,6 +413,116 @@ void main() { expect(pngData, matchesGoldenFile('goldens/pointy_hex_odd.png')); }); }); + + group('isometric staggered', () { + late Uint8List pngData; + late TiledComponent component; + + Future setupMap( + String tmxFile, + String imageFile, + Vector2 destTileSize, + ) async { + Flame.bundle = TestAssetBundle( + imageNames: [ + imageFile, + ], + mapPath: 'test/assets/$tmxFile', + ); + return component = await TiledComponent.load( + tmxFile, + destTileSize, + ); + } + + test('x + odd', () async { + await setupMap( + 'iso_stag_x_odd.tmx', + 'iso_stag.png', + Vector2(128, 74), + ); + + expect(component.size, Vector2(320, 407)); + + final canvasRecorder = PictureRecorder(); + final canvas = Canvas(canvasRecorder); + component.tileMap.render(canvas); + final picture = canvasRecorder.endRecording(); + + final image = await picture.toImageSafe(320, 407); + pngData = (await image.toByteData(format: ImageByteFormat.png))! + .buffer + .asUint8List(); + + expect(pngData, matchesGoldenFile('goldens/iso_stag_x_odd.png')); + }); + + test('x + even + half sized', () async { + await setupMap( + 'iso_stag_x_even.tmx', + 'iso_stag.png', + Vector2(128 / 2, 74 / 2), + ); + + expect(component.size, Vector2(320 / 2, 407 / 2)); + + final canvasRecorder = PictureRecorder(); + final canvas = Canvas(canvasRecorder); + component.tileMap.render(canvas); + final picture = canvasRecorder.endRecording(); + + final image = await picture.toImageSafe(160, 204); + pngData = (await image.toByteData(format: ImageByteFormat.png))! + .buffer + .asUint8List(); + + expect(pngData, matchesGoldenFile('goldens/iso_stag_x_even.png')); + }); + + test('y + odd + half', () async { + await setupMap( + 'iso_stag_y_odd.tmx', + 'iso_stag.png', + Vector2(128 / 2, 74 / 2), + ); + + expect(component.size, Vector2(576 / 2, 222 / 2)); + + final canvasRecorder = PictureRecorder(); + final canvas = Canvas(canvasRecorder); + component.tileMap.render(canvas); + final picture = canvasRecorder.endRecording(); + + final image = await picture.toImageSafe(288, 111); + pngData = (await image.toByteData(format: ImageByteFormat.png))! + .buffer + .asUint8List(); + + expect(pngData, matchesGoldenFile('goldens/iso_stag_y_odd.png')); + }); + + test('y + even', () async { + await setupMap( + 'iso_stag_y_even.tmx', + 'iso_stag.png', + Vector2(128, 74), + ); + + expect(component.size, Vector2(576, 222)); + + final canvasRecorder = PictureRecorder(); + final canvas = Canvas(canvasRecorder); + component.tileMap.render(canvas); + final picture = canvasRecorder.endRecording(); + + final image = await picture.toImageSafe(576, 222); + pngData = (await image.toByteData(format: ImageByteFormat.png))! + .buffer + .asUint8List(); + + expect(pngData, matchesGoldenFile('goldens/iso_stag_y_even.png')); + }); + }); } class TestAssetBundle extends CachingAssetBundle {