Thursday, July 07, 2011

Seamless tile scaling in GameMaker

There has been a long standing issue with tile maps in GameMaker, where 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 your getting these visual artifacts.

First, lets look at an image where cracks are showing through. The image on the right by Titanium, 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 your 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 anyway you like. But... what happens if we scale this? What does the hardware actually do? The tile shown on the left here, 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 textel 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 here 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 it's own way, but if you're careful you can work around it. Now lets say in this case it decides to draw that texel into the screens 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. This is basically a "suck-it-and-see" situation. At some point, we might try and automate this into GameMaker, so that it creates the "smear" area for you, as it does take a long time to draw and maintain, but for now you'll need to do it yourself.

I have created a simple example HERE showing this in action, and it scales the sample map in/out reasonably far without any problems.

13 Smart arse replys:

pucone said...

I'm loving these posts about the guts of Game Maker, thanks.

Knuked said...

Fantastic article Mike. I would like to know two things in relation to scaling.

1. How come GameMaker views only use integer or pixel based scaling and movement? If you don't know what I mean, look at your example, when the view is scaled down, it becomes very shaky and ugly. If I have a player which uses hspeed, the view will not follow smoothly when accelerating or decelerating because views don't use float values. If you are going to have movement variables which are floats or doubles, it's only right to have views which can smoothly follow or scale.

2. If my view is 320x240, and I scale the view to 640x480, why is all my pixel work distorted? There is a community fix for this but it's ugly in my opinion. I think GM should be able to scale viewports properly. Here is a link to the community fix for this problem ... http://forums.tigsource.com/index.php?topic=3142.msg86809#msg86809

Mike said...

Well, the views are designed to view areas of a room, and you can't view a fraction of a pixel... *shrug*

This is an issue with graphics drivers. They seem to have a mind of their own when doing this stuff. It will be fixed in the next version of GameMaker, but it's just too complicated to fix in this one - I tried.

Knuked said...

Well sure I can understand not having a fraction of a pixel. What's amazing is that if I use the orthographic camera in d3d, everything is very smooth. However as you know, then tiles don't work.

Do you have any advice on how to get a smooth view? I've tried everything. Linear interpolation almost works but again, as the camera gets closer to target we get the snapping of the view. There just seems to be something up.

Thanks a lot Mike, I'm sure everyone pestering you about GM gets old :)

Anonymous said...

By the way:
I think is time to fix the gml syntax, should be more "hard" and next version of GM shouldn't allow the mixing of GML basic-like syntax and java-like syntax.
Maybe the syntax to use is a project property, so the user can uses his preferred syntax (java-basic like). I see editables with a mix of the two allowed syntaxes and -how orrible and unprofessional-.
Implementing this is a good step forward (many people report me this).

Continue with your good job... ;-)

Knuked said...

"-how orrible and unprofessional-"

Are you sure you're ready for stricter syntax :P

Stricter syntax in my opinion should be incorporated slowly. There are many young and inexperienced users who will be nothing but confused if you just throw a stricter syntax in their lap. I don't know ... I just think there are bigger fish to fry in GameMaker.

Although I wouldn't mind, I don't see why people push so hard for it. If you want a tighter and more professional language, there are so many out there.

Anonymous said...

Sorry for my english :P
Divide the two syntaxes doesn't confuse young and inexperienced users.
This confuse users:

if foo=1 then{ bar=1 a=b; c=d }

Since 2000 people say that "a stricter syntax should be incorporated slowly"
and now in 2011:

if foo=1 then{ bar=1 a=b; c=d }

Imho is time to fix this and give a more professional style to GM for attract more users (and inexperienced users have not fear about a correct syntax)

Yes there are so many languages out there, but we are talking about GM and his improvements.

TKjogosbrasil.com.br said...

Mr.Dailly My brother want work there DMA Design would have an opportunity for him to work there
he will have a resume please answer this year.I`m brasilian my email jesuseunice362@gmail.com

Please a opportunity!

My names Tiago I`m 12 years old.

Knuked said...

Sorry Anon I was just poking fun :)

I hear what you're saying. Well let's just see what they do :)

Anonymous said...

Knuked, the best way to achieve what you want to do, AFAIK, is definitely drawing everything to a surface and then scaling that (as the link you posted suggested).

The issue here is that there's two ways of achieving scaling. You can draw everything twice as big (which gives you twice the detail) or you can draw everything at normal size and then scale the whole image (which means everything will line up correctly but you get no extra detail). I wonder if there would be a way for GameMaker to easily support both methods without confusing beginners or the need to deal with surfaces...

Roby said...

I agree with the anonymous user: separate the two syntaxes (in the preferences) will give a little more dignity and professionalism to the GM and GML.

I hope to see this in the next update, I do not think this requires much work, but it will be another good thing.

;)

Anonymous said...

really simple time,

width*height^2 :P always use power of twos when working with tilemaps

also

Pack like tiles as close together as possible, they will act as the CLAMP for their adjacent tiles.

html5 audio player said...

Thanks for sharing your info. I really appreciate your efforts and I will be waiting for your further write ups thanks once again.