[Help] Help understanding why Singleton not working, execution order etc for my failed ScenePersist solution

Hi I have been struggling with the code for this lecture.
There are several ways to write code that does work. And I would like to see how other people solved it.

More importantly I would like to understand why my approach doesn’t work as I feel there is more important fundermental learning in that than actually solving the problem.

What I want the code to do.

To make the gameObject persist if the level is being reloaded - (with exception of if lose all lives on first level)

To make the gameObject not persist. instead being replaced by the new version of the gameObject if the scene being reloaded is the same
because the character is restarting and never progressed past the first level

To ensure that as soon as possible if on reload of the same level causes another gameObject to be created that the unwanted one (the new one) is destroyed
and the player does not see for a moment the gameObject children of both momentarily before one is destroyed.


The course code has the following problems in my game unity 2017.4.9f1

On loading a new level it destroys the new gameObject in the awake phase and then itself for not being the right object for the scene in the update
Resulting in no gameObject at all (and no coins) when you next lose a life the coins reappear

I think (I’ve changed lots) it may also keep the gameObject


How i would like to solve it if possible

I would like to solve it completely in the awake method so it is resolved at load and there is no threat of the player seeing items. And also so
I learn a little bit about the use of awake, start,update, fixupdate.

(If you’re reading because you just want to solve the issue I assume if you just use a find and destroy in the load next level and go back to first level methods. Or solve issues in update when scene has updated info
you may wish to disable some part of the gameObject so that they are invisible until you have resolved which you want. )

-> Mochant solved it here - About 'Remembering Pickups'!


My attempt

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

public class ScenePersist : MonoBehaviour {

 // -1 because I want to be able to search for ScenePersist (s) that have not yet been // assigned a sceneBuildindex number to them	which happens in start

private int scenePersisting = -1;    
										

 void Awake(){

 //array of all ScenePersist (s)
		ScenePersist[] scenePersists = FindObjectsOfType<ScenePersist>();  
			foreach(ScenePersist scenePersist in scenePersists){
				if((scenePersist.scenePersisting != -1) && (scenePersist.scenePersisting !=   SceneManager.GetActiveScene().buildIndex)) //destroy gameObjects that dont have this sceneBuild or -1
						{
							Debug.Log(" destroying in foreach ");
							Destroy(scenePersist.gameObject);}}

		Debug.Log(" scenePersist value " + scenePersisting + " I think this level build is (during the wake) " + SceneManager.GetActiveScene().buildIndex);


	//having destroyed the gameObjects I dont want there should only be left the one made with this scene, and ones from previous loads of THIS scene 

		int numScenePersists = FindObjectsOfType<ScenePersist>().Length;  
		Debug.Log("number scenePersist = " + numScenePersists);

	//if there is more than 1 ScenePersist then because this scenePersist is running the Awake method it must be the new one and should be destroyed
		if (numScenePersists>1){Debug.Log("destroying in if >1");  Destroy(gameObject);}                              
		//else{DontDestroyOnLoad(gameObject);}												


//there should only be one ScenePersist that isnt destroyed by the time it reaches this point in the code which will be the first time a scenePersist is made for a particular scene
//it therefore should not be destroyed on load																																																				
		DontDestroyOnLoad(gameObject);																				


		
 
}



	void Start () {

//This means that the ScenePersist is now numbered to match its scene and so will be destroy on loading a different scene
//by other ScenePersist (s) in their awake function or it will survive if it is the same scene reloaded as they will destroy themselves

scenePersisting = SceneManager.GetActiveScene().buildIndex; 


Debug.Log("in start scenePersisting is " + scenePersisting + "I think this level build is (during the wake) " + SceneManager.GetActiveScene().buildIndex);


		
	}
	
	void Update () {

		
	}


}

My git (not done this before sorry if doesnt work) - https://github.com/phy5prt/G08TileVania2/blob/master/Assets/Scripts/ScenePersist.cs
(if you run it sorry about the camera I’ve been running it in scene view until i sort the cameras)


Observed behaviour

My code has the same issues as in the lecture on loading the next level it destroys the new ScenePersist because there is an existing one and the old one
because it does not match the scene.

My code does it because on line 27 (Debug.Log(…numScenePersists)) it reads 2 ScenePersist so destroys the one that is running the awake function which must therefore be the newest.
However the debug says it has destroyed a ScenePersist in the foreach, which must be the old one. And then destroys the new one in the awake as just mentioned.
So it seems I think?! not to register the other scenePersist was deleted as if it counted the number of them before the delete. However the debug has them delete
in the right order so this seems not the case.


Things I assume I understand and are right:

On reloading a scene if an object persists its awake and start are not rerun. So the singleton logic (is that what they called it) works because if
you are running OnAwake you as a gameObject have just been born, and if another of you exists, and one of you have previously existed then because you
are checking in the Awake and finding another you must be the newer version. (As when they ran awake they must of found no other version and so not deleted themselves).

private int scenePersisting = -1; - I believe I can use a private because I am trying to read scenePersisting of other ScenePersist (s) and that it is
private to the class, so if the same class is looking for it doesnt have to be public.

My double not statement seems to be okay.


Ideas why I think it may not work

The code doesn’t execute in order so the number of persistent objects is higher that it should be because no destroys have run.
https://docs.unity3d.com/Manual/ExecutionOrder.html

From reading what other people have found on awake the game doesn’t know its scene build number again due to execution order maybe?

If I create an array of existing gameObject in awake, might it miss gameObjects because this gameObjects Awake() method has been called first? so
the other objects don’t exist yet?

My debug statements seem to occur in the right order but don’t have the information i expect so I am not sure if this means the code is executed top to bottom or not.


Went back to beginning of the course to see how you are supposed to do this as the discussion so far has always covered everything I needed without me asking up to this week.

Thanks so much in advance I’m hoping to get a better understanding by solving this through the awake with help Im given or just by recognising why my solution fails.

Cheers Phil

1 Like

If anyone wants a solution here is one it is still flawed but fairly neat.

The destroy was causing the issue but i’d still love an explaination to some of my questions above if anyone can answer them because i’ve sort of solved most of it on an inkling so feel ive solved the issues ive solved by luck

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

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

I found a solution to this one. It’s simple to implement but it creates coupling to other scripts.
I simly added
Destroy(FindObjectOfType());
Before loading new Scene Under LevelExit.cs -> LoadNextScene(), and under GameSession.cs -> ResetGameSession()
In ScenePersist I added only Awake() method without index checking

Finally, after a good night’s sleep. I have a corrected version of the scene persist class.

using UnityEngine;
using UnityEngine.SceneManagement;

public class ScenePersist : MonoBehaviour
{
    int startingSceneIndex = -1;

    public int GetStartingSceneIndex ()
    {
        return startingSceneIndex;
    }

    void Awake ()
    {
        startingSceneIndex = SceneManager.GetActiveScene ().buildIndex;
        ScenePersist[] scenePersists = FindObjectsOfType<ScenePersist> ();
        if (scenePersists.Length <= 1) {
            // I am alone
            DontDestroyOnLoad (gameObject);
        }
        else {
            bool destroyMe = false;
            // We are Two
            foreach (ScenePersist scenePersist in scenePersists) {
                if (scenePersist != this) {
                    // It's not me
                    if (scenePersist.GetStartingSceneIndex () != startingSceneIndex) {
                        // You have nothing to do here
                        Destroy (scenePersist.gameObject);
                    }
                    else {
                        // I have nothing to do here
                        destroyMe = true;
                    }
                }
            }

            if (destroyMe) {
                // Seppuku!
                Destroy (gameObject);
            }
            else {
                // They are all dead, I will survive!
                DontDestroyOnLoad (gameObject);
            }
        }
    }

    void Update ()
    {
        int currentSceneIndex = SceneManager.GetActiveScene ().buildIndex;
        if (currentSceneIndex != startingSceneIndex) {
            DestroyObject (gameObject);
        }
    }
}

2 Likes

If anyone comes through here, I found Misha_Betekhtin’s solution to be compact and sweet.
However, when I do Destroy(FindObjectOfType(ScenePersist)); it destroys the class, but not the object.
Adjust to Destroy(FindObjectOfType(ScenePersist).gameObject); and everything is peachy.

1 Like

Privacy & Terms