Maurice Bekambo
Maurice Bekambo

Reputation: 325

How can adapt my object pooler to reset for each new scene that I load?

After updating the game according to the specifications given in the answer I now get the following error message:

Assets\ObjectPooler.cs(79,41): error CS0103: The name 'pool' does not exist in the current context. I understand why it doesn't work since it's declared in another method, but how can I change my code to access this variable?

Thanks in advance

I haven't added the release and .sceneloaded scripts to the game yet

This is my ObjectPooler script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ObjectPooler : MonoBehaviour
{
    [System.Serializable]
    public class Pool
    {
        public string tag;
        public GameObject prefab;
        public int size;

    }

    #region Singleton 

    public static ObjectPooler Instance;

    private void Awake()
    {
        // Already another instance?
        if (Instance)
        {
            Destroy(this.gameObject);
            return;
        }

        Instance = this;
        DontDestroyOnLoad(this.gameObject);
    }

    #endregion

    public List<Pool> pools;
    public Dictionary<string, Queue<GameObject>> poolDictionary;


    // Start is called before the first frame update
    //change this method to make it work everytime level is loaded
    private Dictionary<string, Pool> prefabPools;

    private void Start()
    {
        foreach (var pool in pools)
        {
            Queue<GameObject> objectPool = new Queue<GameObject>();

            for (int i = 0; i < pool.size; i++)
            {
                GameObject obj = Instantiate(pool.prefab);
                obj.transform.SetParent(transform);
                obj.SetActive(false);
                objectPool.Enqueue(obj);
            }

            prefabPools.Add(pool.tag, pool);
            poolDictionary.Add(pool.tag, objectPool);
        }
    }

    public GameObject SpawnFromPool(string tag, Vector3 position, Quaternion rotation)
    {
        if (!poolDictionary.ContainsKey(tag))
        {
            Debug.LogWarning("Pool with tag" + tag + " doesn't exist.");
            return null;
        }

        GameObject objectToSpawn;

        // check if there are objects left otherwise insteantiate a new one
        if (poolDictionary[tag].Count > 0)
        {
            objectToSpawn = poolDictionary[tag].Dequeue();
        }
        else
        {
            objectToSpawn = Instantiate(pool.prefab);
            objectToSpawn.transform.SetParent(transform);
        }

        objectToSpawn.transform.position = position;
        objectToSpawn.transform.rotation = rotation;

        objectToSpawn.SetActive(true);

        IPooledObject pooledObj = objectToSpawn.GetComponent<IPooledObject>();

        if (pooledObj != null)
        {
            pooledObj.OnObjectSpawn();
        }

        return objectToSpawn;
    }


}

This is the IPooledObject interface:

using UnityEngine;

public interface IPooledObject
{
    void OnObjectSpawn();
}

This is how the gameObject in the pool gets called from the scripts:

objectPooler.SpawnFromPool("Ball", spawnPoints[randomSpawnPoint].position, Quaternion.identity);

The way it should work is that when I transition between the different scenes of my game the Object Pooler a new instance of the object pooler gets created or it gets reset and they appear on the screen and behave according to the scripts instead of not appearing and acting as if they were destroyed. Some things to note is that if one the objectpooler and objects behave normally in the first scene and only start throwing the error message when the game transitions between scenes and 2 when I edit the script to instantiate the object without using the objectpooler as in:

Instantiate(interact[Interact], spawnPoints[randomSpawnPoint].position,
                      Quaternion.identity); 

Where I store the gameobjects prefabs in a public array of name interact and call them by index, the script also seems to work. However I need to be able to use an ObjectPooler inorder to prevent my code from becoming expensive?

Upvotes: 1

Views: 1179

Answers (1)

derHugo
derHugo

Reputation: 90901

First of all I would not re-enqueue an object that just was spawned but rather keep it removed from the queue. Then when the queue is empty spawn new additional objects to use instead of reusing one that might be in sue already.

private Dictionary<string, Pool> prefabPools;

private void Start()
{
    foreach (var pool in pools)
    {
        Queue<GameObject> objectPool = new Queue<GameObject>();

        for (int i = 0; i < pool.size; i++)
        {
            GameObject obj = Instantiate(pool.prefab);
            obj.SetActive(false);
            objectPool.Enqueue(obj);
        }

        prefabPools.Add(pool.tag, pool);
        poolDictionary.Add(pool.tag, objectPool);
    }
}

public GameObject SpawnFromPool(string tag, Vector3 position, Quaternion rotation)
{
    if (!poolDictionary.ContainsKey(tag))
    {
        Debug.LogWarning("Pool with tag" + tag + " doesn't exist.");
        return null;
    }

    GameObject objectToSpawn;

    // check if there are objects left otherwise insteantiate a new one
    if(poolDictionary[tag].Count > 0)
    {
        objectToSpawn = poolDictionary[tag].Dequeue();
    }
    else
    {
        objectToSpawn = Instantiate(pool.prefab);
    }

    objectToSpawn.transform.position = position;
    objectToSpawn.transform.rotation = rotation;

    objectToSpawn.SetActive(true);

    IPooledObject pooledObj = objectToSpawn.GetComponent<IPooledObject>();

    if (pooledObj != null)
    {
        pooledObj.OnObjectSpawn();
    }

    return objectToSpawn;
}

You could use DontDestroyOnLoad in order to never destroy it on scene load but destroy the newly loaded ones

private void Awake ()
{
    // Already another instance?
    if(Instance) 
    {
        Destroy (this.gameObject);
        return;
    }

    Instance = this;
    DontDestroyOnLoad(this.gameObject);
}

And then make sure your pooled objects also don't get destroyed by making them children of this object

GameObject obj = Instantiate(pool.prefab);
obj.SetParent(transform);

and also in

objectToSpawn = Instantiate(pool.prefab);
objectToSpawn.SetParent(transform);

And you should have some kind of Release method which disables and re-enqueues the objects instead of destroying them like

public void Release(GameObject obj)
{
    obj.SetActive(false);

    // Assuming pool.tag euqals obj.tag
    // In general I would rather go by type instead
    poolDictionary[obj.tag].Enqueue(obj);
}

And finally run this for every object when the scene is changed

private void Awake()
{
    ...

    SceneManager.sceneLoaded -= OnSceneLoaded;
    SceneManager.sceneLoaded += OnSceneLoaded;
}

private void OnDestroy()
{
    ...

    SceneManager.sceneLoaded -= OnSceneLoaded;
}

private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
    foreach(var child in transform)
    {
        if(!child.activeInHierachy) continue;

        Release(child);
    }
}

Alternatively if you don't want to make them always children of the pool object you could also keep track of them in a list instead like

private List<GameObject> currentlySpawnedObjects = new List<GameObject>();

//...

public void Release(GameObject obj)
{
    currentlySpawnedObjects.Remove(obj);

    obj.SetActive(false);

    // here you should still make it a child so it doesn't get destroyed
    // when the scene is changed
    obj.SetParent(transform);

    // Assuming pool.tag euqals obj.tag
    // In general I would rather go by type instead
    poolDictionary[obj.tag].Enqueue(obj);
}

//...

// call this BEFORE switching scenes
public void ReleaseAll()
{
    foreach(var child in currentlySpawnedObjects)
    {
        Release(child);
    }
}

so now you can even extend your spawn method to additionally add the ability of being the child of another GameObject just like the Instantiate method

private Dictionary<string, Pool> prefabPools;

private void Start()
{
    foreach (var pool in pools)
    {
        Queue<GameObject> objectPool = new Queue<GameObject>();

        for (int i = 0; i < pool.size; i++)
        {
            GameObject obj = Instantiate(pool.prefab);
            obj.SetActive(false);
            objectPool.Enqueue(obj);
        }

        prefabPools.Add(pool.tag, pool);
        poolDictionary.Add(pool.tag, objectPool);
    }
}

public GameObject SpawnFromPool(string tag, Vector3 position, Quaternion rotation, Transform parent = null)
{
    if (!poolDictionary.ContainsKey(tag))
    {
        Debug.LogWarning("Pool with tag" + tag + " doesn't exist.");
        return null;
    }

    GameObject objectToSpawn;

    // check if there are objects left otherwise insteantiate a new one
    if(poolDictionary[tag].Count > 0)
    {
        objectToSpawn = poolDictionary[tag].Dequeue();
    }
    else
    {
        objectToSpawn = Instantiate(prefabPools[tag].prefab);
    }

    if(parent)
    {
        objectToSpawn.SetParent(parent, false);
    }

    // you could also decide to use localPosition in case parent is set
    objectToSpawn.transform.position = position;
    objectToSpawn.transform.rotation = rotation;

    objectToSpawn.SetActive(true);

    IPooledObject pooledObj = objectToSpawn.GetComponent<IPooledObject>();

    if (pooledObj != null)
    {
        pooledObj.OnObjectSpawn();
    }

    currentlySpawnedObjects.Add(objectToSpawn);

    return objectToSpawn;
}

Upvotes: 2

Related Questions