3D Hexagonal Tilemap

Recreating my previous 2D project in 3D!

Posted by Dominik Baumann on April 22, 2022 · 7 mins read

Controls: WASD/Arrow keys for panning, hold right mouse additionally to rotate; scroll to zoom; Demo of the project - pathfinding works with some restrictions (i.e. moving from water onto land is possible).

With the idea of (re)creating a strategy game on a hexagonal grid in 3D, I set out to implement my own solution for a 3D (hexagonal) tilemap system. After some experimentation with GL and trying to exploit Unity's 2D tilemap system, I realised that a preview package existed that did exactly the task I wanted to achieve: place Game Objects on a tilemap (see GameObject Brush in the package's documentation).

Yet apparently this solution only works for square grids (I haven't tried rectangular grids) - painting hexagonal tiles resulted in this:

Hex grid fail The tiles simply do not fit?!

So I ended up modyfing the script for the GameObject Brush, with the result being what I called "HexBrush" - it allows me to paint landscapes like this:

Demo Image The tiles simply do not fit?!

which actually fit the grid. Even more excitingly, we can actually change the z-position of tiles to create 3D landscapes:

An actual 3D map.

One might be inclined to say "But if you increase the z-position too much, there will be holes in the map..." - which is absolutely true and looks like this:

Holes in the 3D map.

Does this mean this project is doomed? No.

Quite some blood, sweat and tears later I am proud to present you this:

Fixed holes in the 3D map.

There is two reasons I am particularly proud of this:

  1. The holes have been filled, and such holes will always be filled in maps automatically!
  2. The vertex and triangle counts of the meshes can be reduced significantly (6 vertices and 4 triangles for one tile).
About the second point: I am using the free hexagon kit by Kenney. Using Unity's built-in function for combining meshes each tile uses 36 vertices and 16 triangles (48 vertex indices). The blood, sweat and tears I mentioned earlier where invested into crafting a custom mesh-combination algorithm - the result being a reduction of the vertex count to one 6th. For a flat map the number of triangles is reduced to one 4th, which does not hold anymore when the first point of above list comes into play: Fixing holes in the tilemap is done in two steps:
  • All tiles are stretched s.t. their top surface stays at the same position but their bottom surface is at z = 0.
  • The required vertex indices are added to the list from which the mesh triangles are constructed.
To illustrate the result of this process look at the following picture:

Grid simplification

The result of Unity's combine functionality is the mesh on the left-hand side (for simplicity a single tile is shown), while all we need is the result of the custom algorithm (right-hand side mesh).

Look again at the two pictures above to see how filling holes introduces additional triangles!

Then I added some simple particle effects and a script to rotate the sails of wind mills and the wheels of water mills. It's amazing how much nicer things look already with just a little effort :)

Adding smoke particles and adding rotation to mills.

This is nice and all, but something important is missing! Maybe you noticed the humanoid model in above image? Let's have this guy walk around!

Soon after Dijkstra's algorithm and A*-pathfininding are implemented and our guy can follow the shortest path between two vertices (you can try this yourself in the demo: just click on two different tiles; if not already standing on the first tile, the model will be placed onto it automatically).

At this point I want to highlight these awesome tutorials on pathfinding and hexagonal grids and thank Amit Patel for sharing this amazing content!

Note that pathfinding in the demo has some quirks (which aren't particularly hard to fix, it just is not a high priority for me right now).

  • Walking one tile from water onto a shore is possible.
  • In fact, any tile is a feasible starting tile if the target tile is a direct neighbour (and a grass or forest tile).
  • Let's not talk about the orientation of the character... o.O
Also I haven't yet thought seriously about how to handle actual 3D grids, and especially terrain features like mountains - from a pure optical point of view. Setting the z-coordinate of the target position to the top surface of a tile is no problem. But it also should look good - clipping through trees is already annoying enough...

Also you might have noticed that the mouse highlight in the demo does not work properly on tiles containing rivers, the simple reasons behind that being the structure of the underlying mesh and my vertex extraction not working on it. Not yet!

River tiles are weird.