Seamless Tile Scaling In GameMaker

One of the quirks of using Tile Maps is that if you scale them (or the viewport) you'll get cracks, or other tiles poking in and corrupting the display.

So the aim of this post is to show you why this happens, and how to avoid it. Some users have already figured a way around this, but don't really know the cause, so I want to also clear up what the hardware is actually doing, and why you're getting these visual artifacts.

First, let's look at an image where cracks are showing through. The image above shows the kind of visuals you can expect to get if you zoom in on a tile set that hasn't been created properly. The problem with many tile sets is that they either have little spaces around them, or they are right next to other tiles, and when growing or shrinking (in particular), this can cause some really nasty issues to solve if you're not sure why its happening in the first place. It's also one of these bugs that you'll be convinced are GameMaker's fault, after all... it allows you to scale the tiles, so it should work! Well, yes and no...

First, lets take a look at a couple of normal tile sets that you might create (thanks to Titanium and TeamSkyfire for the tile sets):

So, these are the most common ways people create tile sets. Either jammed up together, or with little spaces between them. Now for a non-scaling game, this is fine. You can happily predict what the hardware will render, and you can pretty much create the tile set any way you like. But... what happens if we scale this? What does the hardware actually do?

The tile below shows how tiles are mapped using D3D. The red line around the tile shows where the U,V coordinates are mapped to. As you can see it picks the center of the texel (a "pixel" in a texture map) to map, this is because this is where D3D "picks" the texel to draw, so we map there directly. Now 1:1, this is all well and good... so what happens when we scale?

Now, the issue with scaling down is that when you shrink, the UVs also scale down and try to fit into a compressed space. This obviously can't happen, so D3D picks which pixels to draw based on pixel centers. Now, if we are "just" moving into a new screen pixel, then because D3D uses the center of the pixel to decide what to draw with, it'll pick a texel surrounding the tile, not a tile texel itself.

Shown above is a tile being draw scaled down into 5 screen pixels. Now, this shows that when you overlap a little into the next screen pixel, there is a choice to be made. It can either drop that texel, or it can try and fill it with what it thinks is a valid texel. This is where a lot of confusion arises, as each graphics card actually does this in its own way, but if you're careful you can work around it. Now let's say in this case it decides to draw that texel into the screen's pixel, since it's now clearly outside our tile, where does it get the pixel from? Well, either the next tile, or the little bits of space you provided around the tiles. This means when it shrinks... you'll get cracks, or corruption.

How about we do this again, but this time we'll draw the other tiles around it; those that exist on the tile page next to it. Now you can see that the hardware will probably either choose the space between the tiles, or a tile next to it.

So knowing this, how can we fix it? Well, if you draw from a single texture (not on a texture page or texture atlas), you have the option of using a texture mode called CLAMP. What this does is repeats the last row of texels infinitely off into the distance. This allows you to scale and forces the hardware to get the last row of pixels no matter what, and for scaling down like we're attempting to do, this works just fine. However, when using a TPage (Texture page), this isn't possible without a shader, but we can cheat by drawing this in ourselves!

Shown above is a "fixed" tile set, you can see it now has a "repeated" section around each tile meaning that when the hardware now overruns, it will always pick the last texel from the tile, rather than one of the spaces, or the tile after that! When importing these new tiles into GameMaker, it's important to make sure you point GameMaker to the tile INSIDE the "smear", this means you still get the original tile, and it now has a "smear" boarder area. The space is optional, but might help you keep track of tiles better, so it's up to you. Below is the resulting image of drawing the tile, complete with the smear. As you can see, it now has plenty of texels to chose from, and since they are all the same, your tile should be drawn correctly (none of this is to proper scale... so don't try and measure things, it's just for illustration purposes).

Now, technically, if you have a VERY large tile, and are zooming a long way out, you might need a bigger buffer zone. Also, if you're using bilinear filtering (where it blurs the image when it scales up/down), then there are further rules you have to consider. First, bilinear filtering uses 2 texels, and blends between them. This means it'll go to the next texel it "would" draw in the image, and that could be a fair jump if you've scaled out a long way. This may mean you need an even BIGGER border if you plan to scale in/out a lot.

Written by Mike Dailly

Mike Dailly was the previous head of development at GameMaker, helping us to transform the game engine during his tenure. He was also the lead game programmer and creator of Lemmings and Grand Theft Auto.