Creating a Hexagon Grid with SFML and THOR

Ever wonder how to make a cool techy-looking grid of hexagons? Well, wonder no more. In this tutorial, I'll show you a simple way to generate and render a cool looking hex grid for your projects.If you want to skip the tutorial and grab the source, check out the repo on github.

It is actually quite simple. We will create a class that inherits from sf::Drawable and create the grid and render it based on a couple parameters in the constructor. This class was created for use with my Space Invaders clone series, and thus is nested in the "nasic" namespace. If this bugs you, feel free to deal with it as you see fit. Here is the definition (hexgrid.hpp):



#ifndef HEXGRID_HPP
#define HEXGRID_HPP

#include <vector>
#include <SFML/Graphics.hpp>
#include <THOR/Shapes.hpp>

namespace nasic
{
    class hexgrid : public sf::Drawable
    {
        public:
            hexgrid(sf::RenderWindow& window, sf::Uint32 style);
            ~hexgrid();

            void draw(sf::RenderTarget& target, sf::RenderStates states) const;

            enum hexStyle
            {
                translucent,
                colorful,
                green,
                cyan
            };

        private:
            //set up background elements
            sf::Uint32 m_style;
            sf::ConvexShape hexagon;
            std::vector<sf::convexshape> hexagrid;
    };
}

#endif // HEXGRID_HPP

Really, the hardest part is playing with the numbers until you are satisfied with how they are distributed on the screen. In my case, I wanted them to be slightly more offset on the y-axis. Here is the implementation (hexgrid.cpp):



#include <hexgrid.hpp>

nasic::hexgrid::hexgrid(sf::RenderWindow& window, sf::Uint32 style)
{
    //set up background elements
    hexagon = thor::Shapes::polygon(6, window.getSize().x/16.f, sf::Color(255,0,0,100), 2.f, sf::Color(100,0,0,100));
    hexagon.setOrigin(hexagon.getGlobalBounds().width/2.f, hexagon.getGlobalBounds().height/2.f);

    std::vector<sf::ConvexShape>::iterator hexit;

    m_style = style;

    for(int i=0; i<12; i++)
    {
        for(int j=0; j<12; j++)
        {
            if(i % 2 == 0)
            {
                hexagon.setPosition(i * (hexagon.getGlobalBounds().width * 0.80f), j * (hexagon.getGlobalBounds().height * 1.1f));
                if(j % 2 == 0)
                {
                    switch(m_style)
                    {
                        case hexStyle::translucent:
                        {
                            hexagon.setFillColor(sf::Color(255,255,255,100));
                        }
                        break;

                        case hexStyle::colorful:
                        {
                            hexagon.setFillColor(sf::Color(0,255,0,100));
                        }
                        break;

                        case hexStyle::green:
                        {
                            hexagon.setFillColor(sf::Color(0,255,0,100));
                        }
                        break;

                        case hexStyle::cyan:
                        {
                            hexagon.setFillColor(sf::Color(0,255,255,100));
                        }
                        break;

                        default:
                            break;
                    }
                }
                else
                {
                    switch(m_style)
                    {
                        case hexStyle::translucent:
                        {
                            hexagon.setFillColor(sf::Color(255,255,255,100));
                        }
                        break;

                        case hexStyle::colorful:
                        {
                            hexagon.setFillColor(sf::Color(0,255,255,100));
                        }
                        break;

                        case hexStyle::green:
                        {
                            hexagon.setFillColor(sf::Color(0,255,0,100));
                        }
                        break;

                        case hexStyle::cyan:
                        {
                            hexagon.setFillColor(sf::Color(0,255,255,100));
                        }
                        break;

                        default:
                            break;
                    }
                }
            }
            else
            {
                hexagon.setPosition(i * (hexagon.getGlobalBounds().width * 0.80f), j * (hexagon.getGlobalBounds().height * 1.1f) - (hexagon.getGlobalBounds().height * 0.5f));
                if(j % 2 == 0)
                {
                    switch(m_style)
                    {
                        case hexStyle::translucent:
                        {
                            hexagon.setFillColor(sf::Color(255,255,255,100));
                        }
                        break;

                        case hexStyle::colorful:
                        {
                            hexagon.setOutlineColor(sf::Color(200,20,0,255));
                            hexagon.setFillColor(sf::Color(255,55,0,100));
                        }
                        break;

                        case hexStyle::green:
                        {
                            hexagon.setFillColor(sf::Color(0,255,0,100));
                        }
                        break;

                        case hexStyle::cyan:
                        {
                            hexagon.setFillColor(sf::Color(0,255,255,100));
                        }
                        break;

                        default:
                            break;
                    }
                }
                else
                {
                    switch(m_style)
                    {
                        case hexStyle::translucent:
                        {
                            hexagon.setFillColor(sf::Color(255,255,255,100));
                        }
                        break;

                        case hexStyle::colorful:
                        {
                            hexagon.setOutlineColor(sf::Color(200,200,200,255));
                            hexagon.setFillColor(sf::Color(255,255,255,100));
                        }
                        break;

                        case hexStyle::green:
                        {
                            hexagon.setFillColor(sf::Color(0,255,0,100));
                        }
                        break;

                        case hexStyle::cyan:
                        {
                            hexagon.setFillColor(sf::Color(0,255,255,100));
                        }
                        break;

                        default:
                            break;
                    }
                }
            }
            hexagrid.push_back(hexagon);
        }
    }
}

nasic::hexgrid::~hexgrid()
{

}

void nasic::hexgrid::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
    std::vector<sf::ConvexShape>::const_iterator hexit;
    for(hexit = hexagrid.begin(); hexit != hexagrid.end(); ++hexit)
    {
        target.draw(*hexit, states);
    }
}

The first thing established is the size of the hexagons. Although this is completely at your discretion, I chose to make larger hexagons sized relative to the window passed to the object. This makes things more consistent on different sized screens, but certainly is not a complete solution. Users with strange screen resolutions may still experience stretched/squashed visuals - but this is a step in the right direction.

Yet again, nested loops are used to index and create hexagon shapes, position them on the screen, and initialize their properties. We use the modulus (%) operator to find the odd and even rows and columns and make the manipulations we need to make. We then push back the hexagons to our container, and voila, the hexgrid is initialized. We then use the pure virtual draw function to implement our draw routine for the hexgrid. Here is a simple minimal example use:


#include <SFML/Graphics.hpp>
#include <hexgrid.hpp>

int main()
{
    sf::RenderWindow window(sf::VideoMode(800,600,32), "Hexgrid Example", sf::Style::Default);

    nasic::hexgrid grid(window, nasic::hexgrid::hexStyle::translucent);
    sf::Event e;
    bool running = true;
    while(running)
    {
        while(window.pollEvent(e))
        {
            if(e.type == sf::Event::Closed)
            {
                window.close();
                return 0;
            }
        }
            window.clear();
            window.draw(grid);
            window.display();
    }
    return 0;
}

Pretty simple, eh? Keep in mind that the class is easily modified using the enumeration and handling your special cases in the nested loop. Also keep in mind that if you need many different types of hexgrids that you may need to employ a more robust method for generating them. However, this method is ultra simple and you can get quite a bit of mileage out of it.Enjoy.