1
votes

I'm currently developing a game in Unity and I ran into a small problem. I'm working on a restart function that gets called automatically when the player dies and loads the first scene again. However for some reason when reloading the scene games objects are duplicated with the version of the gameobject that was active at the time of death being inactive and the version that gets loaded as should be loaded getting set to active and so on every time the player dies adding a new duplicate of the same gameobjects to the hierarchy. I tried to solve this problem in multiple ways. First by trying to check each the gameobjects that get duplicated already have an instance of themselves running by attaching a script that checks every time a change in scene occurs wether or not their already is an instance of the gameobjects present:

 public static GameObject Instance;

 void Awake()
 {
     if(Instance){
         DestroyImmediate(gameObject);
     }else
     {
         DontDestroyOnLoad(gameObject);
         Instance = this;
     }
 }

This seemed to solve the problem at first but it became to teadious towards the end because the scripts made all of my other scene objects behave badly or not at all so I chose to look for another solution.

Secondly I tried to Destroy each individual gameobject before I start loading the first scene. This also seemed to work at first but now my object pooler just recreates new intances of the gameobjects that it adds too the hierarchy esentially displacing the same problem to other gameobjects.

Finally in order to solve this problem I tried to make my objectpooler run only once when the scene that requires it to be loaded gets called but this didn't seem to work either. Does anyone have any idea how I could solve this problem. This is part of the script responsible for loading the original scene upon player death:

void Restart()
{
    GameObject[] allObjects = UnityEngine.Object.FindObjectsOfType<GameObject>();

    foreach (GameObject gos in allObjects)
    {
        if (gos.activeInHierarchy)
        {
            if (gos != GameObject.Find("GameManager") && gos != GameObject.Find("ScreenBound")) 
            {
                gos.SetActive(false);
            }
        }
    }
    MySceneManager.LoadScene(0, this);
}

How could I change this in order to be able to reload the original scene without having any previously loaded GameObject get duplicated and behave according to how it should in the scene in which it got loaded originally?

The class responsible for loading and deloading scenes:

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

public static class MySceneManager
{

    private static int lastLoadedScene = 0;

    public static void LoadScene(int index, MonoBehaviour caller)
    {
        ObjectPooler objP = new ObjectPooler();
        objP.ReleaseAll();
        caller.StartCoroutine(loadNextScene(index));
    }

    private static IEnumerator loadNextScene(int index)
    {

        var _async = SceneManager.LoadSceneAsync(index, LoadSceneMode.Additive);


        _async.allowSceneActivation = false;
        while (_async.progress < 0.9f)
        {

            yield return null;
        }

        _async.allowSceneActivation = true;

        while (!_async.isDone)
        {
            yield return null;
        }


        var newScene = SceneManager.GetSceneByBuildIndex(index);


        if (!newScene.IsValid()) yield break;


        SceneManager.SetActiveScene(newScene);


        if (lastLoadedScene >= 0) SceneManager.UnloadSceneAsync(lastLoadedScene);

        lastLoadedScene = index;
    }
}

This is my ObjectPooler:

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()
    {

        if (Instance)
        {
            Destroy(this.gameObject);
            return;
        }

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

    #endregion

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

    private Dictionary<string, Pool> prefabPools;

    void Start()
    {
        poolDictionary = new Dictionary<string, Queue<GameObject>>();

        foreach (Pool pool in pools)
        {
            Queue<GameObject> objectPool = new Queue<GameObject>();

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

            poolDictionary.Add(pool.tag, objectPool);


        }
    }

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



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

        obj.SetActive(false);


        obj.transform.SetParent(transform);


        poolDictionary[obj.tag].Enqueue(obj);
        DontDestroyOnLoad(obj);
    }

    public void ReleaseAll()
    {
        foreach (var child in currentlySpawnedObjects)
        {
            Release(child);
        }
    }

    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 = poolDictionary[tag].Dequeue();


        objectToSpawn.SetActive(true);
        objectToSpawn.transform.position = position;
        objectToSpawn.transform.rotation = rotation;

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

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

        poolDictionary[tag].Enqueue(objectToSpawn);

        return objectToSpawn;

        currentlySpawnedObjects.Add(objectToSpawn);

        return objectToSpawn;
    }



}
2
The behaviour you describe sounds very weird in the first place ... I would try to solve that instead of trying to find workarounds that look even worse ... loading the scene should reset everything just like if the game was started. Can you show how you load the scene?derHugo
I agree with @derHugo, it sounds like you're hacking a workaround to partially reset your game.Erik Overflow
But thats exactly what I want to happenMaurice Bekambo
I added the class responsible for loading and deloading scenes at the endMaurice Bekambo
It looks like the duplicates come from the Unloaded scene. They stay there and disabled until the scene is fully unloaded. Since the default value for lastLoadedScene is always >=0 maybe this is causing issues since you load and unload the same scene async?derHugo

2 Answers

0
votes

Depends of your needs you can try next ways:

  1. Use singleton pattern if you need save single instance of objects. This case relevant for saving managers (GameplayManager, SceneController, AssetBundleManager, etc.) in other cases will be better to use other ways. To read more about implementation you can see this article.
  2. Destroy all old objects when loaded new scene. To do this you can use SceneManager.LoadScene method with LoadSceneMode.Single as parameter. It will keep DontDestoryOnLoad objects but will remove all others.
0
votes

I'm not sure but the first possible issue to me already seems to be that it is running in Coroutine on an object in the scene you are going to unload.

It is cool that this is doable but have in mind that the Coroutine will stop working as soon as the caller object/component is destroyed or disabled.

To avoid that I would move your script to an object in the DontDestroyOnLoadScene using a Singleton pattern.

The next issue might be you going by SceneIndex ... both scenes, the one you want to unload and the one you want to load have index 0!

So maybe you get a conflict between the scene additively loading and the one you want to unload.

This also might happen again when you called

var newScene = SceneManager.GetSceneByIndex(lastLoadedScene);

To avoid this I would rather go by scene reference for the unloading

public class MySceneManager : MonoBehaviour
{
    private static MySceneManager instance;

    // Lazy initialization
    // With this you wouldn't even need this object in the scene
    public static MySceneManager Instance
    {
        if(instance) return instance;

        instance = new GameObject ("MySceneManager").AddComponent<MySceneManager>();

        DontDestroyOnLoad(instance);
    }

    // Usual instant initialization having this object in the scene
    private void Awake ()
    {
        if(instance && instance != this)
        {
            Destroy(gameObject);
            return;
        }

        instance = this;

        DontDestroyOnLoad(this);
    }



    public void LoadScene(int index)
    {
        StartCoroutine(loadNextScene(index));
    }

    private IEnumerator loadNextScene(int index)
    { 
        // I didn't completely go through your ObjectPooler but I guess you need to do this
        ObjectPooler.Instance.ReleaseAll();

        // Instead of the index get the actual current scene instance
        var currentScene = SceneManager.GetActiveScene();

        var _async = SceneManager.LoadSceneAsync(index, LoadSceneMode.Additive);


        _async.allowSceneActivation = false;
        yield return new WaitWhile(() => _async.progress < 0.9f);

        _async.allowSceneActivation = true;

        yield return new WaitUntil(() => _async.isDone);

        // You have to do this before otherwise you might again
        // get by index the previous scene 
        var unloadAsync = SceneManager.UnloadSceneAsync(currentScene);
        yield return new WaitUntil(()=>unloadAsync.isDone);            

        var newScene = SceneManager.GetSceneByBuildIndex(index);    

        SceneManager.SetActiveScene(newScene);
    }
}

Alternatively since anyway you do nothing special while loading/unloading the scenes:

why using Additive scene loading at all if you could also simply call

ObjectPooler.Instance.ReleaseAll();
SceneManager.LoadSceneAsync(index);

without making it additive so the current scene is simply removed automatically as soon as the new scene is fully loaded.


Note: Types on Smartphone so no warranty but I hope the idea gets clear