Coding your own terrain generator

Modified on Tue, 05 Oct 2021 at 08:31 PM

Although you can always use the OnChunkBeforeCreate and OnChunkAfterCreate events to supply or modify the chunk voxels (check Events section), Voxel Play also allows you to create and plug-in your own terrain generator class. This feature is useful if you want complete control over how the altitude and moisture are generated.


To create your own terrain generator follow these steps:


1.- Create a new C# script, name it "MyTerrainGenerator".


2.- Your terrain generator class must inherit from VoxelPlayTerrainGenerator class and needs to have this skeleton (highlighted parts must be provided):


namespace VoxelPlay {

[CreateAssetMenu (menuName="Voxel Play/Terrain Generators/Your Generator Name",fileName = "MyTerrainGeneratorAsset", order = 101)]
public class MyTerrainGenerator : VoxelPlayTerrainGenerator {

/// <summary>
/// Used to initialize any data structure or noise textures from your own script (optional)
/// </summary>
 protected override void Init() {

              ...
        }

/// <summary>
/// Gets the altitude and moisture (optional)
/// </summary>
/// <param name="x">The x coordinate.</param>
/// <param name="z">The z coordinate.</param>
/// <param name="altitude">Altitude.</param>
/// <param name="moisture">Moisture.</param>
 public override void GetHeightAndMoisture (float x, float z, out float altitude, out float moisture) {
              ...

        }

/// <summary>
/// Fills a chunk with contents (required)
/// </summary>
/// <returns><c>true</c>, if terrain was painted, <c>false</c> otherwise.</returns>
/// <param name="chunk">The chunk object to be filled with content.</param>
public override bool PaintChunk (VoxelChunk chunk) {

            Vector3 chunkCenter = chunk.position;

              ...

        }



Your terrain generator must derive from VoxelPlayTerrainGenerator base class which already provides the maxHeight and seaLevel public fields which are necessary. A property called env is already provided by the base class so you can use it to access the API. Please note that you can’t use methods that modify the world in the terrain generator, like VoxelPlace. Instead, fill the chunk.voxels array directly.


You can override 3 methods from the base class:


The Init() method is called by Voxel Play to initialize your terrain generator. Place here any code you may need to initialize your terrain generator like reading files, textures or precompute some data. If you use voxel definitions that are not registered in the Biome or "More Voxels" sections of the World Definition, make sure to register them in the Init method using "env.AddVoxelDefinition(your_voxel_definition;". The Init() method is only called once when the world is loaded and the associated terrain generator is loaded as well.


The GetHeightAndMoisture() method is called whenever Voxel Play needs to know the elevation and moisture amount for any position in the world. Note that only X/Z positions are passed to this function. You need to return two values: altitude and moisture. If you don't use moisture in your terrain generator (the PaintChunk method) you can simply return 0 as moisture. The altitude must be a value in the 0-1 range. This value is multiplied by the maxHeight property of the terrain generator to determine the real altitude. For example, if in our terrain generator the maxHeight is 255, a value of altitude = 0.5f equals to y = 127 (255 * 0.5 rounding down).


The PaintChunk() method is called every time a chunk needs to be created. The goal of this function is to fill the chunk.voxels array with VoxelDefinitions. The chunk.voxels array is a linearized 16x16x16 array following (read Chunk structure for details). The chunk array is passed as an empty array so you just need to write the positions occupied by voxels. Voxel Play includes some terrain generators located in Voxel Play/Scripts/Private/ScriptableObjects folder:

  • FastFlatTerrainGenerator.cs: a simple terrain generator which only uses a voxel definition (no biomes).
  • TerrainDefaultGenerator.cs: this is the terrain generator used in the world demo scene. It uses a multi-step noise generator plus an optimized PaintChunk() method which calls GetHeightAndMoisture to get heightmap data including altitude and biome at each position. The biome contains the voxel definitions used at the surface and underground for each position of the chunk.
  • UnityTerrainGenerator.cs: a more advanced terrain generator which takes the TerrainData object of an existing Unity terrain and fills chunks according to the splatmaps, vegetation and tree data.


Please take some time to examine the above scripts and learn from the given code.


Important note about chunk.isAboveSurface. The PaintChunk method must set chunk.isAboveSurface = true if the chunk crosses the terrain surface or if it's above the surface. This flag determines how the voxel engine treats lighting and occlusion when a neighbour of a chunk is missing. For example, if a chunk on the surface is painted, the isAboveSurface must be set to true. This specifies that the chunk will receive Sun light from above if the above chunk is missing or empty. If you set chunk.isAboveSurface = false, when Voxel Play renders that chunk, it assumes the chunk is underground so no above lighting is by default generated (only light from existing neighbour chunks will be propagated).



You can also use the NoiseTools static class to load noise or heightmap textures in your Init, GetHeightAndMoisture or PaintChunk methods:


float[] NoiseTools.LoadNoiseTexture(tex, out textureSize): loads noise values from a texture and returns an array of float values. This array is linearized for performance.


2D noise texture methods:

NoiseTools.GetNoiseValue(array, textureSize, x, z): reads noise value from array mapped to world position x, z + offset based on world seed.

NoiseTools.GetNoiseValueBilinear(array, textureSize, x, z): same but applies bilinear filtering which returns an averaged value based on neighbours.


3D noise texture methods:

NoiseTools.GetNoiseValue(array, textureSize, x, y, z): reads noise value from array mapped to world position x, y, z + offset based on world seed.



Was this article helpful?

That’s Great!

Thank you for your feedback

Sorry! We couldn't be helpful

Thank you for your feedback

Let us know how can we improve this article!

Select atleast one of the reasons

Feedback sent

We appreciate your effort and will try to fix the article