DEV Community

Chris
Chris

Posted on

My implementation of RedBlob's hex grid

For a game I'm making, I wanted to create a hex grid based map for navigation instead of a square X/Y grid. Hex-based maps have some advantages and disadvantages over square ones, but frankly, it's mainly because I just love hexagons. Of course I could just use Unity which has support for a hex grid out of the box, but where's the fun in that! I wanted to build it myself.

It involves a lot more maths but thankfully there's a truly excellent guide available at Red Blob Games that was a massive help. I wrote out the basic functions in C# and used WPF to render them, and got a proof of concept working - a small rectangular map with each hex's Q/R/S co-ordinates tagged for bughunting purposes:

A screenshot of some code

Not much to look at now, but it's a start! As you can see, I've gone with flat-topped hexes instead of pointy topped, but I may change that. Unlike the way the RedBlob author does it, each time I instantiate a hex, the algorithm places each of the hex's edges into a HashSet, and then when I render the map, it draws each edge instead of each hex. This way it loops over a smaller data structure, as there's no duplicates, so it should render faster. That's the theory anyway! I'm definitely going to test it so I can confirm or deny.

Some of the lines are a little glitchy (a sin I've hidden in my screenshot by increasing the line thickness) which is probably to do with int/float conversion. I suspect the following code snippets are to blame, which is used to calculate the precise position of each hex, and where their corners lie:

public readonly Point Size;
public readonly Point Origin;
readonly double F0 = (Math.Sqrt(3) / 2);
readonly double F1 = Math.Sqrt(3);
const double F2 = (2 / 3);
const double F3 = (-1 / 3);
readonly double F4 = (Math.Sqrt(3) / 3);

public Point HexToPixel(Hex hex)
{
    double x = Size.X * (hex.Q * 1.5);
    double y = Size.Y * (F0 * hex.Q + F1 * hex.R);
    return new Point((int)(Math.Round(x)) + Origin.X, (int)(Math.Round(y)) + Origin.Y);
}

public Point HexCornerOffset(int corner)
{
    double angle = Math.PI * 2 * -corner / 6;
    double x = Size.X * Math.Cos(angle);
    double y = Size.Y * Math.Sin(angle);
    return new Point((int)(Math.Round(x)), (int)(Math.Round(y)));
}

public List<Point> Corners(Hex hex)
{
    List<Point> result = new();
    Point centre = HexToPixel(hex);
    for (int i = 0; i < 6; i++)
    {
        Point offset = HexCornerOffset(i);
        result.Add(new Point(centre.X + offset.X, centre.Y + offset.Y));
    }
    return result;
}
Enter fullscreen mode Exit fullscreen mode

I tried initially using PointF instead of Point types for some of these to handle the floating point values, but I had some issues with it. I think I need to go back to that plan so I convert the floats to ints as late as possible rather than early on, which should make them more precise.

I'm planning on uploading all of the code once it's all cleaned up nice and is a more presentable mini-application.

Top comments (0)