Elusive Functionality Glitch. Help Please!

I’ve got something wrong with the GameSession object persisting into the Game Over scene. The original design by Rick has the score displaying in the Game Over scene, and mine did when we first set up GameSession. It stopped displaying in video 70, “Reset Game Session”.

The score displays fine in Scene view. The GameSession object is transferred from my last level or from a game over scenario just fine and appears that it should display. My code is throwing no errors or warnings.

I have spent hours trying to find out why, and I’m hoping someone with fresh eyes might have more luck finding it. My GameSession code is below. I’ve added a few things to Rick’s design, just for practice, so hopefully that doesn’t confuse things.

Any help would be much appreciated. I’ve attached a link to my most recent build’s project files, as well as a link to the build as it was in Video 70 without all my custom edits.

Current build: Smasho v0.99

Video 70 build: Smasho v0.8

SIDE ISSUE: Also in GameSession, the AutoPlay feature worked fine transferring from one level to the next until we made a few edits late in video 79. Clicking AutoPlay On in the GameSession prefab prior to starting the game and the AutoPlay transfers from level to level fine. Any help understanding where I may have buggered it would be appreciated. So far as I can tell I made Rick’s edits to the letter.

using UnityEngine;
using TMPro;
using UnityEngine.SceneManagement;

public class GameSession : MonoBehaviour
{

    // Config Parameters
    [Range(0.1f, 10f)] [SerializeField] float gameSpeed = 1f;   // Speed modifier for the game speed
    [SerializeField] int pointsPerBlockDestroyed = 75;          // Amount of points scored per block
    [SerializeField] TextMeshProUGUI scoreText;                 // Displays the current score in the game scene
    [SerializeField] TextMeshProUGUI levelText;                 // Displays the current level in the game scene
    [SerializeField] bool isAutoPlayEnabled;                    // As it says
    [SerializeField] bool winFlag = false;                      // Determines if the player sees You Win instead of Game Over

    // State Variables
    [SerializeField] int currentScore = 0;      // As it says
    int gameEndingSceneIndex = 6;               // Index of the final scene

    // Cached References
    GameEndMessage gameEndMessage;

    private void Awake()
    {
        int gameStatusCount = FindObjectsOfType<GameSession>().Length;   // Stores how many GameStatus.cs objects there are
        if(gameStatusCount > 1)
        {
            Destroy(gameObject);
        }
        else
        {
            DontDestroyOnLoad(gameObject);
        }
    }

    private void Start()
    {
        DisplayScore();
    }

    private void Update ()
    {
        Time.timeScale = gameSpeed;
	}

    public void AddToScore()
    {
        currentScore += pointsPerBlockDestroyed;
        DisplayScore();
    }

    public void DisplayScore()
    {
        scoreText.text = currentScore.ToString();
    }

    public void WinTheGame()
    {
        winFlag = true;
    }

    public void ResetGame()
    {
        Destroy(gameObject);
    }

    // Below functions display the level number when the scene loads
    void OnEnable()
    {
        SceneManager.sceneLoaded += DisplayLevel;

        // Sets game ending message if the game is over
        int currentSceneIndex = SceneManager.GetActiveScene().buildIndex;
        if (currentSceneIndex == gameEndingSceneIndex && winFlag == true)
        {
            gameEndMessage.DisplayGameEnding("YOU WIN!!");
        }
        else if (currentSceneIndex == gameEndingSceneIndex && winFlag == false)
        {
            gameEndMessage.DisplayGameEnding("Game Over");
        }
    }

    void OnDisable()
    {
        SceneManager.sceneLoaded -= DisplayLevel;
    }

    public void DisplayLevel(Scene scene, LoadSceneMode mode)
    {
        int currentSceneIndex = SceneManager.GetActiveScene().buildIndex;
        if (currentSceneIndex != gameEndingSceneIndex)
        {
            levelText.text = "LEVEL " + currentSceneIndex.ToString();
            Debug.Log("Displayed the level");
        }
    }

    public bool IsAutoPlayEnabled()
    {
        return isAutoPlayEnabled;
    }
}

Your GameSession canvas is displayed below your GameEnd canvas and the score is covered by the background image. To fix that you can simply increase the sorting order of your GameSession canvas (or decrease it for the GameEnd canvas).

grafik grafik


The second issue is a bit tricky.

There is a problem with this code here (GameSession.cs):

int gameStatusCount = FindObjectsOfType<GameSession>().Length;   // Stores how many GameStatus.cs objects there are
if(gameStatusCount > 1)
{
    Destroy(gameObject);
}
else
{
    DontDestroyOnLoad(gameObject);
}

Unity does not destroy objects immediately! A second GameSession object (which would trigger the Destroy game object code) would be flagged for destruction and will stay until the end of the current frame.

Now look at the code in Paddle.cs:

private void Start()
{
    gameSession = FindObjectOfType<GameSession>();
    ball = FindObjectOfType<Ball>();
}

Because the second GameSession object still exists (as we are still in the same frame), Unity finds it and returns a reference to it. After that the second GameSession object is destroyed and gameSession becomes null, leaving the paddle without a reference to any GameSession object.


The simplest solution would be to use
DestroyImmediate(gameObject);

That way the second GameSession game object is destroyed immediately and Unity will always find the correct GameSession object when calling the Start method inside the paddle script.

Or you could make your paddle persistent just like the game session.

1 Like

I have a post on improving the singleton here:

Thanks for posting that Anthony, and cheers Banpa cause that solved the 1st and 2nd issue. I can’t believe I missed the sorting order, newbie or not. I wasted hours on that glitch.

@Anthony_Juarez: That made good reading, though a bit challenging at times. I may have misunderstood something however, because option B still leaves the duplicate script in existence for one frame. The simple solution Banpa proposed seems to solve that even using the singleton that is now being taught.

So now it reads:

    private void Awake()
    {
        int gameSessionCount = FindObjectsOfType<GameSession>().Length;   // Stores how many GameSession.cs objects there are
        if(gameSessionCount > 1)
        {
            DestroyImmediate(gameObject);
        }
        else
        {
            DontDestroyOnLoad(gameObject);
        }
    }

New script is created and awakes. Realizes it’s not alone instantly and self destructs instantly. Problem solved, no?

New Problem
This is blowing my mind like my sorting order screw up did because two of the methods I’ve tried have worked fine in the same game. I have tried solving this with 4 different approaches now, and I get the same error every time: Object reference not set to an instance of an object… GameEndMessage.Start()… line 12.

I’m trying to display You Win or Game Over depending on if the player beats the final level. I get the error with each approach when gameSession or the reference to GameSession is first used.

using TMPro;
using UnityEngine;

public class GameEndMessage : MonoBehaviour
{

    GameSession gameSession;

    void Start()
    {
        TextMeshPro tmProObject = GetComponent<TextMeshPro>();
        bool playerWon = gameSession.ReturnWinFlag();    // ************ Line 12 *************
        if (playerWon == true)
        {
            tmProObject.text = "YOU WIN!!";
        }
        if (playerWon == false)
        {
            tmProObject.text = "Game Over";
        }
    }
}
using UnityEngine;
using TMPro;
using UnityEngine.SceneManagement;

public class GameSession : MonoBehaviour
{

    // Config Parameters
    [Range(0.1f, 10f)] [SerializeField] float gameSpeed = 1f;   // Speed modifier for the game speed
    [SerializeField] int pointsPerBlockDestroyed = 75;          // Amount of points scored per block
    [SerializeField] TextMeshProUGUI scoreText;                 // Displays the current score in the game scene
    [SerializeField] TextMeshProUGUI levelText;                 // Displays the current level in the game scene
    [SerializeField] bool isAutoPlayEnabled;                    // As it says
    public bool winFlag = false;                                // Determines if the player sees You Win instead of Game Over

    // State Variables
    [SerializeField] int currentScore = 0;      // As it says
    int gameEndingSceneIndex = 6;               // Index of the final scene

    private void Awake()
    {
        int gameSessionCount = FindObjectsOfType<GameSession>().Length;   // Stores how many GameSession.cs objects there are
        if(gameSessionCount > 1)
        {
            DestroyImmediate(gameObject);
        }
        else
        {
            DontDestroyOnLoad(gameObject);
        }
    }

    private void Start()
    {
        DisplayScore();
    }

    private void Update ()
    {
        Time.timeScale = gameSpeed;
	}

    public void AddToScore()
    {
        currentScore += pointsPerBlockDestroyed;
        DisplayScore();
    }

    public void DisplayScore()
    {
        scoreText.text = currentScore.ToString();
    }

    public void WinTheGame()
    {
        winFlag = true;
    }

    public void ResetGame()
    {
        Destroy(gameObject);
    }

    // Below functions display the level number when the scene loads
    void OnEnable()
    {
        SceneManager.sceneLoaded += DisplayLevel;
    }

    void OnDisable()
    {
        SceneManager.sceneLoaded -= DisplayLevel;
    }

    public void DisplayLevel(Scene scene, LoadSceneMode mode)
    {
        int currentSceneIndex = SceneManager.GetActiveScene().buildIndex;
        if (currentSceneIndex != gameEndingSceneIndex)
        {
            levelText.text = "LEVEL " + currentSceneIndex.ToString();
            Debug.Log("Displayed the level");
        }
    }

    public bool ReturnWinFlag()
    {
        return winFlag;
    }

    public bool IsAutoPlayEnabled()
    {
        return isAutoPlayEnabled;
    }
}

Thanks in advance for any insight anyone can provide

@ErrantBacon you atted at wrong person, by the way.

Cheers!

I can’t help but find the tagging of the wrong Anthony ironic in a discussion about singletons… Lol…

Regarding your NullReferenceException error…

You have declared a member variable in your GameEndMessage, as you havent specified an access modifier it uses the default which in C# is private. As such I can assume you haven’t made a reference to the GameSession GameObject via the Inspector.

At no point do you initialose this variable, then on line 12 you try to access a member of it, but it has no reference to the object, hence the error.

Befors you try using it, initialise it with something along the lines of;

gameSession = FindObjectOfType<GameSession>();

Assuming you have one in the scene, this will initialise your member variable and the NullReferenceException error should no longer occur.

Hope this helps. :slight_smile:

I can’t help but find the tagging of the wrong Anthony ironic in a discussion about singletons… Lol…

LOL, I nearly spilled my drink there! :smiley:

1 Like

Realizes it’s not alone instantly and self destructs instantly. Problem solved, no?

I’ve been warned against using DestroyImmediate().

Raistael has posted this recently in Discord:

“Basically speaking, when you call Destroy(), Unity runs around and starts cleaning up things behind the scenes. If you call DestoryImmediate() and can mess up this process, depending on what you’re destroying and what it’s doing, leaving orphaned stuff around and forcing extra calls to the GC and whatnot. It’s meant primarily for Editor scripts, since the delayed Destroy() doesn’t happen in Edit mode. Also… DestroyImmediate() can actually destroy assets permanently, if I remember right, which can be bad juju.”

And:

“The more complicated the game is, the more likely it is to cause problems if you don’t know what you’re doing. That said, if you’re using DestroyImmediate() as some hack to get around some weird issue you’re experiencing with a standard Destroy() in gameplay code, you’re probably abusing its intended use and should reconsider a proper fix. Again, it was included primarily for use with Editor scripts.”

because option B still leaves the duplicate script in existence for one frame.

Yes, but it doesn’t matter, because each duplicate has a reference to the original script. So if you can find the duplicate, its really easy to point yourself toward the original.

2 Likes

Unity’s documentation also recommends it isn’t used, unless you are using it for Editor scripts;

A way to avoid any duplication would be to have the instance of the singleton actually create the GameObject and attach the script component when it checks to see if one exists or not, rather than having one already in the scene. e.g it self-instantiates when required.

I would handle that through the static property, so you could use;

GameSession.Instance.DoSomething();

You would reference the class not the GameObject, you then reference the property which does the check to see if the _instance variable is equal to null or not.

Something along the lines of;

if(_instance == null)
{
    GameObject gameSession = new GameObject();
    gameSession.name = "GameSession";

    _instance = gameSession.AddComponent<GameSession>();

    DontDestroyOnLoad(gameSession);
}

Hope this helps :slight_smile:

1 Like

Keep in mind that the simplest way is not always the best way ^^

DestroyImmediate is the simplest way because you just have to add one word and it works as intended without adding any new concepts (i don’t think Rick has introduced static variables in this part of the course yet).


As @Rob and @Anthony_Juarez said, the best way to do it is to add a static variable that stores your GameSession object.

GameSession.cs would look like this (I’ve added comments for you to understand what’s going on):

public class GameSession : MonoBehaviour
    {
        // Static Variables
        public static GameSession Instance { get; private set; }    // Holds the first GameSession object, the setter is private so we can't modify the value from outside

        // Config Parameters
        [Range(0.1f, 10f)] [SerializeField] float gameSpeed = 1f;   // Speed modifier for the game speed
        [SerializeField] int pointsPerBlockDestroyed = 75;          // Amount of points scored per block
        [SerializeField] TextMeshProUGUI scoreText;                 // Displays the current score in the game scene
        [SerializeField] TextMeshProUGUI levelText;                 // Displays the current level in the game scene
        [SerializeField] bool isAutoPlayEnabled;                    // As it says
        public bool winFlag = false;                                // Determines if the player sees You Win instead of Game Over

        // State Variables
        [SerializeField] int currentScore = 0;      // As it says
        int gameEndingSceneIndex = 6;               // Index of the final scene

        private void Awake()
        {
            if (Instance == null)                  // we check if there is nothing stored inside the Instance variable (null means 'nothing')
            {
                // the Instance variable is empty meaning that this is the first GameSession object
                Instance = this;                   // we store this GameSession object inside the Instance variable
                DontDestroyOnLoad(gameObject);     // and we don't want Unity to destroy this object when changing the scene
            }
            else                                   // if we arrive here then Instance is not empty and that means there is a GameSession object stored in it already
            {
                // we don't need this instance of GameSession and destroy it by destroying the gameObject it's sitting on
                Destroy(gameObject);
            }
        }

then, inside your Paddle.cs (or anywhere else) you would refer to the GameSession object like this:

GameSession.Instance.(function/variable/etc.);

as you see, that also eliminates the need of using the costly FindObjectOfType function.


Btw regarding DestroyImmediate:

In my opinion it is perfectly fine to use it in this case. We are calling it from Awake which is called after we press play and after Unity has taken a snapshot of all objects in the scene.
It is impossible that it would delete any data permanently because Unity would restore the snapshot as soon as we stop the game (unless there is a bug in Unity lol).
Now if you use it in a function like OnValidate which is called by the editor even outside the play mode then there is a risk of losing assets.
Also, there are no variables or lists that would store the second GameSession object (as we get rid of it as quickly as possible), so the GC won’t have much to do.

As you can see, there is no harm to use it here. Just don’t use it EVERYWHERE instead of the Destroy function.

1 Like

Haha… I’ve got my newbie hat on tight. I didn’t realize using @ tagged the atted person. Forgive me trying to save letters where I could Anthony!

@Rob I feel silly for forgetting about initializing gameSession. Problem solved. Thanks man

@Anthony_Juarez The duplicate is being found because Destroy() is in the Start() function of the script which is pretty simple. Searching for a way to reference a specific instance of a script provides a long list of totally unrelated articles and posts. How would I reference the original script as you suggested? Would the approach also work for finding originals of an object if there were duplicates? Are you suggesting to use an approach like Rob and Banpa suggest below?

@Rob & @Banpa RE: Instance variable approach
That seems a lot more airtight. and you’re right Banpa, Rick hasn’t covered Static variables yet. I should finish the course before diving into making new scripts I think haha. Thanks for the commented code block Banpa. It took a few reads to fully understand it but you really spelled it out. I hope others with similar issues can stumble on this as well and find that useful.

Cheers guys, I really appreciate all the help. You’ve taught a man to fish

EDIT: Many kudos to you guys. I implemented Banpa’s code and the Instance variable for GameSession in all my scripts, and it works great. It actually feels a lot more simple this way, and streamlined since I was able to remove a lot of code like caching instances and using FindObjectOfType lines. The only hurdle was wrapping my brain around the concept. Was well worth the effort haha

Here is the final version if anyone wants a peek. The zip file contains the unity project files, not a build.
Smasho v1.0

2 Likes

@Rob I feel silly for forgetting about initializing gameSession. Problem solved. Thanks man

Don’t sweat it, we all do things like this from time to time, you are not alone and you’re very welcome :slight_smile:

That seems a lot more airtight. and you’re right Banpa, Rick hasn’t covered Static variables yet.

I think in the original content it got covered, not sure if Rick will be covering this later or not, but in all cases, it’s certainly a topic worth investing some time on to be happy about.

Enjoy the rest of the course :slight_smile:

Privacy & Terms