You have to understand how Unity works behind the scenes. Mono / .NET is a garbage collected language and it works exactly the same in Unity. In most managed languages / environments there is no manual way to destroy objects. Objects get destroyed by the GC when you “clear” all references to the object.
However a lot objects in Unity, to be more precisely all that are inherited from UnityEngine.Object, consists of two parts. A managed object and a native code c++ class in the engine itself. In the managed environment you only “see” the managed object that is “linked” with the native part. This is where two totally different worlds collide.
When you call destroy on the managed object, Unity will destroy the native code part of the object immediately (well DestroyImmediate will since Destroy is delayed one frame). The managed part of the class is still there! Unity will flag the class internally as “destroyed”. Unity has implemented a custom == operator and Equals function. In those functions unity will pretend the reference is null even when it isn’t. This makes it basically impossible to do anything with the reference at all once it’s marked as destroyed.
The managed part will be destroyed by the GC when there is no reference to the object left.
You can simply test this with the following two classes:
// Test.cs
using UnityEngine;
using System.Collections;
public class Test : MonoBehaviour
{
private static int m_UniqueCounter = 0;
private int m_MyID = m_UniqueCounter++;
~Test()
{
Debug.Log("Test destroyed (" + m_MyID + ")" );
}
void Awake()
{
Debug.Log("Test created (" + m_MyID + ")");
}
}
And this one:
// TestManager.cs
using UnityEngine;
using System.Collections;
public class TestManager : MonoBehaviour
{
private Test otherScript;
private Test secondRef;
void Update()
{
if (Input.GetKeyDown(KeyCode.C))
{
otherScript = (new GameObject("TestObject")).AddComponent<Test>();
}
else if (Input.GetKeyDown(KeyCode.D))
{
Destroy(otherScript.gameObject);
}
else if (Input.GetKeyDown(KeyCode.N))
{
otherScript = null;
}
else if (Input.GetKeyDown(KeyCode.S))
{
secondRef = otherScript;
}
else if (Input.GetKeyDown(KeyCode.M))
{
secondRef = null;
}
}
void OnGUI()
{
GUILayout.BeginVertical();
GUILayout.Label("C : otherScript = 'new Test'");
GUILayout.Label("D : Destroy(otherScript.gameObject);");
GUILayout.Label("N : otherScript = null;");
GUILayout.Label("S : secondRef = otherScript;");
GUILayout.Label("M : secondRef = null;");
GUILayout.Label("---");
GUILayout.Label("Is otherScript == null? " + (otherScript == null));
GUILayout.Label("Is secondRef == null? " + (secondRef == null));
GUILayout.EndVertical();
}
}
Just add the TestManager to an GameObject and start the game.
- If you press “C” you will see that a Test script has been created on a new GameObject
- If you press now “D” the GameObject and the Test script will be removed but the destructor of Test will not be executed.
- If you press now “N” you will see that the destructor will get executed after a short time (when the GC collects it)
- Now press again “C” and then “D”. Now we have again a ghost reference which seems to be null but it can even be copied.
- Press now “S” to copy the reference.
- Press “N” to wipe out first reference → nothing will happen
- Press “M” to wipe the second reference → the object will be collected
This “pretending null” behaviour allows a strange looking piece of code which will actually have an effect:
if (otherScript == null)
otherScript = null;
Usually this makes no sense but in the case of Unity it will effectively wipe the reference.
To answer your question about storing referenced in Lists or Array:
The values stored in an array or List behave the same as any other variable. Nobody will remove anything from an array (which isn’t even possible) or a List. The reference will remain the same but will evaluate to “null” once you used Destroy on the object.