Making levels by hand is hard. That’s why I’ love Tiled It’s a wondeful level editor with a powerfull terrain system )
To take advantage of the terrain system you need a tileset. This is a set of all possible combination of border tiles between two terrains.
Theres are some nice tilesets on open game art
There’s some theory about it:
What I want
I want to draw my levels to have biomes of 4 distinct terrains.
The transitions between these terrains must have some kind of bespoke tile that might have some specific gameplay effect like collision. In this case, I want use the Cliff tiles between the Grass and Snow terrain to be the “walls” of my levels. The Dune and Beach transition tiles should be decorative
I’ll loop the terrains around so that snow can also border water. This creates a forth transition type in case i want to make water navigable and need a world barrier
So each biomes will combine 4 tilesets. One for each combination of two terrains:
The Tileset
I like boristhebrave’s 6x8 Blob Tileset that I’m going to steal for this example
It consists of 42 tiles X 4 transitions = 168 unique tiles per biome.
I’m not drawing 168 tiles with unique tessellating patterns, are you out of your mind?
Why can’t I provide a set of patterns and generate a tile map automatically?
What we have:
I have my general template containing all 168 tiles chroma keyed for 8 patterns:
Terrains:
- RED
- GREEN
- BLUE
- YELLOW
Transitions:
- CYAN
- MAGENTA
- BLACK
- WHITE
This will be our tempalate
The patterns
So now we only have to make 8 tileable patterns that we can use to overlay on the tileset
Putting it all together
Okay so let’s start putting stuff together. I’m not going to click it together, so ill be using ImageMagick . I cannot undestate how powerful this titan of OSS is and implore you to sponsor them in any way you can.
We have a pipeline of image magick that has concrete outputs so we will be using make . No it’s not just for C compilations. It’s an amazing tool that let’s me declare outputs and dependencies and runs on anything.
Step 1 Making the template
We have to take the tileset layout and turn in into a color coded template for all 4 terrain types and corresponding transitions. This will be the master template used to overlay our patterns on
We will start out with a meta_template.png I made in paint.net real quick.
This is based on the 6x8 tileset template and is intended for 64x64 pixel tiles. Its a 512x384 meta template representing all tiles for one terrain.
I’m using three beautiful pastels to represent different sections:
- #ffff88 represents the floor area, the pattern that you’ll draw this terrain type with
- #ff88ff represents the transition area between the floor and the border
- #88ffff represents the border area that is the terrain type that is directly connected to the floor
The terrain templates
We want to generate a nice terrain template for all 4 terrains and transitions in one template.
4 terrains, means 4 patterns + 4 transition patterns. That means we need 8 unique color codes for our 8 terrains. I used the funky pastels earlier so that I can reserve the primary colors for the actual template:
- RED FF0000
- GREEN 00FF00
- BLUE 0000FF
- YELLOW FFFF00
- CYAN 00FFFF
- MAGENTA FF00FF
- BLACK 000000
- WHITE FFFFFF
Let’s start with a template for the first terrain. That floor should be red, the borders should be green, and we will make the transition cyan
that be the first part of our makefile
template1: meta_template.png
convert meta_template.png \
-fill "#00ff00" -opaque "#88ffff" \ # BORDER
-fill "#ff0000" -opaque "#ffff88" \ # FLOOR
-fill "#00ffff" -opaque "#ff88ff" \ # TRANSITION
template1.png
The result:
Now for the other 3 and combine them together
template1: meta_template.png
convert meta_template.png \
-fill "#00ff00" -opaque "#88ffff" \ # BORDER
-fill "#ff0000" -opaque "#ffff88" \ # FLOOR
-fill "#00ffff" -opaque "#ff88ff" \ # TRANSITION
template1.png
template2: meta_template.png
convert meta_template.png \
-fill "#0000ff" -opaque "#88ffff" \
-fill "#00ff00" -opaque "#ffff88" \
-fill "#ff00ff" -opaque "#ff88ff" \
template2.png
template3: meta_template.png
convert meta_template.png \
-fill "#ffff00" -opaque "#88ffff" \
-fill "#0000ff" -opaque "#ffff88" \
-fill "#000000" -opaque "#ff88ff" \
template3.png
template4:
convert meta_template.png \
-fill "#ff0000" -opaque "#88ffff" \
-fill "#ffff00" -opaque "#ffff88" \
-fill "#ffffff" -opaque "#ff88ff" \
template4.png
template: template1 template2 template3 template4
montage template1.png template2.png template3.png template4.png \
-tile 2x2 -geometry +0+0 template.png
rm template1.png template2.png template3.png template4.png
That is super wordy and very repetative, i just kinda want to declare the purpose for each color coding and go from there. Let’s pull each of the sets into variables.
border_template1 := 00ff00
floor_template1 := ff0000
transition_template1 := 00ffff
border_template2 := 0000ff
floor_template2 := 00ff00
transition_template2 := ff00ff
border_template3 := ffff00
floor_template3 := 0000ff
transition_template3 := 000000
border_template4 := ff0000
floor_template4 := ffff00
transition_template4 := ffffff
template%: input/meta_template.png
convert input/meta_template.png \
-fill "#$(border_template$*)" -opaque "#88ffff" \
-fill "#$(floor_template$*)" -opaque "#ffff88" \
-fill "#$(transition_template$*)" -opaque "#ff88ff" \
template$*.png
template: template1 template2 template3 template4
montage template1.png template2.png template3.png template4.png \
-tile 2x2 -geometry +0+0 template.png
rm template1.png template2.png template3.png template4.png
```makefile
That's a bit better and now if I run
``` bash
$ make template
The results are epileptically great!

Step 2 Creating the patterns
Now its time to make 8 tiles. One for each of the necessary patterns that we will overlay.
BAM!
Not great but, they will do for the purposes of demonstration.
Step 3: Scale the patterns
Now we should scale one of these tiles to the size of our template so we can overlay it over our template.
convert -size 1024x768 tile:grassTile.png grassOverlay.png
Now we can make our makefile a bit fancier, I don’t want to copy the same target five times ill make it dynamic
%-overlay:
convert -size 1024x768 tile:$*.png $*-overlay.png
now if we go
$ make grass-overlay
make will create grass-overlay.png
and then we create another target that depends on a bunch of the previous ones
# Our original target
%-overlay:
convert -size 1024x768 tile:$*.png $*-overlay.png
# New target
overlays: grass-overlay beach-overlay cliff-overlay dirt-overlay dune-overlay glacier-overlay water-overlay snow-overlay
now if we go
$ make overlays
We generate overlays for all our pattern tiles.
But that’s a bit silly lets declare the overlays as a nice list
OVERLAYS := grass-overlay \
beach-overlay \
cliff-overlay \
dirt-overlay \
dune-overlay \
glacier-overlay \
water-overlay \
snow-overlay
%-overlay:
convert -size 1024x768 tile:$*.png $*-overlay.png
overlays: $(OVERLAYS)
Better, but still very wordy and i feel if we can make it simpler we can reuse it later. Let’s try to construct the name from just the pattern names.
TILES := grass beach cliff dirt dune glacier water snow
OVERLAYS := $(TILES:%=%-overlay) # Appends -overlay to every item in the TILES list
%-overlay:
convert -size 1024x768 tile:$*.png $*-overlay.png
overlays: $(OVERLAYS)
A sweet salve for the sight.
Step 4: Create the cutouts
What we want to do is take the template, extract specific colored sections and create cutouts from the pattern overlays. We will do that in two stages
First we create a green section from the template
convert template.png -alpha set +transparent "#00ff00" green-section.png
again, i don’t want to do this 8 times, so i want this target to be dynamic, however, the target requires some specific color information to know what pixels to target. I have to map the colors somehow so i will use variables
#all the colors I need
COLOR_red := ff00000
COLOR_green := 00ff00
COLOR_blue := 0000ff
COLOR_yellow := ffff00
COLOR_cyan := 00ffff
COLOR_magenta := ff00ff
COLOR_black := 000000
COLOR_white := ffffff
# A mapping function to map color string to the value of the mapped color
color_hex = $(COLOR_$(1))
%-section: template.png
# Here we set the color code
$(eval HEX := $(call color_hex,$*))
# And we extract the section by name
convert template.png -alpha set +transparent "#$(HEX)" $*-section.png
Now we can go
make green-section
or
make red-section
to get color-coded sections
Neat!
In stage 2 we will combine the section with the pattern overlays from step 3 to create a cutout
the problem is that our patterns are called grass, dirt, water etc. And our sections are color coded: red, green, blue etc. We could rename all our patterns according to their target colors, but im afraid that will become confusing, so we need to create another mapping of colors to terrain names.
#... The other targets are still up here
# %-section: template.png
#...
# %-overlay: %.png
#...
TERRAIN_red := grass
TERRAIN_green := dirt
TERRAIN_blue := water
TERRAIN_yellow := snow
TERRAIN_magenta := dune
TERRAIN_cyan := beach
TERRAIN_white := glacier
TERRAIN_black := cliff
terrain_name = $(TERRAIN_$(1))
%-cutout: %-section # For the cutout we need the section and the overlay
# Evaluate the terrain name
$(eval TERRAIN_NAME := $(call terrain_name,$*))
# We can't dynamiclally set dependencies easily
# so we will run make as part of this target to create the overlays
$(MAKE) $(TERRAIN_NAME)-overlay
# Then we get the overlay file and make the cutout
convert $(TERRAIN_NAME)-overlay.png $*-section.png -compose copyopacity -composite $*-cutout.png6
rm $*-section.png
Just running
make green-cutout
Will get us results!
Let’s do this for all our colors, like we batched our overlays earlier
# ...
COLORS := red green blue yellow magenta cyan white black
CUTOUTS := $(COLORS:%=%-cutout)
cutouts: $(CUTOUTS)
Then we run
make cutouts
to get all the necessary cutouts. Perfection
For the cherry on top, let’s merge all the cutouts into one final tileset
#...
CUTOUT_IMAGES := $(CUTOUTS:%=%.png)
tileset: cutouts
convert $(CUTOUT_IMAGES) -background none -layers merge tileset.png
The time of truth:
make tileset.png
I need a tissue.
Step 5 Cleanup our makefile
if we combine all our scripts into one makefile and clean it up a bit we improve it with the following:
- split it up with comments
- drag all the variable declarations to the top
- added cleanup targets for each stage
- let all the input files live in an input folder and let the final output live in the ouput folder
#all the colors I need
COLOR_red := ff0000
COLOR_green := 00ff00
COLOR_blue := 0000ff
COLOR_yellow := ffff00
COLOR_cyan := 00ffff
COLOR_magenta := ff00ff
COLOR_black := 000000
COLOR_white := ffffff
TERRAIN_red := grass
TERRAIN_green := dirt
TERRAIN_blue := water
TERRAIN_yellow := snow
TERRAIN_magenta := dune
TERRAIN_cyan := beach
TERRAIN_white := glacier
TERRAIN_black := cliff
terrain_name = $(TERRAIN_$(1))
TILES := grass beach cliff dirt dune glacier water snow
COLORS := red green blue yellow magenta cyan white black
color_hex = $(COLOR_$(1))
CUTOUTS := $(COLORS:%=%-cutout)
CUTOUT_IMAGES := $(CUTOUTS:%=%.png)
border_template1 := 00ff00
floor_template1 := ff0000
transition_template1 := 00ffff
border_template2 := 0000ff
floor_template2 := 00ff00
transition_template2 := ff00ff
border_template3 := ffff00
floor_template3 := 0000ff
transition_template3 := 000000
border_template4 := ff0000
floor_template4 := ffff00
transition_template4 := ffffff
# STAGE 1 TEMPLATE GENERATION
template%: input/meta_template.png
convert input/meta_template.png \
-fill "#$(border_template$*)" -opaque "#88ffff" \
-fill "#$(floor_template$*)" -opaque "#ffff88" \
-fill "#$(transition_template$*)" -opaque "#ff88ff" \
template$*.png
template: template1 template2 template3 template4
montage template1.png template2.png template3.png template4.png \
-tile 2x2 -geometry +0+0 template.png
# STAGE 2 CUT SECTIONS
%-section: template
# Here we set the color code
$(eval HEX := $(call color_hex,$*))
# And we extract the section by name
convert template.png -alpha set +transparent "#$(HEX)" $*-section.png
# STAGE 3 SCALE OVERLAYS
%-overlay: input/%.png
$(eval TERRAIN_NAME := $(call terrain_name,$*))
convert -size 1024x768 tile:input/$*.png $*-overlay.png
# STAGE 4 CREATE CUTOUTS
%-cutout: %-section # For the cutout we need the section and the overlay
# Evaluate the terrain name
$(eval TERRAIN_NAME := $(call terrain_name,$*))
# We can't dynamiclally set dependencies easily
# so we will run make as part of this target to create the overlays
$(MAKE) $(TERRAIN_NAME)-overlay
# Then we get the overlay file and make the cutout
convert $(TERRAIN_NAME)-overlay.png $*-section.png -compose copyopacity -composite $*-cutout.png
rm $*-section.png
cutouts: $(CUTOUTS)
# STAGE 5 MERGE CUTOUTS INTO FINAL TILESET
tileset: cutouts
mkdir -p output
convert $(CUTOUT_IMAGES) -background none -layers merge output/tileset.png
$(MAKE) clean
clean:
rm template1.png template2.png template3.png template4.png
rm template.png
rm *-section.png
rm *-overlay.png
rm *-cutout.png
echo "cleaned🧼"
Here’s a visualisation of our pipeline:

I have to deliver a meta template and the 8 tiles that capture our patterns and the one makefile automatically generates the final tileset and every artifact in between. Why that’s important is that we want to be able to modify some of the in-between steps manually and continually automate the process.
Step 6 Tile Editor!
Now it’s time to play with our final tileset
I made a little tileset template that fits this pattern. It points to tileset.png and has the correct tile size and canvas size to fit our tileset.
If we put it in the same directory as our output we can open Tiled and create a new tilemap and bingo:
Next steps
So this was all kind of crude. Having a single makefile is nice, but the bit set of variables makes it cumbersome and I’m not a fan of make’s import system. Furthermore, this system is kind of tied to hard-coded tileset layouts with hard tile sizes. These should really be configurable. There’s one more thing that needs to happen but for now:
- We want to use yq and a config file to be able to configure all the variables like colors, relationships, fine names and sizes’
- File sizes should be calculated based on inputs. We should detect the size of input files to determine the correct outputs
- Right now we are hardcoded to 4 terrains and 4 transitions, but we should calculate all possible combinations and generate transitions between them
- We should be able to support an arbitrary number of terrain types
- We want to include some tile randomness with multiple versions of the same tiles
- We need to generate the tileset template not have that hardcoded
Lastly we need to support dynamic elevated terrains…
