Search Unity

Help with procedural terrain efficiency.

Discussion in 'Scripting' started by Aidenjl, Nov 23, 2015.

  1. Aidenjl

    Aidenjl

    Joined:
    Jan 5, 2014
    Posts:
    81
    Hello!

    So for the past few hours I've been working on a 2D procedural terrain system (Basically trying to clone the terraria terrain generation). However I quickly encountered a problem that I should've seen coming.
    upload_2015-11-23_1-3-49.png
    As you can see it can generate a fairly simple terrain currently. However the problem is with efficiently, I quickly discovered that there is no way my generation script will be able to handle generating a terraria sized world (I tried and Unity ran out of memory). So I was wondering if there was an easy shortcut to solve my problem. The terrain generation currently generates the world in chunks so I was wondering if I should make it so that the chunk is generated, serialized and deleted so that it can be loaded up when it needs to be rendered. Another problem is that it takes waaaay too long to generate even a world this size (10 seconds or so, which may not seem like much but when I want to generate a much larger terrain with biomes it is going to take forever).

    Here's my current script:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. [System.Serializable]
    6. public class Tile
    7. {
    8.     public GameObject tile; // The tile you want to spawn
    9.     public float pernlinMultiplier; // how big the veins are
    10.     public float popThreshold; // The chance of it spawning (the lower the more common and the larger the groups)
    11.     public int seed;
    12. }
    13.  
    14. [System.Serializable]
    15. public class Biome
    16. {
    17.     public string name;
    18.     public GameObject baseTile; // This will be the main tile in the biome
    19.     public int rarity; // Rarity of the biome
    20.     public List<Tile> tiles = new List<Tile>(); // List of tiles that can spawn in the biome
    21.     public int maxHeight; // Max height that the biome can spawn at
    22.     public int minHeight; // Min height the biome can spawn at
    23. }
    24. public class Terraingen : MonoBehaviour
    25. {
    26.     public int worldSeed;
    27.  
    28.     public int chunkWidth; // Width of the rendering chunks
    29.     public int chunkDepth; // Depth of the rendering chunks
    30.  
    31.     public int worldWidth;
    32.     public int worldDepth;
    33.  
    34.     public int biomeMaxSize;
    35.     public int biomeMinSize;
    36.  
    37.     public float hillMultiplier; // Hill depth
    38.     public float hillSample; // Hill closeness
    39.     public int hillSeed;
    40.  
    41.     public float perlinMultiplier; // Basically determines the size of caves
    42.  
    43.     public float popThreshold = 0.5f; // Chance of caves, the smaller the more common they are
    44.  
    45.     public List<Biome> biomes = new List<Biome>();
    46.  
    47.     public List<GameObject> chunks = new List<GameObject>();
    48.  
    49.     void Start ()
    50.     {
    51.         Generate();
    52.     }
    53.  
    54.     void Generate()
    55.     {
    56.         hillSeed = worldSeed;
    57.  
    58.         foreach (Biome b in biomes)
    59.         {
    60.             for (int i = 0; i < b.tiles.Count; ++i)
    61.             {
    62.                 b.tiles[i].seed = worldSeed * (i + 1);
    63.             }
    64.         }
    65.  
    66.         for (int i = 0; i < Mathf.Round(worldWidth / chunkWidth); ++i) // Figure out how many chunks will fit in the world width wise
    67.         {
    68.             for (int i1 = 0; i1 < Mathf.Round(worldDepth / chunkDepth); ++i1) // figure out how many chunks will fit in the world height wise
    69.             {
    70.                 GenerateChunk(i * chunkWidth, i1 * chunkDepth); // Spawn the chunks according to the world size vs chunk size
    71.             }
    72.         }
    73.     }
    74.  
    75.     void GenerateChunk(int x, int y)
    76.     {
    77.         GameObject chunk = new GameObject(); // Instantiate the chunk parent
    78.         chunk.name = "Chunk";
    79.         chunk.AddComponent<BoxCollider2D>();
    80.         BoxCollider2D bc = chunk.GetComponent<BoxCollider2D>();
    81.         bc.offset = new Vector2(chunkWidth / 2, chunkDepth / 2);
    82.         bc.size = new Vector2(chunkWidth, chunkDepth);
    83.         chunk.transform.position = new Vector3(x, y, 1);
    84.         for (int i = 0; i < chunkWidth; ++i) // Cycle through each row of the chunk
    85.         {
    86.             for (int i1 = 0; i1 < chunkDepth; ++i1) // Cycle through each column of the chunk
    87.             {
    88.                 if (!((i1 + y) > worldDepth - (Mathf.PerlinNoise(x + i / hillSample, hillSeed) * hillMultiplier))) // This part is to generate hills
    89.                 {
    90.                     PlaceTile(i + x, i1 + y, chunk, GetOccupyingTile(i + x, i1 + y, 0));
    91.                 }
    92.             }
    93.         }
    94.         chunks.Add(chunk);
    95.     }
    96.  
    97.     GameObject GetOccupyingTile(int x, int y, int biome)
    98.     {
    99.         GameObject tile = biomes[biome].baseTile;
    100.         foreach (Tile t in biomes[biome].tiles)
    101.         {
    102.             float val = Mathf.PerlinNoise((x + t.seed) * t.pernlinMultiplier, (y + t.seed) * t.pernlinMultiplier);
    103.             if (val >= t.popThreshold)
    104.             {
    105.                 tile = t.tile;
    106.             }
    107.         }
    108.         return tile;
    109.     }
    110.  
    111.     void PlaceTile(int x, int y, GameObject chunk, GameObject tile)
    112.     {
    113.         GameObject tileToPlace = (GameObject)GameObject.Instantiate(tile, new Vector3(x, y, 1), Quaternion.identity);
    114.         tileToPlace.transform.parent = chunk.transform;
    115.     }
    116. }
    Any help or suggestions would be appreciated,

    Many thanks,

    Aiden Leeming
     
  2. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    What it comes down to is that you can't have a separate GameObject for every tile. There isn't really any easy way to solve it (well, aside from just buying an asset to take care of it for you). One possibility is to create meshes using the Mesh class, but what I did with SpriteTile is to create only enough sprites to fill the screen, and recycle them as the camera moves. You can see with this demo that you can procedurally generate a million tiles (using Perlin noise similar to what you did, in fact) in a small fraction of a second; also it doesn't use much RAM (~7MB in this case).

    --Eric
     
    Kiwasi likes this.
  3. Aidenjl

    Aidenjl

    Joined:
    Jan 5, 2014
    Posts:
    81
    Oh wow, that's actually a really good idea, thanks I'll give it a shot