Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

How Do I Get Multidimensional Arrays to Persist?

Discussion in 'Scripting' started by Derydoca, Mar 16, 2011.

  1. Derydoca

    Derydoca

    Joined:
    May 13, 2010
    Posts:
    189
    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
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class ArrayPlacer : MonoBehaviour {
    6.    
    7.     public int width;
    8.     public int depth;
    9.     public Vector3[,] nodes;
    10.    
    11.     public void PlaceNodes(){
    12.         nodes = new Vector3[width+1, depth+1];
    13.        
    14.         for (int i = 0; i <= width; i++){
    15.             for (int j = 0; j <= depth; j++){
    16.                 nodes[i,j] = new Vector3(i, 0, -j);
    17.             }
    18.         }
    19.     }
    20.  
    21.     void OnDrawGizmos () {
    22.         if(nodes == null)
    23.             return;
    24.         for (int i = 0; i < nodes.GetUpperBound(0); i++){
    25.             for (int j = 0; j < nodes.GetUpperBound(1); j++){
    26.                 Gizmos.DrawWireCube(nodes[i, j], Vector3.one * 0.05f);
    27.             }
    28.         }
    29.     }
    30. }
    31.  
    ArrayPlacerEditor.cs
    NOTE: This needs to be in a folder called Editor in the root of the project
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using System.Collections;
    5.  
    6. [CustomEditor(typeof(ArrayPlacer))]
    7. public class ArrayPlacerEditor : Editor {
    8.    
    9.     public override void OnInspectorGUI(){
    10.         ArrayPlacer myTarget = (ArrayPlacer)target;
    11.        
    12.         DrawDefaultInspector();
    13.         if(GUILayout.Button("Place Nodes"))
    14.            myTarget.PlaceNodes();
    15.     }
    16. }
    17.  
     
    Mehrdad995 likes this.
  2. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
    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:-
    Code (csharp):
    1. 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.
     
    Bunny83, ADRIA-JOFRE, qqqbbb and 4 others like this.
  3. Derydoca

    Derydoca

    Joined:
    May 13, 2010
    Posts:
    189
    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
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class ArrayPlacer : MonoBehaviour {
    6.    
    7.     public int width;
    8.     public int depth;
    9.     public Vector3[] nodes;
    10.    
    11.     public void PlaceNodes(){
    12.         nodes = new Vector3[width*depth];
    13.        
    14.         for (int i = 0; i < width; i++){
    15.             for (int j = 0; j < depth; j++){
    16.                 nodes[j*width+i] = new Vector3(i, 0, -j);
    17.             }
    18.         }
    19.     }
    20.  
    21.     void OnDrawGizmos () {
    22.         if(nodes == null)
    23.             return;
    24.         for (int i = 0; i < nodes.Length; i++)
    25.             Gizmos.DrawWireCube(nodes[i], Vector3.one * 0.05f);
    26.     }
    27. }
    28.  
     
    Last edited: Mar 16, 2011
  4. bradmarxmoosepi

    bradmarxmoosepi

    Joined:
    Jul 24, 2012
    Posts:
    3
    Thanks so much! This solved my issue that I had with persisting a GameObject[,]. I changed it to a GameObject[] and indexed as Andeeee suggested.
     
  5. thedude22

    thedude22

    Joined:
    Jun 26, 2016
    Posts:
    9
    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.
     
    Last edited: Aug 17, 2018
    BackgroundMover likes this.
  6. Yandalf

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    491
    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:

    Code (CSharp):
    1. [Serializable]
    2. public class Grid3D : MonoBehaviour, ISerializationCallbackReceiver
    3. {
    4.         //Serialized Fields
    5.         [SerializeField] private int seed;
    6.         [SerializeField] private int x = 1, y = 1, z = 1;
    7.         [SerializeField] private float tileSize = 1f;
    8.         [HideInInspector][SerializeField] private VoxelTile[] grid;
    9.         //Runtime Fields
    10.         private VoxelTile[,,] _grid3D;
    11.         private int _oldX, _oldY, _oldZ;
    12.         private int _oldSeed;
    13.         private float _oldSize;
    14.  
    15.         public void OnBeforeSerialize()
    16.         {
    17.             grid = ArraysHelper.From3DTo1D(_grid3D);
    18.             Debug.Log("Before Serialization grid elements: " + grid.Length);
    19.         }
    20.  
    21.         public void OnAfterDeserialize()
    22.         {
    23.             _grid3D = ArraysHelper.From1DTo3D(grid, x, y, z);
    24.             grid = null;          
    25.             Debug.Log("After Serialization grid elements: " + _grid3D.Length);
    26.         }
    27.        
    28.         private void OnValidate()
    29.         {
    30.             Debug.Log("OnValidate");
    31.             if (_oldX != x || _oldY != y || _oldZ != z || _oldSize != tileSize || _oldSeed != seed)
    32.             {
    33.                 x = Mathf.Max(0, x);
    34.                 y = Mathf.Max(0, y);
    35.                 z = Mathf.Max(0, z);
    36.                 UpdateGrid(); //creates the grid using xyz
    37.                 _oldX = x;
    38.                 _oldY = y;
    39.                 _oldZ = z;
    40.                 _oldSize = tileSize;
    41.                 _oldSeed = seed;
    42.             }
    43.         }
    44. }
    45.  
    46. public static class ArraysHelper
    47. {
    48.     static public T[] From3DTo1D<T>(T[,,] sourceArr)
    49.     {
    50.         int x = sourceArr.GetLength(0);
    51.         int y = sourceArr.GetLength(1);
    52.         int z = sourceArr.GetLength(2);
    53.  
    54.         T[] arr = new T[x * y * z];
    55.  
    56.         for (int i = 0; i < z; i++)
    57.         {
    58.             for(int j = 0; j < y; j++)
    59.             {
    60.                 for(int k = 0; k < x; k++)
    61.                 {
    62.                     arr[k + (x * j) + (y * x * i)] = sourceArr[k, j, i];
    63.                 }
    64.             }
    65.         }
    66.         return arr;
    67.     }
    68.  
    69.     static public T[,,] From1DTo3D<T>(T[] sourceArr, int x, int y, int z)
    70.     {
    71.         T[,,] arr = new T[x, y, z];
    72.         for(int i = 0; i < z; i++)
    73.         {
    74.             for(int j = 0; j < y; j++)
    75.             {
    76.                 for(int k = 0; k < x; k++)
    77.                 {
    78.                     arr[k, j, i] = sourceArr[k + (x * j) + (y * x * i)];
    79.                 }
    80.             }
    81.         }
    82.         return arr;
    83.     }
    84. }
    85. public enum TileType { Empty, Filled }
    86.  
    87. [Serializable]
    88. public class VoxelTile
    89. {
    90.     public TileType tileType;
    91.  
    92.     public VoxelTile(TileType tileType)
    93.     {
    94.         this.tileType = tileType;
    95.     }
    96. }
    97.  
    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?
     
    Deleted User, tonytopper and cdr9042 like this.
  7. nandanramesh13

    nandanramesh13

    Joined:
    Sep 27, 2020
    Posts:
    4
    Short and simple.

    Usage:
     public Array2D<int> Ints = new Array2D<int>(10,10) ;

    Ints[3,3]=420;
    int i = Ints[2,5];

    Code (CSharp):
    1.  
    2. [Serializable]
    3. public class Array2D<T> where T : struct
    4. {
    5.     public int x, y;
    6.  
    7.     /// <summary>2D array stored in 1D array.</summary>
    8.     public T[] SingleArray;
    9.  
    10.     public T this[int x, int y]
    11.     {
    12.         get => SingleArray[y * this.x + x];
    13.         set => SingleArray[y * this.x + x] = value;
    14.     }
    15.  
    16.     public Array2D(int x, int y)
    17.     {
    18.         this.x = x;
    19.         this.y = y;
    20.         SingleArray = new T[x * y];
    21.     }
    22.  
    23.     /// <summary>Gets the total number of elements in X dimension (1st dimension). </summary>
    24.     public int Get_X_Length => x;
    25.  
    26.     /// <summary>Gets the total number of elements in Y dimension. (2nd dimension).</summary>
    27.     public int Get_Y_Length => y;
    28.  
    29.     /// <summary>Gets the total number of elements all dimensions.</summary>
    30.     public int Length => SingleArray.Length;
    31.  
    32. }
     
    Karmate and Ahmadabd97 like this.