Unable to remove GameObjects from list

Hello everyone,

I am trying to keep track of every object in my scene by storing them in certain lists and I am keeping track of those lists in one big list. My code works fine up untill the part where I need to remove the object from the lists when they get destroyed. I got it working with a Debug.Log mesage that can tell me that I have to remove “That” gameobject from “this” list but for whatever reason the .Remove doesn’t actually seem to happen. Can somebody help me out please?

Thanks in advance, Vozze

PS. The relevant part of the code is the objectDestroyed part that gets called by the destroyed object before it actually gets destroyed.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManagerScript : MonoBehaviour {

    //Lists
    List<List<GameObject>> objectContainerLists;
    public List<GameObject> targetableObjects;
    public List<GameObject> players;
    public List<GameObject> Team1;
    public List<GameObject> Team2;

    bool checkedLists;
    GameObject objectToRemove;

    //HealthManager
    GameObject destinationObject;
    HealthManager healthManager;

    //Matchmaker
    int playerCount;



    
    // Use this for initialization
    void Start () {
        players.AddRange(GameObject.FindGameObjectsWithTag("Player"));
        playerCount = players.Count;

        targetableObjects.AddRange(GameObject.FindGameObjectsWithTag("Target"));
        targetableObjects.AddRange(GameObject.FindGameObjectsWithTag("Player"));

        objectContainerLists = new List<List<GameObject>>();
        objectContainerLists.Add(new List<GameObject>(targetableObjects));
        objectContainerLists.Add(new List<GameObject>(players));
        Debug.Log(objectContainerLists);
    }
	
	// Update is called once per frame
	void FixedUpdate () {

        Debug.Log("--------------------------");
        foreach (GameObject obj in targetableObjects) {
            Debug.Log(obj.name);
                }
    }

    public void objectDestroyed(GameObject destroyedObject)
    {
        Debug.Log("Message Recieved");
        foreach (List<GameObject> list in objectContainerLists)
        {
            if (list.Contains(destroyedObject))
            {
                list.Remove(destroyedObject);
                Debug.Log("Removed" + destroyedObject.name + " from " + list);
            }
        }
    }

EDIT:

After going through your repro project it became quite apparent what’s going on:

In GameManagerScript you have the public targetableObjects variable, which is what you observe in the Inspector during gameplay.

However, in the objectDestroyed(GameObject destroyedObject) method you remove the destroyedObject from a List, which is contained in the objectContainerLists List of Lists. Since destroyedObject is found inside one of the Lists in objectContainerLists, it is removed from that particular List, but this doesn’t affect the targetableObjects List, because in Start() you have initialized objectContainerLists with a clone of targetableObjects.

If you want to directly remove the destroyedObject from targetableObjects by way of removing it from objectContainerLists, you should include a reference to it, rather than initialize it with a clone. So, change Start() like this:

    void Start () {
        //players.AddRange(GameObject.FindGameObjectsWithTag("Player"));
        //playerCount = players.Count;

        targetableObjects.AddRange(GameObject.FindGameObjectsWithTag("Target"));
        targetableObjects.AddRange(GameObject.FindGameObjectsWithTag("Player"));

        //missiles.AddRange(GameObject.FindGameObjectsWithTag("Missile"));

        objectContainerLists = new List<List<GameObject>>();
        //objectContainerLists.Add(new List<GameObject>(targetableObjects)); //Creates clone of targetableObjects
        //objectContainerLists.Add(new List<GameObject>(players));  //Creates clone of players

        objectContainerLists.Add(targetableObjects); //References targetableObjects
        objectContainerLists.Add(players);  //References players

        Debug.Log(targetableObjects);
    }

Additionally, I commented out LateUpdate() and added some Debug.Log statements inside objectDestroyed(GameObject destroyedObject) so that it reports what happens in more detail:

    public void objectDestroyed(GameObject destroyedObject)
    {
        /*targetableObjects.Remove(destroyedObject);
        players.Remove(destroyedObject);*/
        /*foreach (List<GameObject> list in objectContainerLists)
        {
            for (int i = 0; i < list.Count; i++)
            {
                // Check if list at index of i is equal to destroyedObject / list is containing destroyedObject
                if (list *== destroyedObject)*

{
list.Remove(list*);*
}
}
}*/

foreach (List list in objectContainerLists)
{
if (list.Contains(destroyedObject))
{
Debug.Log("Before removing item from " + list + “, objectContainerLists contains:”);

foreach (GameObject go in list)
{
Debug.Log(go.name);
}

list.Remove(destroyedObject);
Debug.Log(“Removed: " + destroyedObject.name + " from " + list + " in objectContainerLists”);
//Debug.Log(list);

Debug.Log(“List " + list + " in objectContainerLists now contains:”);

foreach (GameObject go in list)
{
Debug.Log(go.name);
}
}
}
}

Sorry, I didn’t catch this earlier, but it’s much easier to see what’s going on when a repro project is available.
----------
One more thing I’ve just noticed: In HealthManager.Update() you call objectDestroyed(gameObject) if health < 0but health will be less than zero for many frames. So, in the first frame the gameObject is removed from the lists and is then requested to be removed from the list for 10 seconds until the gameObject is destroyed. At 60 FPS there will be 600 calls to objectDestroyed(gameObject), and only the first one will succeed in removing the gameObject from the lists. In fact Destroy(gameObject, 10f) will also be called 600 times at 60 FPS.
Apparently, you have one healthmanager to each destroyable GameObject, so it’s not that different healthmanagers call objectDestroyed(gameObject) with the same gameObject (per my comment), it’s the same healthmanager calling it for 10 seconds.
In that case, use a boolean variable:
private bool hasBeenDestroyed;

void Update () {

health -= damage;

if (health < 0 && !hasBeenDestroyed)
{
gameManagerScript.objectDestroyed(gameObject);

if (explosionCode != null)
{
explosionCode.ExplosionEnter(rigidbody.velocity);
}

Destroy(gameObject, 10f); //10 seconds to make sure everything gets executed right.

hasBeenDestroyed = true; // this will make sure that the “Destroy” code gets called only once
}
}

Looks like List has a built in ForEach method.
Also I do believe that a List of Lists could be better structured as a Dictionary? Not sure, dont use em much.
list.Remove(T) has a boolean return that lets you know if the item was found(and removed) or not present.

I think you should remove list at index where it’s contain/equal to that destroyedObject, I usualy using for loop for this kind of task.
Maybe try this.

public void objectDestroyed(GameObject destroyedObject)
	{
		foreach (List<GameObject> list in objectContainerLists)
		{
			for (int i = 0; i < list.Count; i++)
			{
				// Check if list at index of i is equal to destroyedObject / list is containing destroyedObject
				if (list *== destroyedObject)*
  •  		{*
    

_ list.Remove(list*);_
_
Debug.Log(“Removed” + destroyedObject.name + " from " + list);_
_
}_
_
}_
_
}_
_
}*_

The destroyed GameObject is possibly NULL. Try to remove it from the list right before you destroy it, instead afterwards.

Also “objectContainerLists” probably doesn’t get serialized (because it’s a generic list of generic lists and private)

Check with the debugger if the GameObject is null and if objectContainerLists contains any values.

If objectContainerLists is not serialized you can create a wrapper like this:

[System.Serializable]
public class ObjectContainer
{
    public List<GameObject> Objects = new List<GameObject>();
}

and then use it in your script instead of the List>:

[SerializeField] List<ObjectContainer> objectContainerLists;