ScenePersist bug after loading a new scene

Hi everyone, I realized there is an annoying glitch with the scene persist when we are loading a new scene and in the previous scene was already an ScenePersist. The problem is that the first time a new scene loads and there is already a scene persist (i.e. when we load Level 2 finishing Level 1) the pickups are not appearing when we load the level for the first time. I was a bit confused with this problem because when loading from the main Menu (Scene index 0) to Level 1 (Scene index 1) this problem wasn’t occuring.

I did some Debugging check of the code and realized what was going on, the problem is actually due to the singleton pattern in ScenePersist, although is a bit tricky to solve even when having identified it. The error flow is as follows:

-When we are on main menu (scene index 0) there is no ScenePersist, then we load level1 (sceneindex1).

-We are now in level1 (si 1) and there is an Scenepersists, on Awake the singleton pattern is executed and since there is any other ScenePersists, the DontdestroyOnLoad is executed. Then in the update the CheckifStillinSameScene is executed (remember the scene indexAtStart is executed also when the class is instantiated for the first time)

private void CheckIfStillInSameScene(){
        int actualSceneIndex = SceneManager.GetActiveScene().buildIndex;
        if (actualSceneIndex != sceneIndexAtStart)
        {
            Destroy(gameObject);
            Debug.Log("ScenePersist has been destoyed due to SceneChange");
        }
        else
        {
            DontDestroyOnLoad(gameObject);
        }
    }

So in this case nothing happens (eventually a second DontDestroyOnLoad) is executed.

-Now if we die everithing is fine, the new ScenePersist is Destroyed due to the singleton pattern and the initial one is persisting and so are the pickups, that part of the behaviour is working beautifuly.

-If we die completely (0 lives) then it’s also fine, since the ScenePersist ist Destroyed once Main Menu (scene index 0) is loaded since the indexes are different.

-The glitch appears when we move from a scene that starts already with a ScenePersist to a new scene which initially has also one (i.e from scene index 1 to scene index 2). The problem here is that previously, the ScenePersist has executed the DontdestroyOnLoad, so it wont be destroyed once the next scene is loaded. To solve that problem we have the condition checking that the scene indexes are different, but due to the script execution order we have a glitch happening in which once we load the scene, both ScenePersists are Destroyed and no pickups are shown, and then if we die in the scene, the ScenePersist is created and the pickups appear as usually.
The reason is that the Check for the different indexes is realized in the Update, and it needs the ScenePersist to reach the Update for the first time to store the actual scene index and compare it with the old one stored at the start the firt time the class was created. However, as soon as we enter the scene, the second ScenePersist is called and we execute in Awake() inmediately the singletone pattern, which causes its destruction, since the other ScenePersist is still in the scene. Inmediately after, the persistent ScenePersists executes the update, makes the index check, and since the indexes are different, it destroys itself also, leaving the scene with 0 of the two ScenePersist that where on the scene when it was loaded. Then as soon as we die, the ScenePersist that should be in the scene is instantiated and of course it is not destroyed since it is the only one in the scene.

I found a quick patch solution to this problem, pasting on the LevelExit class the following code right before the SceneManager.LoadScene(buildIndex +1);

ScenePersist scenePersist = FindObjectOfType<ScenePersist>();
Destroy(scenePersist);

SceneManager.LoadScene(currentSceneIndex + 1);

This causes the ScenePersist to be Destroyed right before the scene is loaded and solves the problem. However i don’t like this solution because we are managing something related to the ScenePersist from other class and I don’t think this is a good practice. So I would like to know how would you have solve this and eventually would be nice if Rick could update that section or add an extra section to fix that issue.

I just finished the course with this section and it has been an incredible cruise. Thanks to Ben and Rick and Eventually Bryce, (since I started the course from its old version) from all the effort put on this course to make it a brilliant Product with an excellent Quality, I have really enjoyed the whole and I’m starting now the seconod part and the RPG courses.

Hope everyone is still awake :smiley:

I found a much more elegant way of going around this problem, I defined a static variable to store the value of the last scene loaded, and I update this value at the Start. Then I placed a condition on the awake to check the actual scene index and compare it with the value of the static variable. If the values coincide, then the singleton is executed as originally. If the values don’t coincide, then the singleton is executed as a corroutine with a delay of Time.deltaTime, which is the duration of a single frame. That allows the first update call to be done before the Singleton is executed if we advance to a new level, but just execute it inmediatel without delay on Awake if we just die. I paste my modified code of the ScenePersist here.

public class ScenePersist : MonoBehaviour {

    static int sceneIndexofTheLastScene=0;
    private int sceneIndexAtStart;
    

    private void Awake()
    {
        int actualSceneIndex = SceneManager.GetActiveScene().buildIndex;

        if (sceneIndexofTheLastScene == actualSceneIndex)
        {
            int numScenePersist = FindObjectsOfType<ScenePersist>().Length;
            if (numScenePersist > 1)
            {
                Destroy(gameObject);
                Debug.Log("ScenePersist has been destoyed due to Singleton");
            }
            else
            {
                DontDestroyOnLoad(gameObject);
            }
        }
        else
        {
            StartCoroutine(Singleton());
        }
        
    }

    IEnumerator Singleton()
    {
        
        float yieldDuration;

        yield return new WaitForSecondsRealtime(Time.deltaTime);
        
        int numScenePersist = FindObjectsOfType<ScenePersist>().Length;
        if (numScenePersist > 1)
        {
            Destroy(gameObject);
            Debug.Log("ScenePersist has been destoyed due to Singleton");
        }
        else
        {
            DontDestroyOnLoad(gameObject);
        }
    }

    // Use this for initialization
    void Start() {
        sceneIndexAtStart = SceneManager.GetActiveScene().buildIndex;
        sceneIndexofTheLastScene = SceneManager.GetActiveScene().buildIndex;
    }

    // Update is called once per frame
    void Update() {
        CheckIfStillInSameScene();
        
    }

    private void CheckIfStillInSameScene(){
        int actualSceneIndex = SceneManager.GetActiveScene().buildIndex;
        if (actualSceneIndex != sceneIndexAtStart)
        {
            Destroy(gameObject);
            Debug.Log("ScenePersist has been destoyed due to SceneChange");
        }
        else
        {
            DontDestroyOnLoad(gameObject);
        }
    }
}

I hope you found that useful guys. The fact that the static variable is updated at the Start() allows the Awake() to still read the value of the previous level index, while updating it rightly afterwards. I initialized it to 0, since that might be the scene index that we might start with, but it might be actually unnecessary.

7 Likes

This is my “solution” still has the issue of if you lose all lives and die on first level you dont get coins back.

I dont really understand some of the problems occuring so i’ve put a post up if anyone can explain why my old solution didnt work and why this one does more that would help

This is what Im using at the moment

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

public class ScenePersist : MonoBehaviour {

	//no longer assigning -1 as i believe we know this is the new scenePersist because it is running awake
	//and dont destroy on load doesnt cause a gameObject to rerun awake and start methods (i think)


	//still unsure if this needs to be [serialized] what effect would have if public, and if I could still access it if I deleted it here and
	//put i solely in the Awake() does that work because same class can find any variable in another of same class?!
	private int scenePersisting;     

										

 void Awake(){

 //code works but still fails if you die on first level after losing all lives, in this instance the oldscene persist is not replaced by the new one
 //a solution would be either changing the scene perist's scenePerisisting int in the revert to level one to restart game method
 //or simply destroying it in that instance.

 		int numSPWithThisSceneBuildNum = 0;
		scenePersisting = SceneManager.GetActiveScene().buildIndex;


		//array of all ScenePersist (s) and counting the ones with the scene build number
		//this works whereas counting the remaining ones after destroying the ones we did not want
		//did not work because it seemed to count the ones that had been destroyed in previous lines of code
		//i think this is due to execution order but i dont really understand it and I am not sure.

		//this works instead by counting the ones that match the build order we want as one instruction
		//deleting the gameObjects as a seperate instruction so it does not matter if they are not destroyed in an order
		//matching the code.


		ScenePersist[] scenePersists = FindObjectsOfType<ScenePersist>();   
			foreach(ScenePersist scenePersist in scenePersists){
				if(scenePersist.scenePersisting ==  SceneManager.GetActiveScene().buildIndex) {numSPWithThisSceneBuildNum++;}else	{Destroy(scenePersist.gameObject);} }  

		
		//because we are no longer destroying the gameObjects in the awake we are having to do it in the update,
		//i had wanted to avoid this if possible


	//if there is more than 1 ScenePersist assign with this scenes build ord 
	//then because this scenePersist is running the Awake method it must be the new one and should be destroyed
	//However if the player has died losing all their lives in the first scene it should be the old one that dies
		if (numSPWithThisSceneBuildNum>1){Debug.Log("destroying in if >1");  Destroy(gameObject);}                              
																																																				
		DontDestroyOnLoad(gameObject);																				


		
 
}
	void Start () {
	}
	void Update () {
	}
}

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms