Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Hexagonal maps #1892

Merged
merged 5 commits into from
Sep 6, 2022
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
190 changes: 180 additions & 10 deletions packages/flame_tiled/lib/src/renderable_tile_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ class RenderableTiledMap {
/// Layers to be rendered, in the same order as [TiledMap.layers]
final List<_RenderableLayer> renderableLayers;

/// The target size for each tile in the tiled map.
final Vector2 destTileSize;

/// Camera used for determining the current viewport for layer rendering.
/// Optional, but required for parallax support
Camera? camera;
Expand All @@ -44,7 +47,8 @@ class RenderableTiledMap {
/// {@macro _renderable_tiled_map}
RenderableTiledMap(
this.map,
this.renderableLayers, {
this.renderableLayers,
this.destTileSize, {
this.camera,
}) {
_refreshCache();
Expand Down Expand Up @@ -155,6 +159,7 @@ class RenderableTiledMap {
return RenderableTiledMap(
map,
renderableLayers,
destTileSize,
camera: camera,
);
}
Expand Down Expand Up @@ -256,11 +261,79 @@ class _RenderableTileLayer extends _RenderableLayer<TileLayer> {
}

void _cacheLayerTiles() {
if (_map.orientation == MapOrientation.isometric) {
_cacheIsometricTiles();
} else if (_map.orientation == MapOrientation.hexagonal) {
_cacheHexagonalTiles();
} else {
_cacheOrthogonalLayerTiles();
}
}

void _cacheOrthogonalLayerTiles() {
final tileData = layer.tileData!;
final batchMap = _cachedSpriteBatches;
final size = _destTileSize;

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;
}

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;
}

final src = tileset.computeDrawRect(tile).toRect();
final flips = SimpleFlips.fromFlips(tileGid.flips);
final scale = size.x / src.width;
final anchorX = src.width / 2;
final anchorY = src.height / 2;

late double offsetX;
late double offsetY;
offsetX = (tx + .5) * size.x;
offsetY = (ty + .5) * size.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 _cacheIsometricTiles() {
final tileData = layer.tileData!;
final batchMap = _cachedSpriteBatches;
final halfTile = _destTileSize / 2;
final halfDestinationTile = _destTileSize / 2;
final size = _destTileSize;
final isometricXShift = _map.width * size.x * 0.5;

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) {
Expand All @@ -281,19 +354,116 @@ class _RenderableTileLayer extends _RenderableLayer<TileLayer> {

final src = tileset.computeDrawRect(tile).toRect();
final flips = SimpleFlips.fromFlips(tileGid.flips);
final size = _destTileSize;
final scale = size.x / src.width;
final anchorX = src.width / 2;
final anchorY = src.height / 2;

late double offsetX;
late double offsetY;
if (_map.orientation == MapOrientation.isometric) {
offsetX = halfTile.x * (tx - ty);
offsetY = halfTile.y * (tx + ty);

offsetX = halfDestinationTile.x * (tx - ty) + isometricXShift;
offsetY = halfDestinationTile.y * (tx + ty) - size.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;
if (_map.orientation == MapOrientation.hexagonal) {
// Hexagonal Ponity Tiles move down by a fractional amount.
if (_map.staggerAxis == StaggerAxis.y) {
staggerY = _map.tileHeight * 0.75;
}
// Hexagonal Flat Tiles move right by a fractional amount.
if (_map.staggerAxis == StaggerAxis.x) {
staggerX = _map.tileWidth * 0.75;
}
}

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) {
if ((ty.isOdd && _map.staggerIndex == StaggerIndex.odd) ||
(ty.isEven && _map.staggerIndex == StaggerIndex.even)) {
staggerX = halfDestinationTile.x;
} else {
offsetX = (tx + .5) * size.x;
offsetY = (ty + .5) * size.y;
staggerX = 0.0;
}
}

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: 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;
offsetY = ty * staggerY + halfDestinationTile.y;
} else {
offsetX = tx * staggerX + halfDestinationTile.x;
offsetY = ty * _map.tileHeight + staggerY + halfDestinationTile.y;
}

final scos = flips.cos * scale;
Expand Down Expand Up @@ -344,12 +514,12 @@ class _RenderableTileLayer extends _RenderableLayer<TileLayer> {
static Future<Map<String, SpriteBatch>> _loadImages(TiledMap map) async {
final result = <String, SpriteBatch>{};

await Future.forEach(map.tiledImages(), (TiledImage img) async {
for (final img in map.tiledImages()) {
final src = img.source;
if (src != null) {
result[src] = await SpriteBatch.load(src);
}
});
}

return result;
}
Expand Down
43 changes: 43 additions & 0 deletions packages/flame_tiled/lib/src/tiled_component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:flame/components.dart';
import 'package:flame/game.dart';

import 'package:flame_tiled/src/renderable_tile_map.dart';
import 'package:tiled/tiled.dart';

/// {@template _tiled_component}
/// A Flame [Component] to render a Tiled TiledMap.
Expand All @@ -15,6 +16,10 @@ class TiledComponent<T extends FlameGame> extends Component with HasGameRef<T> {
/// Map instance of this component.
RenderableTiledMap tileMap;

/// The logical size of the component. The game assumes that this is the
/// approximate size of the object that will be drawn on the screen.
late final Vector2 size = _computeSize();

/// {@macro _tiled_component}
TiledComponent(
this.tileMap, {
Expand Down Expand Up @@ -51,4 +56,42 @@ class TiledComponent<T extends FlameGame> extends Component with HasGameRef<T> {
priority: priority,
);
}

Vector2 _computeSize() {
final tMap = tileMap.map;

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(
tileMap.map.width * tileScaled.x,
tileScaled.y + ((tileMap.map.height - 1) * tileScaled.y * 0.75),
);
} 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;
}
}
6 changes: 6 additions & 0 deletions packages/flame_tiled/test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

The following test assets were retrieved from [open game art](https://opengameart.org/content/minimalistic-hexagonal-tilesets-both-orientations). They were released as Public domain.

* Tileset_Hexagonal_FlatTop_60x39_60x60.png
* Tileset_Hexagonal_PointyTop_60x52_60x80.png

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions packages/flame_tiled/test/assets/flat_hex_even.tmx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.9" tiledversion="1.9.1" orientation="hexagonal" renderorder="right-down" width="5" height="5" tilewidth="60" tileheight="39" infinite="0" hexsidelength="30" staggeraxis="x" staggerindex="even" nextlayerid="2" nextobjectid="1">
<tileset firstgid="1" name="flat_hex" tilewidth="60" tileheight="60" tilecount="12" columns="4">
<image source="Tileset_Hexagonal_FlatTop_60x39_60x60.png" width="240" height="180"/>
</tileset>
<layer id="1" name="Tile Layer 1" width="5" height="5">
<data encoding="base64">
AQAAAAYAAAAIAAAACQAAAAkAAIAEAAAACAAAAAYAAAAIAAAABwAAAAUAAAAIAAAABwAAAAsAAIADAABABQAAAAgAAAAHAAAABwAAAAcAAAADAAAACAAAAAoAAAACAAAACgAAAA==
</data>
</layer>
</map>
11 changes: 11 additions & 0 deletions packages/flame_tiled/test/assets/flat_hex_odd.tmx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.9" tiledversion="1.9.1" orientation="hexagonal" renderorder="right-down" width="5" height="5" tilewidth="60" tileheight="39" infinite="0" hexsidelength="30" staggeraxis="x" staggerindex="odd" nextlayerid="2" nextobjectid="1">
<tileset firstgid="1" name="flat_hex" tilewidth="60" tileheight="60" tilecount="12" columns="4">
<image source="Tileset_Hexagonal_FlatTop_60x39_60x60.png" width="240" height="180"/>
</tileset>
<layer id="1" name="Tile Layer 1" width="5" height="5">
<data encoding="base64">
AQAAAAYAAAAIAAAACQAAAAkAAIAEAAAACAAAAAYAAAAIAAAABwAAAAUAAAAIAAAABwAAAAsAAIADAABABQAAAAgAAAAHAAAABwAAAAcAAAADAAAACAAAAAoAAAACAAAACgAAAA==
</data>
</layer>
</map>
15 changes: 15 additions & 0 deletions packages/flame_tiled/test/assets/pointy_hex_even.tmx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.9" tiledversion="1.9.1" orientation="hexagonal" renderorder="right-down" width="5" height="5" tilewidth="60" tileheight="52" infinite="0" hexsidelength="26" staggeraxis="y" staggerindex="even" nextlayerid="2" nextobjectid="1">
<tileset firstgid="1" name="Tileset_Hexagonal_PointyTop_60x52_60x80" tilewidth="60" tileheight="80" tilecount="12" columns="4">
<image source="Tileset_Hexagonal_PointyTop_60x52_60x80.png" width="240" height="240"/>
</tileset>
<layer id="1" name="Tile Layer 1" width="5" height="5">
<data encoding="csv">
6,5,5,5,5,
5,4,8,12,5,
5,2147483656,6,2,1073741826,
5,2147483659,6,6,6,
5,5,11,5,11
</data>
</layer>
</map>
15 changes: 15 additions & 0 deletions packages/flame_tiled/test/assets/pointy_hex_odd.tmx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.9" tiledversion="1.9.1" orientation="hexagonal" renderorder="right-down" width="5" height="5" tilewidth="60" tileheight="52" infinite="0" hexsidelength="26" staggeraxis="y" staggerindex="odd" nextlayerid="2" nextobjectid="1">
<tileset firstgid="1" name="Tileset_Hexagonal_PointyTop_60x52_60x80" tilewidth="60" tileheight="80" tilecount="12" columns="4">
<image source="Tileset_Hexagonal_PointyTop_60x52_60x80.png" width="240" height="240"/>
</tileset>
<layer id="1" name="Tile Layer 1" width="5" height="5">
<data encoding="csv">
6,5,5,5,5,
5,4,8,12,5,
5,2147483656,6,2,1073741826,
5,2147483659,6,6,6,
5,5,11,5,11
</data>
</layer>
</map>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified packages/flame_tiled/test/goldens/isometric.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading