6 min read
Pixel Worldmap

Long ago, I wrote worldmap generation using voronoi, perlin noise, and a node graph. It used placeholder visuals and assets.

Placeholder assets worldmap

Though I’d been mulling over what this might look like in pixel art, I had been procrastinating the conversion. I spent quite a bit of time on worldmap generation, and it seemed like 80-90% of my code would be thrown out.

And while I love deleting code, this didn’t fill me with warm and fuzzies.

Pixel Worldmap Aesthetic

My first gut reaction was overworlds from games like Final Fantasy. Comically large trees and buildings, not build to scale. Though I stumbled upon some pixel art maps that attempted to match scale (e.g. Arco, Stardew Valley), those seemed far too polished to work well with random terrain generation.

Though I cannot understate the beauty of Arco’s maps. If I had even a fraction of that team’s artistic talent, I would certainly try.

If you haven’t played Arco, stop reading this blog post and go play it. It is an underrated work of art.

Inspiration finally struck when I found Aleksandr Makarov’s Overworld asset pack.

I rarely use asset packs because they don’t match Skymagi’s style perfectly:

  • Substantially lower pixel count (3x magnification rather than 2x)
  • Thick black outlines
  • Doesn’t match the color scheme

However, they would serve as excellent pixel art placeholders until I can create my own.

I used photoshop to quickly mock-up a sample worldmap.

Pixel worldmap mockup

I loved the lower resolution pixel art for the world map. It is a cardinal design sin to change pixel resolution within the same context, but fair game between contexts. It felt natural for a map to be lower resolution and more symbolic than the rest of the game.

The floating wizard tower felt natural floating among this overworld.

Time to throw out code

At least, I thought. Turns out, I only threw out a small 10% fraction.

Voronoi was still the best method to space nodes on the map. Before, I had two layers of voronoi. One that determined the actual terrain and another that collected up the terrain into regions.

Voronoi

Since the pixel art map is driven by tiles, I need to convert my lower level voronoi into a 2d tilemap. All of my code for the heightmap can still apply, I just map the perlin noise to tile centers rather than voronoi centroids.

2d tiles

Now, rather than coloring blocks, I created a Terrain configuration for a tileset that maps heights to particular tiles. I replaced my placeholder vector art with placeholder pixel art from the asset pack.

Getting closer

Surprisingly quick to switch over with few changes to the core generation code. There are some issues though:

  • The coastline needs to be autotiled to have the proper edge.
  • Large map nodes (e.g. castle, huts) should not hang off of ledges.
  • The waterline is a bit too stark for my taste.

Autotiling with bitmasks

Conceptually, autotiling is simple. The current tile should look at its neighbors and determine what kind of tile it should be. Complexity arises from combinations.

Shotout to Excalibur JS for their excellent demonstration of autotiling techniques.

The asset pack had enough variations to cover 16 edge cases, which was enough variation to make it feel good. In my case, I had to cover mixing land and water. I treated water as the base.

I created a BitmaskToTileId map for 0-15 on the Terrain configuration and matched each combination to a tileId in the tileset. I did not line up my tileset nicely, which was probably a mistake. Too late now. :)

// All water
BitmaskTileId.put(0, (short) 1);
// All land
BitmaskTileId.put(15, (short) 2);
// Corners partially land
BitmaskTileId.put(1, (short) 13);   // Top-left land 2 -> bot left
BitmaskTileId.put(2, (short) 12);   // Top-right land 3 -> bot right
BitmaskTileId.put(4, (short) 22);   // Bottom-left land 4 -> top left
BitmaskTileId.put(8, (short) 21);   // Bottom-right land 5 -> top right
// Mixed corners
BitmaskTileId.put(3, (short) 11);   // Bottom-left and Bottom-right land
BitmaskTileId.put(5, (short) 31);   // Top-left and Bottom-left land 7
BitmaskTileId.put(10, (short) 30);  // Top-right and Bottom-right land 8
BitmaskTileId.put(12, (short) 20);  // Top-left and Top-right land 9
// Three corners land
BitmaskTileId.put(7, (short) 23);  // top-right water 10
BitmaskTileId.put(11, (short) 24); // top-left water 11
BitmaskTileId.put(13, (short) 14); // bot-right water 12
BitmaskTileId.put(14, (short) 15); // bot-left water 13

Each tile had a 4 bit flag to represent the 4 corners of the tile. For each corner that had land, I set the flag to 1.

And like magic (aka hours of debugging mistakes), autotiling worked.

Autotiling works

I added a blend of deep/shallow water based on land height as well, minecraft style.

It needs clutter

Doodads should be specific to the chosen map and overall world. Since tiles have a concept of what map node owns their territory (through voronoi), I have the power to customize doodads.

Like a logging camp would have more trees, a coast city would have beach stuff.

Or perhaps if we have a giant castle, we shouldn’t put it on the tile right next to the coast…

Since I already had region-specific generation, I added a size-check for placing map nodes that were large to ensure they would fit in the region on land tiles.

And while region specific doodad generation is important, it is not the current priority.

I added support / weights for what doodads are allowed in each region, but didn’t customize it yet. I’ll save that for when I have my own assets.

So here’s a simple random doodad generation that won’t allow overlaps with map nodes.

Pixel Worldmap

It isn’t perfect, but it is much closer to my intended aesthetic. Even with my placeholder cloud cover and nodes, it matches the aesthetic well.