- Home /
Need help with an issue regarding lists (I think).
Hello. I apologize in advance if this is not the correct place to post this question. I recently started learning how to use Unity, and I ran into a problem I can't seem to solve.
For a bit of context, I'm trying to create a Tetris-style game. Rather than randomly generate all pieces, I decided to do what the actual games do and give pieces in sets of 7, just in a random order. I created an empty spawner to hold 2 scripts: one to "hold" the default pieces and a list of the upcoming pieces in the game, and one to actually spawn them. While I was doing this second one, I ran into this problem.
The "UpcomingPiecesManager" script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UpcomingPiecesManager : MonoBehaviour
{
// An array with the pieces that will be generated by default
public GameObject[] standardPieces;
// A list of the upcoming pieces, which are decided in advance
private List<GameObject> upcomingPieces = new List<GameObject>();
// Start is called before the first frame update
void Start()
{
// Start with at least 16 pieces
while (upcomingPieces.Count < 16)
{
AddSetOfPieces();
}
}
// Update is called once per frame
void Update()
{
// Keep at least 16 pieces generated at a time
if (upcomingPieces.Count < 16)
{
AddSetOfPieces();
}
}
// Randomize the order of a given set of pieces
public GameObject[] RandomizePieceOrder(GameObject[] pieces)
{
for (int currentPiece = 0; currentPiece < pieces.Length; currentPiece++)
{
int randomIndex = Random.Range(0, pieces.Length);
GameObject temp = pieces[randomIndex];
pieces[randomIndex] = pieces[currentPiece];
pieces[currentPiece] = temp;
}
return pieces;
}
// Add a set of the default pieces in a randomized order
public void AddSetOfPieces()
{
GameObject[] upcomingPiecesAddition = RandomizePieceOrder(standardPieces);
foreach (var piece in upcomingPiecesAddition)
{
upcomingPieces.Add(piece);
}
}
public GameObject GetNextPiece(int index = 0)
{
return upcomingPieces[index];
}
public void DiscardNextPiece()
{
upcomingPieces.RemoveAt(0);
}
public void AddUpcomingPiece(GameObject piece)
{
upcomingPieces.Add(piece);
}
}
The "PieceSpawner" script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PieceSpawner : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
SpawnNextPiece();
}
// Update is called once per frame
void Update()
{
}
// Spawns the next piece in the list and removes it from it
public void SpawnNextPiece()
{
GameObject pieceToSpawn = gameObject.GetComponent<UpcomingPiecesManager>().GetNextPiece();
gameObject.GetComponent<UpcomingPiecesManager>().DiscardNextPiece();
Instantiate(pieceToSpawn, transform.position, Quaternion.identity);
}
}
When playing the game, I run into this issue:
ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Since the index is 0, it seems to imply the list doesn't contain anything yet. I tried changing "AddSetOfPieces" to show a message whenever a piece is added, and it confirms they are added, so that is working, but the message saying they were added only happens after the error message. That seems to imply to me that the scripts are trying to access the list before the pieces are added. How can I solve this?
I apologize if this is a bit hard to read. As I said, I only just started, but I kind of want to solve this.
Answer by Namey5 · Mar 06, 2020 at 01:18 AM
Logically, there isn't actually anything wrong with your scripts; they function exactly as they should. The error itself however would come from script order. The order in which scripts are executed in Unity isn't always obvious, and in this case chances are your Start function in the second script is being called before the Start function in the first script, therefore the array hasn't been initialised by the time it's being used.
Whilst you can force scripts to execute in a certain order, that's more of a hack and doesn't actually 'fix' the problem. You could also initialise the list in a method that is called earlier, such as OnEnable or Awake, and that would work fine too. Aside from Unity specific hacks though, I would have suggested initialising the array and spawning the first piece in the same place so that you can control the order, but instead I will pose a question; is there a reason these need to be separate scripts in the first place? If the piece spawner script relies entirely on variables from the manager script, would it not be easier to just have the spawning process be a part of the manager script? That way, you could force the calls to be in order, it would centralise the whole process and it shouldn't make things any less readable as the processes in the manager script would still fall under piece management.
Thank you for your answer! I'm honestly not entirely sure why I decided to separate the scripts... I guess it seemed like a good idea at the time? That's what I get for doing this while tired, I guess.
I'll join the scripts, then. But I'm glad the issue wasn't entirely my fault and was more of a quirk in how Unity works. I'll keep that in $$anonymous$$d in the future. Thanks once again!
Honestly, your scripts are actually very well written (especially when compared to a lot of the samples we get on this website), and I can see why most people like to separate functionality. I just personally prefer to have all relevant code in the same place because it gives me the ability to keep track of everything better, and in cases like this it means that things should just work. At the end of the day, it's all personal preference really - so long as it works the way it was intended and is performant & readable it really doesn't matter.
Yeah, it's just better to join them. And thanks, I was already a bit familiar with program$$anonymous$$g, so I wasn't totally lost. I sometimes fear my code is excessively verbose, but I like to make it as readable as possible.
Your answer
