• Subcribe to Our RSS Feed

Hexagon of Hexagons

Feb 13, 2011   //   by Josh   //   Games, Uncategorized  //  5 Comments

I’ve been thinking of a slightly new direction for HexDev, which led me to thinking it would be appropriate to generate a hexagonal canvas instead of a rectangular one for creating HexWorlds.  I also decided to document the process of where it is today and how to get there for fun and for others to learn from.

So first a little background.  Let’s start with generating a rectangular grid of hexagons as HexDev already does.  To do this, we need a little bit of math (or you can ignore the math and use magic numbers).  I chose to orient the hexagons with their left and right side being vertically aligned, points facing up and down.  If you choose to orient them a different way, the math may look a little different.

From http://mathworld.wolfram.com/Hexagon.html, we learn the following properties which are very useful for this kind of grid generation:

Given a regular hexagon with side length a:

The inradius: r = 0.5 * sqrt(3) * a
The circumradius: R = a.

The inradius r is important because it tells us how much horizontal distance we need to shift between each tile and how much horizontal space we need to shift every other row.
The circumradius R is useful because we can calculate how much vertical distance we need to shift for every row.  Fortunately for us, R = a, so that keeps the math easy. :)

The horizontal and vertical space between the center of each tile is then calculated by the following formulas:
horizontalSpace = 2.0 * inradius
verticalSpace = 1.5 * R = 1.5 * a

So, given these fomula, here’s the code to tile a Prefab in a rectangular grid in Unity3D:

public class HexGrid : MonoBehaviour
{
    public int Width = 10;
    public int Height = 10;
 
    public float HexSideLength = 1.0f;
    public GameObject HexTilePrefab = null;
 
    public void GenerateGrid()
    {
        float inradius = (float)(0.5 * Mathf.Sqrt(3) * HexSideLength);
	float spaceBetweenTilesHorizontal = 2.0f * inradius;
	float spaceBetweenTilesVertical = 1.5f * HexSideLength;
 
	if (HexTilePrefab != null)
        {
            for (int x = 0; x < Width; x++)
            {
                for (int y = 0; y < Height; y++)
                {
                    GameObject tile = (GameObject)Instantiate(HexTilePrefab, Vector3.zero, Quaternion.identity);
                    tile.transform.parent = gameObject.transform;
                    tile.transform.localPosition = new Vector3(x * spaceBetweenTilesHorizontal + (y & 1) * inradius, 0, y * spaceBetweenTilesVertical);
	        }
	    }
        }
 
        // Center the Grid
        gameObject.transform.Translate(new Vector3(-spaceBetweenTilesHorizontal * Width / 2.0f + inradius, 0, -spaceBetweenTilesVertical * Height / 2.0f - HexSideLength));
    }
}

Using this code will produce a visually “tight” grid of hexagons, if HexSideLength equals the visual length of the hexagon.  If you want some space in between your hexagons, you can set HexSideLength to a value > the actual side length of the hexagon, producing an even space around each hexagon.

Here we show HexSideLength = 1 and HexSideLength = 1.05 for a hexagon whose actual side length = 1.

So with a little background out of the way, we can now attempt to generate hexagonal grids.  Using HexDev’s Free Tile Builder, I generated the hexagonal grids that I want to automatically generate.  I started with 1×1 and worked up to 5×5 to determine the pattern and here’s what we end up with:

So what we have here are forests that represent the tiles that I actually want to generate and water to represent the empty spaces in the rectangular grid space.  My planned input is a single value a that represents the number of tiles per side.

To figure out how we are going to generate this grid, some observable stats are useful.  If we can find general equations for these values in terms of a, we’ve got enough information to render the grid, so let’s do that.

Here are some stats for every grid:

a rows midRowColumns midRowNumber
1 1 1 1
2 3 3 2
3 5 5 3
4 7 7 4
5 9 9 5

Let’s break down a = 5 to some even further stats:

row leadingSpaces tileCount abs(distanceFromMid)
1 2 5 4
2 2 6 3
3 1 7 2
4 1 8 1
5 0 9 0
6 1 8 1
7 1 7 2
8 2 6 3
9 2 5 4

So here are some equations that we can derive from these tables:

rowCount = 2a – 1
distanceFromMid[row] = abs(a – row)
tileCount[row] = rowCount – distanceFromMid[row]

The hardest one to derive I think is the leadingSpaces variable, because it changes depending on even/odd a values, but I’ve found this to be true:
For even values of a:  leadingSpaces[row] = Floor(distanceFromMid[row] / 2 )
For odd values of a: leadingSpaces[row] = Floor((distanceFromMid[row] + 1) / 2)

At the moment, I’m not going to share my code for this, because it’s getting late and it needs to be cleaned up… but if there is enough interest, I don’t have any qualms against posting it.  For now, I leave you with a screenshot of HexDev using a hexagonal canvas.  HexDev’s Free Tile Builder mode has been updated to also generate Hexagonal boards in 4, 5, and 8 length sizes.  Enjoy!

5 Comments

  • [...] About « Hexagon of Hexagons [...]

  • Your work with Hexes is an inspiration for me, as Im trying to make a hex turn based strategy game. Thank you for posting this.
    I have a question about your code.
    Instead of:
    GameObject tile = (GameObject)Instantiate(HexTilePrefab, Vector3.zero, Quaternion.identity);
    tile.transform.parent = gameObject.transform;
    tile.transform.localPosition = new Vector3(x * spaceBetweenTilesHorizontal + (y & 1) * inradius, 0, y * spaceBetweenTilesVertical);

    I have

    GameObject tile = (GameObject)Instantiate(HexTilePrefab, new Vector3(x * spaceBetweenTilesHorizontal + (y & 1) * inradius, 0, y * spaceBetweenTilesVertical);, Quaternion.identity);

    which gives me the same effect. Is there a reason to use these transform and localPosition code?
    Sorry for my ignorance, I’m new to Unity.

  • Hi Lampis! Glad some of the code is helpful to someone!

    Both of the code samples should give you the same result. The difference is whether or not the tiles are parented to another game object or not. In my case, I am setting each tile as a child of a single grid object (so I could for example shift the grid object around and all tiles move with it), so I represent the tile’s coordinates in terms of local coordinates instead of world coordinates.

    In your code, by passing the position vector directly into the Instantiate function, you are declaring the world coordinates for the tile, not relative to any parent.

    Here are some links that might help:
    http://unity3d.com/support/documentation/ScriptReference/Transform-localPosition.html
    http://answers.unity3d.com/questions/40439/whats-the-difference-between-transformlocalpositio.html

    Keep at it! And send me links when you have something to share. :)

  • Great clarification, thanks again. :)

  • Congratulations for the home page, but unless there is a data structure, the people can’t play on the board.

Leave a comment