2d top down rendering in Flame

Lim Chee Keen
3 min readFeb 28, 2024

--

2d top down view in Pokemon Ruby/Sapphire

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

The Flame engine was designed for 2d rendering. It has helper methods and functions to render single images, spritesheets and even animated spritesheets. The Super Dash demo is a 2d platformer example of this. However, for Gomiland, I needed to make the game appear like players were looking at it from a 45°-67.5° angle rather than a completely top down (0°) or side (90°) view.

Object 1 is partially obscured by the player, while the player is partially obscured by object 2

This is seen in games like Pokemon and Stardew Valley. Could this be done with Flutter and Flame. Good news is that it can! Bad news is that it requires effort and a bit of a hack. You see, the main problem is that Flame’s render order is fixed. This means you are unable to dynamically sort objects by their position, making objects with higher y-axis coordinates appear in front of objects with lower y-axis coordinates.

You could alter the priority value of components as mentioned in the docs, but to do this for every object is very tedious and inefficient especially when you have hundreds like in Gomiland. Furthermore, priority is not global which means children component priorities not different from parents and siblings. So priority cannot be used as a way to dynamically change the sprites order of appearance.

Solution? First, you have to decide if the player will i) always appear in front of; ii) always appear behind; or iii) can go in front and behind an object. The first 2 are simple. Objects that always appear behind players will be added before rendering the player sprite and vice versa.

await add(Barrier());
await add(Player());
await add(ToriGate());

In the above code, the player is added after the barrier but before the tori gates to give the following effect. This gives the illusion that the player is walking under/in front of objects. Don’t forget the await keyword. Some sprites may load faster than others, so take no chances.

Player always appears behind the tori gates, but in front of barriers

The third type are the tricky ones. These are objects that players can go both in front and behind. We have to split up the image of the sprite, then add the bottom half and top half separately. In order to make it more computationally efficient, the bottom half which always appears behind the player can be added as a tilemap layer. Check out how to optimise tilemaps in a post related to this game here.

await add(ObjectBottom()); // include in the tile map instead, for efficiency
await add(Player());
await add(ObjectTop());

This is the resultant effect. The lower half of the fountain is rendered with the ground while the upper half is added after the player sprite is added, making it always appear above the player. Be sure to add a hitbox to the object, else the player can ‘walk through’ the object.

The player can walk around the fountain

Hopefully the Flame engine will be able to do dynamic appearances of sprites in 2d like some game engines can do, but until then this method works. It does not affect the rendering speed because once sprites are loaded, their appearance does not need to be recalculated or changed. Nevertheless, it is cumbersome especially when rendering many sprites.

For more features created for Gomiland using the Flame engine, checkout the main article. Good luck!

--

--

Lim Chee Keen

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