During the development of Super Dash, we encountered a significant challenge known as “ghost lines”- rendering artifacts that shouldn’t be rendered, causing unwanted noise in the game. In this blog we’ll examine the origins of the issue, and explore a solution for smoother gaming experiences.
Identifying the Problem
To better illustrate the problem, we created a small map where the ghost lines are easily noticeable:
You probably were able to spot the rendering issues, but for a closer look, wee have zoomed into a couple of occurrences:
Before we dive into how to solve this, let's understand how the map is built and why the issue happens.
Understanding the map construction
Both our example map, and the one in Super Dash are tile based. This game development concept relies on the idea that your game component, or in this case, the map, is composed of tiles that are laid out in a grid. Those tiles come from a Tileset, which is nothing more than a palette of tiles that the developer will use to compose the final component.
For our example, this is the tileset:
Note how everything seen in the game screenshot comes from this single image, and the patterns that form the hills are simple repeating tiles taken from the tileset.
There are many ways and tools to create tile based maps, one tool commonly used in the gamedev community is Tiled. Tiled allows you compose maps using the concept presented, and the format of the maps that it generates is supported by practically all game engines around, including Flame.
This is how Tiled works in practice:
In computer science,the precision of floating-point numbers is a long-discussed topic. We will not go in depth on this for the sake of keeping the focus of this article, just know that computers have limitations in representing real numbers precisely. (If you want to learn more about that subject check out this Wikipedia's article on how computers store and deal with real numbers.) .
That precision limitation can eventually cause rounding errors and since our tileset is a single image, with many tiles that are composed, when selecting a portion of it from tileset and rendering that tile in the game, such rounding errors might cause the rendering engine to wrongly "pick" or "leave out" a pixel from a tile. This effect is known in game development as Pixel Bleeding.
And how can we solve that? Well, there isn't much we can do about the floating-point number precision as that is still a big area of study in computer science. So we need to be conscious of that issue, and work around it!
One interesting way to work around those rounding errors, is to simply create an empty spacing between the tiles! Since the rounding errors usually will get the pixel right next to it, if we add a spacing on the tiles, even if a rounding error happens, the texture sampling will pick a transparent pixel. Seems simple enough, let's give it a try.
To make our life easier, we developed a small tool called Tile Patcher that given a tileset, it will automatically add the spacing for all the tiles. Lets see it in action:
Now we need to tell Tiled that our tileset has a spacing of 2 pixels:
And if we run our game again:
Wait, something doesn’t seem right, there are even more lines now!
That is true, but take a closer look, you will notice that those artifacts that were showing on top of the plants, in which the small plant tile would "pick" the bottom pixel from the white flower, are not showing anymore!
And these consistent lines showing in the grid are now a result of the rounding errors picking the transparent pixel between the tiles. But we can use those rounding errors to our advantage!
These transparent pixels being picked up, happens only on tiles that need to perfectly fit to another tile on one of its sides. Given that premise, we can patch all tiles that can be placed tidily to others to instead of adding that transparent pixel, repeat its last pixel, so when a round error happens, it will pick up a repeated pixel and that line will be filled!
Happily, that Tile Patcher tool also include a feature to patch tiles, so that is what we will do, select every tile that can have one of its borders side by side to other tile and ask for the tool to patch it:
Basically what we are doing here is to patch the side of the tiles which will be tidily to other tiles, so the tiles in the center will have all its sides patched, the ones on the left side will have all patched, except for the left ones, and the same logic applied to the other ones.
By understanding the nuances of tile-based maps and addressing precision issues with tools like Tile Patcher, we successfully eliminated ghost lines in Super Dash, creating a more polished and seamless gaming experience. By approaching the issue in this way, we ensure that rounding errors work in our favor, enhancing the overall visual integrity of the game.
And that is it, if we run our game again, everything will be working fine!