Using large, animated tilemaps with Flame

Lim Chee Keen
3 min readFeb 28, 2024

--

Gomiland’s neighbourhood tilemap

This article is part of the series Building a 2d-top down RPG with Flutter and Flame.

Gomiland’s tilemap images were created in an image editor called GIMP. It’s free and easy to use and can export to widely-used image formats like png, jpg, etc. After creating images files, I used Tiled to create Gomiland’s map. Tiled is again free and it’s a level editor that can directly integrate with Flame’s engine. This is as far as most tutorials go and it would be great if this is all it takes. However aspiring RPG creators, like you, would soon realise you run into a few problems.

The first issue came immediately when Flame engine tried to load the Tiled file (.tmx). Blank image. Nothing was displayed. Why? After some trial and error, I realised that tilemaps with size set to infinite will not load. You have to decide thesize of the tilemap if you want Flame to load it. First issue, albeit minor, resolved.

Once I added all objects to the level, I hit the second issue — jank. Gomiland has 3 ‘levels’. The room level is 800x512 pixels, the neighbourhood and park are 6400x4800 pixels each. Additionally, the neighbourhood and park maps have animated tiles. Plus, for the neighbourhood and park maps I have 10 tile layers and 8 object layers with >100 objects including trees, NPCs buildings and lights. That’s enough to drop the frames per second (FPS) to 10–20 FPS . The jank is very severe at this point.

Added map TiledComponent

The first remedy obviously is to reduce the number of objects. I reduced some lights and decorations here and there, but the FPS was still <20 which was not acceptable.

Luckily there is a great package called flame_tiled_utils. This package helped with 2 things. First, it collapses all static tile layers into a single layer and renders it as a single image. Second, it “merge(s) animated tiles of the same type into one big SpriteAnimation component”. Here is the code to load the neighbourhood map in Gomiland.

// static tile layers
final imageCompiler = ImageBatchCompiler();
final layerNames = [
'sand',
'road',
'pavement',
'grass',
'overlays',
'barriers',
'bases',
'trees',
'buildings',
];
final ground = imageCompiler.compileMapLayer(
tileMap: map.tileMap, layerNames: layerNames);
add(ground);
// animated tile layer
final animationCompiler = AnimationBatchCompiler();
await TileProcessor.processTileType(
tileMap: map.tileMap,
processorByType: <String, TileProcessorFunc>{
'water': ((tile, position, size) async {
return animationCompiler.addTile(position, tile);
}),
},
layersToLoad: [
'water',
]);
final animatedWater = await animationCompiler.compile();
animatedWater.priority = -1;
add(animatedWater);

This increases the FPS to about 20–30, which is still not great, but once Flutter builds the application, this jank disappears completely.

Added map using ImageBatchCompiler and AnimationBatchCompiler

If you notice, the package also solved another issue: ghost lines. These are lines that sometimes appear in between tiles. Read more about it here. This was an unintentional consequence of merging all layers into a single image but pleasantly surprising nonetheless.

I believe this is the maximum size of map and number of sprites that can be added to Gomiland’s 2 maps before the jank becomes severe. However, having a large tilemap and animated tiles in Gomiland is very statisfying. Players have freedom to explore many different areas of the map and it gives life to the water surface. Managing the FPS was helped by limiting the number of objects that were rendered and using flame_tiled_utils package.

Hope to see more creative maps created in the Flame engine!

--

--

Lim Chee Keen

Former Navy Captain Turned Software Engineer | Flutter & React developer | ML & AI programmer | Co-founder for Group Buy service