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:
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() == 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.
Answer
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 fromMonoBehaviour
if everything
implementing it is going to be aMonoBehaviour
anyway, so the
correct comparison is used automatically. - Try casting
interacting
to (something descended from...)UnityEngine.Object
before checking fornull
to catch when it's
beenDestroy()
ed. - Make your interactive objects aware that someone is trying to
interact with them, and use anOnDestroy
method to notify the
interactor when the interaction is no longer available.
No comments:
Post a Comment