Search Unity

A Good Way To Instantiate Thousands of Objects

Discussion in '2D' started by themusicguy1989, Oct 3, 2019.

  1. themusicguy1989

    themusicguy1989

    Joined:
    Feb 22, 2017
    Posts:
    15
    So, I'm pretty new to Unity. Been messing about for over 2 years off and on, but I haven't completed anything. I'm making this RTS game as my first game. The players will be collecting the tiles on the map to craft stuff. And they will need to see the entire map at one time and the map will constantly be changing (tiles being removed and structures being built). Using offset hexagon tiles, so I decided not to use the tilemap because I wasn't sure of a good way to make the tiles go off-grid with the tilemap. Idk if the tilemap would make it speedier or not. So the problem that I'm having is the map takes a long time to load and everything responds slowly once everything is loaded. The map that I uploaded is about half the size of the map I will need for a basic game.


    So what I'm wondering is, is there a better way to instantiate all of these tiles? They will not have rigidbodies but, they will have colliders, and scripts on them. Also, I'm getting an ArugmentOutOfRangeException.

    ArugmentOutOfRangeException: Index was out of range, Must be non-negative and less than the size of the collection.

    I don't understand why I'm getting this exception.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class MapGenerator : MonoBehaviour
    6. {
    7.     [Header("Map Generator Attributes")]
    8.     [SerializeField] float mapDelayTime = 0.01f;
    9.     [SerializeField] int mapWidth = 256;
    10.     [SerializeField] int mapHeight = 256;
    11.     [SerializeField] int dirtPercentage = 45;
    12.     [SerializeField] int clayPercentage = 25;
    13.     [SerializeField] int sandPercentage = 15;
    14.     [SerializeField] int gravelPercentage = 15;
    15.     [SerializeField] int rockPercentage = 15;
    16.     [SerializeField] int waterPercentage = 15;
    17.  
    18.     [Header("Perlin Noise Attributes")]
    19.     public int width = 240;
    20.     public int height = 135;
    21.     public float scale = 8f;
    22.     public float offsetX = 100f;
    23.     public float offsetY = 100f;
    24.     [SerializeField] float clayMin;
    25.     [SerializeField] float clayMax;
    26.     [SerializeField] float rockMin;
    27.     [SerializeField] float rockMax;
    28.     [SerializeField] float waterMin;
    29.     [SerializeField] float waterMax;
    30.  
    31.     [Header("Tiles")]
    32.     [SerializeField] GameObject clay = null;
    33.     [SerializeField] GameObject dirt = null;
    34.     [SerializeField] GameObject gravel = null;
    35.     [SerializeField] GameObject sand = null;
    36.     [SerializeField] GameObject rock = null;
    37.     [SerializeField] GameObject water = null;
    38.  
    39.     float tileXOffset = .5f;
    40.     float tileYOffset = 0.375f;
    41.  
    42.     List<GameObject> tempTileList = new List<GameObject>();
    43.  
    44.     void Awake()
    45.     {
    46.         //StartCoroutine(GenerateMap());
    47.     }
    48.     void Start()
    49.     {
    50.         offsetX = Random.Range(0f, 99999f);
    51.         offsetY = Random.Range(0f, 99999f);
    52.         GeneratePerlinTiles();
    53.         GenerateLooseTiles();
    54.     }
    55.     Color CalculateColor(int x, int y)
    56.     {
    57.         float xCoordinate = (float)x / width * scale + offsetX;
    58.         float yCoordinate = (float)y / height * scale + offsetY;
    59.         float sample = Mathf.PerlinNoise(xCoordinate, yCoordinate);
    60.         return new Color(sample, sample, sample);
    61.     }
    62.     private int GetNumber(int percentageOf, int wholeNumber)
    63.     {
    64.         int number = (wholeNumber / 100) * percentageOf;
    65.         return number;
    66.     }
    67.     private void GenerateList(GameObject tileToUse, int percentageOf, int wholeNumber)
    68.     {
    69.         int tempPercent = GetNumber(percentageOf, wholeNumber);
    70.         for (int x = 0; x < tempPercent; x++)
    71.         {
    72.             tempTileList.Add(tileToUse);
    73.         }
    74.     }
    75.     private void GenerateLooseTiles()
    76.     {
    77.         int numberOfTiles = mapWidth * mapHeight;
    78.  
    79.         GenerateList(dirt, dirtPercentage, numberOfTiles);
    80.         GenerateList(sand, sandPercentage, numberOfTiles);
    81.         GenerateList(gravel, gravelPercentage, numberOfTiles);
    82.  
    83.         for (int x = 0; x < mapWidth; x++)
    84.         {
    85.             for (int y = 0; y < mapHeight; y++)
    86.             {
    87.                 GameObject tempTile = tempTileList[Random.Range(0, tempTileList.Count)];
    88.                 if (y % 2 == 0)
    89.                 {
    90.                     if (tempTile.tag == "Dirt")
    91.                     {
    92.                         Instantiate(tempTile, new Vector3(x * tileXOffset + Random.Range(-0.3f, 0.3f), y * tileYOffset + Random.Range(-0.3f, 0.3f), 2), Quaternion.identity);
    93.                         tempTileList.Remove(tempTile);
    94.                     }
    95.                     else if (tempTile.tag == "Sand" || tempTile.tag == "Gravel")
    96.                     {
    97.                         Instantiate(tempTile, new Vector3(x * tileXOffset + Random.Range(-0.3f, 0.3f), y * tileYOffset + Random.Range(-0.3f, 0.3f), 3), Quaternion.identity);
    98.                         tempTileList.Remove(tempTile);
    99.                     }
    100.                     else
    101.                     {
    102.                         Instantiate(tempTile, new Vector3(x * tileXOffset + Random.Range(-0.3f, 0.3f), y * tileYOffset + Random.Range(-0.3f, 0.3f), 4), Quaternion.identity);
    103.                         tempTileList.Remove(tempTile);
    104.                     }
    105.                 }
    106.                 else
    107.                 {
    108.                     if (tempTile.tag == "Dirt")
    109.                     {
    110.                         Instantiate(tempTile, new Vector3(x * tileXOffset + (tileXOffset / 2) + Random.Range(-0.3f, 0.3f), y * tileYOffset + Random.Range(-0.3f, 0.3f), 2), Quaternion.identity);
    111.                         tempTileList.Remove(tempTile);
    112.                     }
    113.                     else if (tempTile.tag == "Sand" || tempTile.tag == "Gravel")
    114.                     {
    115.                         Instantiate(tempTile, new Vector3(x * tileXOffset + (tileXOffset / 2) + Random.Range(-0.3f, 0.3f), y * tileYOffset + Random.Range(-0.3f, 0.3f), 3), Quaternion.identity);
    116.                         tempTileList.Remove(tempTile);
    117.                     }
    118.                     else
    119.                     {
    120.                         Instantiate(tempTile, new Vector3(x * tileXOffset + (tileXOffset / 2) + Random.Range(-0.3f, 0.3f), y * tileYOffset + Random.Range(-0.3f, 0.3f), 4), Quaternion.identity);
    121.                         tempTileList.Remove(tempTile);
    122.                     }
    123.                 }
    124.             }
    125.         }
    126.     }
    127.     private void GeneratePerlinTiles()
    128.     {
    129.         for (int x = 0; x < width; x++)
    130.         {
    131.             for (int y = 0; y < height; y++)
    132.             {
    133.                 Color color = CalculateColor(x, y);
    134.                 if (y % 2 == 0)
    135.                 {
    136.                     if (color.r >= clayMin && color.r < clayMax)
    137.                     {
    138.                         Instantiate(clay, new Vector3(x * tileXOffset + Random.Range(-0.3f, 0.3f), y * tileYOffset + Random.Range(-0.3f, 0.3f), 1), Quaternion.identity);
    139.  
    140.                     }
    141.                 }
    142.                 else
    143.                 {
    144.                     if (color.r >= clayMin && color.r < clayMax)
    145.                     {
    146.                         Instantiate(clay, new Vector3(x * tileXOffset + (tileXOffset / 2) + Random.Range(-0.3f, 0.3f), y * tileYOffset + Random.Range(-0.3f, 0.3f), 1), Quaternion.identity);
    147.                     }
    148.                 }
    149.             }
    150.         }
    151.     }
    152. }
     
    Last edited: Oct 4, 2019
  2. themusicguy1989

    themusicguy1989

    Joined:
    Feb 22, 2017
    Posts:
    15
    Forgot to upload the images QotH_1.PNG QotH_2.PNG
     
  3. themusicguy1989

    themusicguy1989

    Joined:
    Feb 22, 2017
    Posts:
    15
  4. vakabaka

    vakabaka

    Joined:
    Jul 21, 2014
    Posts:
    1,153
    no idea, do you have the line from the script which make this error (i think, you can make double click on the red message) ? Maybe the list is empty and you are trying to delete one member.
     
  5. Ledesma099

    Ledesma099

    Joined:
    May 15, 2018
    Posts:
    26
    I don't know if there is a good way to instantiate many objects. Maybe with corutins. And for the error, maybe you remove an item from the list when it's empty. For that you must check if the List has no more objects
     
  6. themusicguy1989

    themusicguy1989

    Joined:
    Feb 22, 2017
    Posts:
    15
    Ahhh, thank you. I always forget about those checks. Thanks.
     
  7. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    I think you approaching it from wrong angle.
    You want to avoid needing instantiating 56k objects.
    Player is not going see whole map?
    You should rather create a grid in form of array, if you want performance.
    Then also, you don't need collides for tails, as you can check simply index offset, of tail position.
    Then use polling objects, to display only required objects.

    If you want a minimal, to show whole map, you probably want some texture to be rendered and display that, rather 65k objects.

    If you insist on 65k objects, and that only considering tails, you would need look into DOTS entities.
    But I am afraid, that may be beyond your expertise at current state.
     
  8. themusicguy1989

    themusicguy1989

    Joined:
    Feb 22, 2017
    Posts:
    15
    Okay, I have a couple of questions.

    The players will be seeing the whole map, often. (Now that I think of it we are going to use a "fog of war" mechanic so now that I think of it while a player can see the whole map at once it would difficult to pull off. And pieces of the map will constantly be getting removed so yes, maybe the whole map isn't going to be seen at once.) It's an rts and everyone one of those hexes/pieces of the map will be able to be gathered for materials. That's why I haven't used object pooling yet, if it will still work in this scenario then I will go for it.

    I thought of turning it into a texture but wasn't sure how it would work. But maybe it can. The main problem I was having with that was how would I get them to remove a hex when gathering materials if it's a single texture and not individual pieces with colliders? It's the art style that we're going with that's making this so difficult for me. If it were a perfect grid I could visualize what to do a lot easier but the randomly overlapping hexes thats not on a perfect grid makes it a bit difficult.
     
  9. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    I would realm advise start with regular grid. Once you get working that, you could consider overlapping feature. But otherwise you may get yourself stuck.

    Regarding rendering whole map, I would try approach it, with making chunkified rendering. Render section of maps rather whole map at once. And update such chunks of map with new texture, when changed.