Issue with accessing object after its been destroyed

  1. Hey guys, sorry for the long one but I’m having abit of an issue with the space invaders game. I’m working on getting the aliens to fire back which works fine until they are destroyed and then i get an error
MissingReferenceException: The object of type 'UnityEngine.GameObject' 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.
UnityEngine.Object+MarshalledUnityObject.TryThrowEditorNullExceptionObject (UnityEngine.Object unityObj, System.String parameterName) (at <44f3679c53d1477a9c6e72f269e3a3a9>:0)
UnityEngine.Bindings.ThrowHelper.ThrowNullReferenceException (System.Object obj) (at <44f3679c53d1477a9c6e72f269e3a3a9>:0)
UnityEngine.GameObject.get_transform () (at <44f3679c53d1477a9c6e72f269e3a3a9>:0)
basicAlien.shoot () (at Assets/Scripts/basicAlien.cs:36)
basicAlien.Update () (at Assets/Scripts/basicAlien.cs:29)

The way I’m handling shooting is to create a timer and then shoot every 3 seconds like this using a basicAlien script:

private float shootTimer = 3f;
private const float shootTime = 3f;

public static List<GameObject> allAliens = new List<GameObject>();

void Start()
{
    foreach (GameObject alien in GameObject.FindGameObjectsWithTag("alien")) {
        allAliens.Add(alien);
    }
}

void Update()
{
    if (shootTimer <= 0) {
        shoot();
    }

    shootTimer -= Time.deltaTime;
}

private void shoot() {
    Vector2 enemyBulletPosition = allAliens[Random.Range(0, allAliens.Count)].transform.position;
    GameObject enemyBullet = Instantiate(spawnEnemyBullet, enemyBulletPosition + (Vector2.down * 0.8f), Quaternion.identity);
    //GetComponent<AudioSource>().Play();
    Destroy(enemyBullet, 1.0f);
    shootTimer = shootTime;
}
}

Next, on my playerBullet script I have this, which calls a different alien script kill method:

private void OnCollisionEnter2D(Collision2D collision) {
    if (collision.gameObject.CompareTag("alien")) {
        collision.gameObject.GetComponent<alien>().kill();
        Destroy(gameObject);
    }
}

Here is that separate alien script:

public GameObject explosion;

public void kill() {
    //GameManager.increaseScore();
    //GetComponent<AudioSource>().Play();
    basicAlien.allAliens.Remove(gameObject);
    GameObject alienExplosion = Instantiate(explosion, transform.position, Quaternion.identity);
    Destroy(alienExplosion, 0.2f);
    Destroy(gameObject);
}

I can’t quite work out why the object is still trying to be accessed after it has been destroyed, as the remove is called before the object is destroyed, so it should no longer be in the list when the shoot method is called. I’m at abit of a loss!

Gotta find what object, find why, fix that.

Based on the stack trace I’d say it’s related to the hairy long line that extracts something from a collection and uses its position to fire a bullet. Tear that line apart:

… and I’m sure you’ll find that allAliens[whatever] is null.

The answer is always the same… ALWAYS!

How to fix a NullReferenceException error

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

NullReference is the single most common error while programming. Fixing it is always the same.

Some notes on how to fix a NullReferenceException error in Unity3D:

If you have more than one or two dots (.) in a single statement, you’re just being mean to yourself.

Putting lots of code on one line DOES NOT make it any faster. That’s not how compiled code works.

The longer your lines of code are, the harder they will be for you to understand them.

How to break down hairy lines of code:

Break it up, practice social distancing in your code, one thing per line please.

“Programming is hard enough without making it harder for ourselves.” - angrypenguin on Unity3D forums

“Combining a bunch of stuff into one line always feels satisfying, but it’s always a PITA to debug.” - StarManta on the Unity3D forums

PS… the original space Invaders is one of the best “learn how different things interact” projects to make… if you make a feature-complete feature-perfect space invaders game and understand all the parts and can talk about it, you are WAY ahead of every other game engineer candidate. :slight_smile:

1 Like

Thanks for getting back to me! So I’ve read those links and had a play around.
I’ve changed my shoot() method to narrow down where the null is happening:

public void shoot() {
    //Vector2 enemyBulletPosition = allAliens[Random.Range(0, allAliens.Count)].transform.position;

    if (allAliens[Random.Range(0, allAliens.Count)] != null) {
        foreach (var x in allAliens) {
            Debug.Log("inside list: " + x.ToString());
        }
    }

    if (allAliens[Random.Range(0, allAliens.Count)] == null) {
        Debug.Log("allAliens returned null");
    }
    
    //GameObject enemyBullet = Instantiate(spawnEnemyBullet, enemyBulletPosition + (Vector2.down * 0.8f), Quaternion.identity);
    //GetComponent<AudioSource>().Play();
    //Destroy(enemyBullet, 1.0f);
    shootTimer = shootTime;
}

And you are correct, the list is returning null, but only after the alien is destroyed, which I guess suggests the remove method is not removing the gameobject, but instead setting its position as null?

So, I went back to my kill method:

public void kill() {
    //GameManager.increaseScore();
    //GetComponent<AudioSource>().Play();
    basicAlien.allAliens.Remove(gameObject);
    GameObject alienExplosion = Instantiate(explosion, transform.position, Quaternion.identity);
    Destroy(alienExplosion, 0.2f);
    Destroy(gameObject);
}

And updated it to the below:

public void kill() {
    //GameManager.increaseScore();
    //GetComponent<AudioSource>().Play();
    GameObject alienExplosion = Instantiate(explosion, transform.position, Quaternion.identity);
    Destroy(alienExplosion, 0.2f);
    Destroy(gameObject);
    basicAlien.allAliens.RemoveAll(gameObject => gameObject == null);
}

And this seems to work exactly as I expect, removing the nulls after the game object has been destroyed. However, this seems a little convoluted to me, and wanted to ask if there is a better way of handling this? Is there a method I’m unaware of that removes the object directly, instead of setting as null and then removing nulls on another line?

UPDATE: Sorry, I spoke too soon, on further testing the error still occurs. I will continue digging!

This is the pattern I use:

If I have a List of GameObjects, generally I leave them ALL in the list even when they get destroyed.

Then before I process anything in the list I do a RemoveAll with a “not true” delegate, ideally doing it only in one place:

// remove any dead ones
myList.RemoveAll( x => !x);

// now process the list of GameObjects
1 Like

If the above code is part of the BasicAlien class, then it would get executed once per every instance of that component in the scene - which could result in many duplicates of each instance getting added into the list.

What you could do instead is to have each alien instance only add themself to the list when they become active, and also handle removing themself from the list when they are destroyed:

public class BasicAlien : MonoBehaviour
{
	public static IReadOnlyList<BasicAlien> AllAlients => allAliens;
	static readonly List<BasicAlien> allAliens = new(64);

	void Awake() => allAliens.Add(this);
	void OnDestroy() => allAliens.Remove(this);
}

This way the list is ensured to always remain up-to-date, without you having to keep track of all the different ways that enemies could get destroyed in potentially multiple external locations.

1 Like

Hi all!
I’ve figured out the issue and solved it slightly differently, though similarly to @SisusCo suggestion.

For those interested, the issue was I’d attached the basicAlien script to each alien, in addition to the stand alone alien script.
What this was doing on start() was each alien was composing a list of all aliens in the scene, and causing all aliens to fire multiple times.
When destroyed, the alien would be removed from its own list, but each other alien in the scene would still have the object in their list, resulting in a null exception when an alien tried to get another destroyed alien to shoot.

To fix this issue, I created an empty parent named alienManager, and placed all alien objects inside it. I then removed the basicAlien script from each of the aliens and instead attached it to the parent object, ensuring only one list was created at start.

Finally, this just caused one final null exception after all aliens were destroyed. This I fixed by just wrapping my shoot method in an if statement to check if allAliens.count was greater than 0.

Thanks for your assistance guys! :slight_smile:

1 Like