GameManager goes null when I run the game in Unity and change C# code realtime

Hey guys, I created a GameManager (code below). Everything seems to work fine, until I change some code in Visual Studio while the game is running. It doesn’t matter what I change, it will go null after it rebuilds while running. At this point, I start getting NullReferenceException errors on this line of code in my player object:
if(GameManager.instance.EventSwingSword)

Here is the Game Manager Code:

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


public class GameManager : MonoBehaviour
{
    public static GameManager instance;

    // Player Actions From UI
    public bool EventSwingSword = false;

    private void Awake()
    {
        if(GameManager.instance != null)
        {
            Destroy(gameObject);
            return;
        }

        instance = this;
        DontDestroyOnLoad(gameObject);
    }
}

And here is the error that happens when I change something while its running:
8154074--1059446--upload_2022-5-24_15-47-17.png

I understand that I could get by with not changing code during runtime - but its a very convenient feature for testing/troubleshooting/debugging/etc. Plus, it makes me think I don’t have my GameManager set up correctly or something. I did create it based off online tutorials, so I am not sure what I am missing here.

Any help or advice with this is greatly appreciated, thanks!

Interesting. The error is in MoveByTouch.cs, can you provide that code?

Sure. Here is the piece of code that error is refering to (under “FixedUpdate”). The line “if (GameManager.instance.EventSwingSword)”. In this case its line #26 - I stripped out the rest.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;

public class MoveByTouch : MonoBehaviour
{
    private BoxCollider2D boxCollider;
    public bool SwingingSword = false;
    private float LastSwing;
    private float SwingingCooldown = 0.3f;

    private void Start()
    {
        boxCollider = GetComponent<BoxCollider2D>();
    }

    // Update is called once per frame
    void Update()
    {

    }

    private void FixedUpdate()
    {
        if (GameManager.instance.EventSwingSword)
        {
            if (Time.time - LastSwing > SwingingCooldown)
            {
                LastSwing = Time.time;
                GameManager.instance.EventSwingSword = false;
                SwingingSword = true;
            }
        }
    }

    private void LateUpdate()
    {

    }
}

Static variables are nuked during domain reload after compiling scripts during play mode.

There are so many restrictions to what you can do to make play-mode compilation work that it’s not really worth it for the trouble. When scripts are recompiled, Unity first serializes everything in the scene, reloads the C# domain and then reloads everything back. Only stuff that can be serialized by Unity can survive the transition. This means your statics are gone, references to pure C# classes/structs which aren’t serializable are gone (this includes dictionaries, queues, stacks, hashSets, etc), coroutines are gone, the list goes on.

https://github-wiki-see.page/m/antfarmar/Unity-3D-Asteroids/wiki/Hot-Reloads-%26-Live-Recompilation
https://capeguy.dev/2015/06/no-more-unity-hot-reload/

2 Likes

I would like to add that there are many, many things that might break your application during domain reload, so I just stopped caring at some point. I mean it’s great feature, but it is ultra hard to keep it possible.

However, it is possible to turn off domain reload and static variable will not be nuked, however it has some other consequences. Unity - Manual: Domain Reloading

That only disables the automatic domain reload that happens when entering/leaving play mode, making it faster. When a C# script is modified a domain reload is absolute necessary, the changes wouldn’t show up otherwise.

Oh man, you are right… I somehow forgot in the second part we are talking about reload caused by scirpt :roll_eyes:

Hey guys, I am still struggling with the exact same problems. I created two serialized fields (WorldPan, and WorldTilt), and like before, everything works fine until a domain reload happens. In this case, the GameManager goes null, and thus my serialized fields can’t be read. Is there anything I can do to at least retain those 2 variables during a domain reload?

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

public class GameManager : MonoBehaviour
{
    public static GameManager Instance;

    [SerializeField]
    public float WorldPan = 45;

    [SerializeField]
    public float WorldTilt = 35;

    private void Awake()
    {
        if (GameManager.Instance != null)
        {
            Destroy(gameObject);
            return;
        }

        Instance = this;
        DontDestroyOnLoad(gameObject);
    }

Does anyone have some insight into making the GameManager work better?

No, you can’t save your game manager instance during domain reload that happens in play mode. In fact, you can’t save any static variables at all, as it was already explained.
Best thing you can do is to disable domain reload during playmode by going to Edit->Preferences->General->Script Changes While Playing and set that to Recompile After Finished Playing.

The only way to save domain reload (probably!) here would be doing something like this, but honestly you’ll have better time without that feature.

private static GameManager _instance;

public static GameManager Instance
{
    get
    {
        if(!_instance)
            _instance = FindObjectOfType<GameManager>();

        return _instance;
     }
     set => _instance = value;
}

The only way to persist data across domain reloads is to use either PlayerPrefs or EditorPlayerPrefs.

Or you know… scriptable objects.

4 Likes

Yeah. Don’t make it static, initialize all needed systems for the game loop there with a bool switch that confirms its presence in an update() loop. If the bool switch flips false it should do a GetComponent() to get the necessary references again and populate them. If you try to control game mechanics from a static GameManager you are looking at a possible world of hurt down the road. Control the game mechanic from the object itself and not the GameManger. That should only make sure everything needed in your scene is up and running and that is that… If it needs persistence from scene to scene add DontDestroyOnLoad. For your sanity’s sake down the road as well stay away from Vanilla C# events and Interfaces. If events and listeners are your habit from enterprise coding practices then break the habit and learn to use the game loop to comprehensively track the state of your scene. Lastly…do not over-engineer your framework. KISS and Principle of Least Astonishment should be adhered to.