using DOTween to fade a CanvasGroup, getting a MissingReferenceException when reloading scene

So I need a quick way to just reload the current scene with

void onReloadScene(InputAction.CallbackContext context)
{
Scene scene = SceneManager.GetActiveScene();
SceneManager.LoadScene(scene.name);
}

I get this error message

MissingReferenceException: The object of type 'CanvasGroup' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.

I’m super new to programming but I’m assuming DOTween tries to animate the fading even tho all element havent been loaded yet? Is there a quick fix? I tried using the singlton pattern ‘DontDestroy’ on the UI gameobject (that has the canvas group) but it didn’t work. Could I potentially trigger DOTween slightly later?

Thanks for the help in advance!

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
using TMPro;
using UnityEngine.UI;

public class DistanceCalculation : MonoBehaviour
{

    [SerializeField] GameObject closestPainting;
    [SerializeField] float distanceToPainting;

    [SerializeField] TextMeshProUGUI titleText;
    [SerializeField] TextMeshProUGUI descriptionText;

    [SerializeField] CanvasGroup fadingDescriptionText;
    [SerializeField] CanvasGroup fadingTitleText;
    [SerializeField] CanvasGroup fadingScrim;
    [SerializeField] CanvasGroup fadingRawImageForQRCode;

     [SerializeField] RawImage RawImageReferenceForQRCode;
     [SerializeField] string[] paintingTitle =
    {
        "The Family",
        "Nu au Voile",
        "The Dance Class"
    };
    [SerializeField] string[] paintingDescription =
    {
        "The Kiss ...",
        "Albert Aublet was a pupil ...",
        "This work and its ..."
    };
   
    [SerializeField] Texture[] paintingQRCodes;

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

        if (distanceToPainting < 20f)
        {
       
            switch (closestPainting.name)
            {
                case "Klimt":
                    titleText.SetText(paintingTitle[0]);
                    descriptionText.SetText(paintingDescription[0]);
                                      
                    RawImageReferenceForQRCode.texture = paintingQRCodes[0];

                    FadeIn();
                    // Debug.Log("The Familiy");
                    break;

                case "Lady":
                    titleText.SetText(paintingTitle[1]);
                    descriptionText.SetText(paintingDescription[1]);
                    RawImageReferenceForQRCode.texture = paintingQRCodes[1];
                    FadeIn();
                    // Debug.Log("Lady");
                    break;

                case "Dance Class":
                    titleText.SetText(paintingTitle[2]);
                    descriptionText.SetText(paintingDescription[2]);
                    RawImageReferenceForQRCode.texture = paintingQRCodes[2];
                    FadeIn();
                    // Debug.Log("Dance Class");
                    break;

                case null:
                    Debug.Log("Something went wrong");
                    break;
            }

        }
        else
        {
            FadeOut();
        }

       
    }

    public GameObject FindClosestPainting()
    {
        GameObject[] paintings;
        paintings = GameObject.FindGameObjectsWithTag("Painting");
        GameObject closest = null;
        float distance = Mathf.Infinity;
        Vector3 position = transform.position;
        foreach (GameObject painting in paintings)
        {
            Vector3 diff = painting.transform.position - position;
            float curDistance = diff.sqrMagnitude;
            if (curDistance < distance)
            {
                closest = painting;
                distance = curDistance;

               
            }
        }
        closestPainting = closest;
        distanceToPainting = distance;
        return closest;
    }
   
    void FadeIn()
    {
        fadingScrim.DOFade(1,1);
        fadingTitleText.DOFade(1,2);
        fadingDescriptionText.DOFade(1,2);
        fadingRawImageForQRCode.DOFade(1,2);
    }

    void FadeOut()
    {
        fadingScrim.DOFade(0,1);
        fadingDescriptionText.DOFade(0,1);
        fadingTitleText.DOFade(0,1);
        fadingRawImageForQRCode.DOFade(0,1);
    }  
}

this is what i had on the UI gameobject that has all the text/canvasgroups as children

public class DontDestroy : MonoBehaviour
{

public static DontDestroy Instance { get; private set; }

void Awake()
{
// If there is an instance, and it's not me, delete myself.

if (Instance != null && Instance != this) 
{ 
Destroy(this); 
} 
else 
{ 
Instance = this; 
} 

}
}

It really doesn’t matter what you’re doing. The answer is always the same:

How to fix a NullReferenceException error

https://forum.unity.com/threads/how-to-fix-a-nullreferenceexception-error.1230297/

Three steps to success:

  • Identify what is null ← any other action taken before this step is WASTED TIME
  • Identify why it is null
  • Fix that

Isolate, isolate, isolate, find the null, fix it.

That code looks like a partial yet defective implementation of something singleton-ish.

To understand why, here is some timing diagram help:

https://docs.unity3d.com/Manual/ExecutionOrder.html

If you ACTUALLY need a singleton-ish construct, here is a correct way to do it:
Simple Singleton (UnitySingleton):

(if you don’t actually need it, don’t do it!)

Some super-simple Singleton examples to take and modify:

Simple Unity3D Singleton (no predefined data):

https://gist.github.com/kurtdekker/775bb97614047072f7004d6fb9ccce30

Unity3D Singleton with a Prefab (or a ScriptableObject) used for predefined data:

https://gist.github.com/kurtdekker/2f07be6f6a844cf82110fc42a774a625

These are pure-code solutions, DO NOT put anything into any scene, just access it via .Instance!

If it is a GameManager, when the game is over, make a function in that singleton that Destroys itself so the next time you access it you get a fresh one, something like:

public void DestroyThyself()
{
   Destroy(gameObject);
   Instance = null;    // because destroy doesn't happen until end of frame
}

There are also lots of Youtube tutorials on the concepts involved in making a suitable GameManager, which obviously depends a lot on what your game might need.

OR just make a custom ScriptableObject that has the shared fields you want for the duration of many scenes, and drag references to that one ScriptableObject instance into everything that needs it. It scales up to a certain point.

And finally there’s always just a simple “static locator” pattern you can use on MonoBehaviour-derived classes, just to give global access to them during their lifecycle.

WARNING: this does NOT control their uniqueness.

WARNING: this does NOT control their lifecycle.

public static MyClass Instance { get; private set; }

void OnEnable()
{
  Instance = this;
}
void OnDisable()
{
  Instance = null;     // keep everybody honest when we're not around
}

Anyone can get at it via MyClass.Instance., and only while it exists.

Thank you so much, Kurt-Dekker for taking all this time to write up a very comprehensive response. I do really appreciate it.

I will try to understand it now and read through the links you included. I’ll report if I solve it (or if I’m still stuck)!

Cheers

1 Like

I found the solution here: Scene reloading stops tweening functionality · Issue #59 · Demigiant/dotween · GitHub

I had to call DOTween.Clear(true); before I reloaded the scene, (it did work in SAFE MODE, but I knew it had to be fixed)

2 Likes

Oh thanks, great find, thanks also for coming back to post here… making the forum a better place!

Please stop spamming the forums recommending your product. Whist recommending your product isn’t bad, making many, many such posts as you’ve done today (necroing in a lot of cases) isn’t welcome.

Thanks.

1 Like