Procedural Hexagonial World Generation

Made by: Mika Bos

Table of Contents

  • Introduction
  • Scope and goals
  • The hexagon map
  • Storing tile data
  • Perlin noise
  • Color blending
  • Vegatation
  • Optimization
  • Water
  • Conclusion
  • References

Introduction

Procedural generated maps are widly used in the game industry. Hexagonial tile maps are also quite common. But what if we combine the 2 and make a procedural generated map that uses hex tiles? And what are the steps needed to create a naturally feeling map? These are the questions i asked myself at the beginning of this project.

Scope and goals

At the beginning of the project i had alot of ideas i wanted to implement but we had limited amount of time for our project so i had to make a priority list out of the possible ideas i had. This is the list i came up with:

  • A size adjustable tile map using hexagonial tiles
  • Muliple biomes
  • Color blending
  • Vegatation
  • Water tiles

During my project i worked through this priority list from top to bottom. But the most important thing was the question: How do i make a naturally feeling hexagonial map that is procedurally generated?

The Hexagon Map

Before we can start with anything procedural related we must first make a plain map which we can adjust in size. For this we obviously need a hexagon first. I made a hexagon model in blender because it was relatively easy to do. But at the end of my project i realized it would have been cleaner if i made the mesh myself in code. Because now the mesh has some vertices and triangles which are not visible and this comes at the cost of a little bit of peformance.

For placing the hexagons i made a drawing to help me with visualization of the places where to tiles should be. This is how the tile map should look like:

To instantiate the hexagons i used a dubble for loop which uses the mapXSize and mapZSize so the map is dynamic. But as you can see in the picture above the x position of a tile changes based on wheter is has an even or uneven z value. To differentiate between even and uneven z values i used the modulo operator as seen below:

            if (z % 2 == 0)
            {
                instantiatePos = new Vector3(x * xDistance + xDistance / 2, 0, z * zDistance);
            }
            else
            {
                instantiatePos = new Vector3(x * xDistance , 0, z * zDistance);
            }

At the end of this step we have a dynamic map which uses hexagons.

Storing Tile Data

Because we will need to acces the tiles in later steps i needed to somehow store the tiles in arrays. Luckily i rememberd that the example from the PCG workshop also uses a map with tiles. I was able to use the example project as inspiration for my project. For this i made a 2d array which stores the tiletype as an enum. Here i assign tile types during the generation of the map. And later on instantiate the tiles as descriped in previous chapter. I also made a 2d array that stores the tile gameobjects and fill it when instantiating tiles. We will need this later on for other algorithms.

Perlin Noise

Before we get started with generation we need to understand perlin noise.
For this project i wanted to make a randomly generated map. However randomly does not have a natural feeling. This is where perlin noise comes into place. Perlin noise is a different take on randomness. This algorithm has a more natural feeling to randomness. Perlin noise has a more organic appearance because it produces a naturally ordered (“smooth”) sequence of pseudo-random numbers. (khanacademy.org)

Now that we understand what noise if we can begin implementing noise in our project. To learn on how to use perlin noise in unity i used a video made by brackeys. (link under the references)

The first try i had at implementing noise resulted in a rather unnatural feeling. I used 2 perlin noise maps and crossed this with each other. This resulted in 4 different types of biomes. I did not like how this looked because of the unnatural feeling. The map had for example biomes which were only 1 tile large.
For my second try i had a different idea. I only used a single perlin noise map to make the biomes and i would later on add vegatation on top of this with 2 extra perlin noise maps. This resulted in a map that felt natural and i was happy about. This way of generation had 3 types of biomes: desert, temperate and snowy. An extra good thing about this way is that a desert and snow biome can never be right next to each other. This is because it uses the high values of the noise for desert tiles and the low value for snow tiles. Everything in the middle is always a temperate tile.

To influence my noise i made 2 variables for each perlin noise map:
terrainNoiseFrequency and terrainNoiseAmplitude.

The frequency is used to determine the amount of noise there is. When the value is low the noise will not be dense.
The Amplitude is used to make the noise more extreme. The noise is multiplied by this amount.
I played around with these variables for all three of the perlin noise maps till i got a result i was happy with.

At the end of this step the map looks like this:

Color Blending

Right now when 2 tiles of different types are next to each other the transition between the tiles looks sudden. I wanted to make this a bit smoother. To do this i used blend areas between the tiles. A particular post a catlikecoding really helped me out on this part:
https://catlikecoding.com/unity/tutorials/hex-map/part-2/
So to make these blend areas i had to make some room between the tiles first.

This is how the blend areas should look like (a solid core for the tile and room between the tiles for colour blending.

To make this room between the tiles i simply put some more distance between the tiles when instantiating them.
For the blend areas i had the idea to make a mesh prefab. And in this prefab i can make a mesh by giving the vertex coördinates. The function i made that makes a quad, looks like this:

public void AddQuad(Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4)
{
    mesh = GetComponent<MeshFilter>().mesh;

    verts = new Vector3[4];
    tris = new int[6];
    uvs = new Vector2[4];

    verts[0] = v1;
    verts[1] = v2;
    verts[2] = v3;
    verts[3] = v4;

    tris[0] = 0;
    tris[1] = 1;
    tris[2] = 2;

    tris[3] = 0;
    tris[4] = 2;
    tris[5] = 3;

    mesh.vertices = verts;
    mesh.triangles = tris;
    mesh.RecalculateNormals();
}

I also have the same sort of function for triangles.
I used these functions to create the blend areas around the tiles. Using these functions took alot of time because of the tiles on the edges. For example: the botom row of tiles dont need blend areas below them. So i couldn’t just add a quad at the bottom of every hex. I had to exclude the bottom row for adding meshes on their bottom.

For the coloring of the tiles i needed to assign vertex colours to the blend area according to their neighbours. Before i could do this i need to know which tiles are neighbours for each tile. For this i made an array for each tile and i filled this array with its neighbours.

Now when we have the neighbours for each tile in an array we can assign the vertex colours of the blend areas. For this i made a function in the mesh gameobject that assigns colours to the vertexes:

public void AddQuadColor(Color c1, Color c2, Color c3, Color c4)
{
    colors = new Color[4];

    colors[0] = c1;
    colors[1] = c2;
    colors[2] = c3;
    colors[3] = c4;

    mesh.colors = colors;
}

I have the same type of function for triangles.

Sadly this code didnt work immediatly. It turns out that the standard surface shader from unity does not support assigning vertex colors. So because of this i had to create my own shader. I had no idea on how to do this but luckily i came across a forum post that helped me out:
https://forum.unity.com/threads/finding-and-using-a-vertex-colors-shader.236366/
I added some things from the post to the standard surface shader template from unity. It basicly all comes down to this line of code:
o.Albedo = c.rgb * IN.vertexColor;
Here i use the vertex color of the adjecent tiles to influence the color of the object.

So when we use this shader for the blend areas we get the result i was after in the first place:

Here you can see the color blending
Here you can see that the blend areas are quads

Vegatation

To give my map a better look i wanted to add vegetation. My plan was to add gameobjects like trees to the tiles. but before we do this i needed to update the generation. As i said earlier in this post i was planning to add vegatation on top of the base noise map. To do this i made 2 new noise maps. These are the lightVegatation map and the heavyVegatation map. the light one is used for less denser vegation like bushes. And the heavy one is used for denser vegation like forests. To make this viable with my code i added tiles so that each type of tile has one. For example: the temperate tile has a plains, bush and forest version. For each tiletype i made a tile filled with objects. These are 3 of the 6 tiles i made:

For the objects on the tiles i used some assets on the asset store.

Right now we have different types of biomes with vegatation placed ontop of them with noise. But it looked rather weird that every forest tile looks exactly the same. To fix this i did a couple of things. The first thing was to make the tiles get a random rotation. This changes how a forest biome looks because the tiles are not the same anymore. I did that with this piece of code:

            //Random Hex Rotation
            int rotIndex = Random.Range(0, 6);
            Quaternion instantiateRot = Quaternion.Euler(-90, 0, rotIndex * 60);

I needed to change the rotation of with an interval of 60 degrees because we are talking about a hexagon and it has 6 sides. So there are 6 different possible rotation.

After this i wanted my tiles to feel even more random. This is when i came up with the idea to disable a random amount of objects on each tile. This in combination with the random rotation gave a good looking effect:

A guildmate gave me another good idea to make it feel even more random. His idea was to make tiles in the center of a biome denser than the tiles on the outside of the biome. I implemented this aswell. With this the tiles really feel natural now:

You can see that the bottom tiles are less dense than the once above.

Optimization

Because there are alot of objects in my scene optimalization was imported here. I used static batching to increase my fps my alot. With batching i am able to get around 40-80 fps on my laptop (depends at how many objects you are looking). I think that this is acceptable because my laptop has terrible specs and is not meant for gaming. And the amount of fps i get is sill playable.
I added the batching to my map by making all meshes and hexagons a child of a gameobject. And then i use the function: StaticBatchingUtility.Combine(gameObject);
Note that all the gameobjects i batched are flagged as static. Otherwise static batching won’t work.

As you can see above the batching is quite effective at reducing drawcalls.

Water

When i set my original scope i also included water and lakes in the list. I tried to implement this but it was just to much work for the limited amount of time we had. Generation wise the water and lakes would not have costed a lot of time but to get it working with my blend areas would have been to much.

Conclusion

At the end i think that i made a good looking and naturally feeling map.

I also think that the map looks natural. I think that the color blending had a big part in this. This made it look alot more natural because the tiles are fading into another biome.
I am happy with the vegatation. I think that it looks good. I like that the tiles are all unique.
I think that the noise maps look quite alright. But it was not exactly the idea i had when starting the project. This is because i have limited influence over noise. I think that if i would do it again i would not use perlin noise. But instead look at the different noise types and maybe look into coding biomes without noise. Here are some more pictures of the map:


References

Perlin noise:
https://www.khanacademy.org/computing/computer-programming/programming-natural-simulations/programming-noise/a/perlin-noise

Implementation of perlin noise:
https://www.youtube.com/watch?v=64NblGkAabk&t=0s

Color blending:
https://catlikecoding.com/unity/tutorials/hex-map/part-2/

Bachting:
https://docs.unity3d.com/Manual/DrawCallBatching.html

Shading:
https://forum.unity.com/threads/finding-and-using-a-vertex-colors-shader.236366/

GitHub Link:

https://github.com/bossie911/Hexagonial_Map

Related Posts