- Home /
Null error on 2D array only after instantiation
So I'm creating a script to generate a dungeon for my game. Currently the script takes in a list of room gameobjects from the inspector and uses those to piece together a map. Each room object has the same room script that performs all the functions of a room and stores the room's base stats like floor patter, size, shape, etc. The stats for each room are set at the start by the gen script. My problem is that after I set the variables and instantiate a new copy all the variables are correct except any of the 2D arrays stay null. Below is a stripped down example of my problem.
public class room: MonoBehaviour {
public int[,] floorTiles;
public int other1;
public Vector2 other2;
public void setVars(int[,] f, int a, Vector2 b){
floorTiles = f;
other1 = a;
other2 = b;
}
}
.
public class gen: MonoBehaviour{
public GameObject[] rooms;
void Start(){
rooms[0].GetComponent<room>().setVars(new int[,]{
{1,1,1,1},
{1,0,0,1},
{1,0,0,1},
{1,1,1,1}},
2,
new Vector2(3,3));
//rooms[1]...
//.
//.
//.
print (rooms[0].GetComponent<room> ().other1);
print (rooms[0].GetComponent<room> ().other2.x);
print (rooms[0].GetComponent<room> ().floorTiles [0, 0]);
GameObject tmp = Instantiate (rooms [0], Vector2.zero, Quaternion.identity);
print (tmp.GetComponent<room> ().other1);
print (tmp.GetComponent<room> ().other2.x);
print (tmp.GetComponent<room> ().floorTiles [0, 0]);
}
}
Output:
2
3
1
2
3
NullReferenceException: Object reference not set to an instance of an object
The only work around I have found so far to this problem is to put the code in an ienumrator and add a delay. After testing I found that the delay could be as low as 0.
IEnumerator workAround(){
print (rooms[0].GetComponent<room> ().other1);
print (rooms[0].GetComponent<room> ().other2.x);
print (rooms[0].GetComponent<room> ().floorTiles [0, 0]);
GameObject tmp = Instantiate (rooms [0], Vector2.zero, Quaternion.identity);
print (tmp.GetComponent<room> ().other1);
print (tmp.GetComponent<room> ().other2.x);
yield return new WaitForSeconds(0f);
print (tmp.GetComponent<room> ().floorTiles [0, 0]);
}
Output:
2
3
1
2
3
1
I'm really confused by this behavior and would greatly appreciate any help or insight into this issue.
I tried the simplified code that you posted, and reproduced the problem. It seems Instantiate()
doesn't copy the variable type int[,]
. However, the workaround didn't change anything for me, I got the same NR$$anonymous$$
I will do some further tests to uncover what's behind this, it is worrisome if Instantiate()
doesn't clone all the data!
BTW, I noticed that the posted code and the output do not match: you pass 0
for a
and Vector2.zero
for b
, but the output shows 2
and 3
respectively. Why is that?
Thanks for catching the error I was trying to clear up the code and forgot to update a portion. I have done so and it should be reflected in the code.
print (tmp.GetComponent<room> ().floorTiles [0, 0]);
I think you forgot the room component for this GameObject. Always check null for GetComponent
No, that's not the issue (although it is a good idea to check the result of GetComponent<>()
). If that were the issue, OP would get NRE for all the print calls. $$anonymous$$oreover, if rooms[0]
has a room
component, Instantiate()
ensures that tmp
will also have one.
You are right.
Another work around is changing script Execution order. Because if all object created by Instantiate. Some Object Start() will be called before some this scripts => tmp missing room component I guess. yield return
help the function called during update() not start() when all object are instantiate finished
Answer by Harinezumi · Jun 06, 2018 at 08:19 AM
I've finally found the reason: only serializable types are copied with Instantiate
, and apparently 2D arrays are not serializable! Check this Unity blog post to see what gets serialized, and what not.
To solve the issue, you will need to wrap your floor data into some serializable type that doesn't use 2D array to store the data. Not optimal, in my opinion :/
UPDATE: I couldn't let it rest, so here is a struct
that serializes a 2D array of int, and deserializes on request. Call Deserialize()
in room.Awake()
to initialize int[,] floorTiles
. It is ugly as hell, but it works. An upside is that you can set up floor tiles from prefabs as well, if you don't mind editing serialized arrays.
[System.Serializable]
public struct FloorTiles {
public int[] serializedFloorTiles;
public int length0;
public int length1; // this could be calculated as serializedFloorTiles / length0
public FloorTiles (int[,] floorTiles) {
length0 = floorTiles.GetLength(0);
length1 = floorTiles.GetLength(1);
serializedFloorTiles = new int[length0 * length1];
for (int i = 0; i < length0; ++i) {
for (int j = 0; j < length1; ++j) {
serializedFloorTiles[i + length0 * j] = floorTiles[i, j];
}
}
}
public int[,] Deserialize () {
int[,] floorTiles = new int[length0, length1];
for (int i = 0; i < length0; ++i) {
for (int j = 0; j < length1; ++j) {
floorTiles[i, j] = serializedFloorTiles[i + length0 * j];
}
}
return floorTiles;
}
}
Your answer
Follow this Question
Related Questions
Jagged Array returns null anywhere outside function that populates it 1 Answer
Multiple Cars not working 1 Answer
Distribute terrain in zones 3 Answers
Array not updating in inspector 0 Answers
MergeSort function acting strange 0 Answers