Tilesets and Makefiles Part 4: Less is more

This post is part of the Tilesets and Makefiles series.

Dec 18, 2025

Previously we added some shading to our height maps. But in order to give our sets more character, we want to able to adjust the feel of the template more easily. That means fewer tiles to edit for a revision.

MiniTemplate.gif

Our template was set up nice for variation. Nice and big with a ton of duplication for variations. However I’m making levels for different platforms, like Godot and GBStudio ) These have all kinds of funky requriements. So from now on, we are distilling our terrain set to the bone so we can generate whatever type of template we want.

Today we will radically reduce our template to only the essential information, that we will use to generate a minimal template.

Let’s start out with reconfiguring the overlays to draw 32x32 sized tiles

%-overlays:
    convert input/textures/$(call TEXTURE,$*,floor,value.file) \
        -filter point -resize 32x32 \
        -write mpr:tile +delete \
        -size $(width)x$(height) tile:mpr:tile \
        $(call TEXTURE,$*,floor,key)-overlay.png
        
    convert input/textures/$(call TEXTURE,$*,border,value.file) \
        -filter point -resize 32x32 \
        -write mpr:tile +delete \
        -size $(width)x$(height) tile:mpr:tile \
        $(call TEXTURE,$*,border,key)-overlay.png
  
    convert input/textures/$(call TEXTURE,$*,transition,value.file) \
        -filter point -resize 32x32 \
        -write mpr:tile +delete \
        -size $(width)x$(height) tile:mpr:tile \
        $(call TEXTURE,$*,transition,key)-overlay.png

Reduction

The original meta-template was based on a 15 tile set with different rotations. Those tiles act as a MIXED tileset in Tiled. Earlier I reduced the tilesize from 64x64 pixels to 32x32 on the same scale, so that i could use them as a Corner terrain set instead.

If we cut up our template to those 32x32 tiles we can reduce the tile to 14 unique tiles without rotations.

Pastedimage20251208114159.png

Let’s index the first occurances of each tile

  1. [0,0]
  2. [1,0]
  3. [7,0]
  4. [1,1]
  5. [2,1]
  6. [4,1]
  7. [5,1]
  8. [7,1]
  9. [8,1]
  10. [1,2]
  11. [2,4]
  12. [14,5]
  13. [15,5]
  14. [0,10]

Now let’s cut them up once with a bash script

INPUT="tilesheet.png"
TILE=32

coords=(
  "0,0"
  "1,0"
  "7,0"
  # etc...
)

i=1
for coord in "${coords[@]}"; do
  IFS=, read tx ty <<< "$coord"

  x=$((tx*TILE))
  y=$((ty*TILE))

  # e.g. tile-01-0_0.png, tile-02-1_0.png, etc.
  out=$(printf "tile-%02d-%d_%d.png" "$i" "$tx" "$ty")

  convert "$INPUT" -crop "${TILE}x${TILE}+${x}+${y}" +repage "$out"

  i=$((i+1))
done

Now we can combine them in a 4x4 grid with a simple montage:

montage tiles/tile-*.png -tile 4x4 -geometry 32x32+0+0 -background none tiles-grid-4x4.png
Pastedimage20251208120948.png

Nice! This will be our prime template and we can already see some jagged inconsitencies.

Later i discovered that tile 4 and 10 are the same, but i can’t be bothered to redraw the whole thing. Think of these errors as little paper town drm.

After rearranging the template and cleaning it up re end up with this meta template

Pastedimage20251208123246.png

Here we removed tile 8 and 15 (the full floor and full border tiles), because they take space and can be generate programatically. Alternatively we allow ourselves some duplication to reveal a closed object containing all tiles.

If we are really crazy we can reduce this template even further and mirror it along the y axis, reducing our entire proto template evern further to just 8 tiles.

Let’s first mirror it with a magick flop

convert mini-template-left.png -flop mini-template-right.png

And we replace the colors

Green to Blue: #002200 => #000022 #004400 => #000044 #006600 => #000066

Blue to Green #000044 => #004400 #000088 => #008800

Yellow to khaki #444400 => #888800

We will have to use some intermediate colors in the #ff0 range so the greens don’t overwrite the blues

convert mini-template-right.png \
    -fill "#ff0101" -opaque "#002200" \
    -fill "#ff0202" -opaque "#004400" \
    -fill "#ff0303" -opaque "#006600" \
    -fill "#ff0404" -opaque "#000044" \
    -fill "#ff0505" -opaque "#000088" \
    -fill "#ff0606" -opaque "#444400" \
    -fill "#ff0707" -opaque "#888800" \
    -fill "#000022" -opaque "#ff0101" \
    -fill "#000044" -opaque "#ff0202" \
    -fill "#000066" -opaque "#ff0303" \
    -fill "#004400" -opaque "#ff0404" \
    -fill "#008800" -opaque "#ff0505" \
    -fill "#888800" -opaque "#ff0606" \
    -fill "#444400" -opaque "#ff0707" \
    mini-template-right.png

And we montage the two together

montage mini-template-*.png -tile 2x1 -geometry 64x128+0+0 final-mini-template.png

Why don’t we just make it a makefile target. I’m going to start using filename targets from now for these static artifacts. It makes it faster and easier down the line. This way we can start using more automatic variables

mini-template.png: input/mini-template-left.png
    # Flop it
    convert mini-template-left.png -flop mini-template-right.png
    # Swap it
    convert mini-template-right.png \
    -fill "#ff0101" -opaque "#002200" \
    -fill "#ff0202" -opaque "#004400" \
    # and the rest ..
    mini-template-right.png
    #montage!
    montage mini-template-*.png -tile 2x1 -geometry 64x128+0+0 $@
Pastedimage20251208132713.png

Amazing, now we only have to work on the left part of the template and we can generate an entirely new tileset!

We could mirror it again to only end up with just 4 tiles to work with, but the top/bottom of the templates aren’t entirely symetrical, this asymetry gives us the feeling of elevation so we can’t easily replicate that mechanically.

Pastedimage20251208133059.png

This asymetry right here is what prevents us from generating the whole thing from just 4 tiles.

If we fold this thing horizontally, mutliply the results and clean up, we get 12 distinct zones

Pastedimage20251208215015.png

Zones 3,4 and 5 are our gradient that causes all of our troubles.

We could decide to encode all these subzones and generate a top/bottom tile that we can unfold, but it’s very difficult creating a controllable gradient from this.controllable gradient from this.

We will do it anyway.

Zone 3 needs to be added to zone 2 in the bottom section and zone 4 in the top section

Zone 5 needs to be added to zone 4 in the bottom section and zone 6 in the top section

We need to get rid of the corner for the bottom variant so zone 11 will merge with zone 10 and zone 12 needs to get a different hue.

Here’s the final make

micro-template.png: input/pico-template.png
    cp input/pico-template.png micro-template-top.png
    convert micro-template-top.png \
    -fill "#440044" -opaque "#880088" \
    -fill "#002200" -opaque "#006600" \
    -fill "#880088" -opaque "#cc00cc" \
    micro-template-top.png
  
    cp input/pico-template.png micro-template-bottom.png
    convert micro-template-bottom.png -flip micro-template-bottom.png
    convert micro-template-bottom.png \
    -fill "#ff0101" -opaque "#006600" \
    -fill "#006600" -opaque "#002200" \
    -fill "#006600" -opaque "#440044" \
    -fill "#440044" -opaque "#cc00cc" \
    -fill "#000044" -opaque "#000088" \
    -fill "#004400" -opaque "#ff0101" \
    micro-template-bottom.png
  
    montage  micro-template-top.png micro-template-bottom.png -tile 1x2 -geometry 64x64+0+0 $@
    rm micro-template-*.png

If we plug this target as a depency to our mini-template

mini-template.png: micro-template.png
   # Flop it
   convert micro-template.png -flop mini-template-right.png
   # Swap it
   convert input/mini-template-right.png \
   -fill "#ff0101" -opaque "#002200" \
   -fill "#ff0202" -opaque "#004400" \
   ## the rest...
   mini-template-right.png
   #montage!
   montage micro-template.png mini-template-right.png -tile 2x1 -geometry 64x128+0+0 $@

one last thing we need to add to our mini-template: the two full floor and full border tiles!

We get a super clean mini-template to work with

Pastedimage20251208224736.png

Here we get a super clean and symetrical template to work with, but we lose some of the control if we would work in a micro or a mini template or even a meta template. The good part about using make as a pipeline tool is that at any moment we can choose to interfere and edit one of the target in our pipeline without editing it! Neat!

Let’s add those full tiles


mini-template: micro-template
    #.. the rest is still up here
    
    # full floor and border
    convert -size $(sprite_width)x$(sprite_height) "xc: $(call conf,zones.floor)" full-floor-tile.png
    convert -size $(sprite_width)x$(sprite_height) "xc: $(call conf,zones.border)" full-border-tile.png
    montage full-border-tile.png full-floor-tile.png -background none -tile 1x2 -geometry $(sprite_width)x$(sprite_height)+0+0 full-tiles.png

  

    #montage!
    montage micro-template.png mini-template-right.png -tile 2x1 -geometry 64x128+0+0 mini-template.png
    convert -size 160x128 xc:none mini-template.png  -geometry +0+0 -composite \
    full-tiles.png -geometry +128+0  -composite \
    mini-template.png
Pastedimage20251211130915.png

We are left with two empty squares that we will make use of later!

in the meantime i cleaned up the pico-template to result into more symetrical tilesets. The OG was a bit mangled throughout the project. The result, i discovered later is that the vertical tiles are much narrower that id like, but we can fix that by pronouncing the horizontal borders more

If we index each individual tile we get a nice mapping we get this:

Pastedimage20251211144116.png I’m mappng the two empty tiles seperately because i feel we will need them later.

From here we can slice each individual tile and rearrange them to create any tile layout!

  
template_tiles: mini-template.png
    mkdir -p template-tiles
    convert mini-template.png -crop 32x32 +repage +adjoin template-tiles/tile_%02d.png
    fdupes -d --noprompt  template-tiles
    n=0; for f in template-tiles/*; do ext="$${f##*.}"; mv "$$f" "template-tiles/tile_$$(printf %02d "$$n").$$ext"; n=$$((n+1)); done

Here we use a tool called fdupes to remove the duplicate tiles then we use a saucy one-liner to rename the tiles

Let’s plug this in to our pipeline and generate some full tilesets!

Template patterns

We are going to do something silly.

We are going to map our template tiles to a template pattern. A bitmap where every pixel represents a tile translated from the tiles number to a grayscale value. #000 for tile_01, #aaa for tile_11 etc. Using these maps will allows us to create templates for all kinds of platforms!

Pastedimage20251211153158.png

This will be our simplest template pattern

Let’s also make a pattern for the blob template we started with

Pastedimage20251211162701.png

now lets take our tiles and a pattern and generate a template for our terrain: Pastedimage20251211163329.png

We basically do the same thing as when we overlay our textures on a finished pattern

First we need to make our template sized dynamic: tilesize x pattern size. No more hard-coding the yaml!

width = $(call mul,$(sprite_width),$(shell identify -format "%w" $(pattern_file)))

height = $(call mul,$(sprite_height),$(shell identify -format "%h" $(pattern_file)))

Here’s a target that creates a cutout for one of the tiles

template-cutout-%.png: template-tiles
    #overlay
    #scale the pattern to the full size of the template (sprite x pattern size)
    convert template-tiles/tile_$*.png -filter point -resize $(sprite_width)x$(sprite_height) -write mpr:tile +delete -size $(width)x$(height) tile:mpr:tile template-overlay-tile-$*.png  

    #section
    convert pattern-resized.png -alpha set +transparent "#$(call TEMPLATE_TILE_COLOR,$*)" template-section-$*.png

    #cutout
    # cut the section out from the overlay
    convert template-overlay-tile-$*.png template-section-$*.png -compose copyopacity -composite template-cutout-$*.png

The target above takes the tile’s id (00-16) to select the correct section to cutout

Now since we already created the tiles, we can group them together with a little sed . Here’s a makefile function to get all the available tile id’s

TILE_IDS := $(shell ls template-tiles/tile_*.png | sed -E 's/.*tile_([0-9][0-9])\.png/\1/')

Then we can generate our template cutouts dynamically:

TEMPLATE_CUTOUTS := $(TILE_IDS:%=template-cutout-%.png)

So now all we have to do is make all the cutouts and flatten them:

template.png: $(TEMPLATE_CUTOUTS)
    convert $^ -background none -flatten $@
    # and clean up all the temporary files after were done
    rm template-section-*.png template-overlay-tile-*.png template-cutout-*.png

Give it a run!

template.png produces our template: template.png. So all we have to do is change our template_file variable to point to our new dynamic template:

template_file = template.png

and when we go

make tilesets

all the starts align

Pastedimage20251211133948.png

I see that the terrains have narrower vertical tiles than horizontal tile, we can now fix that by simply editing the pico-template, but we will do that later

Lets make a tiled terrain template Pastedimage20251211133637.png

Let’s give it a shot:

MiniTemplate.gif

I can tell that there’s also an issue of missing transitions. The red tile flashes are the system requiring tile combinations that don’t exist. In order to complete the terrain, there should be transitions for between any possible combination of texture. So aside from our hard-coded terrains, we should generate some intermediate terrains that fill in the gaps.

Conclusion

Wow, that’s a lot of work for not a ton of different results compared to where we started. There is a ton of polish left on the table, but what we have now gives us a ton of possibility to create terrains in all kinds of configurations. We also discovered more interesting problems to tackle in future installments. Most importantly we learned some more bash, how to use fdupe and practiced our imagemagick skills.

Stuff left to do

☐ Fix the narrow vertical tiles

☐ Create Tiled compatible set files

☐ Make dimensions calculated instead of hard-coded in the config

☐ Genereate intermediate terrains

gamedev
Creative

Yasen Dinkov

Tilesets and Makefiles Part 3: Shading