Variable is null in a method but accessible in Update() (legendary, David Helgason-level bug)

Hi! I’ve been thinking on this bug for a day or so and I wonder is someone might have some insight on this. The idea is that I am unable to access the cached GameObject _castingOriginCharge from the InstantiateCharge method whereas is is freely accessible in Update(). Both methods are attached to the GameObject of the player and the behavior happens specifically after the player GameObject is Destroy()'ed as a result of death. I would greatly appreciate any insight on this bug. Alternatively, my question is what is different between accessing something in an Update() vs in another Method()? My opinion is that this can only happen if I am accessing the Update() and InstantiateCharge() of different components and therefore objects.

    private void Update()
    {
        Debug.Log(_castingOriginCharge.transform.position);
        // returns the value (not null), every frame
    }
    public void InstantiateCharge(Element element)
    {
        GameObject elementCharge = Instantiate(Terminus.Utils.Prefabs.ElementToChargePrefab(element));
        Debug.Log(elementCharge.transform.position);
        Debug.Log(_castingOriginCharge.transform.position);
        elementCharge.transform.position = _castingOriginCharge.transform.position;
        elementCharge.transform.parent = _castingOriginCharge.transform;
        elements.Add(element);
    }

Hard to tell without more information. Are you saying that Update continues (forever) to be able to read the reference? Or is it just for a single frame?

If it’s ongoing after you destroy the object then most likely you just have multiple copies of that object in the scene. One is destroyed and one is not.

1 Like

Yes @PraetorBlue , it continues forever to be able to read the reference. I’ll look into debugging with the multiple instances idea in mind.

If you merely keep a reference to a GameObject and then change the scene, that will NOT prevent it from being destroyed by normal scene-changing mechanisms.

You need to either parent it to another object marked as DontDestroyOnLoad(), or else mark the root of that GameObject as DontDestroyOnLoad().

1 Like

Thank you guys! It turned out that the whole thing was happening because the input system had a reference to the supposedly Destroy()'ed player GameObject and it seems that Updated() was running on the new GameObject whereas InstantiateCharge() was running on the old one. Are there any fancy patterns for keeping correct references to GameObjects that are frequently Destroy()'ed apart from just reattaching when I know they were Destroy()'ed?

The most reliable pattern is with MonoBehavior calls OnEnable() and OnDisable().

For instance, my Datasacks package is set up to listen to stuff like so:

    void    OnUserIntent( Datasack ds)
    {
/// stuff...
    }

    void    OnEnable()
    {
        DSM.UserIntent.OnChanged += OnUserIntent;
    }
    void    OnDisable()
    {
        DSM.UserIntent.OnChanged -= OnUserIntent;
    }
1 Like