**GM Version**: Studio

**Target Platform**: Windows (but should work on others)

**Download**: none

**Links**: none

Introduction:

What is auto tiling?

Simply put, auto tiling is a algorithm that calculates which sprite to use from a tile sheet based upon the surrounding tiles. There are different forms of auto tiling, but in this tutorial we will be looking at the most complicated of which. A 47 typed n-layered auto tile system.

So what exactly do I mean by 47 typed and n-layered? First of all, let's look at the 47 typed thing. What I mean is, that the tile sheet consists of 47 different sprites, it is also possible to create a tile system with only 16 sprites. The difference between the two is whether or not you take the corners into considerations.

Okay, so what about the n-layered thing? Well, one of the issues with creating a auto tile system, is that for each different type of sprite, let's say, grass, water, trees, etc, the amount of sprites you'll have to create increases exponentially. With just a few types of sprites interacting with each other, you'll quickly get to millions of sprites. Millions of sprites is not a good thing, so we'll have to come up with a solution for this issue, and the solution is quite simple, but does have it's own draw back.

Instead of having each grass sprites interact with each of the other types of sprites, we will simple have it interact with nothing, that is to say, interact with transparency, this way, the tile that is underneath it will be the sprite that it interacts with. There are two downsides to this solution. We will have to create a vertex buffer for each type of sprite (grass, water, tress, etc) and each tile set can only interact with a tile set that is drawn underneath it.

The basic setup:

In this example we will only need one room, one object, and two script, so go ahead and create that.

We will also be using 4 different tile sets. Water, grass, trees, and stone. I have included a template for them below.

Since we're going to use vertex buffers, and our tile sets will be in .png format, we will have to include the files externally, instead of having them as a internal resource. Otherwise, GameMaker won't calculate the position on the tile sheet correctly. So in your project, right click Included Files under the resource tree (to the right by default) and select insert included file. Then point to the files that I've included above and press open.

Let's start with...

The create event.

Code:

```
//add external resources to the game.
tilesheet[0]=sprite_get_texture(sprite_add("water.png",0,0,0,0,0),0);
tilesheet[1]=sprite_get_texture(sprite_add("grass.png",0,0,0,0,0),0);
tilesheet[2]=sprite_get_texture(sprite_add("tree.png",0,0,0,0,0),0);
tilesheet[3]=sprite_get_texture(sprite_add("stone.png",0,0,0,0,0),0);
```

Code:

```
//generate or load the world information
for(var w=0; w<=64; w++){
for(var h=0; h<=64; h++){
map[w,h]=round(random_range(0,3));
}
}
```

Code:

```
//create the vertex buffer
vertexBuffer[0] = autoTile(map,0);
vertexBuffer[1] = autoTile(map,1);
vertexBuffer[2] = autoTile(map,2);
vertexBuffer[3] = autoTile(map,3);
```

The draw event.

All we need to do here is simply to submit the vertex buffers..

Code:

```
//submit the created vertex buffer
vertex_submit(vertexBuffer[0],pr_trianglelist,tilesheet[0]);
vertex_submit(vertexBuffer[1],pr_trianglelist,tilesheet[1]);
vertex_submit(vertexBuffer[2],pr_trianglelist,tilesheet[2]);
vertex_submit(vertexBuffer[3],pr_trianglelist,tilesheet[3]);
```

The limit script.

Code:

`return ((argument0 % argument1)+argument1) % argument1;`

Now, let's look at...

The auto tile script.

First of all, let's cover the 2 arguments the script needs. First we have the map array that contains the map information, and secondly we have the layer information, this is what layer we're drawing (water, grass, tree, stone, etc).

Code:

```
var map=argument0;
var l = argument1;
```

Code:

```
var size = array_height_2d(map);
var vx,vy,t,sp,tx,ty;
vertex_format_begin();
vertex_format_add_position();
vertex_format_add_normal();
vertex_format_add_color();
vertex_format_add_texcoord();
var vf = vertex_format_end();
var vb = vertex_create_buffer();
vertex_begin(vb, vf);
```

Code:

```
for(var s=0; s<=7; s++){
sp[s]=(s*32)/256;
}
```

First we go through each point in our map array, and if a a point is less or larger than the map array, we go to the other end of the array, making our map loop around itself. This is what we use the limit script for. Then we add 1,2,4,8.. to it as needed, we do this to create a number that we'll be using to select what sprite to use. Some sprites have more than one number associated with it. Then after we've determined the sprite number (t) we assign the correct texture coordinate using the sprite position array we've made earlier.

So to simplify, if the map value we're looking at isn't the same as the as what we're looking for (as determined in argument1) we check and see what kind of edges it have, and assign the appropriate sprite for it. If it is the sprite we're looking for, we apply the full sprite.

Code:

```
for(var w=0; w<size; w++){
for(var h=0; h<size; h++){
t=0;
if(map[w,h]!=l){
if(map[limit(w,size),limit(h-1,size)]==l){t+=1;}//north
if(map[limit(w+1,size),limit(h-1,size)]==l){t+=2;}//north-east
if(map[limit(w+1,size),limit(h,size)]==l){t+=4;}//east
if(map[limit(w+1,size),limit(h+1,size)]==l){t+=8;}//south-east
if(map[limit(w,size),limit(h+1,size)]==l){t+=16;}//south
if(map[limit(w-1,size),limit(h+1,size)]==l){t+=32;}//south-west
if(map[limit(w-1,size),limit(h,size)]==l){t+=64;}//west
if(map[limit(w-1,size),limit(h-1,size)]==l){t+=128;}//north west
if(t==0) {tx[0]=sp[0];tx[1]=sp[1];ty[0]=sp[0];ty[1]=sp[1];}//center
if(t==2) {tx[0]=sp[1];tx[1]=sp[2];ty[0]=sp[0];ty[1]=sp[1];}//north-east
if(t==8) {tx[0]=sp[2];tx[1]=sp[3];ty[0]=sp[0];ty[1]=sp[1];}//south-east
if(t==32) {tx[0]=sp[3];tx[1]=sp[4];ty[0]=sp[0];ty[1]=sp[1];}//south-west
if(t==128) {tx[0]=sp[4];tx[1]=sp[5];ty[0]=sp[0];ty[1]=sp[1];}//north-west
if(t==10) {tx[0]=sp[5];tx[1]=sp[6];ty[0]=sp[0];ty[1]=sp[1];}//north-east,south-east
if(t==40) {tx[0]=sp[6];tx[1]=sp[7];ty[0]=sp[0];ty[1]=sp[1];}//south-east,south-west
if(t==160) {tx[0]=sp[0];tx[1]=sp[1];ty[0]=sp[1];ty[1]=sp[2];}//south-west,north-west
if(t==130) {tx[0]=sp[1];tx[1]=sp[2];ty[0]=sp[1];ty[1]=sp[2];}//north-east,north-west
if(t==34) {tx[0]=sp[2];tx[1]=sp[3];ty[0]=sp[1];ty[1]=sp[2];}//north-east,south-west
if(t==136) {tx[0]=sp[3];tx[1]=sp[4];ty[0]=sp[1];ty[1]=sp[2];}//south-east,north-west
if(t==42) {tx[0]=sp[4];tx[1]=sp[5];ty[0]=sp[1];ty[1]=sp[2];}//north-east,south-east,south-west
if(t==168) {tx[0]=sp[5];tx[1]=sp[6];ty[0]=sp[1];ty[1]=sp[2];}//south-east,south-west,north-west
if(t==162) {tx[0]=sp[6];tx[1]=sp[7];ty[0]=sp[1];ty[1]=sp[2];}//north-east,south-west,north-west
if(t==138) {tx[0]=sp[0];tx[1]=sp[1];ty[0]=sp[2];ty[1]=sp[3];}//north-east,south-east,north-west
if(t==170) {tx[0]=sp[1];tx[1]=sp[2];ty[0]=sp[2];ty[1]=sp[3];}//north-east,south-east,south-west,north-west
if(t==1||t==3||t==129||t==131) {tx[0]=sp[2];tx[1]=sp[3];ty[0]=sp[2];ty[1]=sp[3];}//north
if(t==4||t==6||t==12||t==14) {tx[0]=sp[3];tx[1]=sp[4];ty[0]=sp[2];ty[1]=sp[3];}//east
if(t==16||t==24||t==48||t==56) {tx[0]=sp[4];tx[1]=sp[5];ty[0]=sp[2];ty[1]=sp[3];}//south
if(t==64||t==96||t==192||t==224) {tx[0]=sp[5];tx[1]=sp[6];ty[0]=sp[2];ty[1]=sp[3];}//west
if(t==9||t==11||t==137||t==139) {tx[0]=sp[6];tx[1]=sp[7];ty[0]=sp[2];ty[1]=sp[3];}//north,south-east
if(t==36||t==38||t==44||t==46) {tx[0]=sp[0];tx[1]=sp[1];ty[0]=sp[3];ty[1]=sp[4];}//east,south-west
if(t==144||t==152||t==176||t==184) {tx[0]=sp[1];tx[1]=sp[2];ty[0]=sp[3];ty[1]=sp[4];}//south,north-west
if(t==66||t==98||t==194||t==226) {tx[0]=sp[2];tx[1]=sp[3];ty[0]=sp[3];ty[1]=sp[4];}//west,north-east
if(t==33||t==35||t==161||t==163) {tx[0]=sp[3];tx[1]=sp[4];ty[0]=sp[3];ty[1]=sp[4];}//north,south-west
if(t==132||t==134||t==140||t==142) {tx[0]=sp[4];tx[1]=sp[5];ty[0]=sp[3];ty[1]=sp[4];}//east,north-west
if(t==18||t==26||t==50||t==58) {tx[0]=sp[5];tx[1]=sp[6];ty[0]=sp[3];ty[1]=sp[4];}//south,north-east
if(t==72||t==104||t==200||t==232) {tx[0]=sp[6];tx[1]=sp[7];ty[0]=sp[3];ty[1]=sp[4];}//west,south-east
if(t==41||t==43||t==169||t==171) {tx[0]=sp[0];tx[1]=sp[1];ty[0]=sp[4];ty[1]=sp[5];}//north,south-east,south-west
if(t==164||t==166||t==172||t==174) {tx[0]=sp[1];tx[1]=sp[2];ty[0]=sp[4];ty[1]=sp[5];}//east,south-west,north-west
if(t==146||t==154||t==178||t==186) {tx[0]=sp[2];tx[1]=sp[3];ty[0]=sp[4];ty[1]=sp[5];}//south,north-east,north-west
if(t==74||t==106||t==202||t==234) {tx[0]=sp[3];tx[1]=sp[4];ty[0]=sp[4];ty[1]=sp[5];}//west,north-east,south-east
if(t==5||t==7||t==13||t==133||t==15||t=141||t==135||t==143) {tx[0]=sp[4];tx[1]=sp[5];ty[0]=sp[4];ty[1]=sp[5];}//north,east
if(t==20||t==22||t==28||t==52||t==30||t==60||t==54||t==62) {tx[0]=sp[5];tx[1]=sp[6];ty[0]=sp[4];ty[1]=sp[5];}//east,south
if(t==80||t==88||t==112||t==208||t==120||t==240||t==216||t==248) {tx[0]=sp[6];tx[1]=sp[7];ty[0]=sp[4];ty[1]=sp[5];}//south,west
if(t==65||t==67||t==97||t==193||t==99||t==225||t==195||t==227) {tx[0]=sp[0];tx[1]=sp[1];ty[0]=sp[5];ty[1]=sp[6];}//north,west
if(t==37||t==39||t==45||t==165||t==47||t==173||t==167||t==175) {tx[0]=sp[1];tx[1]=sp[2];ty[0]=sp[5];ty[1]=sp[6];}//north,east,south-west
if(t==148||t==150||t==156||t==180||t==158||t==188||t==182||t==190) {tx[0]=sp[2];tx[1]=sp[3];ty[0]=sp[5];ty[1]=sp[6];}//east,south,north-west
if(t==82||t==90||t==114||t==210||t==122||t==242||t==218||t==250) {tx[0]=sp[3];tx[1]=sp[4];ty[0]=sp[5];ty[1]=sp[6];}//south,west,north-east
if(t==73||t==75||t==105||t==201||t==107||t=233||t==203||t==235) {tx[0]=sp[4];tx[1]=sp[5];ty[0]=sp[5];ty[1]=sp[6];}//north,west,south-east
if(t==68||t==70||t==76||t==100||t==196||t==78||t==108||t==228||t==198||t==102||t==204||t==110||t==236||t==230||t==206||t==238) {tx[0]=sp[5];tx[1]=sp[6];ty[0]=sp[5];ty[1]=sp[6];}//east,west
if(t==17||t==19||t==25||t==49||t==145||t==27||t==57||t==177||t==147||t==51||t==153||t==59||t==185||t==179||t==155||t==187) {tx[0]=sp[6];tx[1]=sp[7];ty[0]=sp[5];ty[1]=sp[6];}//north,south
if(t==21||t==23||t==29||t==53||t==149||t==31||t==61||t==181||t==151||t==55||t==157||t==63||t==189||t==183||t==159||t==191) {tx[0]=sp[0];tx[1]=sp[1];ty[0]=sp[6];ty[1]=sp[7];}//north,east,south
if(t==84||t==86||t==92||t==116||t==212||t==94||t==124||t==244||t==214||t==118||t==220||t==126||t==252||t==246||t==222||t==254) {tx[0]=sp[1];tx[1]=sp[2];ty[0]=sp[6];ty[1]=sp[7];}//east,south,west
if(t==81||t==83||t==89||t==113||t==209||t==91||t==121||t==241||t==211||t==115||t==217||t==123||t==249||t==243||t==219||t==251) {tx[0]=sp[2];tx[1]=sp[3];ty[0]=sp[6];ty[1]=sp[7];}//north,south,west
if(t==69||t==71||t==77||t==101||t==197||t==79||t==109||t==229||t==199||t==103||t==205||t==111||t==237||t==231||t==207||t==239) {tx[0]=sp[3];tx[1]=sp[4];ty[0]=sp[6];ty[1]=sp[7];}//north,east,west
if(t==85||t==87||t==93||t==117||t==213||t==95||t==125||t==245||t==215||t==119||t==221||t==127||t==253||t==247||t==223||t==255) {tx[0]=sp[4];tx[1]=sp[5];ty[0]=sp[6];ty[1]=sp[7];}//north,east,south,west
}else{
tx[0]=sp[5];tx[1]=sp[6];ty[0]=sp[6];ty[1]=sp[7];
}
vx=w*32;
vy=h*32;
vertex_position(vb , vx, vy); vertex_normal(vb , 0, 0, -1); vertex_color(vb, $FFFFFF, 1); vertex_texcoord(vb, tx[0], ty[0]);
vertex_position(vb , vx+32, vy); vertex_normal(vb , 0, 0, -1); vertex_color(vb, $FFFFFF, 1); vertex_texcoord(vb, tx[1], ty[0]);
vertex_position(vb , vx, vy+32); vertex_normal(vb , 0, 0, -1); vertex_color(vb, $FFFFFF, 1); vertex_texcoord(vb, tx[0], ty[1]);
vertex_position(vb , vx, vy+32); vertex_normal(vb , 0, 0, -1); vertex_color(vb, $FFFFFF, 1); vertex_texcoord(vb, tx[0], ty[1]);
vertex_position(vb , vx+32, vy); vertex_normal(vb , 0, 0, -1); vertex_color(vb, $FFFFFF, 1); vertex_texcoord(vb, tx[1], ty[0]);
vertex_position(vb , vx+32, vy+32); vertex_normal(vb , 0, 0, -1); vertex_color(vb, $FFFFFF, 1); vertex_texcoord(vb, tx[1], ty[1]);
}
}
[code]
To end off the script we write..
[code]
vertex_end(vb);
vertex_freeze(vb);
return vb;
```

And that is it! You might not find the result very pleasing to look at, but this is due to our simplfied sprites, and map generation. With better looking sprites and more complex map generation, you might end up with something like this:

I hope this tutorial have been helpful to you, and if you have any questions or suggestions, feel free to post a message.

Last edited: