Search Unity

Pooling more than one PrEfAb

Discussion in 'Scripting' started by Rotzak, Aug 21, 2018.

  1. Rotzak

    Rotzak

    Joined:
    Jul 11, 2018
    Posts:
    92
    (Question at the bottom of the thread)

    I'm currently instantiating and destroying my game objects. I was told to use object pooling so that's what I want to achieve now. Unfortunately I'm having alot of issues implementing this object pooling in my own game :mad:. I've watched the tutorial from Sebastian Lague and copied him and here is what I currently have:


    The PoolManager (attached to a random gameObject):
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class PoolManager : MonoBehaviour {
    6.  
    7.     //Int = key
    8.     Dictionary<int, Queue<GameObject>> poolDictionary = new Dictionary<int, Queue<GameObject>> ();
    9.  
    10.     //Singleton pattern to get acces to these methods without reference to the PoolManager
    11.     static PoolManager _instance;
    12.  
    13.     public static PoolManager instance
    14.     {
    15.         get
    16.         {
    17.             if (_instance == null)
    18.             {
    19.                 _instance = FindObjectOfType<PoolManager>();
    20.             }
    21.             return _instance;
    22.         }
    23.     }
    24.  
    25.     //Create the pool defined by a gameObject and a size
    26.     public void CreatePool(GameObject prefab, int poolSize)
    27.     {
    28.         //Key is unique for each prefab
    29.         int poolKey = prefab.GetInstanceID();
    30.  
    31.         //Make sure the poolkey is already in our dictionary because it will cause errors if we don't
    32.         if (!poolDictionary.ContainsKey(poolKey))
    33.         {
    34.             poolDictionary.Add(poolKey, new Queue<GameObject>());
    35.         }
    36.  
    37.         //Instantiate prefabs to create te pool
    38.         for (int i = 0; i < poolSize; i++)
    39.         {
    40.             GameObject newObject = Instantiate(prefab) as GameObject;
    41.             newObject.SetActive(false);
    42.             //Add to the pool
    43.             poolDictionary[poolKey].Enqueue(newObject);
    44.         }
    45.     }
    46.  
    47.     public void ReuseObject(GameObject prefab, Vector3 position, Quaternion rotation)
    48.     {
    49.         int poolKey = prefab.GetInstanceID();
    50.         //Make sure that the pool contains the key
    51.         if (poolDictionary.ContainsKey(poolKey))
    52.         {
    53.             //Get first object of the queue
    54.             GameObject objectToReuse = poolDictionary[poolKey].Dequeue();
    55.             //Add object back to the end of the queue so we can reuse it again later
    56.             poolDictionary[poolKey].Enqueue(objectToReuse);
    57.  
    58.             objectToReuse.SetActive(true);
    59.             objectToReuse.transform.position = position;
    60.             objectToReuse.transform.rotation = rotation;
    61.         }
    62.     }
    63.    
    64. }
    65.  
    The TestManager (Also attached to a random gameObject. Here I'm defining the prefab and the size of the pool. Also I'm saying here that I want to spawn a "new" prefab every second.):
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class TestManager : MonoBehaviour {
    6.  
    7.     public GameObject prefab;
    8.     public float delayTimer = 1.0f;
    9.     float timer;
    10.    
    11.    
    12.     // Use this for initialization
    13.     void Start () {
    14.        PoolManager.instance.CreatePool(prefab, 5);
    15.        
    16.       timer = delayTimer;
    17.     }
    18.    
    19.     // Update is called once per frame
    20.     void Update () {
    21.  
    22.         timer -= Time.deltaTime;
    23.  
    24.         if (timer <= 0)
    25.         {
    26.             Vector3 poolVector = new Vector3(0, 8, 0);
    27.  
    28.             PoolManager.instance.ReuseObject(prefab, poolVector, Quaternion.identity);
    29.  
    30.             timer = delayTimer;
    31.         }
    32.     }
    33. }
    34.  
    The TestObject (Attached to the prefab --> movement):
    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class TestObject : MonoBehaviour {
    7.     private void Update()
    8.     {
    9.         transform.Translate(-Vector3.up * Time.deltaTime * 10);
    10.     }
    11. }
    12.  
    QUESTION: Sebastian is using everytime the same prefab (a cube), so just ONE object. I have around 70 different prefabs which I want to spawn in a random order. I've tried several things with an array but I just can't get it to work that's why I'm asking you guys.
     
  2. Rotzak

    Rotzak

    Joined:
    Jul 11, 2018
    Posts:
    92
    The best result I had was by editing the TestManager script. I managed to create a pool with all the different objects but only one object was active and actually moving in play mode. Here is the script:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class TestManager : MonoBehaviour {
    6.  
    7.     public GameObject[] prefabArray;
    8.     GameObject prefab;
    9.     public float delayTimer = 1.0f;
    10.     float timer;
    11.  
    12.  
    13.     // Use this for initialization
    14.     void Start () {
    15.  
    16.         foreach (GameObject go in prefabArray)
    17.         {
    18.             prefab = go;
    19.             PoolManager.instance.CreatePool(prefab, 1);
    20.         }
    21.      
    22.       timer = delayTimer;
    23.     }
    24.  
    25.     // Update is called once per frame
    26.     void Update () {
    27.  
    28.         timer -= Time.deltaTime;
    29.  
    30.         if (timer <= 0)
    31.         {
    32.             Vector3 poolVector = new Vector3(0, 8, 0);
    33.  
    34.             PoolManager.instance.ReuseObject(prefab, poolVector, Quaternion.identity);
    35.  
    36.             timer = delayTimer;
    37.         }
    38.     }
    39. }
    40.  
     
  3. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    So what exactly are you wanting from the pooler? Are you wanting:
    1. 70 (completely) different objects each stored only once within the pool.
    2. One object stored 70 times within the pool.
    3. Some other combination?
     
  4. Rotzak

    Rotzak

    Joined:
    Jul 11, 2018
    Posts:
    92
    The first thing. I want to get 70 completely different objects in the pool and reuse them in a random order.

    Currently I can only store one prefab/object and that's the issue :/
     
  5. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    You could create
    List<GameObject> m_objects;
    , populate it and then just use
    Random.Range(0, m_objects.Count)
    to get one at random and remove it from the list.
     
  6. Rotzak

    Rotzak

    Joined:
    Jul 11, 2018
    Posts:
    92
    Hello thanks for the input this is what I made of it (only changed the testManager script). Also needed to add another list because I didn't want to reuse a object while it's still visible in the canvas. Can you overlook this? I'm pretty new to programming and I would love to know if I'm making huge mistakes. Like in this code everything is in the update are there any ways to avoid this or should I use coroutines or something? Should I avoid the while loop?

    PS: I've tested the code several times and it works perfect

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class TestManager : MonoBehaviour {
    6.  
    7.     public List<GameObject> m_objects;
    8.     public List<int> used_numbers;
    9.  
    10.     int carNo;
    11.  
    12.     float carTimer;
    13.     public float carDelayTimer = 1.0f;
    14.  
    15.  
    16.     // Use this for initialization
    17.     void Start () {
    18.  
    19.         carTimer = carDelayTimer;
    20.  
    21.         //Create pool with all the objects in the list
    22.         foreach (GameObject go in m_objects)
    23.         {
    24.             PoolManager.instance.CreatePool(go, 1);
    25.         }
    26.     }
    27.  
    28.     // Update is called once per frame
    29.     void Update () {
    30.  
    31.         carTimer -= Time.deltaTime;
    32.  
    33.         if (carTimer <= 0)
    34.         {
    35.             //Spawnposition
    36.             Vector3 poolVector = new Vector3(0, 8, 0);
    37.  
    38.             carNo = Random.Range(0, m_objects.Count);
    39.  
    40.             //As long as the generated carNo already exists in the used_numbers list we generate a new carNo
    41.             //In this way we can make sure that we don't reuse a object while it's still visible in the canvas
    42.             while (used_numbers.Contains(carNo))
    43.             {
    44.                 carNo = Random.Range(0, m_objects.Count);
    45.             }
    46.  
    47.             used_numbers.Add(carNo);
    48.  
    49.             PoolManager.instance.ReuseObject(m_objects[carNo], poolVector, Quaternion.identity);
    50.  
    51.             carTimer = carDelayTimer;
    52.         }
    53.  
    54.         //We want to clear the list regularly for two reasons
    55.         //1. if we don't do this and our gameObjects are all used the while loop in the update makes the game crash
    56.         //2. we want to reuse the cars
    57.  
    58.         if (used_numbers.Count >= 10)
    59.         {
    60.             used_numbers.RemoveRange(0, used_numbers.Count - 5);
    61.         }
    62.     }
    63. }
    64.  
    65.  
    66.  
    67.  
     
  7. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    Sure, that's a pretty decent stab at it.

    If I had to pick a single main point to focus on there, it would be with the separation of responsibilities. That is to say, let the pool manager look after the handling of the 'available' and 'used' caches. Hide that stuff away from the outside world. So maybe it could look something like this (note: this is untested and deliberately incomplete) :-
    Code (CSharp):
    1. public class PoolManager : MonoBehaviour
    2. {
    3.     public void Add(GameObject addToPool)
    4.     {
    5.         m_poolAvailable.Add(addToPool);
    6.     }
    7.  
    8.     // Be sure to test the return value here.  On exhaustion, it will return null.
    9.     public GameObject GetRandom()
    10.     {
    11.         if (m_poolAvailable.Count == 0)
    12.             return null;
    13.  
    14.         var randIndex = Random.Range(0, m_poolAvailable.Count);
    15.         var available = m_poolAvailable[randIndex];
    16.  
    17.         // TO DO : move 'available' object from the 'available' to the 'in use' pool.
    18.  
    19.         return available;
    20.     }
    21.  
    22.     public void ReturnToPool(GameObject returnToPool)
    23.     {
    24.         // TO DO : is 'returnToPool' in the 'in use' pool?
    25.         if (is_in_use)
    26.         {
    27.             // TO DO : move 'returnToPool' object from the 'in use' to the 'available' pool.
    28.         }
    29.     }
    30.  
    31.     List<GameObject> m_poolAvailable = new List<GameObject>();
    32.     List<GameObject> m_poolInUse = new List<GameObject>();
    33. }
     
    Rotzak likes this.
  8. bomal

    bomal

    Joined:
    Jul 3, 2018
    Posts:
    6
    if someone wants to help something, you should either really help or don't start answering at all and say it's not complete on purpose
     
  9. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,092
    And you're pointlessly necroing a thread from 2018. You're not bringing anything helpful to the thread either.

    Unity has a tutorial for object pooling in their learn section
    https://learn.unity.com/tutorial/introduction-to-object-pooling
     
  10. bomal

    bomal

    Joined:
    Jul 3, 2018
    Posts:
    6
    Now and after so many learn about the pooling, I can manage without such half helps.
    but I think that here is a Community and who you want to help, you should do right and not half-right.
    A question? why did you come to this thread now?
     
  11. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,491
    Please don't necro threads like this, it's against the forum rules. All you're doing here is arguing and not adding anything to the thread. Adding some useful content to a discussion would be acceptable if the thread wasn't too old but you're not doing that. Please refrain from this. Thanks.
     
  12. ensiferum888

    ensiferum888

    Joined:
    May 11, 2013
    Posts:
    317
    Honest question, why aren't old threads locked? I see this a lot on this forum, clearly some people aren't aware of the rule and the simple fact that you are able to reply to old threads sort of implies that it's ok to do so.