I have been trying to figure this one out for a while now, but I cannot figure out a solution. In the example code what happens is that you specify a width and a depth, click ‘Place Nodes’ and it populates an array of vector3s in a square grid. You can see them placed in the screen by some debug wire rectangles I am rendering.
My issue is that when you click run or any code compiles, the array gets wiped clean. I want it to serialize so that it doesn’t wipe itself out. I have tried different things such as placing ‘[UnityEngine.SerializeField]’ before the array declaration. Can anybody point me in the right direction here?
ArrayPlacer.cs
using UnityEngine;
using System.Collections;
public class ArrayPlacer : MonoBehaviour {
public int width;
public int depth;
public Vector3[,] nodes;
public void PlaceNodes(){
nodes = new Vector3[width+1, depth+1];
for (int i = 0; i <= width; i++){
for (int j = 0; j <= depth; j++){
nodes[i,j] = new Vector3(i, 0, -j);
}
}
}
void OnDrawGizmos () {
if(nodes == null)
return;
for (int i = 0; i < nodes.GetUpperBound(0); i++){
for (int j = 0; j < nodes.GetUpperBound(1); j++){
Gizmos.DrawWireCube(nodes[i, j], Vector3.one * 0.05f);
}
}
}
}
ArrayPlacerEditor.cs
NOTE: This needs to be in a folder called Editor in the root of the project
using UnityEngine;
using UnityEditor;
using System.Collections;
[CustomEditor(typeof(ArrayPlacer))]
public class ArrayPlacerEditor : Editor {
public override void OnInspectorGUI(){
ArrayPlacer myTarget = (ArrayPlacer)target;
DrawDefaultInspector();
if(GUILayout.Button("Place Nodes"))
myTarget.PlaceNodes();
}
}
A straightforward way around this is to note that any multidimensional array can be implemented with a single dimensional array using the right offsets (this is how they implemented internally). For example, given an <x,y> coordinate for a two dimensional array, you get the equivalent index in the one dimensional array with the following formula:-
index = y * gridWidth + x
The length of the 1D array is equal to the width times the height of the 2D array. Many data structures can actually be “flattened” into arrays for convenient storage in the editor.
A-hah! Thanks Andeeee! It was something I originally contemplated when starting to code this particular component, but I went with a multidimensional array for simplicity’s sake. I’ve updated the code and it appears to be working perfectly.
In case anyone is wondering what the code looks like here is the new ArrayPlacer.cs. The editor has not changed.
ArrayPlacer.cs
using UnityEngine;
using System.Collections;
public class ArrayPlacer : MonoBehaviour {
public int width;
public int depth;
public Vector3[] nodes;
public void PlaceNodes(){
nodes = new Vector3[width*depth];
for (int i = 0; i < width; i++){
for (int j = 0; j < depth; j++){
nodes[j*width+i] = new Vector3(i, 0, -j);
}
}
}
void OnDrawGizmos () {
if(nodes == null)
return;
for (int i = 0; i < nodes.Length; i++)
Gizmos.DrawWireCube(nodes[i], Vector3.one * 0.05f);
}
}
Yes apparently Unity does not serialize multidimensional arrays, even if the underlying type is compatible with unity’s serialization. This thread helped me out. However for my use case it is much more practical to keep the multidimensional array. So what I did is have my class implement ISerializationCallbackReceiver, and used the two serialization callbacks to only termporarily convert the data to serialization friendly 1D arrays as above. I saved my data in a 1D array just before serializing, and loaded it back into the 2D array just after deserializing. Now everything works perfectly and my 2D array data persists after domain reload. I would suggest setting your 1D arrays to null after deserializing also, so you don’t have dangling references if you stick new objects in your 2D array.
Hey, I’m currently trying this out as well with a 3D array using ISerializationCallbackReceiver.
However, in my case I get a lot of OutofRangeExceptions and also an error that I’m calling GetBool during serialization, even though I don’t call that as far as I can tell. Any clues what I’m doing wrong? Code below:
[Serializable]
public class Grid3D : MonoBehaviour, ISerializationCallbackReceiver
{
//Serialized Fields
[SerializeField] private int seed;
[SerializeField] private int x = 1, y = 1, z = 1;
[SerializeField] private float tileSize = 1f;
[HideInInspector][SerializeField] private VoxelTile[] grid;
//Runtime Fields
private VoxelTile[,,] _grid3D;
private int _oldX, _oldY, _oldZ;
private int _oldSeed;
private float _oldSize;
public void OnBeforeSerialize()
{
grid = ArraysHelper.From3DTo1D(_grid3D);
Debug.Log("Before Serialization grid elements: " + grid.Length);
}
public void OnAfterDeserialize()
{
_grid3D = ArraysHelper.From1DTo3D(grid, x, y, z);
grid = null;
Debug.Log("After Serialization grid elements: " + _grid3D.Length);
}
private void OnValidate()
{
Debug.Log("OnValidate");
if (_oldX != x || _oldY != y || _oldZ != z || _oldSize != tileSize || _oldSeed != seed)
{
x = Mathf.Max(0, x);
y = Mathf.Max(0, y);
z = Mathf.Max(0, z);
UpdateGrid(); //creates the grid using xyz
_oldX = x;
_oldY = y;
_oldZ = z;
_oldSize = tileSize;
_oldSeed = seed;
}
}
}
public static class ArraysHelper
{
static public T[] From3DTo1D<T>(T[,,] sourceArr)
{
int x = sourceArr.GetLength(0);
int y = sourceArr.GetLength(1);
int z = sourceArr.GetLength(2);
T[] arr = new T[x * y * z];
for (int i = 0; i < z; i++)
{
for(int j = 0; j < y; j++)
{
for(int k = 0; k < x; k++)
{
arr[k + (x * j) + (y * x * i)] = sourceArr[k, j, i];
}
}
}
return arr;
}
static public T[,,] From1DTo3D<T>(T[] sourceArr, int x, int y, int z)
{
T[,,] arr = new T[x, y, z];
for(int i = 0; i < z; i++)
{
for(int j = 0; j < y; j++)
{
for(int k = 0; k < x; k++)
{
arr[k, j, i] = sourceArr[k + (x * j) + (y * x * i)];
}
}
}
return arr;
}
}
public enum TileType { Empty, Filled }
[Serializable]
public class VoxelTile
{
public TileType tileType;
public VoxelTile(TileType tileType)
{
this.tileType = tileType;
}
}
ArraysHelper is throwing the OutofRangeException in From1DTo3D. I’ve tested these methods before without serialization and they work correctly there, which would imply that somehow the xyz components at some point get desynchronized from the array lengths. I did some further debugging and when I lower a component instead of raising it, it turns out that the correct length for the arrays will only be applied in the next editor update. Basically in the logs I get:
Before Serialization elements = 3
Getbool error
After serialization elements = 1
OnValidate
I’m guessing since the GetBool error might occur during OnBeforeSerialization everything goes out of sync, which in the case of a component increase results in the OutOfRangeExceptions.
So yeah, any clue what’s causing those GetBool calls?
[Serializable]
public class Array2D<T> where T : struct
{
public int x, y;
/// <summary>2D array stored in 1D array.</summary>
public T[] SingleArray;
public T this[int x, int y]
{
get => SingleArray[y * this.x + x];
set => SingleArray[y * this.x + x] = value;
}
public Array2D(int x, int y)
{
this.x = x;
this.y = y;
SingleArray = new T[x * y];
}
/// <summary>Gets the total number of elements in X dimension (1st dimension). </summary>
public int Get_X_Length => x;
/// <summary>Gets the total number of elements in Y dimension. (2nd dimension).</summary>
public int Get_Y_Length => y;
/// <summary>Gets the total number of elements all dimensions.</summary>
public int Length => SingleArray.Length;
}