Java, LibGDX and rendering isometric maps

A brief explanation of how the isometric rendering works in Terminal System Seven.

Posted by on

## Java, LibGDX and rendering isometric maps.

Hello there, my name is Kristian and I am the programmer of TSS. We thought it would be nice to post a little solution to how we handle isometric maps with LibGDX as there were some problems getting it to work. This post will only scratch the surface of how our rendering system is implemented, but if there's more interest in the subject, we can wright a much more detailed post about this later.

LibGDX is a powerful opensource framework for creating games with Java, and this is the framework we decided to use for Terminal System Seven. Personally, I think LibGDX is an amazing framework and would recommend people to use it for Android games any day of the week, but I also feel that doing things from scratch is a very good lesson. I thought the support for Tiled maps and the Tmx map loaders in LibGDX would make my life as a programmer alot easier and that this would save us alot of development time. I was wrong. It turned out that the Isometric part of the Tmx loader, map and renderer were still experimental and was only good for rendering static maps with no objects or entities moving around. This meant that LibGDX had no way of sorting custom moveable units into the draw order of the isometric map.

The first solution to this problem was to alter the existing source code ( LibGDX is open source ;) ), which kind of worked. This presented a big issue that broke pretty much everything with the rendering a bit down the road though. LibGDX preserves the concept of layers that Tiled uses, which works very well when it comes to top-down games. This isn't very well suited for isometric maps as the rendering isn't sorted in the same way. In isometric maps everything is drawn from the top down and from left to right in the following way.

``````for( y = mapHeight -1; y>=0; y--)
{
for( x =0; x < mapWidth; x++)
{
//Draw code
}
}``````

This is partly how we do it but we also render every cell of the map from the bottom up, which makes it easy to stack drawable objects on top of each other. Each cell of the map is constructed like a bucket so adding and removing objects to the draw order is simply a matter of finding which cell the object is on top of and add that object to that cell. Moving from one cell to another isn't much harder. First remove the object from the cell it was standing on and then add it to the cell it moved to.

A graph illustrating the differences between our rendering system and LIbGDX's.

Something that I've found to be very important when it comes to creating an isometric game is to keep track of which coordinate system you are working in. In our case the isometric coordinates are never used or accessible by us ( me, the programmer ). It is calculated by the rendering part of the engine once it's time to show everything on the screen. This is because it is alot easier to perform calculations, such as collision detection and finding a specific cell when everything is in an axis aligned coordinate system.

That's all for this time. As I said it just scratches the surface of how the rendering works. If you have any questions or want a more in-depth look at it just give me a shout. Either by commenting or sending us an e-mail.

I think I see a problem in your way of handling movement; consider this scenario: Imgur.com
The values on tiles (left illustration) are indices so tiles with larger indices are rendered topmost, per your scheme. Consider now the three boxes on the right illustration. What happens when the blue box moves along the brown arrow behind the red box? If it is immediately assigned to index 1, the green box will (wrongly) be rendered in front. If it is assigned to index 1 only after the translation has finished, it will (wrongly) be rendered in front of the red box for the duration. In fact, it seems there is no point in the transition at which the index can be safely switched.
I remember having wondered how to solve this myself when I (briefly) worked with isometric games, and I never managed to come up with a viable solution. Have you considered these cases, and do you have a proposition?

Author

Hello there! I'm not sure i completely follow. We render everything the other way around. That is that Y is looped first and then X which sets the indicies on the Y-axis first. It is true that it is hard to get the rendering perfect, which I have had to spend a lot of time tweaking to get it to where it is now. In our case something that has a lower X value will never be rendered in front of something that has a higher X value, so the case where the green box is rendered before the blue box can not happen. When it comes to the red box briefly being rendered in front of the blue box, that one is tricky indeed. We solved this by using a separate point in the sprite for the isometric sorting. Offseting it by a value that makes that sprite transition correctly onto the new tile without clipping through other sprites. This is a bit tricky to explain in a comment but I hope that you somewhat understand. I've added a link to a (not so) pretty picture showing how we've numbered our indices. I.imgur.com Feel free to send me a message if you want a more in depth explanation! I have however spent the last week refactoring the rendering code and have greatly optimized it, so this post is not up to date anymore. But the sorting and the indice numbering is still identical.

Right, I must've mixed up X and Y. The problem seems to persist with your scheme, though:
Imgur.com (the mirrored case of that which was problematic if looping X first)
Depending on the point during translation at which the blue box's index is updated, it will appear in front of the red box (if the update happens terminally), appear behind the green box (if the update happens initially), or both (the former until the index is updated, then the latter -- resulting in the clipping you mentioned).
"We solved this by using a separate point in the sprite for the isometric sorting." Aha! I understand if the explanation is lengthy, but I'd certainly like to know the details :)