-
Notifications
You must be signed in to change notification settings - Fork 11
Landscape
The Landscape system generates a randomly generated terrain in runtime based on the empty parcels of Genesis City. In the case of named Worlds, it generates a terrain based on the owned parcels and a border padding.
If you want to try out different landscape settings, you can go to the InifiniteTerrain
scene and press Generate
The most critical part of terrain generation is setting up proper noise settings so each asset has its own rules. The noise algorithm uses different noise types supported by Unity Jobs system ( PERLIN, SIMPLEX, CELLULAR ).
Here's more info about how we generate noise: https://medium.com/@yvanscher/playing-with-perlin-noise-generating-realistic-archipelagos-b59f004d8401
In order to setup a noise asset you have to create a scriptable object called NoiseData by right-clicking on a folder and selecting Create > Landscape > Noise Data
In the image above you can see the custom inspector of the Noise Data
which is going to re-generate a texture in real-time every time you change any setting.
In order to re-use a Noise Data asset we created a Noise Variant asset which uses the settings created by another asset and just overrides the seed
This is an even more complex noise that combines other noises and can also do some additional simple operations without noise
Since some noise values get re-used multiple times, we had to create a cache system for noise, so in order to get the noise working correctly and fast, you have to use the NoiseGeneratorCache
class which receives any of the scriptable object variants (all implement INoiseDataFactory
) and returns an instance of INoiseGenerator
. You have to Dispose this class once you are done with all the noise generation.
INoiseGenerator
entry point is Schedule
, which receives a NoiseDataPointer
and a JobHandle
(which is optional), this function will schedule a NoiseJob
that will create a Unity IJobParallelFor
(or a bunch of them, depending on the complexity of the noise) and will return a JobHandle that can be awaited and Completed.
The NoiseDataPointer
defines the size and position of the noise, if you need a 512x512 texture you just set up the size value as 512.
If you need more textures of the same noise but in different positions, it is recommended that the offsets are set-up as multipliers of the size, for example for a 2x2 grid of 512x512 textures you might want to Schedule 4 jobs with these settings:
- size = 512, offsetX = 0, offsetY = 0
- size = 512, offsetX = 512, offsetY = 0
- size = 512, offsetX = 0, offsetY = 512
- size = 512, offsetX = 512, offsetY = 512
To get the data you will need to access the INoiseGenerator.GetResult(NoiseDataPointer)
all results are cached until the class is disposed.
The results are formatted in a 1 Directional array NativeArray<float>
Generated noise supports negative values, so they can be used to generate rivers and ponds, to see the desired results of height variance check the Show Height
toggle which will show terrain-like colors where blue is ocean level, green is ground and it goes to red if there's a high mountain.
Since the Unity's terrain API does not accept a texture as an input in multiple parts of it, the Noise generator only creates numerical values.
But if you want an example on how we create a texture for the Noise Data inspector you can check the NoiseTextureGenerator
class which uses the same INoiseGenerator
to generate the data and then uses a RenderTexture
and a ComputeBuffer
to create the texture.
Check CS_NoiseTexture.compute for more information on how the texture is being colored.
The Landscape system uses Unity's Terrain API, which is automatically set up based on the user settings.
This is the main asset used by the TerrainGenerator
class.
This class contains all the configurable settings for the terrain and also the heightmap noise, materials, tree, and detail assets.
This class defines an asset for the terrain, it can be a tree with LODs (or whatever physical asset) or just a detail (which is commonly grass and flowers) that has no colliders.
Since we don't want to have mountains on single 1x1 empty parcels, we came up with a system that defines the max height of an empty parcel based on how many empty parcels it has around it.
In this example you can see that owned parcels are marked with an x
, the empty parcel that is far away from the owned parcels will have a higher height than the others.
The base height of each point of the empty parcel is interpolated linearly toward the height of the neighboring parcels so they are properly blending. This is also multiplied by the noise of the heightmap.