Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

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

Discussion in 'Scripting' started by Mahonroy, May 24, 2022.

  1. Mahonroy

    Mahonroy

    Joined:
    May 14, 2022
    Posts:
    14
    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:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5.  
    6. public class GameManager : MonoBehaviour
    7. {
    8.     public static GameManager instance;
    9.  
    10.     // Player Actions From UI
    11.     public bool EventSwingSword = false;
    12.  
    13.     private void Awake()
    14.     {
    15.         if(GameManager.instance != null)
    16.         {
    17.             Destroy(gameObject);
    18.             return;
    19.         }
    20.  
    21.         instance = this;
    22.         DontDestroyOnLoad(gameObject);
    23.     }
    24. }
    25.  
    And here is the error that happens when I change something while its running:
    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!
     
  2. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    14,446
    Interesting. The error is in MoveByTouch.cs, can you provide that code?
     
  3. Mahonroy

    Mahonroy

    Joined:
    May 14, 2022
    Posts:
    14
    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.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Tilemaps;
    5.  
    6. public class MoveByTouch : MonoBehaviour
    7. {
    8.     private BoxCollider2D boxCollider;
    9.     public bool SwingingSword = false;
    10.     private float LastSwing;
    11.     private float SwingingCooldown = 0.3f;
    12.  
    13.     private void Start()
    14.     {
    15.         boxCollider = GetComponent<BoxCollider2D>();
    16.     }
    17.  
    18.     // Update is called once per frame
    19.     void Update()
    20.     {
    21.  
    22.     }
    23.  
    24.     private void FixedUpdate()
    25.     {
    26.         if (GameManager.instance.EventSwingSword)
    27.         {
    28.             if (Time.time - LastSwing > SwingingCooldown)
    29.             {
    30.                 LastSwing = Time.time;
    31.                 GameManager.instance.EventSwingSword = false;
    32.                 SwingingSword = true;
    33.             }
    34.         }
    35.     }
    36.  
    37.     private void LateUpdate()
    38.     {
    39.  
    40.     }
    41. }
    42.  
     
  4. Neto_Kokku

    Neto_Kokku

    Joined:
    Feb 15, 2018
    Posts:
    1,751
    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-&-Live-Recompilation
    https://capeguy.dev/2015/06/no-more-unity-hot-reload/
     
    Joe-Censored and Qriva like this.
  5. Qriva

    Qriva

    Joined:
    Jun 30, 2019
    Posts:
    1,123
    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. https://docs.unity3d.com/2022.2/Documentation/Manual/DomainReloading.html
     
  6. Neto_Kokku

    Neto_Kokku

    Joined:
    Feb 15, 2018
    Posts:
    1,751
    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.
     
  7. Qriva

    Qriva

    Joined:
    Jun 30, 2019
    Posts:
    1,123
    Oh man, you are right... I somehow forgot in the second part we are talking about reload caused by scirpt :rolleyes:
     
  8. Mahonroy

    Mahonroy

    Joined:
    May 14, 2022
    Posts:
    14
    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?

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class GameManager : MonoBehaviour
    6. {
    7.     public static GameManager Instance;
    8.  
    9.     [SerializeField]
    10.     public float WorldPan = 45;
    11.  
    12.     [SerializeField]
    13.     public float WorldTilt = 35;
    14.  
    15.     private void Awake()
    16.     {
    17.         if (GameManager.Instance != null)
    18.         {
    19.             Destroy(gameObject);
    20.             return;
    21.         }
    22.  
    23.         Instance = this;
    24.         DontDestroyOnLoad(gameObject);
    25.     }
     
  9. Mahonroy

    Mahonroy

    Joined:
    May 14, 2022
    Posts:
    14
    Does anyone have some insight into making the GameManager work better?
     
  10. Saniell

    Saniell

    Joined:
    Oct 24, 2015
    Posts:
    168
    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.
    Code (CSharp):
    1.  
    2. private static GameManager _instance;
    3.  
    4. public static GameManager Instance
    5. {
    6.     get
    7.     {
    8.         if(!_instance)
    9.             _instance = FindObjectOfType<GameManager>();
    10.  
    11.         return _instance;
    12.      }
    13.      set => _instance = value;
    14. }
     
  11. Neto_Kokku

    Neto_Kokku

    Joined:
    Feb 15, 2018
    Posts:
    1,751
    The only way to persist data across domain reloads is to use either PlayerPrefs or EditorPlayerPrefs.
     
  12. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    6,015
    Or you know... scriptable objects.
     
  13. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,800
    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<type>() 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.