A Guide on Procedural Generation in Unity


If you only need the Source Code, dive down to the end of this Guide to click on the Link, though I'd still recommend you to read the Guide.

After helping and subsequently being mentioned in a video of Overphil Dev, I decided to write this Guide on writing a powerful and simple to maintain Terrain Generation System, with a simple method of saving, it is currently in use in the Forest of Freedom Game.

THIS GUIDE IS FOR UNITY 2021.3.0f1 , I cannot guarantee it working for different versions, though it should unless drastic changes happened


This Guide will address writing a system for modifying the Unity Terrain based on Height & Alphamaps aswell as placing Gameobjects in the World, that can later be interacted by the player. The interaction system aswell as the saving system itself will not be mentioned in this guide, though they won't be too hard to get working.

First we have to think about the Systems used in this guide, some of them are poorly documented in the unity docs and so it might be quite confusing.

  1. "Terrain" is the physical gameobject that gets placed in the scene when you create a unity terrain
  2. "TerrainData" is the data this Terrain uses, it will automatically be generated as a prefab in your assets folder, but can still be modified, this holds most variables and functions that have an impact on our terrain.
  3. HeightMap is a float[ , ] (2Dimensional Array) with values between 0 and 1 and 1st and 2nd dimension describing the position of the float value, it has the resolution of TerrainData.heightmapResolution, which is the Array's length, for most use cases, it should be the same as TerrainData.heightmapHeight or TerrainData.heightmapWidth, so we will use this in this Guide.
  4. Alphamap is a float[ , , ] (3Dimensional Array) with dimension 1 and 2 describing the size of the Alphamap size and the 3rd dimension describing the Layer. This Value controls the color of the map later on, this for example is pretty badly explained in the docs. The values have to be between 0 and 1, with 0 being the lack of color and 1 being the picked Layer having a strength of 100%.


In this guide we will only cover a binary case of weather a layer is active or not, without talking about any amount of transitioning.

With the basics now covered, let's dive deeper into this system. We will set up a simple "island" with grass spawning above a certain height and Gameobjects getting spawned.

  • In the Hierarchy tab, under 3D Object > Terrain, generate a new Terrain, this will generate us a new Terrain alongside a TerrainData Prefab.


  • With this Terrain, let's create a new Script called "Terrain Generator", which will house the main generation system. We will also create a script called "Interfaces" and one called "DaveUtils" though, you can change it to your name of course.
  • Next up, please set up these variables, in our case, initDone and Invoke("FirstGeneration") can be skipped, as this is only needed when also setting up a Saving System. Use SpawnTerrain(); instead of the Invoke function.
  • Next up will be a simple SpawnTerrain function, we can call in void Start() later on, this will manage the other looping systems.
  • We first generate a new Seed, in the given range of 0f to 1000000f, this will change the look of our map each time.
  • Next up, we start all 3 functions, a short description is written as comments.
  • For the next step, switch to the "Interfaces.cs" script and add this interface to it, interfaces can be seen like "contracts" between several scripts, they can be called whenever needed
  • Finally, for this system, we split up the script into several other scripts to achieve better maintainability, to get all these scripts to run, we simple need to set up the following function
  • This function will get all components of type ITerrainHeightGeneration, an interface type we setup before and pass a heightMap through all of them, this heightmap will then in the end be drawn ontop our Terrain, to give it a wanted height.
  • Next up, set up the DaveUtils class up like this, PerlinToArray will generate a PerlinMap, a simple noise type, in a float[,]
  • CircularFalloff will generate a circular falloff map and give an float[,] as an output.
  • Now we will set up the TerrainGenStack, and write 2 new scripts, one called "heightperlingenerator" and the other "falloffgenerator"
  • This HeightPerlinGenerator can be used several times in the Stack, to increase detail of the map, changing the scale and weight variables will have an impact on the heightmap in the end, it's best you test this out yourself and find values that you like. Change the variables in the inspector, rather than Visual Studio
  • The falloff gen will turn our map from a square, into a round-ish island of sorts, it should only be used once in our Stack
  • We will then create another interface type, like before
  • In a similar vein to before, we will now add a Biome System, this needs little explaination
  • Now let's create the sand generation script, for this example, our int sandLayer will be equal to 0, we will then set every position of the alphamap array to 1 if we are using the sand layer, this should be used before any other part of the BiomeGen part of our Stack, otherwise we will override everything with sand
  • This script will build upon the first one mentioned, and will generate grass if we are above a certain height, hence why we also needed the original _heightMap from the heightmap part of the stack.
  • Lastly we will instantiate gameobjects ontop of our terrain, we could also use the build in TreeMap system, though these trees would be impossible to be interacted with, making them useless in most situations. 
  • Next up, set up these variables in your "ItemPopulate" script, that you need to create aswell
  • Don't forget about this interface obviously
  • Now let's add this function to our ItemPopulate Script, as we are only given the float[x,y] position, we need to convert it to the real world position, this can easily be achieved by using the terrain refrence
  • With most of our helper functions created, we simply try for a wanted of amount of times to spawn our gameobject, then cast a ray down, to get our height, aswell as casting an overlapsphere to check if there is anything near this one, so we don't just get clusters of items and finally instantiating everything
  • This system can then also be used to spawn sticks for example, which is set up like this
  • Remember, that the Wanted PArent is simply needed for orginizing the Hierarchy, it can also just be left out. The exclusion layer would be in this case our sand layer, if you want to place items in the sand, you might want to set the exclusion layer to the grass.

Thank you and have fun with your Generation System! I'd be happy to get credited, though it's not necessary!

All of the Mentioned code can be found on my Github at SpaceDave1337/TerrainGenerationSystem(link)

Click the following Link to see my newest Devlog, regarding the game this code originates from! (LINK)

Get Forest of Freedom

Comments

Log in with itch.io to leave a comment.

did you make this based on sebastian lagues tutorial?

While I have seen the beginning of his tutorial series, I did not use his tutorial for this. The basic premise of procedural map generation is almost always the same. One thing I very much did use was the Unity Docs, although they seem incomplete at times. In the end it was a lot of trial and error over a duration of several weeks and months.

Thanks for the question though!