Autotiling by Code

Kezarus

Endless Game Maker
Good day to you!

I did an autotile code but the end result is ugly AF. The pieces are on the right spot, btw.

Here is the code:
GML:
///@definition bitmask_cell
///@param wx - coord x
///@param wy - coord y

var wx = argument[0];
var wy = argument[1];

var cellSize = global.cellSize;
var myTile = tilemap_get_at_pixel(global.tilemapBiomeId, wx, wy);

//ALLOWED TILES
var tileOffset;
switch( myTile ){
    case MapGenBiomesEnum.Ocean: tileOffset = 0;    break;
    case MapGenBiomesEnum.River: tileOffset = 16*1; break;
    case MapGenBiomesEnum.Grass: tileOffset = 16*2; break;
    case MapGenBiomesEnum.Snow:  tileOffset = 16*3; break;
    default:
        return;
}


var tileTop        = myTile == tilemap_get_at_pixel(global.tilemapBiomeId, wx, wy-1);
var tileBottom    = myTile == tilemap_get_at_pixel(global.tilemapBiomeId, wx, wy+cellSize);
var tileLeft    = myTile == tilemap_get_at_pixel(global.tilemapBiomeId, wx-1, wy);
var tileRight    = myTile == tilemap_get_at_pixel(global.tilemapBiomeId, wx+cellSize, wy);

var newTileId =
        (tileTop    ? 1 : 0) +
        (tileLeft    ? 2 : 0) +
        (tileRight    ? 4 : 0) +
        (tileBottom ? 8 : 0);

//CONVERT BIT TO TILE ID (thread reader: this is here to make a understandable tile image instead of a mambo jambo on the png, the below image shows where to put things =])
switch(newTileId){
    case 0:  newTileId = 0; break;    case 1:  newTileId = 12; break;    case 2:  newTileId = 3; break;    case 3:  newTileId = 15; break;
    case 4:  newTileId = 1; break;    case 5:  newTileId = 13; break;    case 6:  newTileId = 2; break;    case 7:  newTileId = 14; break;
    case 8:  newTileId = 4; break;    case 9:  newTileId = 8;  break;    case 10: newTileId = 7; break;    case 11: newTileId = 11; break;
    case 12: newTileId = 5; break;    case 13: newTileId = 9;  break;    case 14: newTileId = 6; break;    case 15: newTileId = 10; break;
}

newTileId += tileOffset;

tilemap_set_at_pixel(global.tilemapMaskId, newTileId, wx, wy);
Here is the tiles:
1592660958882.png
1592661216949.png

That's the result:
1592661258302.png

I think I need to put a border, not a transparency behind the tiles, but that got even worst.

Maybe I am not understanding something fundamental. Coud anyone help me on that matter?

p.s.: I would love to do some animated water tiles too, but I have so much struggle as it is already... =/
 

Kezarus

Endless Game Maker
Hello, I tried to improve the texture to be a little bit better. But it's still... bad. =/

1592679126158.png

1592679206385.png

What do you guys think? Could anyone help me solve this issue?
 
You need to use different layers for the tiles. Make either the water or the grass the bottom layer and just fill the layer with the "center" piece (as in the one that is literally just full grass/water, no edges), and then put the others on top. The problem is that without a full layer beneath, you just have blank transparency around the edges of tiles and so the background colour of the room is showing through.
 

Psycho_666

Member
You need transitioning tiles there buddy.
For example your tile number 4 needs different variations depending on what other tiles it borders.

One trick you can do to avoid having all those transitional tiles is for example
When you use tile 10 from biom A also on the same place add tile 5 from biom B so they overlap and there are no empty spaces.
 

TheouAegis

Member
If you use transparencies, then you need to have an underlying set of tiles. If you do that, though, then you double up the number of tile draws in certain places. To limit it to one layer of tiles, you'd need to ensure each tile has proper borders pre-filled. So you either need to expand your tilesheet (and rework the code to accommodate), or double up (or even triple up) the number of draws.
 

Kezarus

Endless Game Maker
Thanks for the replies everyone. If I have to place another layer to do that, maybe I could use my algorithm that consists on a sole layer for borders. The tiles cells of that layer will be half of the normal and I could place all transitions without a problem.

I was resisting on using my method because I would like to get this bitmasking as streamlined as possible, but even whe it works it's slow... so... back to my method... ¯\_(ツ)_/¯

Thanks a ton guys. I will try to post a response here as soon as I changed the code. I am a bit annoyed to do that part from scratch, I really think that the engine should handle all that. But... here we are... ಠ_ಠ
 

Kezarus

Endless Game Maker
Well....... I tried.... It went from almost there to almost there... I think I will really have to make another layer...

This is the result...:
1592859115373.png
1592859124437.png
1592859132433.png

Hey @Nocturne, just out of curiosity, how did you make your autotiler? Does it use multiple layers or you could manage to make it in a single layer?

Thanks guys!
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
Hey @Nocturne, just out of curiosity, how did you make your autotiler? Does it use multiple layers or you could manage to make it in a single layer?
My autotiler is for 47 tiles, exactly the same as the one that GMS2 uses in the room editor... as for how it's done... It's really VERY basic (as I wrote it a LONG time ago!) but it gets the job done! It's basically a massive set of If/Else checks to get the right tile based on collisions with those around it. As for your issues, you DON'T need two layers, you just need a properly formatted tileset that includes the water and the land IN THE SAME TILE. Check out this article, which shows how to do exactly what you want I think: https://gamedevelopment.tutsplus.co...ng-to-auto-tile-your-level-layouts--cms-25673
 
I mean, I think to some extent there should be some consideration of the the "level of work" that needs to be done in the different solutions. Using a base layer that is all grass (or water, or whatever) and then plopping the tiles with transparency on top uses more processing power, but we all know that today processing power is cheap compared to developer hours. So depending on the level of work that is needed to modify all the tiles, it's definitely not something to dismiss out of hand.

I used the double layer effect in my procedural generation of the island for Floramancer and it made tile development a lot easier for the artist I was working with as they only had to create a single grass tile, and then only a single version of each other tileset (water, sand and mountain). If we had of gone down the route of using only a single layer, she would have had to make more sets of everything: water bordering on grass, water bordering on sand, water bordering on mountain, sand bordering on grass, sand bordering on mountain and finally mountain bordering on grass. This is instead of just a grass tile, water bordering on alpha, sand bordering on alpha and mountain bordering on alpha.
 

NightFrost

Member
Agreed on transparenies. While my current project uses simple A-to-B transition tiles as there's no complex biome changes, a few years back when I was considering the mechanics of procgen worlds, I concluded that transparent-to-biome tiles are the way to go. They produce the least amount of work on graphics when the number of biomes increases and can potentially connect to any other biome as no tile is locked to specific transition. The best way to pile them seemed to be in order of elevation, starting from oceans and progressing to mountains, so a transition always points upward in elevation. Least headache seemed to be to loop a pick biome at a time, instead of loop and do cells in order, but that was before GMS2 and easy to use layers...
 

TheouAegis

Member
An idea i had to compromise a little with the transparent tiles is after the autotiler is run, loop across the base level and remove all tiles on that layer with full tiles above. If you have three layers, do the same for the second layer. In GMS2, it may not be much of a gain, but in GMS1 it could be huge.
 

Kezarus

Endless Game Maker
Thanks everyone! Very kind of you to share your ideas. =]

Well, the kind of map I have is a changing one. The snow line grows and recedes with the passing of days. Maybe I really have to put a "Snow Layer" on top of everithing and called it solved once and for all. I will try this today after work and post the results and code here.

I hope you all have a wonderful day. =]
 

Kezarus

Endless Game Maker
Yep, layers worked like a charm! =]

I was resistent to use layers 'cause I am already using too much. Like one for topography (temporary), two for biome(lower and upper[trees, mountains]), two for bitmasking (normal and snow), one for humidity (temporary) and one for temperature (temporary). And I didn't even start with objects. ಡ_ಡ

Well, these are the results:

1592927801212.png

1592927832272.png

1592927854954.png
 

Kezarus

Endless Game Maker
This is my entire code. It's somewhat slow whenever I have to clean and calculate the snow line and I really don't know if there is a way around it. If you know how I could improve this code it would be great.

Thanks a ton guys! =D

GML:
    layerBiomeId = layer_create(1010);
    global.tilemapBiomeId = layer_tilemap_create(layerBiomeId, 0, 0, tileBiomes, room_width div global.cellSize, room_height div global.cellSize);
    tilemapBiomeId = global.tilemapBiomeId;

    layerMaskId = layer_create(1005);
    global.tilemapMaskId = layer_tilemap_create(layerMaskId, 0, 0, tileBiomeBitmask, room_width div (global.cellSize div 2), room_height div (global.cellSize div 2));
    tilemapMaskId = global.tilemapMaskId;
    
    layerSnowMaskId = layer_create(1004);
    global.tilemapSnowMaskId = layer_tilemap_create(layerSnowMaskId, 0, 0, tileBiomeBitmask, room_width div (global.cellSize div 2), room_height div (global.cellSize div 2));
    tilemapSnowMaskId = global.tilemapSnowMaskId;

GML:
enum BitmaskEnum{
    CornerTopLeft = 1,
    CornerTopRight = 2,
    SideLeft = 3,
    SideTop = 4,
    EdgeTopLeft = 5,
    EdgeTopRight = 6,
    
    CornerBottomLeft = 8,
    CornerBottomRight = 9,
    SideBottom = 10,
    SideRight = 11,
    EdgeBottomLeft = 12,
    EdgeBottomRight = 13
}


GML:
///@definition bitmask_cell
///@param wx - coord x
///@param wy - coord y

var wx = argument[0];
var wy = argument[1];

var cellSize = global.cellSize;
var halfCell = cellSize div 2;
var tilemapMaskId = global.tilemapMaskId;

var myTile = tilemap_get_at_pixel(global.tilemapBiomeId, wx, wy);

//ALLOWED TILES
var tileOffset;
switch( myTile ){
    case MapGenBiomesEnum.Ocean:
        tileOffset = 0;
        break;
    case MapGenBiomesEnum.Grass:
        tileOffset = 14;
        break;
    case MapGenBiomesEnum.Snow:
        tileOffset = 28;
        tilemapMaskId = global.tilemapSnowMaskId;
        break;
    default:
        return;
}

var tileTopLeft = tilemap_get_at_pixel(global.tilemapBiomeId, wx-1, wy-1);
var tileTop = tilemap_get_at_pixel(global.tilemapBiomeId, wx, wy-1);
var tileTopRight = tilemap_get_at_pixel(global.tilemapBiomeId, wx+cellSize, wy-1);
var tileBottomLeft = tilemap_get_at_pixel(global.tilemapBiomeId, wx-1, wy+cellSize);
var tileBottom = tilemap_get_at_pixel(global.tilemapBiomeId, wx, wy+cellSize);
var tileBottomRight = tilemap_get_at_pixel(global.tilemapBiomeId, wx+cellSize, wy+cellSize);
var tileLeft = tilemap_get_at_pixel(global.tilemapBiomeId, wx-1, wy);
var tileRight = tilemap_get_at_pixel(global.tilemapBiomeId, wx+cellSize, wy);


if(myTile == MapGenBiomesEnum.Grass){
    //CONSIDER GRASS AS NEIGHBORS OF SNOW
    if( tileTopLeft == MapGenBiomesEnum.Snow )        { tileTopLeft = myTile; }
    if( tileTop == MapGenBiomesEnum.Snow )            { tileTop = myTile; }
    if( tileTopRight == MapGenBiomesEnum.Snow )        { tileTopRight = myTile; }
    if( tileBottomLeft == MapGenBiomesEnum.Snow )    { tileBottomLeft = myTile; }
    if( tileBottom == MapGenBiomesEnum.Snow )        { tileBottom = myTile; }
    if( tileBottomRight == MapGenBiomesEnum.Snow )    { tileBottomRight = myTile; }
    if( tileLeft == MapGenBiomesEnum.Snow )            { tileLeft = myTile; }
    if( tileRight == MapGenBiomesEnum.Snow )        { tileRight = myTile; }
}


if( myTile != tileTopLeft && myTile != tileTop && myTile != tileLeft ){
    //CORNER TOP LEFT
    tilemap_set_at_pixel(tilemapMaskId, tileOffset+BitmaskEnum.CornerTopLeft, wx-1, wy-1);
}

if( myTile != tileTopRight && myTile != tileTop && myTile != tileRight ){
    //CORNER TOP RIGHT
    tilemap_set_at_pixel(tilemapMaskId, tileOffset+BitmaskEnum.CornerTopRight, wx+cellSize, wy-1);
}

if( myTile != tileBottomLeft && myTile != tileBottom && myTile != tileLeft ){
    //CORNER BOTTOM LEFT
    tilemap_set_at_pixel(tilemapMaskId, tileOffset+BitmaskEnum.CornerBottomLeft, wx-1, wy+cellSize);
}

if( myTile != tileBottomRight && myTile != tileBottom && myTile != tileRight ){
    //CORNER BOTTOM RIGHT
    tilemap_set_at_pixel(tilemapMaskId, tileOffset+BitmaskEnum.CornerBottomRight, wx+cellSize, wy+cellSize);
}

if( myTile != tileTop ){
    //TOP LEFT LEFT
    if(myTile != tileTopLeft){
        tilemap_set_at_pixel(tilemapMaskId, tileOffset+BitmaskEnum.SideTop, wx, wy-1);
    }else{
        tilemap_set_at_pixel(tilemapMaskId, tileOffset+BitmaskEnum.EdgeBottomLeft, wx, wy-1);
    }
    
    //TOP LEFT RIGHT
    if(myTile != tileTopRight){
        tilemap_set_at_pixel(tilemapMaskId, tileOffset+BitmaskEnum.SideTop, wx+halfCell, wy-1);
    }else{
        tilemap_set_at_pixel(tilemapMaskId, tileOffset+BitmaskEnum.EdgeBottomRight, wx+halfCell, wy-1);
    }
}

if( myTile != tileBottom ){
    //BOTTOM LEFT LEFT
    if(myTile != tileBottomLeft){
        tilemap_set_at_pixel(tilemapMaskId, tileOffset+BitmaskEnum.SideBottom, wx, wy+cellSize);
    }else{
        tilemap_set_at_pixel(tilemapMaskId, tileOffset+BitmaskEnum.EdgeTopLeft, wx, wy+cellSize);
    }
    
    //BOTTOM LEFT RIGHT
    if(myTile != tileBottomRight){
        tilemap_set_at_pixel(tilemapMaskId, tileOffset+BitmaskEnum.SideBottom, wx+halfCell, wy+cellSize);
    }else{
        tilemap_set_at_pixel(tilemapMaskId, tileOffset+BitmaskEnum.EdgeTopRight, wx+halfCell, wy+cellSize);
    }
}


if( myTile != tileLeft ){
    //SIDE LEFT TOP
    if(myTile != tileTopLeft){
        tilemap_set_at_pixel(tilemapMaskId, tileOffset+BitmaskEnum.SideLeft, wx-1, wy);
    }
    
    //SIDE LEFT BOTTOM
    if(myTile != tileBottomLeft){
        tilemap_set_at_pixel(tilemapMaskId, tileOffset+BitmaskEnum.SideLeft, wx-1, wy+halfCell);
    }
}

if( myTile != tileRight ){
    //SIDE RIGHT TOP
    if(myTile != tileTopRight){
        tilemap_set_at_pixel(tilemapMaskId, tileOffset+BitmaskEnum.SideRight, wx+cellSize, wy);
    }
    
    //SIDE RIGHT BOTTOM
    if(myTile != tileBottomRight){
        tilemap_set_at_pixel(tilemapMaskId, tileOffset+BitmaskEnum.SideRight, wx+cellSize, wy+halfCell);
    }
}

1592928435946.png
 
Top