Thboss
Thboss

Reputation: 15

How to solve InvalidOperationException?

In my Unity project I have a LevelGenerator creating Stages (game objects) and adding these to a list. When I am dying I want to destroy and remove all items in the list. But when I am iterating through the list, only the first object is destroyed and removed, then an error happens:

InvalidOperationException: Collection was modified; enumeration operation may not execute.
System.ThrowHelper.ThrowInvalidOperationException (System.ExceptionResource resource) (at :0)
System.Collections.Generic.List1+Enumerator[T].MoveNextRare () (at <fb001e01371b4adca20013e0ac763896>:0) System.Collections.Generic.List1+Enumerator[T].MoveNext () (at :0)

Could you help me?

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

public class LevelGenerator : MonoBehaviour
{
    private const float PLAYER_DISTANCE_SPAWN_LEVEL_PART  = 25f;

    [SerializeField] private Transform Startingstage;
    [SerializeField] private List<Transform> StageList;
    [SerializeField] private Transform player;

    public List<GameObject> gameObjectsToDestroy;
    public bool creatingGoesOn = false;

    private Vector3 lastEndPosition;

    public void CreateEnviroment()  // Every time called when game is started
    {
        lastEndPosition = Startingstage.Find("EndingPoint").position;

        int startingSpawnLevelParts = 7;

        for (int i = 0; i < startingSpawnLevelParts; i++)
        {
            SpawnLevelPart();
        }

        creatingGoesOn = true;
    }

    private void Update()
    {
        if (creatingGoesOn)
        {
            if (Vector3.Distance(player.position, lastEndPosition) < PLAYER_DISTANCE_SPAWN_LEVEL_PART)
            {
                // Spawn a new level part if player is to close to the end.
                SpawnLevelPart();
            }
        }
    }

    private void SpawnLevelPart()
    {
        Transform randomStage = StageList[Random.Range(0, StageList.Count)];
        Transform lastLevelPartTransform = SpawnLevelPart(randomStage, lastEndPosition);
        lastEndPosition = lastLevelPartTransform.Find("EndingPoint").position;
    }

    private Transform SpawnLevelPart(Transform stage, Vector3 spawnPosition)
    {
        Transform levelPartTransform = Instantiate(stage, spawnPosition,  Quaternion.identity);
        gameObjectsToDestroy.Add(levelPartTransform.gameObject);
        return levelPartTransform;
    }
}

Inside the error script:

private void OnTriggerEnter2D(Collider2D collision)
{
    if (collision.transform.CompareTag("Player"))
    {
        destroyEnviroment.creatingGoesOn = false;
        Debug.Log(destroyEnviroment.gameObjectsToDestroy.Count);

        foreach (GameObject toDestroy in destroyEnviroment.gameObjectsToDestroy)
        {
            destroyEnviroment.gameObjectsToDestroy.Remove(toDestroy);
            Destroy(toDestroy); //Somewhere here is the error
            Debug.Log(destroyEnviroment.gameObjectsToDestroy.Count);
        }
        ...
    }
    ...
}

Upvotes: 1

Views: 2478

Answers (2)

Jay
Jay

Reputation: 2946

Your problem is you're removing items from a list as you're iterating over them, and this changes the size of the list!

The easiest way to do this would probably be

foreach (GameObject toDestroy in destroyEnviroment.gameObjectsToDestroy)
{
    Destroy(toDestroy);
}
destroyEnviroment.gameObjectsToDestroy.Clear();

Edit:
It's worth noting that here I Destroy before removing, this might seem counter-intuitive but the reason is calling destroy will set the object to be destroyed at the end of the frame, we can then forget about it and clear the list - and everything else will be taken care of.
I'm a big advocate of not removing items from lists if you don't need to - in this case you're clearing the whole list so you might as well use clear()

Upvotes: 1

Omar Abdel Bari
Omar Abdel Bari

Reputation: 964

Anytime you see InvalidOperationException: Collection was modified; enumeration operation may not execute where the collection in question is being accessed on a single thread you should know that it is because you are modifying the same list for which you are iterating over

This often happens when making use of a functionality that uses IEnumerable such as:

  • foreach loops as in your case
  • GetEnumerator() directly and using that to iterate through the collection

The common solution in the case of 'foreach' loops is to consider a different construct that makes sense to your problem.

I can also provide a method of applying this in your case if needed. Just let me know.

Upvotes: 0

Related Questions