Marching Squares is an algorithm that allows us to determine which tile to place in order to get the best looking terrain, something similar to the image on the left.
For a demo of Marching Squares, view the GitHub page.
You can download the project files from the video tutorial here.
Information before Implementation
We begin by creating a TileMap. This tilemap contains columns and rows, which we are going to use to place tiles on. We want to generate random grid map data containing values of either 1 or 0.
Each tile will use the 4 points around it to determine what kind of tile we are going to place inside. This means we will have to generate columns+1 points horizontally, and rows+1 points vertically, in order to get the last points for the number of columns and rows you are going to use. For the sake of this demonstration, let us use 10 columns and 10 rows. This means we need to generate 11 points in both directions in the grid map.
After generating the grid map values, we get something similar to this. (Note that the images are for demonstration, in order to understand better how this works)
Black dots represent points with a value of 1, and white dots a value of 0. We will then have to iterate through each column and row and add together the values to get our final tile value, that determines which tile to place.
The four points around a tile can reach up to a total value of 15. This is determined by adding together the grid map values on each of the corner points.
We start by checking the first point. If it has a value of 1, represented as a black dot, we add the value of 1.
We then move on to the second dot, and add the value of 2, which gives us a total value of 3 so far.
Then we move on to the third dot, that has a value of 4, add it to the rest and lastly we take the fourth dot with a value of 8 and add it together with the rest.
This gives us a total value of 15.
We would then place the tile from the tilesheet that has the value 15.
This tile sheet image below contains a total of 16 tiles which covers all the possible tile shapes, each valuing from 0 to 15.
If the four corner points were a bit different such as this;
We would then get a total value of 6, and place the tile with value 6. The trick to calculating this is by multiplying the corner values with the value they represent. In this case the first dot's point value is multiplied with 1. The second dot's point value would be multiplied with 2, then the third with 4 and the fourth with 8. Which gives us a formula like this;
tile_to_place = point1 + point22 + point34 + point4*8.
Implementing Marching Squares
Note that you need to create and "convert to..." TileSet from these tiles and add them to a TileMap in your scene.
You can use this image or make your own. I recommend you just download this one and try it out first. If you are unsure how to create a TileMap, feel free to download and try the full marching squares demo from my GitHub page, or view my video tutorial. We will need to generate grid map data that generate random values of 0 or 1.
var grid_map = []
var columns = 10
var rows = 10
func generate_grid_map():
for x in range(0, columns + 1):
grid_map.append([])
for y in range(0, rows + 1):
randomize()
var rand_value = int(rand_range(0, 2))
grid_map[x].append(rand_value)
Note the use of randomize(). This is important, as without it you will get the same generated values each time you run it.
After we have generated the grid map data, we begin and end it all by drawing the tiles.
func draw_tiles():
for x in range(0, columns):
for y in range(0, rows):
var dot_1 = grid_map[x][y]
var dot_2 = grid_map[x+1][y]
var dot_3 = grid_map[x+1][y+1]
var dot_4 = grid_map[x][y+1]
var value = dot_1 + (dot_2*2) + (dot_3*4) + (dot_4*8)
get_node("tile_map").set_cellv(Vector2(x,y), value)
You don't need to place tile 0 (zero). Tile 0 could (and probably should) be ignored, or preferably set as -1. You can do that by checking if the total value is equal to 0 and then set the value to -1. Setting a tilemap cell value to -1 will clear whatever is on the same TileMap.
We are done!
The end result should look something like this.
If you have any questions or comments about it, please don't hesitate to let me know!