Hexagonal grid: Generating the grid

Why hexagonal grid is better than square grid

If even such well established strategy franchise like “Civilisation” adapted hex grids in their latest game, there should be a good reason to use them, right? Well, there are a couple of good reasons why people choose hex grids. Firstly, in hex grids distance between the center of each hex cell and the center of all six surrounding hexes is the same, while it’s not the case with square grids where the distance from the center of each square cell to the center of diagonal adjacent cells is not the same as the distance to the four adjacent cells the square shares an edge with. Secondly, in hex grids neighbouring cells always share edges. Also, hex grids look cool 🙂

Preparations

I’m not going to explain Unity basics here, so if you are not familiar with unity editor and don’t know how to do simple things like setting up the scene or creating prefabs, I would suggest you to watch some tutorial videos at unity3d.com or some of the video tutorials created by Peter Laliberte. Also if you want to be able to control the camera in the game, you should just attach KeyboardCameraControl or/and MouseCameraControl scripts to the main camera and adjust some settings in the inspector. We’ll be making camera script tailored for our game a bit later. Finally, before proceeding you should either make your own hexagon tile model or just drop this one (created by ForceX from unity forums) to your project.

Generating the grid when the number of hexagons in a row and the number of rows is known

I’ll explain here two ways to generate grids: the first being when it is known beforehand exact number of hexes in a row and the number of rows plus the second one when we have to figure out how many hexagons can fit on a certain size ground ourselves. We’ll start with an easier one: the number of hexagons is already known and proceed by coding our first script (to explain what’s going on in the script I have added some comments).

using UnityEngine;
using System.Collections;

public class GridManager: MonoBehaviour
{
    //following public variable is used to store the hex model prefab;
    //instantiate it by dragging the prefab on this variable using unity editor
    public GameObject Hex;
    //next two variables can also be instantiated using unity editor
    public int gridWidthInHexes = 10;
    public int gridHeightInHexes = 10;

    //Hexagon tile width and height in game world
    private float hexWidth;
    private float hexHeight;

    //Method to initialise Hexagon width and height
    void setSizes()
    {
        //renderer component attached to the Hex prefab is used to get the current width and height
        hexWidth = Hex.renderer.bounds.size.x;
        hexHeight = Hex.renderer.bounds.size.z;
    }

    //Method to calculate the position of the first hexagon tile
    //The center of the hex grid is (0,0,0)
    Vector3 calcInitPos()
    {
        Vector3 initPos;
        //the initial position will be in the left upper corner
        initPos = new Vector3(-hexWidth * gridWidthInHexes / 2f + hexWidth / 2, 0,
            gridHeightInHexes / 2f * hexHeight - hexHeight / 2);

        return initPos;
    }

    //method used to convert hex grid coordinates to game world coordinates
    public Vector3 calcWorldCoord(Vector2 gridPos)
    {
        //Position of the first hex tile
        Vector3 initPos = calcInitPos();
        //Every second row is offset by half of the tile width
        float offset = 0;
        if (gridPos.y % 2 != 0)
            offset = hexWidth / 2;

        float x =  initPos.x + offset + gridPos.x * hexWidth;
        //Every new line is offset in z direction by 3/4 of the hexagon height
        float z = initPos.z - gridPos.y * hexHeight * 0.75f;
        return new Vector3(x, 0, z);
    }

    //Finally the method which initialises and positions all the tiles
    void createGrid()
    {
        //Game object which is the parent of all the hex tiles
        GameObject hexGridGO = new GameObject("HexGrid");

        for (float y = 0; y < gridHeightInHexes; y++)
        {
            for (float x = 0; x < gridWidthInHexes; x++)
            {
                //GameObject assigned to Hex public variable is cloned
                GameObject hex = (GameObject)Instantiate(Hex);
                //Current position in grid
                Vector2 gridPos = new Vector2(x, y);
                hex.transform.position = calcWorldCoord(gridPos);
                hex.transform.parent = hexGridGO.transform;
            }
        }
    }

    //The grid should be generated on game start
    void Start()
    {
        setSizes();
        createGrid();
    }
}

Once you have examined and understand the script, attach it to some game object already in the scene, for example, “Main Camera”, drag your hexagon tile prefab on the Hex public variable in the inspector pane, set the number of hexes and start the game. Hopefully, everything should work just fine.

Generating the grid when only ground size is known

The second method to generate the grid is a bit more advanced because it involves calculating how many hexes fit on top of certain size ground. Again I’ll try to explain what’s going on using comments inside the code but this time I will not explain again what was already explained in the previous section.

using UnityEngine;
using System.Collections;

public class GridManager: MonoBehaviour
{
    public GameObject Hex;
    //This time instead of specifying the number of hexes you should just drop your ground game object on this public variable
    public GameObject Ground;

    private float hexWidth;
    private float hexHeight;
    private float groundWidth;
    private float groundHeight;

    void setSizes()
    {
        hexWidth = Hex.renderer.bounds.size.x;
        hexHeight = Hex.renderer.bounds.size.z;
        groundWidth = Ground.renderer.bounds.size.x;
        groundHeight = Ground.renderer.bounds.size.z;
    }

    //The method used to calculate the number hexagons in a row and number of rows
    //Vector2.x is gridWidthInHexes and Vector2.y is gridHeightInHexes
    Vector2 calcGridSize()
    {
        //According to the math textbook hexagon's side length is half of the height
        float sideLength = hexHeight / 2;
        //the number of whole hex sides that fit inside inside ground height
        int nrOfSides = (int)(groundHeight / sideLength);
        //I will not try to explain the following calculation because I made some assumptions, which might not be correct in all cases, to come up with the formula. So you'll have to trust me or figure it out yourselves.
        int gridHeightInHexes = (int)( nrOfSides * 2 / 3);
        //When the number of hexes is even the tip of the last hex in the offset column might stick up.
        //The number of hexes in that case is reduced.
        if (gridHeightInHexes % 2 == 0
            && (nrOfSides + 0.5f) * sideLength > groundHeight)
            gridHeightInHexes--;
        //gridWidth in hexes is calculated by simply dividing ground width by hex width
        return new Vector2((int)(groundWidth / hexWidth), gridHeightInHexes);
    }
    //Method to calculate the position of the first hexagon tile
    //The center of the hex grid is (0,0,0)
    Vector3 calcInitPos()
    {
        Vector3 initPos;
        initPos = new Vector3(-groundWidth / 2 + hexWidth / 2, 0,
            groundHeight / 2 - hexWidth / 2);

        return initPos;
    }

    Vector3 calcWorldCoord(Vector2 gridPos)
    {
        Vector3 initPos = calcInitPos();
        float offset = 0;
        if (gridPos.y % 2 != 0)
            offset = hexWidth / 2;

        float x =  initPos.x + offset + gridPos.x * hexWidth;
        float z = initPos.z - gridPos.y * hexHeight * 0.75f;
        //If your ground is not a plane but a cube you might set the y coordinate to sth like groundDepth/2 + hexDepth/2
        return new Vector3(x, 0, z);
    }

    void createGrid()
    {
        Vector2 gridSize = calcGridSize();
        GameObject hexGridGO = new GameObject("HexGrid");

        for (float y = 0; y < gridSize.y; y++)
        {
            float sizeX = gridSize.x;
            //if the offset row sticks up, reduce the number of hexes in a row
            if (y % 2 != 0 && (gridSize.x + 0.5) * hexWidth > groundWidth)
                sizeX--;
            for (float x = 0; x < sizeX; x++)
            {
                GameObject hex = (GameObject)Instantiate(Hex);
                Vector2 gridPos = new Vector2(x, y);
                hex.transform.position = calcWorldCoord(gridPos);
                hex.transform.parent = hexGridGO.transform;
            }
        }
    }

    void Start()
    {
        setSizes();
        createGrid();
    }
}

Once the script is created, the steps are pretty much the same as in the previous section: attach the script, initialise public variables and start, except one thing: if your ground is not a plane you should set the y coordinate on line 62 to something that would lift your tiles just enough to be seen by the player. That’s it for now. Coming next – path finding.

Advertisements

47 Responses to Hexagonal grid: Generating the grid

  1. Tommy says:

    Hey just wondering, where does hexSizeX come from in line 74 in the second version of the script?

  2. Christopher says:

    I can’t seem to get the first script to position the hex’s along the z-axis, the x-axis works fine: All hexes line up along the x-axis. Any suggestions?

    • hakimio says:

      Maybe rotation of your hexagon tile prefab is wrong?

    • joystickmonkey says:

      I put hex piece in a prefab and then set the rotation to 0, and that worked fine.

    • Robert Rennex says:

      The problem is, your prefab is being accepted in the wrong orientation (Rotation x=270 seems to be the default when importing the hex prefab).
      This means the script is reading the following line
      ” hexHeight = Hex.renderer.bounds.size.z;”
      and is receiving the WRONG height value. It is now measuring the thickness of the hexes (which are incredibly thin).
      And so your hexes “lining up” is actually the result of the hexes being offset by that tiny thickness.

      In order to counteract this, you may take two paths.
      1) You can re-do the prefab so that it is correctly oriented.
      2) You can edit the code so that it reads
      ” hexHeight = Hex.renderer.bounds.size.y;”
      This will give the correct height, for the incorrectly imported prefab.

  3. Tortyfoo says:

    Hi, This tutorial looks excellent for what I am trying to do but seems I am a bit to new at Unity to work it out as can’t get more than 1 hex to appear despite leaving width and height at default 10 and adding the HexGrid object to the script which I added to a plane. Do you have a saved project I could study to see my mistakes. I am using the hex model you mentioned.

  4. Amdouz says:

    Hi everybody.

    First of all very nice Tutorial ! Just the right amount of comments !

    I had a problem with my Hexes, they were created in a single line and stepped on one another.

    I messed around in the code, nothing there.

    Just check your prefab’s scale value mine were 50.1.50…

    Everything went back to normal with 1.1.1.

    Have a good day !

  5. JOS says:

    The ‘Hex’ that is noted in the article has it’s Z-axis pointing up out o the screen. Unity’s y-axis is ‘up’, so the building package needs to setup the y-axis being up prior to export.

    I’m fiddling with the scripts (great information and tutorial by the way) and will see if I can get the correct code with the ‘z-axis’ mesh.

  6. ori says:

    what i did was to create an empty prefab. drag the hexagon to the viewport, change rotation from (270,0,0) to (0,0,0) so that y is up , and then drag this object into the empty prefab. then drag the prefab into the public hexagon variable that is in the scripts. this solved the situation

  7. unityPro says:

    I have tried to do this but I can’t get anything to show up on my plane. Its like it won’t render the hex. Any ideas?

  8. ZombieBait says:

    How can I change the initial position of the grid, I tried to add a Vector3 position to the InitPos method and it does not seem to offset the z direction correctly.

    • hakimio says:

      Do you want to raise it? Check out “calcWorldCoord()” method. You need to change 0 there with sth that fits your needs better.

      • ZombieBait says:

        Hi Hakimio, thank you for the quick response. I am not looking to modify the grid height I am trying to create a small hex grid centered at a specific vextor3 world coordinate. I have tried to add this specific vector to the initpos method to change its starting position in world space but I cannot seem to change the z vector. It will always remain at zero. I am struggling to see where this is overwritten. I see the y vector is overwritten in the calcworldcoor method as you have pointed out however changing the height is not my concern at the moment. Any help would be appreciated. Thank you.

    • tomas says:

      Is your tile rotated correctly? You should be able to change initial position in calcInitPos().

      • ZombieBait says:

        Yes I have the hex object orientated correctly. I was able to complete the tutorial series and have the project run perfectly. I checked the code and it matches the provided code exactly. If you place a plane or any other reference object behind the grid and apply a vector3 offset to the starting position it does seem to not behave correctly at least for me.

  9. mike says:

    This seems to result in one line of hexagons, ie it doesn’t move in the z plane at all. From examining the code and looking at peoples responses i can only guess that it’s to do with the model, however whatever i tried to do to fix it did not work (none of the mentioned quick fixes of changing rotation etc).

    But if this can be resolved by making our own models great. I wanted to ask though, I am a beginner in this looking to make my own TBS game, when generating a map that might be 25×25 or possibly even larger, would creating that many game objects (tiles) not become an issue with performace?

    • mike says:

      i understand the problem now, the model’s y and z planes are confused so that it is layering the hexagons deeper into the screen rather than down the screen in rows.

    • hakimio says:

      The performance issues might arise if your model is very complicated. In case of a simple tile model like the one presented in the example there shouldn’t be any issues creating 25×25 grid even on low end machine with intel gpu.

  10. mike says:

    the problem is that i might be using 100×100 grid. I have run into a new issue now, with the downloaded model’s rotation ‘causing irritating problems i made my own. however the script doesn’t seem to duplicate it. I only see one on the screen.

  11. mike says:

    no i have not done that, if i am not mistaken the one forceX one you provided doesn’t have one either. Why would a collider make a difference?

  12. mike says:

    Have actually got your model working now, I had changed the hexheight to measure y instead of z and forgot to change it back, feel stoopid lol. Frustration remains at the fact that the script is only creating one of my model. not a problem now but it implies my model is exported with some fault which could ’cause major issues in the future =/.

  13. Fuhans Puji Saputra says:

    Hai, i got problem, i already generate the grid, but why the grid is not exactly in the position? here is the link of image:

    • hakimio says:

      What haxagon tile model are you using? Did you copy the code exactly the way it is here? Which code version are you using? Did you try rotating your model?

  14. Fuhans Puji Saputra says:

    I am using from your given link, this one: http://www.mediafire.com/download.php?98732c476jg3mlf

    yeah, i copy your code exactly same. How do i check what version of code (c#) did i using?

    Yeah, i already rotate it by 90 degrees on Y axis, but it is not fit into each other, here is the image when i rotate it 90 degrees, i using the box and apply the texture of it (transparant grid), the scale of the box is 1,1,1:

    • hakimio says:

      The prefab tile I’ve uploaded to mediafire doesn’t have to be rotated like the one presented in the article (the one from dropbox). There are two versions of the code in the article: one which requires you to specify number of rows and columns and one that determines the number of rows and columns based on ground size.

      • Fuhans Puji Saputra says:

        I followed the second one (determine the grid sizes based on ground size), i just take the texture of Transparant Grid, create the material and apply that texture to the cube. My current cube rotation is 0,0,0 with scale 1,1,1 and it is position is on 0,-1,0.. Something wrong with this?

      • hakimio says:

        Leave the tile prefab with exactly the same parameters you have when you download it from mediafire (the corrected version should be there) and make sure you assign correct values to the public variables of the script. If it still doesn’t work for you, you can investigate the script here -> https://github.com/hakimio/KR/blob/master/Project/Assets/Scripts/Arena/GridManager.cs

  15. Fuhans Puji Saputra says:

    Done, it is worked, here is the image:

  16. Hello,

    Trying to get this to work as a total unity noob.

    I tried most of the above mentioned. I have created a new unity scene and imported the hex (http://www.mediafire.com/download.php?98732c476jg3mlf) by dragging it in the project panel.

    In my scene there is a camera rotated down, a plane and the hex prefab. I get a warning: “The type or namespace Tile could not be found.” I also have the GridManager code from the article but there is not way to attach it to an object in the screen.

    My guess is that public Tile tile should be defined in the unity editor, dragging a prefab onto the variable in the inspector.

    Been trying to get this to work for weeks (not at an end) and getting frustrated. What am I doing wrong?

    • hakimio says:

      There is a
      “public GameObject Hex;”
      which you need to instantiate with your tile prefab in unity editor. Anyway, where are you getting that warning from? I don’t use “Tile” namespace or variable in the code presented in this article. Did you follow some other tutorial or add it yourself? Can you double click on the warning and find out where is it coming from?

  17. Hey Hakimio, good time there getting back to me! 🙂

    I have made some progress, I have gotten the hex grid to appear using only the GridManager script, where I failed before.

    When I import the Hex.unitypackage file from the aforementioned link, it comes with a folder Arena > Scripts > TileBehaviour that contains some behaviour on Mouse events and such. It pretty much starts with:

    public class TileBehaviour: MonoBehaviour
    {
    public Tile tile

    My error references to that line.

    You’re right its not referenced in the original script, I can find it the script linked on https://github.com/hakimio/KR/blob/master/Project/Assets/Scripts/Arena/GridManager.cs.

    Something covered in later tutorials maybe?

  18. Thanks man! I’m sure I will have more questions but I love that there is a tutorial like this.

  19. seiti says:

    Hi! I have one question (even though your post is old) – How does unity handle your method performance wise (for big maps).
    Would you recommend me to look into generating terrain using meshes (submeshes for each tile)? (Did you find more performant ways of doing this)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: