Search Unity

Resolved How to make Biomes in 2d perlin noise (TileMaps)

Discussion in 'Scripting' started by EnderSkelly34, Aug 17, 2021.

  1. EnderSkelly34

    EnderSkelly34

    Joined:
    Jun 13, 2020
    Posts:
    27
    Hello, I am making a 2d Game and I currently have a working Perlin Noise script that makes separate temp/rainfall stats for each tile and it is completely random. Only I do not want it to be completely random (I am using Perlin Noise), I want it to be pseudo Random, in which biomes are a minimum size, or at least look more natural. (Separate Biomes, Natural Look)

    What I Want (Similar):

    render-trees-2d-terrain.png


    What I Get:

    Capture.PNG

    My Code Is here:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Tilemaps;
    5. using UnityEditor;
    6.  
    7. public class WorldGeneration : MonoBehaviour
    8. {
    9.  
    10.     public Tilemap tileMap;
    11.     public TileBase[] forestTiles;
    12.     public TileBase[] desertTiles;
    13.     public GameObject[] forestVegetation;
    14.     public GameObject[] desertVegetation;
    15.  
    16.     public float perlin;
    17.  
    18.     public int xOffset;
    19.     public int yOffset;
    20.  
    21.     public float width;
    22.     public float height;
    23.  
    24.     public float vegetationChance;
    25.  
    26.     private void Start()
    27.     {
    28.  
    29.         generateWorld();
    30.  
    31.     }
    32.  
    33.     public void generateWorld()
    34.     {
    35.        
    36.         for (int x = xOffset; x < width / 2; x++)
    37.         {
    38.  
    39.             for (int y = yOffset; y < height / 2; y++)
    40.             {
    41.                
    42.                 float rainFall = Mathf.PerlinNoise(x + Random.value, y + Random.value);
    43.                 float temperature = Mathf.PerlinNoise(x + Random.value, y + Random.value);
    44.  
    45.                 generateTile(x, y, temperature, rainFall);
    46.  
    47.             }
    48.  
    49.         }
    50.  
    51.     }
    52.  
    53.     void generateTile(int x, int y, float temperature, float rainFall)
    54.     {
    55.  
    56.         Debug.Log("Generating Tile");
    57.  
    58.         perlin = Mathf.PerlinNoise(x + Random.value, y + Random.value);
    59.  
    60.         float random = Random.Range(0f, 1f);
    61.  
    62.         if (temperature <= 0.75f && rainFall >= 0.15f || temperature > 0.75f && rainFall >= 0.15f) {
    63.  
    64.             if(random <= vegetationChance)
    65.             {
    66.  
    67.                 perlin *= forestTiles.Length - 1;
    68.                 int tileNum = Mathf.RoundToInt(perlin);
    69.  
    70.                 tileMap.SetTile(new Vector3Int(x, y, 0), forestTiles[tileNum]);
    71.                 GameObject plant = Instantiate(forestVegetation[Random.Range(0, forestVegetation.Length)], new Vector3(x + 0.5f, y + 5.15f, 0), Quaternion.identity);
    72.                 plant.transform.parent = gameObject.transform.GetChild(0);
    73.  
    74.             } else {
    75.  
    76.                 perlin *= forestTiles.Length - 1;
    77.                 int tileNum = Mathf.RoundToInt(perlin);
    78.  
    79.                 tileMap.SetTile(new Vector3Int(x, y, 0), forestTiles[tileNum]);
    80.  
    81.             }
    82.  
    83.         } else if (temperature > 0.75f && rainFall < 0.15f || temperature <= 0.75f && rainFall < 0.15f) {
    84.  
    85.  
    86.             if (random <= vegetationChance)
    87.             {
    88.  
    89.                 perlin *= desertTiles.Length - 1;
    90.                 int tileNum = Mathf.RoundToInt(perlin);
    91.  
    92.                 tileMap.SetTile(new Vector3Int(x, y, 0), desertTiles[tileNum]);
    93.                 GameObject plant = Instantiate(desertVegetation[Random.Range(0, desertVegetation.Length)], new Vector3(x, y, 0), Quaternion.identity);
    94.                 plant.transform.parent = gameObject.transform.GetChild(0);
    95.  
    96.             } else {
    97.  
    98.                 perlin *= desertTiles.Length - 1;
    99.                 int tileNum = Mathf.RoundToInt(perlin);
    100.  
    101.                 tileMap.SetTile(new Vector3Int(x, y, 0), desertTiles[tileNum]);
    102.  
    103.             }
    104.  
    105.         }
    106.  
    107.     }
    108.  
    109. }
    110.  

    Settings in Inspector:

    Capturee.PNG
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,745
    These lines are rechoosing ALL the random offset every single cell for every single axis for every single quantity for every single point.

    Despite the involvement of PerlinNoise, this rechoosing makes your output essentially indistinguishable from random noise.

    BEFORE you make your level, store the x, y offsets for rain and temperature, and use them throughout the generation.

    NOTE: you will also likely need a scaling term that you multiply the X and Y by, because moving at +1 per step is pretty fast. If you don't know what I mean, refer to a few examples of Perlin noise on the net.
     
    exiguous likes this.
  3. exiguous

    exiguous

    Joined:
    Nov 21, 2010
    Posts:
    1,749
    Computers cannot create real randomness out of the box. When you use such functions you always get pseudo random numbers. As Kurt pointed pointed out you get this because you add a random factor to your sampling coordinate. I wondered yesterday in your other thread already what this should be good for. For an approach which uses the location of a "cell" as seed for a custom pseudorandom number generator I can recommend this video. It's in another Engine but the concepts also apply to Unity.

    As for biomes I suggest to lookup some tutorials about biome generation as there are many different approaches. Probably you will need to use scaled coordinates for your biome perlin as when you sample it with "real world" coordinates you get many bumps. You need to sample it from a smaller region to have few bumps similar to mountains.

    Edit: As clarification. When you scale the sampling coordinates with a value < 1 you "zoom in" the noise. I suggest to visualize the noise in a texture so you get a feeling how perlin of a certain scale looks like.
     
    Last edited: Aug 17, 2021
    Kurt-Dekker likes this.
  4. EnderSkelly34

    EnderSkelly34

    Joined:
    Jun 13, 2020
    Posts:
    27
    @exiguous @Kurt-Dekker

    Maybe Use Multidimensional arrays to store the value for each tile, and go from there?
     
  5. exiguous

    exiguous

    Joined:
    Nov 21, 2010
    Posts:
    1,749
    What should this be good for? I assume you only need the noise for creation time (when you don't want to run further simulations like precipitation). So once the tiles are decided you would discard it anyway. So you could also calculate it "on the fly" for each tile/cell and save the memory.

    I already suggested to visualize the noise you get from perlin and look for patterns you would like your biomes to look like. Perlin will give you some values similar to heights of hills. For biomes usually voronoi noise is suited better. Or you "combine" different perlin values similiar like you already did fe one map for temp, one for humidity, one for height and decide which biome this is.

    If you feel overwhelmed this is natural since procedrual terrain generation IS a complex topic. Even more so when you want it to "look right" or realistic. Then watch some tutorials. You could also define a biome map in another application (remember voronoi noise) and use a random section of it. Or you leave it as is and focus more on gameplay for the moment as I doubt it to be game breaking.
     
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,745
    Make sure you stay focused on the problem at hand. Your questions so far have to do with producing these areas of your game and getting the frequency reasonable.

    If that is still what you are interested in, multidimensional arrays have nothing to do with this. They would simply be one possible way to store data about your world for later rapid lookup.
     
  7. EnderSkelly34

    EnderSkelly34

    Joined:
    Jun 13, 2020
    Posts:
    27
    @exiguous @Kurt-Dekker

    Got it! Randomly Generating worlds with multiple biomes, plants, and such! Here's the code!

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Tilemaps;
    5. using UnityEditor;
    6.  
    7. public class WorldGeneration : MonoBehaviour
    8. {
    9.  
    10.     public Tilemap tileMap;
    11.     public TileBase[] forestTiles;
    12.     public TileBase[] desertTiles;
    13.     public GameObject[] forestVegetation;
    14.     public GameObject[] desertVegetation;
    15.  
    16.     public float perlin;
    17.     public float scale;
    18.     public float seed;
    19.  
    20.     public int xOffset;
    21.     public int yOffset;
    22.  
    23.     public float width;
    24.     public float height;
    25.  
    26.     public float vegetationChance;
    27.  
    28.     private void Start()
    29.     {
    30.  
    31.         seed = Random.Range(0f, 100000f);
    32.         generateWorld();
    33.  
    34.     }
    35.  
    36.     public void generateWorld()
    37.     {
    38.  
    39.         for (int y = yOffset; y < height / 2; y++)
    40.         {
    41.  
    42.             for (int x = xOffset; x < width / 2; x++)
    43.             {
    44.  
    45.                 float xCoord = xOffset + x / width * scale + seed;
    46.                 float yCoord = yOffset + y / height * scale + seed;
    47.  
    48.                 float rainFall = Mathf.PerlinNoise(xCoord, yCoord);
    49.                 float temperature = Mathf.PerlinNoise(xCoord, yCoord);
    50.  
    51.                 generateTile(x, y, temperature, rainFall);
    52.  
    53.             }
    54.  
    55.         }
    56.  
    57.     }
    58.  
    59.     void generateTile(int x, int y, float temperature, float rainFall)
    60.     {
    61.  
    62.         Debug.Log("Generating Tile");
    63.  
    64.         perlin = Mathf.PerlinNoise(x + Random.value, y + Random.value);
    65.  
    66.         float random = Random.Range(0f, 1f);
    67.  
    68.         if (temperature <= 0.75f && rainFall >= 0.25f || temperature > 0.75f && rainFall >= 0.25f) {
    69.  
    70.             vegetationChance = 0.0175f;
    71.  
    72.             if(random <= vegetationChance)
    73.             {
    74.  
    75.                 perlin *= forestTiles.Length - 1;
    76.                 int tileNum = Mathf.RoundToInt(perlin);
    77.  
    78.                 tileMap.SetTile(new Vector3Int(x, y, 0), forestTiles[tileNum]);
    79.                 GameObject plant = Instantiate(forestVegetation[Random.Range(0, forestVegetation.Length)], new Vector3(x + 0.5f, y + 5.15f, 0), Quaternion.identity);
    80.                 plant.transform.parent = gameObject.transform.GetChild(0);
    81.                 plant.layer = 1;
    82.  
    83.             } else {
    84.  
    85.                 perlin *= forestTiles.Length - 1;
    86.                 int tileNum = Mathf.RoundToInt(perlin);
    87.  
    88.                 tileMap.SetTile(new Vector3Int(x, y, 0), forestTiles[tileNum]);
    89.  
    90.             }
    91.  
    92.         } else if (temperature > 0.75f && rainFall < 0.25f || temperature <= 0.75f && rainFall < 0.25f) {
    93.  
    94.             vegetationChance = 0.015f;
    95.  
    96.             if (random <= vegetationChance)
    97.             {
    98.  
    99.                 perlin *= desertTiles.Length - 1;
    100.                 int tileNum = Mathf.RoundToInt(perlin);
    101.  
    102.                 tileMap.SetTile(new Vector3Int(x, y, 0), desertTiles[tileNum]);
    103.                 GameObject plant = Instantiate(desertVegetation[Random.Range(0, desertVegetation.Length)], new Vector3(x + 0.5f, y + 0.5f, 0), Quaternion.identity);
    104.                 plant.transform.parent = gameObject.transform.GetChild(0);
    105.  
    106.             } else {
    107.  
    108.                 perlin *= desertTiles.Length - 1;
    109.                 int tileNum = Mathf.RoundToInt(perlin);
    110.  
    111.                 tileMap.SetTile(new Vector3Int(x, y, 0), desertTiles[tileNum]);
    112.  
    113.             }
    114.  
    115.         }
    116.  
    117.     }
    118.  
    119. }
    120.  
    f.PNG

    w.PNG
     
    exiguous and Kurt-Dekker like this.