Home > Back-end >  How to fix Unity List error ArgumentOutOfRangeException?
How to fix Unity List error ArgumentOutOfRangeException?

Time:10-21

Hello hope you're doing well

I have a GameObject that adds and removes other valid GameObjects to a List as they drift through a collider trigger. Under certain conditions it will select an object in the List at random and replace it with another GameObject. It works fine for the most part but occasionally it will give an ArgumentOutOfRangeException error. Here is the code:

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

public class BubbleScriptNecroGrab : MonoBehaviour
{

    [SerializeField] public List<GameObject> bubbleOnes = new List<GameObject>();
    [SerializeField] public List<GameObject> necroAcolytes = new List<GameObject>();
    [SerializeField] GameObject activeBubble;
    [SerializeField] GameObject necroAcolyte;

    bool necroReady = true;

    void Update()
    {

        if (bubbleOnes.Count > 0 && necroReady == true)
        {
            StartCoroutine(bubbleSignal());
        }

    }


    // These two functions add and remove eligible bubbles from its associated list
    // as they pass through the collider.
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.tag == "Bubble1")
        {
            GameObject other = collision.gameObject;
            bubbleOnes.Add(other);
        }

    }
    private void OnTriggerExit2D(Collider2D collision)
    {
        if(collision.gameObject.tag == "Bubble1")
        {
            GameObject other = collision.gameObject;
            bubbleOnes.Remove(other);
        }
    }


    // When this is called, select a random bubble from the list, delete it and
    // replace it with a prefab.
    IEnumerator bubbleSignal()
    {
        necroReady = false;
        yield return new WaitForSeconds(Random.Range(0.1f, 1.5f));
        int randomList = Random.Range(0, bubbleOnes.Count -1);                  // adding -1 reduced amount of errors
        Vector3 targetBubblePos = bubbleOnes[randomList].transform.position;    // Code seems to break on this line
        Destroy(bubbleOnes[randomList]);
        GameObject necroAcolyteClone = Instantiate(necroAcolyte, targetBubblePos, Quaternion.identity);
        necroAcolyteClone.GetComponent<AcolyteScript>().activeTarget = transform.parent.gameObject;
        necroReady = true;

    }

}

What I suspect is happening is as the bubbleSignal function operates, it selects a large or largest value just as it gets removed from the list by drifting out of the collider. How do I fix this?

CodePudding user response:

In general the -1 in

int randomList = Random.Range(0, bubbleOnes.Count -1);
 

does not do what you think.

The upper bound already is exclusive so this will never ever return the last index.

Beyond that I see nothing obviously wrong in your code (at this point) which would lead me to the assumption that bubbleOnes is empty and therefore randomList = 0 but since there is no element at all bubbleOnes[randomList] already throws the exception.

This could e.g. happen if OnTriggerExit2D is called while there is only a single element in the list before the bubbleSignal routine or more exactly the WaitForSeconds within it finished. In that case you would remove the last element from the list, leave an empty list and then WaitForSeconds would eventually finish and you reach your exception line with an empty list.

You should handle this case and do e.g.

IEnumerator bubbleSignal()
{
    necroReady = false;

    yield return new WaitForSeconds(Random.Range(0.1f, 1.5f));

    if(bubbleOnes.Count > 0)
    {
        var randomList = Random.Range(0, bubbleOnes.Count);                  
        var targetBubblePos = bubbleOnes[randomList].transform.position;
        Destroy(bubbleOnes[randomList]);
        var necroAcolyteClone = Instantiate(necroAcolyte, targetBubblePos, Quaternion.identity);
        necroAcolyteClone.GetComponent<AcolyteScript>().activeTarget = transform.parent.gameObject;
    }

    necroReady = true;
}

In general in your use-case I would not use a Coroutine and poll-check in Update whether to run it or not. Here it would be way more flexible to directly stay in Update and do e.g.

[SerializeField] private float minTime = 0.1f;
[SerializeField] private float maxTime = 1.5f;

private float timer;

void Start()
{
    timer = Random.Range(minTime, maxTime);
}

void Update()
{
    if (bubbleOnes.Count > 0)
    {
        timer -= Time.deltaTime;

        if(timer <= 0)
        {
            var randomList = Random.Range(0, bubbleOnes.Count);        
            var targetBubblePos = bubbleOnes[randomList].transform.position;
            Destroy(bubbleOnes[randomList]);
            var necroAcolyteClone = Instantiate(necroAcolyte, targetBubblePos, Quaternion.identity);
            necroAcolyteClone.GetComponent<AcolyteScript>().activeTarget = transform.parent.gameObject;

            timer  = Random.Range(minTime, maxTime);
        }
    } 
    // [optionally]
    // Instead of just continuing the counter from where it stopped last time
    // you could also reset it while the list is empty
    else
    {
        timer = Random.Range(minTime, maxTime);
    }
}

This way you can be sure that there is no timing issue as all the code is only actually executed if the list is not empty in that moment.

  • Related