
In our last post we created a makefile that can generate a full tileset based on just a few tiled patterns. However, we want to include some improvements.
The first improvement we want is to be able to create elevated tilesets. To do this, we will need more information baked into our meta-template. We added 4 additional colors:
- South Wall: 8888ff
- East Wall: 88ff88
- West Wall: ff8888
Raised Template
This let’s us create a new tile set for a raised template. Here we draw an elevated floor, where the borders draw walls pointing outwards from the tile:

The creamy yellow part is the floor that we will draw with, the blue part is our border. We will keep the “transition” area ff88ff and convert it conditionally. For FLAT terrains (no elevation) all the Wall areas will be converted to the transition color for the ELEVATED terrains (terrains with walls), we will keep the walls distinct and convert the transition area to the floor color ffff88.
Sunken Template
But we will also need a sunken template. This template is for when we are drawing in the recessed area. In these areas, the borders are pointing inwards towards the tile.
Conditional formatting
Both of these have some extra information in the form of the $ff88ff area that can be recolored conditionally to either the floor or the border texture to flatten it
>Note: The bottom transform is labled elevated, but the result is actually a sunken template. Sorry.
Multiplex Template
Ideally we can combine our raised and sunken template into a master template that can be converted in either a raised, sunken or flat terrain.
if we multiply the two templates we get an interesting pattern
> Creating the two patterns by hand made sure they are misaligned and that leaves a ton of cleanup work. But the idea of creating all possible combinations of zones this ways is still clear.
Lets zoom in on one of the sections and compare them
If we multiply them and clean up the results we get 11 unique colors
>Note: I Cleaned up the colors for clarity the idea is that we create distinct colors by multiplying the values, but the results were hard to read easily, so I recolored them for clarity.
These will be our new zones let’s give them a nice new palette to make them pop out a bit more.
Note: in the image above, I numbered them, and the table below is zero-indexed. I’m too lazy to retrace my previous steps, so just do the math yourself.
| Index | Zone | Color | Color Names |
|---|---|---|---|
| 0 | Floor | #cccc00 | Mustard |
| 1 | Shared Transition | #888800 | Olive |
| 2 | Border | #444400 | Khaki |
| 3 | Sunken West Wall | #00cc00 | Lime |
| 4 | Shared West Wall | #008800 | Forest |
| 5 | Raised West Wall | #004400 | Pine |
| 6 | Sunken East Wall | #0000cc | Navy |
| 7 | Shared East Wall | #000088 | Ocean |
| 8 | Raised East Wal | #000044 | Midnight |
| 9 | Sunken North Wall | #440044 | Violet |
| 10 | Raised South Wall | #880088 | Imperial |
multiplex_template.png:

Wow, that looks gross. This palette doesn’t pop, it looks digested, but it does create reasonably separable color zones, so we will keep it.
Starting over
Okay the previous makefile was getting a bit too convoluted, so we will start fresh. We will remake our make file to create each terrain seperately and combine them all together. To deal with the intricate steps we will keep track of the relationships between our colorcoded zones, the textures and our required terrains in a seperate config file.
Configuration
Dealing with all the relationships between zones, colors and textures is becoming a real pain with all these extra possible combinatorics introduced by height. We need to keep track of our variables in a better way, seperately from our makefile. Which in all honesty is really bad at managing variables in a legible way.
Let’s start with a yaml to declare all our dimensions and colors
#summer.yaml
title: "summer"
dimensions:
sprite_width: 64
sprite_height: 64
horizontal_tiles: 20
vertical_tiles: 15
template_width: 512
template_height: 384
colors:
# Primary Colors
red: "#ff0000"
green: "#00ff00"
blue: "#0000ff"
yellow: "#ffff00"
cyan: "#00ffff"
magenta: "#ff00ff"
black: "#000000"
white: "#fffff
# Zone Colors
mustard: "#cccc00"
olive: "#888800"
khaki: "#444400"
lime: "#00cc00"
forest: "#008800"
pine: "#004400"
navy: "#0000cc"
ocean: "#000088"
midnight: "#000044"
violet: "#440044"
imperial: "#880088"
Now the reason we wanted to use yaml is to make use of it’s super useful anchor feature
This means we can give all these colors names like this:
# summer.yaml
# ...
# dimensions
# ...
colors:
# Primary Colors
red: &red "#ff0000"
green: &green "#00ff00"
blue: &blue "#0000ff"
yellow: &yellow "#ffff00"
cyan: &cyan "#00ffff"
magenta: &magenta "#ff00ff"
black: &black "#000000"
white: &white "#ffffff"
# Zone Colors
mustard: &mustard "#cccc00"
olive: &olive "#888800"
khaki: &khaki "#444400"
lime: &lime "#00cc00"
forest: &forest "#008800"
pine: &pine "#004400"
navy: &navy "#0000cc"
ocean: &ocean "#000088"
midnight: &midnight "#000044"
violet: &violet "#440044"
imperial: &imperial "#880088"
And reuse them further down the line. For example let’s declare our template zones as described above
# summer.yaml
# ...
# dimensions, colors
# ...
zones:
floor: *mustard
shared_transition: *olive
border: *khaki
sunken_west_wall: *lime
shared_west_wall: *forest
raised_west_wall: *pine
sunken_east_wall: *navy
shared_east_wall: *ocean
raised_east_wal: *midnight
sunken_north_wall: *violet
raised_south_wall: *imperial
Now in our config our zones will carry the color value assigned to them by the human-readable anchors. This makes it a lot easier to spot errors and overwrite values.
Let’s declare our different textures and map them to some primary colors. We can make them into a little object array.
# summer.yaml
# ...
# dimensions, colorsm, zones
# ...
textures:
snow: &snow
color: *white
file: "snow.png"
cliff: &cliff
color: *black
file: "cliff.png"
grass: &grass
color: *green
file: "grass.png"
dune: &dune
color: *red
file: "dune.png"
dirt: &dirt
color: *yellow
file: "dirt.png"
sand: &sand
color: *cyan
file: "beach.png"
water: &water
color: *blue
file: "water.png"
glacier: &glacier
color: *magenta
file: "glacier.png"
I don’t like yaml’s whitespace restrictions and these long objects are pretty clunky. I prefer toml’s optional horizontality. But you can’t argue with the power of anchors and not having to repeat yourself. Here I’m poiting to the human readable colors and im also anchoring the entire individual texture objects for further use later.
Now we can declare our terrains. We define a terrain as a combination of two three textures: 2 main textures and a transition. So a terrain from now on will refer to a single instance of our template. We also add some height information that we will use later.
```yaml # summer.yaml # … # dimensions, colorsm, zones, textures # …
terrains: mountain: floor: *snow transition: *cliff border: *grass type: “elevated” meadow: floor: *grass transition: *dune border: *dirt type: “flat” beach: floor: *dirt transition: *sand border: *water type: “flat” sea: floor: *water transition: *glacier border: *snow type: “sunken”
How nice is that?! We can just reuse all the anchors we set up earlier and create a little yaml database of all the relationships between our textures. Neat!
## Querying our yaml
Now that we have a little yaml database of our biome, we are going to query it using [yq](https://github.com/mikefarah/yq) . YQ is a wonderful tool with a great command line that let's us query, update and transform not only yaml but all kinds of config languages. And it runs on everything!
If we run
```bash
$ yq .colors.red summer.yaml
we get back:
#ff0000
And as promised an anchorred reference like:
$ yq -ot .terrains.meadow.transition.color summer.yaml
note to resolve the anchors we need yq to output to json or in this case tsv
And we get red!
#ff0000
Makefile
Let’s make a makefile that uses yq as a config and makes color coded templates for our terrains
# set the template file as a variable
template_file = input/multiplex_template.png
#Here we create a little function that wraps
the yq command to extract values from the yaml config
conf = $(shell yq -ot '.$(1)' input/summer.yaml)
# STAGE 1 TERRAIN TEMPLATE GENERATION
# Here we can encode the mapping between the color coding of the different terrain textures and the template colors
# the mapping below generates a FLAT terrain
%-flat-template: $(template_file)
convert $(template_file) \
-fill "$(call conf,terrains.$*.border.color)" -opaque "$(call conf,zones.border)" \
-fill "$(call conf,terrains.$*.floor.color)" -opaque "$(call conf,zones.floor)" \
# all the transition zones will be set to the same transition color, so this will be a flat terrain.
-fill "$(call conf,terrains.$*.transition.color)" -opaque "$(call conf,zones.shared_transition)" \
-fill "$(call conf,terrains.$*.transition.color)" -opaque "$(call conf,zones.sunken_west_wall)" \
-fill "$(call conf,terrains.$*.transition.color)" -opaque "$(call conf,zones.shared_west_wall)" \
-fill "$(call conf,terrains.$*.transition.color)" -opaque "$(call conf,zones.raised_west_wall)" \
-fill "$(call conf,terrains.$*.transition.color)" -opaque "$(call conf,zones.sunken_east_wall)" \
-fill "$(call conf,terrains.$*.transition.color)" -opaque "$(call conf,zones.shared_east_wall)" \
-fill "$(call conf,terrains.$*.transition.color)" -opaque "$(call conf,zones.raised_east_wal)" \
-fill "$(call conf,terrains.$*.transition.color)" -opaque "$(call conf,zones.sunken_north_wall)" \
-fill "$(call conf,terrains.$*.transition.color)" -opaque "$(call conf,zones.raised_south_wall)" \
$*_template.png
This part is a bit opaque and hard to fit into 90 character lines, so i kept it big for clarity. Copy and paste it yourself in a text editor to see what it does.
Now we can go
$ make meadow-flat-template
And we get:
Gorgeous flat terrain
Raised terrain
our yaml contains information on whether the terrain we want is raised, sunken or flat. Let’s create mappings for raised and sunken terrains. For this one we want to map the SUNKEN zones to merge witht the floor pattern
# %-flat-template: $(template_file)
# ...
# For RAISED terrain we merge the sunken zones to the floor color
%-raised-template: $(template_file)
convert $(template_file) \
-fill "$(call conf,terrains.$*.border.color)" -opaque "$(call conf,zones.border)" \
-fill "$(call conf,terrains.$*.floor.color)" -opaque "$(call conf,zones.floor)" \
-fill "$(call conf,terrains.$*.floor.color)" -opaque "$(call conf,zones.shared_transition)" \
-fill "$(call conf,terrains.$*.floor.color)" -opaque "$(call conf,zones.sunken_west_wall)" \
-fill "$(call conf,terrains.$*.transition.color)" -opaque "$(call conf,zones.shared_west_wall)" \
-fill "$(call conf,terrains.$*.transition.color)" -opaque "$(call conf,zones.raised_west_wall)" \
-fill "$(call conf,terrains.$*.floor.color)" -opaque "$(call conf,zones.sunken_east_wall)" \
-fill "$(call conf,terrains.$*.transition.color)" -opaque "$(call conf,zones.shared_east_wall)" \
-fill "$(call conf,terrains.$*.transition.color)" -opaque "$(call conf,zones.raised_east_wal)" \
-fill "$(call conf,terrains.$*.transition.color)" -opaque "$(call conf,zones.sunken_north_wall)" \
-fill "$(call conf,terrains.$*.floor.color)" -opaque "$(call conf,zones.raised_south_wall)" \
$*-template.png
With fantastic results

Sunken terrain
For the sunken terrain we do the inverse. The raised zones merge with the floor and the sunken zones get the transition code.
# %-flat-template: $(template_file)
# %-raised-template: $(template_file)
# ...
# For sunken terrain we merge the raised zones to the floor color
%-sunken-template: $(template_file)
convert $(template_file) \
-fill "$(call conf,terrains.$*.border.color)" -opaque "$(call conf,zones.border)" \
-fill "$(call conf,terrains.$*.floor.color)" -opaque "$(call conf,zones.floor)" \
-fill "$(call conf,terrains.$*.floor.color)" -opaque "$(call conf,zones.shared_transition)" \
-fill "$(call conf,terrains.$*.transition.color)" -opaque "$(call conf,zones.sunken_west_wall)" \
-fill "$(call conf,terrains.$*.transition.color)" -opaque "$(call conf,zones.shared_west_wall)" \
-fill "$(call conf,terrains.$*.floor.color)" -opaque "$(call conf,zones.raised_west_wall)" \
-fill "$(call conf,terrains.$*.transition.color)" -opaque "$(call conf,zones.sunken_east_wall)" \
-fill "$(call conf,terrains.$*.transition.color)" -opaque "$(call conf,zones.shared_east_wall)" \
-fill "$(call conf,terrains.$*.floor.color)" -opaque "$(call conf,zones.raised_east_wal)" \
-fill "$(call conf,terrains.$*.floor.color)" -opaque "$(call conf,zones.sunken_north_wall)" \
-fill "$(call conf,terrains.$*.transition.color)" -opaque "$(call conf,zones.raised_south_wall)" \
$*-template.png
So now if we go:
make sea-sunken-template
And we have again, great results!

I realise theres some chipped corners in the multiplex template that I can fix. but this looks great already!
Let’s combine the three. Let the makefile figure out what type of template it should be based on the yaml config.
%-template:
# A guard that checks whether the requested terrain actually exists
[ $(call conf,terrains.$*.type) != "null" ] || { echo exit 1; }; \
# Call the correct build target
$(MAKE) "$*-$(call conf,terrains.$*.type)-template"
Then we can just go
$ make mountain-template
And it will figure out what to do <3
Overlays
We can just order templates for any terrain, but each terrain contains 3 textures we need to overlay. The texture relationships are all encoded in the yaml config, so we just need to hook them up together
I need a query that i can plug in our terrain and section (floor, border or transition) and it will return the correct texture
we could have jsut simply go
yq -oy '.terrains.sea.floor' input/summer.yaml | cut -c 2-
To return the anchor value and strip the leading ‘*’ but that would mean that the anchor names and the actual keys of the objects have to be aligned and that’s not something I want to worry about
So now we have to make a tricky join between two objects. To show case the power of yq I made this query:
bash yq -ot '.terrains as $t | .textures | to_entries[] | select(.value.file == $t.sea.floor.file).key' input/summer.yaml
This takes the sea.floor terrain and returns the key associated with that texture and we can ignore possible data inconsistency issues with our yaml.
Let’s wrap these up in a nice makefile function we can call at will
TEXTURE_PROPERTY = $(shell yq -ot '.terrains as $$t | .textures | to_entries[] | select(.value.file == $$t.$(1).$(2).file).$(3)' $(config_file))
This takes three parameters:
- the terrain
- the section (floor, border or transition)
- the property of the target texture we want >The nature of yq queries makes it so that for the third parameter I need to call ‘key’ to get the name of the texture while the actual properties of the texture object are found under ‘value.color’ ‘value.file’
Now let’s use it to create the overlays
%-overlays:
convert -size $(width)x$(height) tile:input/textures/$(call TEXTURE_PROPERTY,$*,border,value.file) $(call TEXTURE_PROPERTY,$*,floor,key)-overlay
convert -size $(width)x$(height) tile:input/textures/$(call TEXTURE_PROPERTY,$*,border,value.file) $(call TEXTURE_PROPERTY,$*,border,key)-overlay.png
convert -size $(width)x$(height) tile:input/textures/$(call TEXTURE_PROPERTY,$*,border,value.file) $(call TEXTTEXTURE_PROPERTY,$*,transition,key)-overlay.png
so this one takes the target terrain then uses the function above to get the properties and scales the required overlays
Cutouts
Now it’s time to create the cutouts. First we isolate the floor section from the template
%-floor-section: %-template
convert $*-template.png -alpha set +transparent "$(call TEXTURE_PROPERTY,$*,floor,value.color)" $*-floor-section.png
So,
$ make sea-floor-section
get’s us
and we apply the associated overlay to complete the cutout
%-floor-cutout: %-floor-section %-overlays
convert $(call TEXTURE_PROPERTY,$*,floor,key)-overlay.png $*-floor-section.png -compose copyopacity -composite $*-floor-cutout.png
And,
$ make sea-floor-cutout
get’s us
With a little bit of duplication can get recipes for the border and transitions
# SECTIONS
%-floor-section: %-template
convert $*-template.png -alpha set +transparent "$(call TEXTURE_PROPERTY,$*,floor,value.color)" $*-floor-section.png
%-border-section: %-template
convert $*-template.png -alpha set +transparent "$(call TEXTURE_PROPERTY,$*,border,value.color)" $*-border-section.png
%-transition-section: %-template
convert $*-template.png -alpha set +transparent "$(call TEXTURE_PROPERTY,$*,transition,value.color)" $*-transition-section.png
# CUTOUTS
%-floor-cutout: %-floor-section %-overlays
convert $(call TEXTURE_PROPERTY,$*,floor,key)-overlay.png $*-floor-section.png -compose copyopacity -composite $*-floor-cutout.png
%-border-cutout: %-border-section %-overlays
convert $(call TEXTURE_PROPERTY,$*,border,key)-overlay.png $*-border-section.png -compose copyopacity -composite $*-border-cutout.png
%-transition-cutout: %-transition-section %-overlays
convert $(call TEXTURE_PROPERTY,$*,transition,key)-overlay.png $*-transition-section.png -compose copyopacity -composite $*-transition-cutout.png
Putting it all together
The last thing we need to do is put all the cutouts together
%-tileset: %-border-cutout %-floor-cutout %-transition-cutout
convert $*-border-cutout.png \
$*-floor-cutout.png -compose over -composite\
$*-transition-cutout.png -compose over -composite \
$*-tileset.png
And with zero intervention
$ make sea-tileset
All we have to do now is loop through all the available terrains an stich the tilesets together
# Loop through all the terrain keys we can find in the config
TILESETS := $(foreach terrain,$(call conf,terrains|keys),$(terrain)-tileset.png)
create tilesets for them and stich them together
tilesets: $(TILESETS)
mkdir -p output
montage $(TILESETS) -tile 2x2 -geometry +0+0 output/tilemap.png
$(MAKE) clean
So the original textures are 64x64 pixels and the tilemaps are arrange as 6x8 tiles of that size. This is so that i can make use of Tiled’s mixed terrain set mapping .
I discovered that using the results as 12x16 tilemaps of size 32x32 and treating them as corner terrain set, yeilds much better results, so I created an alternative template with a 2x2 mapping for corner sets.
Once hooked up, we are able to draw our elevated levels!
# Conclusion
Wow we overhauled the whole thing! Our final makefile is funnily enough still 100 lines, even though we added another 100 line yaml configuration file.
We are however, now capable of generating tile sets for different heights! In addition to that declaring the contents of the tilesets in a yaml file without having to touch the makefile makes us so much more powerfull. We can mix and match textures to create all kind of tilesets!
Next Steps
We can now create tilesets from yaml at will, but there is still more to be done.
- We want to create shadow maps to enhance the 3D feel of the maps
- We want to detect the dimensions of the tilesets and templates dynamically instead of having to encode them by hand
- We want to scale our textures to the correct tile size (in this case 32x32)
- We want to file off the sharp edges from our new meta-template and make our elevations even more pronounced
- We want to generate the Tiled tileset xml dynamically instead of relying on the dummy.

