MissingReferenceException when in fact we can see the reference in inspector

Hello everyone!
Sometimes Unity make me feel myself an idiot.
Let’s imagine the situation, when we have 2 classes GameManager and UIGameManager with very simple logic and also simple scene with gameobjects for our GameManager and UIGameManager scripts, also we have Canvas with Button “End game” and Panel which contains Button “Restart game”. So, what we want is change GameStatus when “End game” button is pressed and after that we want show Panel which contains our “Restart game” button. When “Restart game” button is pressed we just reload our scene. It’s pretty easy, isn’t it? Here is my implementation of that.

public class UIGameManager : MonoBehaviour {

    [SerializeField]
    private GameObject _panel;
    [SerializeField]
    private Button _restartButton;
    [SerializeField]
    private Button _endGameButton;

    public static event Action EndGameButtonPressed = () => { };
    public static event Action RestartGameButtonPressed = () => { };

    private void Awake()
    {
        GameManager.GameStatusChanged += OnGameStatusChanged;
    }

    private void Start()
    {
        _restartButton.onClick.AddListener(() =>
        {
            RestartGameButtonPressed.Invoke();
        });
        _endGameButton.onClick.AddListener(() =>
        {
            EndGameButtonPressed.Invoke();
        });
    }

    private void OnGameStatusChanged(GameStatus gameStatus)
    {
        if (gameStatus == GameStatus.GameOver)
        {
            OpenMenu();
        }
    }

    private void OpenMenu()
    {
       // if (_endGameButton)
        {
            _endGameButton.gameObject.SetActive(false);
        }
       // if (_panel)
        {
            _panel.SetActive(true);
        }
    }
}

public enum GameStatus
{
    GameOver,
    GameNotOver
}

public class GameManager : MonoBehaviour {

    public static event Action<GameStatus> GameStatusChanged = (gameStatus) => { };

    private void Awake()
    {
        UIGameManager.EndGameButtonPressed += OnEndGame;
        UIGameManager.RestartGameButtonPressed += RestartGame;
    }

    private void OnEndGame()
    {
        GameStatusChanged.Invoke(GameStatus.GameOver);
    }

    private void RestartGame()
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
    }
}

What is the expectable behaviour of this code? On my opinion everything should be fine. But if we “end the game”, after that “restart it” and finally we will try to “end the game” again we will see “MissingReferenceException: The object of type ‘Button’ has been destroyed but you are still trying to access it.”
In fact the same is with ‘Panel’. But in inspector we can see that all the references still exist. I was really confused, so I started to search the answer and I found this article Null Reference for Object that is Not Null in the Inspector - Unity Engine - Unity Discussions
Especially look on the answer #9. So, if we add the null check for “_endGameButton” and “_panel” (just uncomment my ‘if’ statements) unity won’t consider them destroyed and our code would work fine as we expected.

I do want understand in which situations I should check variables on null. Obviously we can avoid assigning variables in inspector and use GetComponent or Find in script, but it’s the best approach which everyone recommends for obvious reasons.

And also, you don’t think that it’s bug of unity and it shouldn’t work in this way?

It’s not a bug. You have made your EndGameButtonPressed/RestartGameButtonPressed static. That means they live forever and hang onto all references, destroyed and otherwise.

When you poke “invoke” you are calling ALL past instances of subscribers to those events.

What is happening is when you change scenes, the buttons (and any other references) are going away, but you aren’t taking them off its two static event lists. You could provide an OnDisable() function in the GameManager script that does this:

void OnDisable()
{
   UIGameManager.EndGameButtonPressed -= OnEndGame;
   UIGameManager.RestartGameButtonPressed -= RestartGame;
}

I forget exactly but I think you might need to guard that a wee bit because otherwise the event might complain if it isn’t already in there, like when the editor transitions in and out of gameplay mode, for instance.

One thing that stood out was your if statement commented out (two of them).

Okay, however your issue… is it maybe because the event is static? You have old listeners there, still.
You could try “OnDisable” to remove the listeners (for example, when the scene changes, this will call).

  • Very similar answer given a moment before I finished mine :slight_smile:
1 Like

Thanks for response guys. Obviously unsubscribe from event resolves the situation. But I want find out how does it works when null check also helps. As I understood it is something like on the second time static events calls two handlers with old and new references. And obviously in the old one these references were destroyed (coz that canvas was destroyed), but in the new one they are exist. Does it work in this way? And on my opinion if it works so, it’s definetely bug, because it’s unexpected behaviour. As user I can see in inspector that my references still exist, but Unity telling me that they were destroyed. I think Unity should unsubscribe from all static events by default when OnDisable invokes.

Oh my, that would be BAD. Unity would have to anticipate ALL the possible ways that could happen: they would have to follow not only statics, but every static object’s reference to instance objects, which can also keep references, and since they are referred to by the static, won’t get garbage collected… seriously, you don’t want Unity to be responsible for every possible object hierarchy nightmare imaginable going forward, especially when you have seen some of the crazy inheritance trees (bushes really) that I’ve seen.

This is just fine the way it is. It’s a resource. You allocated it and told Unity to use it. When you’re done, free it.

Not all the possible ways that could happen, just to exclude situations with unexpected behaviour. To prevent antipatterns like “crazy inheritance trees” there should be much more lessons about correct architecture in Unity. There are a lot “How easy is to make the game using Unity” videos, but not a single “How to make high-quality game using Unity”.

Sounds like I’m writing on C, but I thought I’m writing on C# :slight_smile: That’s what I’m talking about - Unity shouldn’t change language principles it should complement them.

But we can’t change this and as I understand there are a lot of developers who accepted that.

You’re supposed to unsubscribe from events in “core” C# too. :stuck_out_tongue:

You’re also supposed to null check before accessing a reference’s members.

There’s only so much a language can do to protect the user from themselves. Every time a language tried to be too “smart”, it generally caused bugs and frustrations, and users were forced to find workarounds to stop the language from trying to be too clever for its own good. Take the garbage collector for example. Most of the time it generally does a good job, but when it fails… oh boy. Have fun working around GC issues. (Calling native functions is a good source of these types of errors).

So, I’d prefer my languages to err on the dumb side rather than trying to be too smart and create difficult to fix issues for me.

1 Like

Oh, and for the record, there ARE a couple of cases where Unity tries to complement C#, and it’s proven to bite people in the ass. The fake null comes to mind.

The null coalescing operator currently doesn’t work on UnityEngine.Object derived classes because of that.

Of course if I want them to stop handling. In described case I don’t really want to stop event handling, but I should unsubscribe from events to prevent null exceptions in old event handlers (because objects in old handlers were destroyed, but not the handler). And it’s definetely the place where Unity should complement C#. Obviously if I load/reload scene usually I want forget about all the objects, handlers generally speaking references which were on the old scene.

Generally speaking yes and it’s good practice in C#. But if we will add null check everywhere our code become awful. That’s why first of all we should ask ourself “Could this object be null here in any case?”. And only if we get positive answer we should add null check.

I’m not talking about “smart” languages or technologies (game engines as example), I’m talking about avoiding unexpected behaviour. In case of these theme Inspector saying me that everything ok and I have references, but in console I see exception that object is destroyed. Sure we can explain it, as well as we can explain a lot of crashes in different application. But all these is unexpected behaviour and have only one name - bug!

I agree with you, it’s true! But it’s only because it’s wrong way to complement C#, not because the idea to complement C# is bad.