Index was out of range error.

Hello, Im stuck with this error:
I have a problem, in my game, when I click a button, I get an “Index was out of range error”, I know these errors are caused by trying to get the N+1 element of an array with only N elements, however I triple checked my code and I cant find a single place where I could be getting this error.

This is the relevant code:

 public void SpawnRandomLevelCards()
    {
        FirebaseDatabase.DefaultInstance.GetReference("Levels").GetValueAsync().ContinueWith(task =>
        {
            if(task.IsFaulted || task.IsCanceled)
            {
                Debug.Log("ERROR: Couldnt load levels");
            }
            else if (task.IsCompleted)
            {
                Debug.Log("Loaded levels properly!");
                long totalLevelsCreated = task.Result.ChildrenCount;

                long levelsToLoad = 10;
                if (totalLevelsCreated < 10) levelsToLoad = totalLevelsCreated;
                Debug.Log("total levels created: " + totalLevelsCreated + "Levels to load: " + levelsToLoad);

                tilemapDataOnlineLevels = new List<TilemapData>();
                for (int i = 0; i < levelsToLoad; i++)
                {
                    string json = task.Result.Child(i.ToString()).GetRawJsonValue();
                    TilemapData data = JsonUtility.FromJson<TilemapData>(json);

                    tilemapDataOnlineLevels.Add(data);
                }

                finishedLoading = true;
            }
        });
    }

    private void Update()
    {
        if (finishedLoading)
        {
            for (int i = 0; i < tilemapDataOnlineLevels.Count; i++)
            {
                GameObject card = Instantiate(levelCardPrefab, content);
                card.transform.GetChild(0).GetComponent<TMP_Text>().text = tilemapDataOnlineLevels[i].levelName;
                card.transform.GetChild(2).GetComponent<TMP_Text>().text = "Online ID: " + tilemapDataOnlineLevels[i].onlineLevelID;
                Button playBttn = card.transform.GetChild(1).GetComponent<Button>();

                playBttn.onClick.AddListener(() => { PlayOnlineLevel(tilemapDataOnlineLevels[i].onlineLevelID, tilemapDataOnlineLevels[i].levelName); });
                playBttn.onClick.AddListener(() => { PlayButtonSFX(); });
                Debug.Log("Created card");
            }

            //PlayOnlineLevel(tilemapDataOnlineLevels[0].onlineLevelID, tilemapDataOnlineLevels[0].levelName);

            finishedLoading = false;
        }
    }

    public void PlayOnlineLevel(int id, string name)
    {
        DataTransfer.Instance.SetLevelID(id, name, true);
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 2);
    }
    public void PlayButtonSFX()
    {
        SFXManager.Instance.PlayUIButtonClickSFX(SFXManager.Instance.gameObject.transform);
    }

The error in the console doesnt show in which line the error is produced but it occurs when I click the button called “playBttn”
Note: I am using void Update() because Unity wouldnt let me Instantiate objects inside Asyncs or inside functions called from Asyncs (I dont know why this happens but changing it to the Update method worked and the cards are Instantiated correctly)

I attached an image of what I am getting in the console.

Just to clarify if it wasnt already clear: this error only pops up when I click the button (refered to as “playBttn” in the code) in playtime.
Also, you can see in the code that I have commented out a line, in which I call the same Method as when I click the button, I tested to see if it would actually call the method or not, and indeed it does. So I have no clue why the same Method wont work when called from a button click.

Any help will be appreciated :smile:

well , you’ve hampered us by not indicating which is line 66…

Oh sorry, I didnt see the line in the error, my bad.
The line 66 is:

playBttn.onClick.AddListener(() => { PlayOnlineLevel(tilemapDataOnlineLevels[i].onlineLevelID, tilemapDataOnlineLevels[i].levelName); });

in the update method.
Hope this helps

then my friend you fell foul of a time old tradition of not localising i before you did it, ergo, instead of

for i = 1 to 10.. blah => { do something(i); }

resulting in something(1),something(2) … it results in whatever the current value of i is, and if i is global maybe you are ok cos the last 1 is kept, so you run something(10) 10 times…

you need to localise it

so

int itemno = i; 
playBttn.onClick.AddListener(() => { PlayOnlineLevel(tilemapDataOnlineLevels[itemno].onlineLevelID, tilemapDataOnlineLevels[itemno].levelName); });

it has to be a new variable each time.

2 Likes

I tried your solution and it worked perfectly!
I didnt remember you had to do that
Thank you so much for your help!

The reason is that when you create a closure (an anonymous method which captures local variables) inside a for loop, the closure does actually capture variables, not values. Since the for loop variable “i” only exists once, each of your callbacks would reference the same closure instance variable. After the for loop that variable would be equal to tilemapDataOnlineLevels.Count since that’s the first value that does break the for loop. Unfortunately that value is not a valid index since the highest index is one less than the count.

When you create a new local variable inside the for loop body, the compiler would create a new variable instance for each closure. It still captures a variable / closure object, but that variable does not change after it has been set.

1 Like

Hey can you please share the source code of the football manager card game, i wanna create a god team that has an overall rating of 999 or even 1 million and i wanna mod the game in such a way that i always win by like 999 or 10000 goals, so can you please share the source code of this game?
And btw are my modifications possible?