Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions doc/bridge_packages/flame_tiled/tiled.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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) ||
Expand All @@ -61,12 +58,7 @@ class HexagonalTileLayer extends FlameTileLayer {
// painted over. See the second pass loop after tx.
final xSecondPass = <TileTransform>[];

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;
Expand Down Expand Up @@ -125,14 +117,15 @@ 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,
offsetY,
-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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -78,18 +70,19 @@ class IsometricTileLayer extends FlameTileLayer {
final scos = flips.cos * scale;
final ssin = flips.sin * scale;

transforms[tx][ty] = MutableRSTransform(
final transform = MutableRSTransform(
scos,
ssin,
offsetX,
offsetY,
-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),
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,15 @@ 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;
if (batch == null) {
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;
Expand Down Expand Up @@ -83,18 +75,19 @@ class OrthogonalTileLayer extends FlameTileLayer {
final scos = flips.cos * scale;
final ssin = flips.sin * scale;

transforms[tx][ty] = MutableRSTransform(
final transform = MutableRSTransform(
scos,
ssin,
offsetX,
offsetY,
-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),
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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) ||
Expand All @@ -61,12 +58,7 @@ class StaggeredTileLayer extends FlameTileLayer {
// painted over. See the second pass loop after tx.
final xSecondPass = <TileTransform>[];

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;
Expand Down Expand Up @@ -125,14 +117,15 @@ 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,
offsetY,
-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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:collection';

import 'package:flame/components.dart';
import 'package:flame/extensions.dart';
import 'package:flame/rendering.dart';
Expand Down Expand Up @@ -35,7 +37,20 @@ import 'package:meta/meta.dart';
abstract class FlameTileLayer extends RenderableLayer<TileLayer> {
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<List<MutableRSTransform?>> 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 = <TileAnimation>[];
final Map<Tile, TileFrames> animationFrames;
final bool ignoreFlip;
Expand Down Expand Up @@ -179,16 +194,70 @@ abstract class FlameTileLayer extends RenderableLayer<TileLayer> {
@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 = <List<MutableRSTransform?>>[];
} 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<int, List<(int x, Gid gid)>> tilesByWorldRow() {
final rows = SplayTreeMap<int, List<(int x, Gid gid)>>();
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.
///
Expand Down
Loading
Loading