- Home /
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);
}
}
}
$$anonymous$$ake sure destroyedObject is not null. Otherwise it's expected behaviour.
Yes, I know for sure it's not null. That's why I'm confused. I got it working with:
targetableObjects.Remove(destroyedObject);
players.Remove(destroyedObject);
but this is only a temporary solution.
putting a breakpoint and seeing what comes in and what list you're dealing with is the easiest solution to finding out what's happening here
How/where do you call objectDestroyed(GameObject destroyedObject) ?
Also, before Debug.Log("$$anonymous$$essage Recieved");
use another debug statement Debug.Log(destroyedObject.name);
objectDestroyed can get called by every healthmanager in the scene. here is the code: using System.Collections; using System.Collections.Generic; using UnityEngine;
public class Health$$anonymous$$anager : $$anonymous$$onoBehaviour {
[Header("Health")]
public float health = 100;
ExplosionCode explosionCode;
Rigidbody rigidbody;
GameObject game$$anonymous$$anager;
Game$$anonymous$$anagerScript game$$anonymous$$anagerScript;
float damage;
int frames;
// Use this for initialization
void Start () {
health -= damage;
explosionCode = GetComponentInChildren<ExplosionCode>();
rigidbody = GetComponent<Rigidbody>();
game$$anonymous$$anager = GameObject.Find("Game$$anonymous$$anager");
game$$anonymous$$anagerScript = game$$anonymous$$anager.GetComponent<Game$$anonymous$$anagerScript>();
}
// Update is called once per frame
void Update () {
health -= damage;
if (health < 0)
{
game$$anonymous$$anagerScript.objectDestroyed(gameObject);
if (explosionCode != null)
{
explosionCode.ExplosionEnter(rigidbody.velocity);
}
Destroy(gameObject, 10f); //10 seconds to make sure everything gets executed right.
}
}
public void takeDamage(float inco$$anonymous$$gDamage)
{
damage = inco$$anonymous$$gDamage;
if (damage > 0)
{
Debug.Log("Damage taken: " + damage);
}
}
}
Answer by pako · Apr 01, 2018 at 11:29 AM
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[i] == destroyedObject)
{
list.Remove(list[i]);
}
}
}*/
foreach (List<GameObject> 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 < 0
but 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
}
}
Thanks for the feedback! This is the current situation, breakpoints and unity debugger used and I changed my health$$anonymous$$anager (Thanks!): https://ibb.co/m8V2kS https://ibb.co/iCtoQS https://ibb.co/eWC4zn The item gets removed out of the list in the "objectDestroyed" class but this doesn't seem to affect the list in the game$$anonymous$$anager because unity happily keeps printing the gameObject in the debugger (See "Update"). I am completely lost now.
I can see the following from your screenshots:
Before: when Game$$anonymous$$anagerScript starts there are 2 targets, namely redTarget6 & 7, and a Player (apparently also a target), inside the targetableObjectsList. Fine...
While: at some point during the execution of the
objectDestroyed(GameObject destroyedObject)
method, and apparently whenlist = targetableObjects
, the redTarget6 GameObject has been removed from the List, but it still appears in the Hierarchy. This is expected, sinceDestroy(gameObject, 10f)
doesn't get called untilobjectDestroyed(gameObject)
finishes, and it hasn't finished yet. Fine again.After: No breakpoints hit, meaning that
objectDestroyed(GameObject destroyedObject)
has finished executing, andDestroy(gameObject, 10f)
called. However, redTarget6 is still in the hierarchy. Apparently the 10 seconds haven't yet passed. Also, as you say, redTarget6 is still showing in the console.
doesn't seem to affect the list in the game$$anonymous$$anager because unity happily keeps printing the gameObject in the debugger (See Update)**
It seems to me that you mean (See **Fixed**Update), because as far as I can tell that's where you call:
foreach (GameObject obj in targetableObjects) {
Debug.Log(obj.name);
}
...and apparently that's how you "verify" that the destroyed object hasn't been removed from the list.
Well, you shouldn't be using FixedUpdate()
for this purpose, because it's meant to be used for smooth physics motion, and it may be called several times per frame. You'd better use LateUpdate()
, which is called after Update()
, to make sure that all your code in Update()
has finished running, before you verify the contents of the list.
Try that and see what happens.
Okay so I tried that. This are the results: https://ibb.co/kkq227 https://ibb.co/f07h27 https://ibb.co/dqUvN7 I also removed the 10 seconds from the health$$anonymous$$anager and it produced the exact same results (images are without).
Thanks you so much! I gave you all the points I had left.
Answer by meat5000 · Apr 01, 2018 at 05:10 PM
https://msdn.microsoft.com/en-us/library/bwabdf9z(v=vs.110).aspx
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.
No, a Dictionary would not help a lot with a List of Lists (the key would be the reference to the List, or its index, but List is already more efficient with that). Or maybe you are thinking of a more exotic use of Dictionaries? Or a HashSet?
However, you are right about using foreach
with List, and also List.Remove(), which is safe to call even if the item is not present in the list; that is no need to use List.Contains().
Answer by EpicAsMe · Mar 31, 2018 at 08:18 PM
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[i] == destroyedObject)
{
list.Remove(list[i]);
Debug.Log("Removed" + destroyedObject.name + " from " + list);
}
}
}
}
Thanks for the reply but sadly this code produces exactly the same result.
I'm sorry if that didn't help, but in case I get your question wrong, did you want to remove the destroyed object from the list or from the scene?
No you got it right it is my question and the code should do it only it doesn't.
Answer by whisp · Apr 01, 2018 at 09:02 PM
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;
Your answer
![](https://koobas.hobune.stream/wayback/20220612155741im_/https://answers.unity.com/themes/thub/images/avi.jpg)
Follow this Question
Related Questions
A node in a childnode? 1 Answer
can add to a list but not remove 1 Answer
How to remove project from the Project Wizard 6 Answers
When removing something from a list it says missing 1 Answer
How do I make a list of lists? 2 Answers