1. Sweet Transit
  2. News
  3. About Sweet Transit #7

About Sweet Transit #7

Dynamic Sprite Loading

I wanted to write something more technical for this post. We touched on some parts of the gameplay and what you can expect. My inner dev just wants to talk about struggles and solutions. One of these struggles was finding a solution for future expansion. I wanted to solve the ever-increasing VRAM requirement.


Sweet Transit is an isometric 2D game. That means that everything is images or the correct term would be sprites. Sprites are superior to 3D in terms of performance. I can put as many details as I want into a sprite and that will not influence how hard the GPU needs to work. However, with 3D models you are not limited to a single view angle. If you want to rotate a train then it is simple, just rotate it. In 2D I need to make sure that I have those rotations in sprites. This means that every new train is quite expensive to have. But this is a game about trains and players should not be limited by how many different trains they can have.



With my plan 1.0 should be playable with 4GB of VRAM if all settings are ramped up to the full. Of course, to support the wider audience there are plenty of settings to minimize that requirement. For example, you can change sprite resolution to low, that alone will make the VRAM requirement to about 256MB. Furthermore turning off shadows and other effects, turning on compression can make that requirement below 100MB.



The problem is what happens when people start adding mods or I start creating some DLC. Vanilla sprites are optimized. Wagons for example are symmetrical and use only 64 sprites for 128 rotations. I do not want to add extra requirements like these for making mods, just the option to do them. But at some point even most high-end GPUs will struggle when enough sprites are present. The solution is obvious - streaming. In this case sprite streaming. At the surface it sounds easy enough. Just load sprites that are needed and unload that are not. But with game development it is never as easy as it sounds.

I usually start with my requirements when adding something new. In this case those were:
  • All sprites need to be packed to their atlases.
  • Atlas packing or unpacking should minimally influence performance.
  • Loads sprites fast on HDD.
  • Decided what to load or unload should minimally influence performance.
  • Most of the work done should be done in a separate thread.




In 2D games having sprites in atlases is a huge performance boost due to the cost of draw calls. In Sweet Transit it is a requirement. In some cases there can be around 100k sprites on the screen. If you have a 4k screen and are zoomed out there is a chance that even 200k sprites will be visible. Having so many sprites and a stable 60 fps is not possible without atlases. That means the first step for sprite streaming is solving how to efficiently load and unload those sprites to the atlas.

In 3D games Megatextures are quite popular for loading model textures in one big atlas. It is a solid base. It is balanced around loading square textures in sizes of power of 2. Having this constraint searching for a possible position is really fast. Sprites however have many different variations of sizes. Furthermore unloading sprites leaves unusable spaces that can lead to memory fragmentation problems. I opted for a custom Binary Tree algorithm. It offers fast possible position searches with good unloading and merging options.

To solve fragmentation I divided the atlas into separate smaller regions. If I cannot find a new place to put my sprites I just can unload the least used region. With the region unloaded I can then pack all needed sprites tightly again. To avoid invisible sprites while they are loading I preloaded atlases with downscaled sprites.



The next problem to solve was loading fast on HDD. Standard sprite loading opens the whole image and loads all sprites to the atlas. This solution is only good if all sprites are needed, but that was not the case. For example, we have a train going with its wagons and goods loaded to its destination. Sprite streaming looks at the needed sprite requests and loads 64 rotations for the locomotive, 64 rotations for each wagon, wheels, shadows in all 4 directions for all rotations and so on... In most cases it would load around 2k of sprites when only a handful was needed. If you have a slower CPU or HDD then good luck seeing your sprites.



To solve this problem I made custom-formated sprite files. It is not far off of how zip works. Png files are converted to these custom files during game load. Now when the game runs any specific sprite can be loaded if needed. This was enough to make the HDD happy.

Other problems were trivial to solve and it is really not worth mentioning them. Now a player will be able to load all sprites at once or use sprite streaming. This will allow an even wider range of audience who can enjoy those sweet trains. I am happy with the solution even if it is not perfect. But it was really scary to start given the lack of information and solutions for sprite streaming.


I hope you enjoyed reading about the technical parts. Developing games offers a lot of smaller problems. It is a great feeling when you solve them and get closer to the final game bit by bit. Write a comment if you want to hear about something specific!