Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Voting for the Unity Awards are OPEN! We’re looking to celebrate creators across games, industry, film, and many more categories. Cast your vote now for all categories
    Dismiss Notice
  3. Dismiss Notice

2D procedural mesh chunk handling

Discussion in '2D' started by MoltnMachine, Jun 19, 2018.

  1. MoltnMachine

    MoltnMachine

    Joined:
    May 19, 2016
    Posts:
    22
    Hi all,
    I am currently working on a terrain generator which generates terrain similar to terraria, I can choose my chunk count and change biomes and such, i can generate a line of chunks with above ground noise and caves but it updates way to slow because of the mesh size(64,64) im trying to bring it down to (10,10) but im not sure how i would make my chunks know what to contain, For example in a single column the top chunk would have just solid air but as they go down chunks it would have the randomly generated terrain then after it would have the underground area's. How would i do this?
     
  2. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,840
    I don't understand your question. You're making a terrain generator; that's what determines what the chunks should contain.

    Can you explain more what it is you're stuck on?
     
  3. MoltnMachine

    MoltnMachine

    Joined:
    May 19, 2016
    Posts:
    22
    So i drew up a quick image of what im trying to do, essentially im trying to make each 10x10 chunk link up as you can see in the image but it also know's what it needs, like some are just solid air but others have grass and dirt also it going to be an infinite world with chunk loading and it will need to make chunks based on and linking up to whats next to them.
    Does this help?
     

    Attached Files:

    Last edited: Jun 20, 2018
  4. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,840
    No. :)

    I get that you're making 10x10 chunks. I get that each piece is a different type. You said in your first post that you've made a terrain generator; that is what determines the type. So what's the problem?
     
  5. MoltnMachine

    MoltnMachine

    Joined:
    May 19, 2016
    Posts:
    22
    Nine-Chunks(10,10).png Single-Chunk(64,64).png
    The left screenshot(25 chunks of 10x10) shows that the generator can make the caves and dirt patches but im not sure what logic i will need to get a result similar to the image on the right(a single chunk with the size of 64x64).

    Note that i have disables multiple biomes for now and just trying to get the generation working.
     
    Last edited: Jun 21, 2018
  6. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,840
    It seems like your generator is affected by the chunk size. It shouldn't be; chunk size should be a free parameter you can tune as you see fit, and it makes no difference to the generator (or the generated world) at all.

    You accomplish this by either (1) generating the whole world at once, and just stuffing the randomly-generated world into chunks for storage right away; or (2) not using Random at all, but instead building your world up out of noise functions (such as Mathf.PerlinNoise) so that you can generate chunks on the fly, but still have them mesh seamlessly together.
     
  7. MoltnMachine

    MoltnMachine

    Joined:
    May 19, 2016
    Posts:
    22
    I have considered the first option but it would not work for an infinite generated world, I am currently using Mathf.PerlinNoise to generate the blocks, caves and dirt area's, the problem i am having is keeping the noise carry on with the next chunks to keep it seamless.
     
  8. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,840
    Well, perhaps you should post your code for the generator. If you're using PerlinNoise based on the world coordinates of each tile, then it should be seamless, no matter how the tiles are divided into chunks.
     
  9. MoltnMachine

    MoltnMachine

    Joined:
    May 19, 2016
    Posts:
    22
    This is the main generator script.
     

    Attached Files:

  10. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,840
    So you're not just using noise; you're using UnityEngine.Random. You'll need to stop doing that.

    In case anybody else is following this thread, here's the code, without having to download it and open in an editor.
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using System.Linq;
    5. using System;
    6.  
    7. namespace WorldGeneration.Generator
    8. {
    9.     public class GeneratorVoxel : MonoBehaviour
    10.     {
    11.         public int wantedChunksX = 5;
    12.         public int wantedChunksY = 5;
    13.         public int width = 64;
    14.         public int height = 64;
    15.         public List<TerrainChunk> chunks = new List<TerrainChunk>();
    16.  
    17.         private MeshCollider col;
    18.  
    19.         private float textureUvSize = 0.1f;
    20.  
    21.         [HideInInspector] public bool update = false;
    22.         [Range(-100000, 100000)]
    23.         public int noiseSeed;
    24.         public bool randomiseSeed;
    25.         [Space(2)]
    26.         public bool randomTerrainTypes;
    27.         public List<TerrainType> terrainTypes = new List<TerrainType>();
    28.  
    29.         void Start()
    30.         {
    31.             if (randomiseSeed)
    32.             {
    33.                 noiseSeed = UnityEngine.Random.Range(-100000, 100000);
    34.             }
    35.  
    36.             for (int x = 0; x < wantedChunksX; x++)
    37.             {
    38.                 for (int y = 0; y < wantedChunksY; y++)
    39.                 {
    40.                     GenTerrain(x,y);
    41.                 }
    42.             }
    43.         }
    44.         private void OnValidate()
    45.         {
    46.             if(width > 64)
    47.             {
    48.                 width = 64;
    49.             }
    50.             else if(width <= 0)
    51.             {
    52.                 width = 1;
    53.             }
    54.             if (height > 64)
    55.             {
    56.                 height = 64;
    57.             }
    58.             else if (height <= 0)
    59.             {
    60.                 height = 1;
    61.             }
    62.         }
    63.  
    64.         public void UpdateChunkMesh(TerrainChunk chunk)
    65.         {
    66.             BuildMesh(chunk);
    67.             UpdateMesh(chunk);
    68.         }
    69.  
    70.         void GenerateTopTile(TerrainChunk chunk)//GenerateMeshFeatures? check chunk biome and generate to varibles?
    71.         {
    72.             bool lastBlockWasAir = false;
    73.  
    74.             for (int _x = 0; _x < width; _x++)
    75.             {
    76.                 for (int _y = height - 1; _y > 0; _y--)
    77.                 {
    78.  
    79.                     if (chunk.blocks[_x, _y] == 0)
    80.                     {
    81.                         lastBlockWasAir = true;
    82.                     }
    83.                     else if (chunk.blocks[_x, _y] != 0)
    84.                     {
    85.                         if (lastBlockWasAir)
    86.                         {
    87.                             lastBlockWasAir = false;
    88.                             chunk.blocks[_x, _y] = 3;
    89.                             try
    90.                             {
    91.                                 chunk.blocks[_x, (_y - chunk.terrainType.middleTileUnderTopTileHeight)] = 2;
    92.                             }
    93.                             catch (IndexOutOfRangeException e)
    94.                             {
    95.  
    96.                             }
    97.                             break;
    98.                         }
    99.                     }
    100.                 }
    101.             }
    102.         }
    103.  
    104.         void GenTerrain(int offsetX,int offsetY)
    105.         {
    106.             List<TerrainType> avalibleTerrain = new List<TerrainType>();
    107.             for (int terrain = 0; terrain < terrainTypes.Count; terrain++)
    108.             {
    109.                 if (terrainTypes[terrain].useInList)
    110.                 {
    111.                     avalibleTerrain.Add(terrainTypes[terrain]);
    112.                 }
    113.             }
    114.             TerrainType selectedTerrain;
    115.             int terrainIndex = UnityEngine.Random.Range(0, avalibleTerrain.Count);
    116.             selectedTerrain = terrainTypes[terrainIndex];
    117.  
    118.             byte[,] blocksInChunk = new byte[width, height];
    119.             for (int x = 0; x < width; x++)
    120.             {
    121.                 int stone = Noise(x, 0, 80, 20, 1);
    122.                 stone += Noise(x, noiseSeed, 50, 10, 1);
    123.                 stone += 35;
    124.  
    125.                 int dirt = Noise(x, 0, 100, 20, 1);
    126.                 dirt += Noise(x, noiseSeed, 10, 10, 1);
    127.                 dirt += 35;
    128.  
    129.                 for (int y = 0; y < height; y++)
    130.                 {
    131.                     if (y < stone)
    132.                     {
    133.                         blocksInChunk[x, y] = 1;
    134.  
    135.                         //The next three lines make dirt spots in random places
    136.                         if (Noise(x, y, 12, 16, 1) > 10)
    137.                         {  //dirt spots
    138.                             blocksInChunk[x, y] = 2;
    139.                         }
    140.                         //The next three lines remove dirt and rock to make caves in certain places
    141.                         if (Noise(x, y * 2, 16, 14, 1) > 10)
    142.                         { //Caves
    143.                             blocksInChunk[x, y] = 0;
    144.                         }
    145.                     }
    146.                     else if (y < dirt)
    147.                     {
    148.                         blocksInChunk[x, y] = 2;
    149.                     }
    150.                 }
    151.             }
    152.             TerrainChunk chunk = new TerrainChunk();
    153.             chunk.chunkNumber = chunks.Count + 1;
    154.             chunk.terrainType = selectedTerrain;
    155.             chunk.offset = new Vector2(offsetX,offsetY);
    156.  
    157.             GameObject terrainOb = CreateNewChunkObject();
    158.             chunk.mesh = terrainOb.GetComponent<MeshFilter>().mesh;
    159.             terrainOb.GetComponent<MeshRenderer>().sharedMaterial = chunk.terrainType.materialType;
    160.             terrainOb.transform.localPosition = Vector3.back;
    161.             terrainOb.transform.SetParent(gameObject.transform);
    162.             chunk.terrain = terrainOb;
    163.             chunk.blocks = blocksInChunk;
    164.             chunks.Add(chunk);
    165.  
    166.             GenerateTopTile(chunk);
    167.             BuildMesh(chunk);
    168.             UpdateMesh(chunk);
    169.         }
    170.  
    171.         GameObject CreateNewChunkObject()
    172.         {
    173.             GameObject terrainOb = new GameObject("TerrainChunk");
    174.             terrainOb.AddComponent<MeshFilter>();
    175.             terrainOb.AddComponent<PolygonCollider2D>();
    176.             terrainOb.AddComponent<Mesh2DCollider>();
    177.             terrainOb.AddComponent<MeshRenderer>();
    178.             terrainOb.AddComponent<ChunkObject>();
    179.             terrainOb.layer = 8;
    180.             terrainOb.isStatic = true;
    181.             return terrainOb;
    182.         }
    183.  
    184.         int Noise(int x, int y, float smoothness, float mag, float exp, bool useSeed = true)
    185.         {
    186.             int chunkAddition = chunks.Count - 1;
    187.             if(useSeed)
    188.             {
    189.                 return (int)(Mathf.Pow((Mathf.PerlinNoise(x / smoothness + (noiseSeed + chunkAddition), y / smoothness + (noiseSeed + chunkAddition)) * mag), (exp)));
    190.             }
    191.             return (int)(Mathf.Pow((Mathf.PerlinNoise(x / smoothness + chunkAddition, y / smoothness + chunkAddition) * mag), (exp)));
    192.         }
    193.  
    194.         void BuildMesh(TerrainChunk chunk)
    195.         {
    196.             for (int px = 0; px < width; px++)
    197.             {
    198.                 for (int py = 0; py < height; py++)
    199.                 {
    200.                     int x = px + (width * (int)chunk.offset.x);//Becomes the coord in world space
    201.                     int y = py + (width * (int)chunk.offset.y);//Becomes the coord in world space
    202.  
    203.  
    204.                     //If the block is not air
    205.                     if (chunk.blocks[px, py] != 0)
    206.                     {
    207.                         if (chunk.blocks[px, py] == 1)
    208.                         {
    209.                             GenerateBlockGraphics(x, y, chunk.terrainType.blockTypes[0].texture, chunk);
    210.                         }
    211.                         else if (chunk.blocks[px, py] == 2)
    212.                         {
    213.                             GenerateBlockGraphics(x, y, chunk.terrainType.blockTypes[1].texture, chunk);
    214.                         }
    215.                         else if (chunk.blocks[px, py] == 3)
    216.                         {
    217.                             GenerateBlockGraphics(x, y, chunk.terrainType.blockTypes[2].texture, chunk);
    218.                         }
    219.                     }
    220.                     //End air block check
    221.                 }
    222.             }
    223.         }
    224.  
    225.         void GenerateBlockGraphics(int x, int y, Vector2 texture, TerrainChunk chunk)
    226.         {
    227.             chunk.newVertices.Add(new Vector3(x, y, 0));
    228.             chunk.newVertices.Add(new Vector3(x + 1, y, 0));
    229.             chunk.newVertices.Add(new Vector3(x + 1, y - 1, 0));
    230.             chunk.newVertices.Add(new Vector3(x, y - 1, 0));
    231.  
    232.             chunk.newTriangles.Add(chunk.squareCount * 4);
    233.             chunk.newTriangles.Add((chunk.squareCount * 4) + 1);
    234.             chunk.newTriangles.Add((chunk.squareCount * 4) + 3);
    235.             chunk.newTriangles.Add((chunk.squareCount * 4) + 1);
    236.             chunk.newTriangles.Add((chunk.squareCount * 4) + 2);
    237.             chunk.newTriangles.Add((chunk.squareCount * 4) + 3);
    238.  
    239.             chunk.newUV.Add(new Vector2(textureUvSize * texture.x, textureUvSize * texture.y + textureUvSize));
    240.             chunk.newUV.Add(new Vector2(textureUvSize * texture.x + textureUvSize, textureUvSize * texture.y + textureUvSize));
    241.             chunk.newUV.Add(new Vector2(textureUvSize * texture.x + textureUvSize, textureUvSize * texture.y));
    242.             chunk.newUV.Add(new Vector2(textureUvSize * texture.x, textureUvSize * texture.y));
    243.  
    244.             chunk.squareCount++;
    245.         }
    246.  
    247.         void UpdateMesh(TerrainChunk chunk)
    248.         {
    249.             chunk.mesh.Clear();
    250.             chunk.mesh.vertices = chunk.newVertices.ToArray();
    251.             chunk.mesh.triangles = chunk.newTriangles.ToArray();
    252.             chunk.mesh.uv = chunk.newUV.ToArray();
    253.             chunk.mesh.RecalculateNormals();
    254.             //Clear graphics lists
    255.             chunk.newVertices.Clear();
    256.             chunk.newTriangles.Clear();
    257.             chunk.newUV.Clear();
    258.             chunk.squareCount = 0;
    259.             //Create mesh and set it up
    260.             Mesh newMesh = new Mesh();
    261.             chunk.terrain.GetComponent<ChunkObject>().UpdateMesh(chunk.mesh);
    262.             chunk.terrain.GetComponent<ChunkObject>().UpdateTerrainData(chunk);
    263.             chunk.terrain.GetComponent<ChunkObject>().UpdatePolyCollider();
    264.         }
    265.  
    266.  
    267.         byte Block(int x, int y, TerrainChunk chunk)
    268.         {
    269.             if (x == -1 || x == width || y == -1 || y == height)
    270.             {
    271.                 return (byte)1;
    272.             }
    273.             while (x >= width)
    274.             {
    275.                 x = x -= width;
    276.             }
    277.             return chunk.blocks[x, y];
    278.         }
    279.  
    280.         ChunkObject Chunk(int offsetX, int offsetY)
    281.         {
    282.             for (int i = 0; i < chunks.Count; i++)
    283.             {
    284.                 if(offsetX == (int)chunks[i].offset.x && offsetY == (int)chunks[i].offset.y)
    285.                 {
    286.                     return chunks[i].terrain.GetComponent<ChunkObject>();
    287.                 }
    288.             }
    289.             return null;
    290.         }
    291.     }
    292.  
    293.  
    294. }
    295. [System.Serializable]
    296. public class TerrainChunk
    297. {
    298.     public int chunkNumber;
    299.     public TerrainType terrainType;
    300.     public Vector2 offset;//what coord(*) the chunk is at
    301.     public byte[,] blocks;
    302.     public GameObject terrain;
    303.     public Mesh mesh;
    304.     public List<Vector3> newVertices = new List<Vector3>();
    305.     public List<int> newTriangles = new List<int>();
    306.     public List<Vector2> newUV = new List<Vector2>();
    307.     public int squareCount;
    308. }
    309. [System.Serializable]
    310. public class TerrainType
    311. {
    312.     public string terrainName;
    313.     public bool useInList;
    314.     public int topAirHeight;
    315.     public int middleTileUnderTopTileHeight;
    316.     public Material materialType;
    317.     public List<BlockType> blockTypes = new List<BlockType>();
    318.     [Header("Ores")]
    319.     public List<OreType> ores = new List<OreType>();
    320.  
    321. }
    322. [System.Serializable]
    323. public struct OreType
    324. {
    325.     public string oreName;
    326.     public Vector2 texture;
    327.     public float oreChance;
    328. }
    329. [System.Serializable]
    330. public struct BlockType
    331. {
    332.     public string blockName;
    333.     public Vector2 texture;
    334. }
    335.  
     
  11. MoltnMachine

    MoltnMachine

    Joined:
    May 19, 2016
    Posts:
    22
    I don't see Random anywhere that could affect the chunk generation apart from the seed.
     
  12. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,840
    Yes, the seed for one — take that out (unless I misunderstand how it's used). Then you're using Random to pick the terrain type on line 115. That's got to go too.

    If you're still stuck, refactor your generator completely so that it doesn't generate by chunks at all. What you need is a function that, given the x and y coordinates of a single tile anywhere in the world, returns the corresponding tile type. It will then be impossible for that to generate chunk seams, because that function doesn't know anything about chunks. (And you'll then see that you can't pick a terrain type per chunk — terrain types, if you have them at all, must also be chosen via noise functions.)