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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Best way to instantiate 2500 objects

Discussion in 'Scripting' started by nmizrahi, Oct 6, 2016.

  1. nmizrahi

    nmizrahi

    Joined:
    Aug 6, 2013
    Posts:
    45
    I have an isometric game with a 50x50 grid of tile objects and instantiating them all takes 1.5-2 minutes at runtime.

    My first thought was to create all the objects in the scene before runtime.

    Here, I'm instantiating prefab through an editor script but when I hit play it takes just as long to load the game as doing it at runtime.


    Code (CSharp):
    1.         PoolObject= GameObject.Find ("Tile");
    2.         Vector3 pos = PoolObject.transform.position;
    3.         UnityEngine.Object prefabRoot = PrefabUtility.GetPrefabParent(PoolObject);
    4.         GameObject dup;
    5.         int gridsize = 50;
    6.  
    7.         for (int x = 0; x < gridsize; x++) {
    8.             for (int z = 0; z < gridsize; z++) {
    9.                 dup = (GameObject) PrefabUtility.InstantiatePrefab (prefabRoot);
    10.                 dup.transform.position= new Vector3(x,0,z);
    11.                 dup.transform.Rotate(90,0,0);
    12.             }
    13.         }
    Unless I"m doing it wrong it seems that pooling won't save me the cost of having to instantiate all the objects in the first place. It will however save me the cost of destroying and creating new ones.

    Is that correct or am I not using prefabs and pooling the right way here?

    Is there a way to increase peformance (instantiation at runtime) even though all objects need to be visible?

    Thank you.
     
    Last edited: Oct 6, 2016
  2. absolute_disgrace

    absolute_disgrace

    Joined:
    Aug 28, 2016
    Posts:
    253
    GameObject.Find is generally considered a bad thing to use and its very very slow. Have you tried finding a solution that doesn't use this? You might find speed improvements.
     
  3. Errorsatz

    Errorsatz

    Joined:
    Aug 8, 2012
    Posts:
    555
    I don't think that's the issue in this case, however, since it's only happening once.
    What's on your Tile prefab? How many components and child objects? Do any of them do anything heavy in Awake/Start?
     
    Mycroft likes this.
  4. absolute_disgrace

    absolute_disgrace

    Joined:
    Aug 28, 2016
    Posts:
    253
    I've been using a TilePrefab in the project i'm working on and haven't had any load issues. I'm working with just 2D sprites so i'd have to check if you are loading tile 3d Objects?

    My level generation takes 2 seconds or less and its building between 70 and 160 tiled levels so far. Most of the load time for mine is to do with placing rooms, checking there is space on the map, placing obstacles and path finding to ensure paths aren't blocked.
     
  5. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,190
    Do you really need all objects at once?
    Can you just instance the objects that are visible on screen?
    Does each object have a physics/rigidbody requirement that you can remove
     
  6. nmizrahi

    nmizrahi

    Joined:
    Aug 6, 2013
    Posts:
    45
    Nothing heavy, just a sprite renderer and a single child object.

    The cost seems to be just the instantiation of so many objects at once.

    Perhaps there is no "best" way?
     
  7. passerbycmc

    passerbycmc

    Joined:
    Feb 12, 2015
    Posts:
    1,739
    you could use a coroutine to spread out the instantiation of objects over multiple frames, maybe only create 100 objects per frame
     
    nmizrahi and absolute_disgrace like this.
  8. absolute_disgrace

    absolute_disgrace

    Joined:
    Aug 28, 2016
    Posts:
    253
    That's a good idea. You could even spawn the items that the player can see and co-routine the other items into the background. You could also take a leaf out of game consoles and have some sort of cut-scene or intro that hides all of the loading happening in the background.
     
  9. zombiegorilla

    zombiegorilla

    Moderator

    Joined:
    May 8, 2012
    Posts:
    8,986
    Do the items you are spawning have other things on them like colliders, scripts or other components?
     
  10. nmizrahi

    nmizrahi

    Joined:
    Aug 6, 2013
    Posts:
    45
    Spreading it out over multiple frames makes sense. I think 10x10 chunks over multiple frames will work fine.

    Thanks for the advice!
     
  11. zombiegorilla

    zombiegorilla

    Moderator

    Joined:
    May 8, 2012
    Posts:
    8,986
    It sounds like something else may be affecting it. ~2 sounded long, so I did a quick test and got ~3 seconds for 250k sprite tiles. (all in camera view).
    Code (CSharp):
    1.         public void doSpawn()
    2.         {
    3.             System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
    4.             stopwatch.Reset();          
    5.             stopwatch.Start();  
    6.            
    7.             Vector3 loc = new Vector3.zero;
    8.             for (int r = 0; r < rows; r++)
    9.             {
    10.                 for (int c = 0; c < cols; c++)
    11.                 {
    12.                     loc.y = (r*gap) - ((rows*gap)/2) + (gap/2);
    13.                     loc.x = (c*gap) - ((cols*gap)/2) + (gap/2);
    14.  
    15.                     GameObject tile = Instantiate(prefabs[Random.Range(0,prefabs.Length)],loc,Quaternion.identity) as GameObject;
    16.                     count++;
    17.                 }
    18.             }          
    19.             stopwatch.Stop();
    20.             Debug.Log("Objects:"+count+" - "+"Time:"+stopwatch.Elapsed);
    21.         }
    22.  
    Results:
    Simple sprite prefabs:
    Objects:2500 - Time:00:00:00.0268690
    Objects:250000 - Time:00:00:03.0614670

    With 2d box colliders:
    Objects:2500 - Time:00:00:00.0459810
    Objects:250000 - Time:00:00:44.4832910
     
  12. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    Two minutes? You sure you don't mean two seconds? Something's definitely up with whatever you're instantiating; it should not take two minutes to instantiate a few thousand objects. You should use the profiler and see what's taking so long.
     
    Mycroft likes this.
  13. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    ...

    Profile it

    ...

    Enough said
     
  14. nmizrahi

    nmizrahi

    Joined:
    Aug 6, 2013
    Posts:
    45
    The timer was really useful - I realized there were a couple components that were significantly slowing it down.

    I have the time down to .001 seconds for each 10x10 chunk of objects.

    I'm now using a coroutine to spawn each chunk but it doesn't appear that it's doing it over multiple frames.

    I would expect Start to finish almost immediately and the coroutines to finish over multiple frames.

    With the code below, Start finishes after all the coroutines instead of before (about .05 seconds).

    Is there a special syntax for coroutines that I'm missing?

    Thank you.

    Code (CSharp):
    1. private IEnumerator coroutine;
    2.  
    3.     // Use this for initialization
    4.     void Start () {
    5.         System.Diagnostics.Stopwatch startTimer = new System.Diagnostics.Stopwatch ();
    6.         startTimer.Reset ();        
    7.         startTimer.Start ();
    8.  
    9.         // Load a 10x10
    10.         for (int z = 0; z < 50; z += 10) {
    11.             for (int x = 0; x < 50; x += 10) {
    12.                 coroutine = loadChunk (new Vector3(x, 0, z),Resources.Load <GameObject > ("Test"), 10);
    13.                 StartCoroutine (coroutine);
    14.             }
    15.         }
    16.  
    17.         startTimer.Stop ();
    18.         Debug.Log (string.Format("Start Time: {0}",startTimer.Elapsed));
    19.     }
    20.  
    21.     // Update is called once per frame
    22.     void Update () {
    23.  
    24.     }
    25.  
    26.  
    27.     private IEnumerator loadChunk(Vector3 startPosition, GameObject prefab, int amount){
    28.         System.Diagnostics.Stopwatch loadTimer = new System.Diagnostics.Stopwatch ();
    29.         loadTimer.Reset ();        
    30.         loadTimer.Start ();
    31.  
    32.             int numObjects = 0;
    33.             int startIntx = (int) startPosition.x;
    34.             int startIntz = (int) startPosition.z;
    35.  
    36.  
    37.             for (int x= 0; x < amount; x++)
    38.             {
    39.                 for (int z = 0 ; z < amount; z++)
    40.                 {
    41.                     GameObject tile = Instantiate (prefab) as GameObject;
    42.                     numObjects++;
    43.                     tile.transform.position = new Vector3((startPosition.x +x),0,(startPosition.z+ z));
    44.                     tile.transform.Rotate (90, 0, 0);
    45.  
    46.                 }
    47.             }
    48.  
    49.         loadTimer.Stop ();
    50.         Debug.Log (string.Format("Elapsed Time: {0} for {1} Objects",loadTimer.Elapsed,numObjects));
    51.         yield return null;
    52.  
    53.     }
     
  15. zombiegorilla

    zombiegorilla

    Moderator

    Joined:
    May 8, 2012
    Posts:
    8,986
    You are firing the coroutines all at the same time, so it is effectively the same as just looping through them in start. If you use yield in a coroutine, it will wait until the next frame to continue. That may be what you are looking for. But bear in mind that waits for next frame. So spawning a 1000 items will take 1000 frames to complete. Though you could do multiple at a time.
     
  16. jimroberts

    jimroberts

    Joined:
    Sep 4, 2014
    Posts:
    560
    What possessed you into using a coroutine to accomplish this task? The entire thing should just be an iteration in update. I believe the following should work... However, I don't have time to test it.

    PseudoCode:
    Code (csharp):
    1. public class PrefabSpawnBehaviour : MonoBehaviour
    2. {
    3.    [SerializeField]
    4.    private GameObject myPrefab;
    5.    [SerializeField]
    6.    private Vector3 spawnPositionOffset;
    7.    [SerializeField]
    8.    private Vector3 spawnEulerRotation;
    9.    private int numSpawned;
    10.    private int totalSpawns;
    11.    [SerializeField]
    12.    private int maxSpawnsPerFrame;
    13.  
    14.    private int x;
    15.    [SerializeField]
    16.    private int maxX;
    17.  
    18.    private int z;
    19.    [SerializeField]
    20.    private int maxZ;
    21.  
    22.    void Awake()
    23.    {
    24.      this.Reset();
    25.    }
    26.  
    27.    void Update()
    28.    {
    29.      for (int spawns = 0; this.x < this.maxX; ++this.x)
    30.      {
    31.        for (; this.z < this.maxZ; ++this.z, ++this.numSpawned, ++spawns)
    32.        {
    33.          if (spawns >= this.maxSpawnsPerFrame)
    34.          {
    35.            return;
    36.          }
    37.          if (this.numSpawned >= this.totalSpawns)
    38.          {
    39.            this.enabled = false;
    40.            return;
    41.          }
    42.          Vector3 position = new Vector3(this.spawnPositionOffset.x + x, this.spawnPositionOffset.y + 0, this.spawnPositionOffset.z + z);
    43.          GameObject newInstance = Instantiate(this.myPrefab, position, Quaternion.Euler(this.spawnEulerRotation)) as GameObject;
    44.  
    45.          //doSomething with the newInstance
    46.        }
    47.      }
    48.    }
    49.  
    50.    void OnEnable()
    51.    {
    52.      this.Reset();
    53.    }
    54.  
    55.    private void Reset()
    56.    {
    57.      this.numSpawned = 0;
    58.      this.totalSpawns = maxX * maxZ;
    59.      this.x = 0;
    60.      this.z = 0;
    61.    }
    62. }
    You would also benefit significantly if you cached your tile instances and re-used them.
     
    Last edited: Oct 8, 2016
  17. zombiegorilla

    zombiegorilla

    Moderator

    Joined:
    May 8, 2012
    Posts:
    8,986
    No, not in update, that would be bad!
     
    cloutiertyler and Kiwasi like this.
  18. jimroberts

    jimroberts

    Joined:
    Sep 4, 2014
    Posts:
    560
    I would love to hear your reasoning as to why my example would be bad.
     
  19. Vedrit

    Vedrit

    Joined:
    Feb 8, 2013
    Posts:
    514
    I kind of skipped, but what I did is I created the objects without instantiating them, added them to a list, and ran a corouting that would go through 5-10 in the list and instantiate the object before yielding the thread. You could probably increase the count.
     
  20. zombiegorilla

    zombiegorilla

    Moderator

    Joined:
    May 8, 2012
    Posts:
    8,986
    Because you are running something that only needs to run once, every frame via monobehaviour. It is massively over-complicating a simple loop with unnecessary code that has no benefit. Coroutines exist for this purpose. It is creating a whole component for just a spawn method, additionally it you are going to use actually track or make use of the objects that are spawned, that means refactoring or external tracking, instead of just storing references in the array. Abstraction without benefit creates unmaintainable code.
     
    cloutiertyler and Kiwasi like this.
  21. zombiegorilla

    zombiegorilla

    Moderator

    Joined:
    May 8, 2012
    Posts:
    8,986
    Simple, spawning in batches:
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class StaggeredSpawnExample : MonoBehaviour {
    6.    
    7.     public int rows = 100;
    8.     public int cols = 100;
    9.     public int max_per_frame = 10;
    10.     public GameObject prefab;
    11.     public Vector3 startPosition;
    12.     public Quaternion startRotation;
    13.  
    14.     void Start () {
    15.         StartCoroutine ("staggeredSpawn");
    16.     }
    17.  
    18.     private IEnumerator staggeredSpawn() {
    19.         int count = max_per_frame;
    20.         for (int row = 0; row < rows; row++){
    21.             for (int col = 0; col < cols; col++){
    22.                 GameObject tile = Instantiate(prefab, startPosition+(Vector3.up*col)+(Vector3.forward*row), startRotation ) as GameObject;
    23.                 count--;
    24.                 if(count<0){
    25.                     count = max_per_frame;
    26.                     yield return null;
    27.                 }
    28.             }
    29.         }
    30.     }
    31. }
    32.  
    33.  
     
    Kiwasi likes this.
  22. jimroberts

    jimroberts

    Joined:
    Sep 4, 2014
    Posts:
    560
    Spawning objects and managing objects after they are spawned are two completely separate behaviours. Combining those responsibilities means you don't care about the single responsibility principle... However, your coroutine doesn't have to track the current total number of spawned objects in order to "stop" which is arguably slightly better.
     
  23. zombiegorilla

    zombiegorilla

    Moderator

    Joined:
    May 8, 2012
    Posts:
    8,986
    Spawning (in this instance) is trival, under single responsibility, it is reasonable to include it under tile management. Spawning, managing, destroying, ect would fall under the same control. Just as you wouldn't have seperate classes to translate and rotate an object. Granted in a larger system with a service approach, for example, you might centralize all entity creation, but even then it would a persistent service, not single use.
     
    Kiwasi likes this.
  24. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    It really depends on the project scope. For something that requires a lot of complex initialisation it might make sense to seperate initialisation from management. But in many cases it doesn't.

    SRP is a pretty slippery concept in games. Picking what exactly defines a single responsibility is unclear. You can split everything down till you only have one function in each component. But that doesn't really make sense for developer efficiency.