3
\$\begingroup\$

I've created an interface class for some mechanic I'm using to interact with things in my game. Now, I noticed that checking if that value is null never returns true.
Here's a screenshot of where this happens:

Screenshot comparing interacting to null — debugger says its value is "null"

The error I get is the following:

MissingReferenceException: The object of type 'InteractiveItem' 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.  
InteractiveItem.CurrentGameObject () (at Assets/Scripts/Interactive/InteractiveItem.cs:10)
Interactive.ManualStopInteract () (at Assets/Scripts/Interactive/Interactive.cs:30)

The line where this fails is:

if (IsInteracting() && this.interacting.CurrentGameObject().GetComponent<InteractiveUtilityChest>() == null && !MainReferences.UIReferences.IsAnyMenuWindowOpen()) {
        // do something
}

The IsInteracting() check is for some reason returning true here because the last line (InteractiveItem.cs:10) is in the method interacting.CurrentGameObject()

I don't get how this can happen or how I should solve it. As far as I know, an interface is a nullable type.

\$\endgroup\$
1
  • \$\begingroup\$ Can you provide relevant code parts? Also debugger being attached to a program may cause different behaviour - I am afraid a screenshot is not sufficient to find the problem here. \$\endgroup\$
    – wondra
    Commented Aug 26, 2016 at 17:28

4 Answers 4

9
\$\begingroup\$

I have a hypothesis for what might be causing this...

First, I need to explain something about null in Unity: when an instance descended from UnityEngine.Object (including GameObject or MonoBehaviour) gets Destroy()ed, it does not actually become null.

(In C#, a variable will only hold a value of null if it's uninitialized, or if it's been assigned myVariable = null explicitly - nothing can delete the object out from under you as long as any active script holds a reference to it)

Once the Destroy() takes effect at the end of the current frame's updates (before rendering), references to the instance will compare as equal to null, because Unity overloads the == operator for UnityEngine.Object. But the reference is still non-null. Try this example:

IEnumerator NullTest()
{
    var myObject = new GameObject();
    Destroy(myObject);

    Debug.Log("Is it null immediately?"
             + (myObject == null));                             // false

    yield return null; // Wait one frame for Destroy() to take effect

    Debug.Log("NOW is it null? "
              + (myObject == null));                            // true

    Debug.Log("But is it *really* null? "
              + System.Object.ReferenceEquals(myObject, null)); // false
}

This "pseudo-null" stub is what lets Unity understand what you were trying to do and give you a tailored error message:

MissingReferenceException: The object of type 'InteractiveItem' has been destroyed but you are still trying to access it.

All real null values look alike, so if it was a real null Unity wouldn't be able to tell it came from a destroyed object.

Okay, so with that background, why is your code giving this confusing result?

Without seeing more of your code it's hard to say for sure, but I have a suspicion that you're implementing this interface on a MonoBehaviour, and your null check doesn't know it's working with a class descended from UnityEngine.Object - all it knows is that it implements the InteractiveItem interface.

So this line:

return interacting != null;

is using the standard comparison, like System.Object.ReferenceEquals(). That replies "Well, no, it's been Destroy()ed, but it's not literally null" so IsInteracting() returns true.

Then your code proceeds and tries to call

this.interacting.CurrentGameObject()

and Unity steps in to say "Why are you trying to access a member of a Destroy()ed MonoBehaviour?" and throws an error.

So, some possible fixes...

  • Make InteractiveItem descend from MonoBehaviour if everything
    implementing it is going to be a MonoBehaviour anyway, so the
    correct comparison is used automatically.
  • Try casting interacting to (something descended from...)
    UnityEngine.Object before checking for null to catch when it's
    been Destroy()ed.
  • Make your interactive objects aware that someone is trying to
    interact with them, and use an OnDestroy method to notify the
    interactor when the interaction is no longer available.
\$\endgroup\$
3
  • \$\begingroup\$ Thanks for all this information. Never heard about this, but it makes sense. The possible fix with the OnDestroy would possibly be the best option (and the one that works), so I'm going to try that out as fast as possible. \$\endgroup\$
    – Dries
    Commented Aug 28, 2016 at 11:43
  • \$\begingroup\$ The OnDestroy solution did the trick! \$\endgroup\$
    – Dries
    Commented Aug 28, 2016 at 11:54
  • 1
    \$\begingroup\$ Glad to hear it! :) Good luck! \$\endgroup\$
    – DMGregory
    Commented Aug 28, 2016 at 11:58
1
\$\begingroup\$

I ran into this same problem while working with a list of interfaces and ended up figuring out a different solution.

Instead of

if( InterfaceToGameObject == null )

I used

if( InterfaceToGameObject.Equals(null) )  

Worked like a charm.

\$\endgroup\$
1
\$\begingroup\$

To add to what DMGregory wrote, you can use this to check interfaces for unity's "null" objects (taken from Unity.VisualScripting):

    public static bool IsUnityNull(this object obj)
    {
        // Checks whether an object is null or Unity pseudo-null
        // without having to cast to UnityEngine.Object manually

        return obj == null || ((obj is UnityObject) && ((UnityObject)obj) == null);
    }
\$\endgroup\$
-2
\$\begingroup\$

Without seeing the rest of the code, my guess is that your variable interacting was never instantiated after it was declared.

if it was a member variable (not sure of your coding experience). It would be the same as doing something like this.

private MyClass obj;

if(obj != null) { //this code would never run because object wasn't initialized }

This would be fixed by changing your code to create an object, rather than just declaring the type for the variable.

private MyClass obj = new MyClass();

Now there is an object created and can be manipulated.

If that is not the case, are you writing specific logic in your interface, or a concrete class that extends/implements the interface?

\$\endgroup\$
1
  • 2
    \$\begingroup\$ This answers the question "why is this instance null," not the question which was asked, which is "why is this null check failing to reject this null instance?" Also this bit: "my guess is that your variable interacting was never instantiated after it was declared" is wrong - the error message above tells us explicitly that "The object of type 'InteractiveItem' has been destroyed" \$\endgroup\$
    – DMGregory
    Commented Aug 27, 2016 at 12:34

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .