GMS 2 n-layered auto tiling using vertex buffers.

Gzebra

Member
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);
Next we will have to create a 2D array containing information about what type of sprites should be where. Usually you'd either read that information from a file, or generated it procedural using simplex noise, etc. But in this example, we're simply going to generate it randomly.

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));
   }
}
Next we're gonna create the vertex buffer using our special auto tile script. (We haven't covered this script yet, but just follow along please)

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);
Now, let's do...

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]);
Before we cover the auto tile script, let's first cover a small script that I will be using in the auto tile script. I call it limit, so let's do...

The limit script.

Code:
return ((argument0 % argument1)+argument1) % argument1;
Simply put, it's just a mod function that doesn't go negative.

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;
next up, we will have to initiate a few variables we're going to be using throughout the script, along with the vertex format.
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);
Next we're going to calculate the sprite position, since our sprite sheet is 224x224 and our sprites are 32x32, we will have 7x7 sprite positions. Even though our sprite sheets are only 224x224 pixels, GameMaker will use 256x256 as the texture page for it, so we'll have to divide the position by 256 and not 224.
Code:
for(var s=0; s<=7; s++){
   sp[s]=(s*32)/256;
}
Next up is the actual auto tile algorithm itself..
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:
Top