Initializing 2D array via inspector

Hi, I’ve got this script that will be used for some kind of a grid map system:

using UnityEngine;
using System.Collections;

[System.Serializable]
public class GridMap : MonoBehaviour {
#region Member Variables
	private static GridMap instance = null; /**< The instance for singleton */

	[SerializeField] private Grid gridPrefab = null; /**< The prefab of the base grid */
	[SerializeField] private float gridScaleFactor = 1; /**< The size / scale factor of the grids */
	[SerializeField] private Vector2 mapSize = new Vector2(10, 10); /**< The size of the map. In [width, height]. */
	
	public int[,] mapLayout; /**< The representation of the whole grid in the map. 0 = passabale / floor, 1 = unpassable / wall. */
	
	private Grid[,] gridArray; /**< The array that holds all the grids in the map */
	
#endregion

#region Member Properties
	/**
	 * Map size property
	 */
	public Vector2 MapSize
	{
		get{return mapSize;}
	}
#endregion

#region Monobehaviour Methods
	/** 
	 * Use this for initialization BEFORE Start()
	 */
	void Awake () 
	{
		//Setting the instance
		if (instance == null) //Set instance if null
		{
			instance = this;
		}
		else if (instance != this)
		{
			DestroyImmediate(this.gameObject); //Destroy the double occurence.
		}
		
		//Populate the map with grids
		PopulateMap();
	}

	/** 
	 * Use this for initialization
	 */
	void Start () 
	{
	}

	/** 
	 * Update is called once per frame
	 */	
	void Update () 
	{
	}
	
	/** 
	 * Use this for deinitialization
	 */
	void OnDestroy()
	{
		if (instance == this)
		{
			instance = null;
		}
	}
#endregion

#region Static Member Methods
	/**
	 * Populate the grid map with grids
	 */
	public void PopulateMap()
	{
		gridArray = new Grid[(int)MapSize.x, (int) MapSize.y];
	
		float xOffset = this.transform.position.x - (mapLayout.GetLength(0) * gridPrefab.Size / 2.0f);
		float zOffset = this.transform.position.z - (mapLayout.GetLength(1) * gridPrefab.Size / 2.0f);
		Vector2 offset = new Vector2( xOffset, zOffset);
		for (int i = 0; i < mapLayout.GetLength(0); ++i)
		{
			for(int j = 0; j < mapLayout.GetLength(1); j++)
			{
				gridArray[i, j] = CreateGrid(i, j, offset);
			}
		}
	}
	
	/**
	 * Create a grid at index [i,j]
	 *
	 * @param[in] i The index of the grid along the width of the map
	 * @param[in] j The index of the grid along the height of the map
	 * @param[in] offset The offset of the position at [0,0] index
	 *
	 * @return The instance of the grid
	 */
	private Grid CreateGrid(int i, int j, Vector2 offset)
	{
		Grid grid = Instantiate(gridPrefab).GetComponent<Grid>();
		grid.transform.localScale *= gridScaleFactor; //Scale by the factor
		
		//Reposition the grid
		float xPos = offset.x + (i * grid.Size);
		float zPos = offset.y + (j * grid.Size);
		grid.transform.position = new Vector3(xPos, this.transform.position.y, zPos);
		
		return grid;
	}
#endregion

#region Member Methods
	/**
	 * Create the grids
	 */
	public static bool Exists()
	{
		if (instance == null)
		{
			Debug.LogError("GridMap doesn't exist! Please add a game object with an instance of the GridMap.");
			return false;
		}
		
		return true;
		
	}
	
	/**
	 * Get the size of the grids
	 */
	public static float GetGridScaleFactor()
	{
		float size = Exists() ? instance.gridScaleFactor : 0.0f;
		return size;
	}
#endregion
}

And it has an editor class:
using UnityEngine;
using UnityEditor;
using System.Collections;

[CustomEditor(typeof(GridMap))]
public class GridMapEditor : Editor {
#region Member Variables
	private SerializedObject gridMapSO;
	private GridMap gridMap;

	bool isShowing;
	
	int width;
	int height;
#endregion

#region Member Properties
#endregion

#region Monobehaviour Methods
	void OnEnable () {
		gridMapSO = new SerializedObject(target);
		gridMap = (GridMap)target;
	}

	public override void OnInspectorGUI()
	{
		gridMapSO.Update();
	
		base.OnInspectorGUI();
		
		//Check the size of the array
		CheckArray(gridMap);
		
		isShowing = EditorGUILayout.Foldout(isShowing, "Map Layout");
		if(isShowing)
		{
		
			for (int i = 0; i < gridMap.mapLayout.GetLength(1); ++i)
			{
				EditorGUILayout.BeginHorizontal();
				for(int j = 0; j < gridMap.mapLayout.GetLength(0); j++)
				{
					gridMap.mapLayout[j,i] = EditorGUILayout.IntField(gridMap.mapLayout[j,i], GUILayout.Width(20));
				}
				EditorGUILayout.EndHorizontal();
			}
		}
		
		if(GUI.changed){
			EditorUtility.SetDirty(target);
			EditorUtility.SetDirty(gridMap);
		}
		
		gridMapSO.ApplyModifiedProperties();
	}
#endregion

#region Member Methods
	private void CheckArray(GridMap gridMap)
	{
		width = (int) gridMap.MapSize.x;
		height = (int) gridMap.MapSize.y;
		
		//int gridmapWidth = gridMap.mapLayout.GetLength(0);
		//int gridmapHeight = gridMap.mapLayout.GetLength(1);
		
		//Debug.Log("width = " + width + " height = " + height + " gridmapWidth = " + gridmapWidth + " gridmapHeight = " + gridmapHeight );
		
		if (gridMap.mapLayout == null || width != gridMap.mapLayout.GetLength(0) || height != gridMap.mapLayout.GetLength(1))
		{
			RecreateArray(gridMap, width, height);
		}
	}
	
	private void RecreateArray(GridMap gridMap, int width, int height)
	{
	
		Debug.Log("TEST!!!");
		int[,] tempArray = gridMap.mapLayout;
		
		gridMap.mapLayout = new int[width, height];
		for (int i = 0; i < gridMap.mapLayout.GetLength(0); ++i)
		{
			if (i >= tempArray.GetLength(0))
				break;
				
			for(int j = 0; j < gridMap.mapLayout.GetLength(1); j++)
			{
				if (j >= tempArray.GetLength(1))
					break;
					
				gridMap.mapLayout[i,j] = tempArray[i,j];
			}
		}
	}
#endregion
}

It worked pretty good in the editor. But when I pressed “play”, it shows this error:

NullReferenceException: Object reference not set to an instance of an object

GridMap.PopulateMap () (at Assets/Scripts/GridSystem/GridMap.cs:93)

GridMap.Awake () (at Assets/Scripts/GridSystem/GridMap.cs:56)

It turns out, the mapLayout 2D array (that is used as the data that should be inserted from the editor) is not instantiated. I can change the declaration of mapLayout into this:

public int[,] mapLayout = new int[10,10];

But the array’s length will always be the same 10x10 value. Even when I changed the array into something like 12x5 in the inspector. I need the length of the mapLayout array to be variable and dynamic. How do I do this?

Thanks in advance.

Multidimensional arrays aren’t supported by the Unity serializer. You have to store your information in a data type that is supported by the serializer. One way is to use a serializable helper class and use seperate arrays:

[Serializable]
public class Row
{
    public int[] rowdata;
}

public class GridMap : MonoBehaviour
{
    // ...
    public Row[] mapLayout; 
    // ...

}

This is supported by the serialization system. You don’t have a multidimensional array anymore but some kind of jagged array so access would look like this:

mapLayout[row].rowdata[column]

An alternative is to use the new [ISerializationCallbackReceiver][1] interface and implement some kind of conversion into a datatype that is supported. For multi dim array i would suggest using a flattend array as well as a row or column count:

public class GridMap : MonoBehaviour, ISerializationCallbackReceiver
{
    public int[,] mapLayout;

    [HideInInspector]
    [SerializeField]
    private int[] m_FlattendMapLayout;

    [HideInInspector]
    [SerializeField]
    private int m_FlattendMapLayoutRows;

 
    public void OnBeforeSerialize()
    {
        int c1 = mapLayout.GetLength(0);
        int c2 = mapLayout.GetLength(1);
        int count = c1*c2;
        m_FlattendMapLayout = new int[count];
        m_FlattendMapLayoutRows = c1;
        for(int i = 0; i < count; i++)
        {
            m_FlattendMapLayout *= mapLayout[i % c1, i / c1];*

}
}
public void OnAfterDeserialize()
{
int count = m_FlattendMapLayout.Length;
int c1 = m_FlattendMapLayoutRows;
int c2 = count / c1;
mapLayout = new int[c1,c2];
for(int i = 0; i < count; i++)
{
mapLayout[i % c1, i / c1] = m_FlattendMapLayout*;
_
}_
_
}_
_
}_
This would work as well. Everytime Unity wants to serialize your class it will call “OnBeforeSerialize” which will copy all items into a one-dim-array which then can be serialized by unity. When your class is deserialized OnAfterDeserialize is being called which will recreate your multidim array and copy the items back.
_
[1]: http://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.html*_

The accepted answers the questions correctly, but I’d like to add my personal opinion:

It’s much better to just always use a single-dimensional array, than 2D or jagged arrays. Internally, all array types are actually stored as 1D arrays in memory. Using the C# syntax only adds conversion math and additional checks. Implementing this yourself is actually faster and doesn’t require any additional steps for serialization in Unity.

This is how it could look:

public class TileMap : MonoBehaviour
{
	public int width = 12;
	public int height = 10;

	[SerializeField]
	private string[] tiles;

	public void SetTile(int x, int y, string type)
	{
		// 2D representation stored in row-major order.
		tiles[y * width + x] = type;
	}

	public string GetTile(int x, int y)
	{
		return tiles[y * width + x];
	}

	public Vector2Int GetCoordinate(int index)
	{
		int x = index % width;
		int y = index / width;
		return new Vector2Int(x, y);
	}
}

Hello there, I just came across this question today at work and solved it,

I made a GitHub for this: GitHub - Eldoir/Array2DEditor: Use 2-dimensional arrays in Unity's Inspector.

Hope you’ll like it :slight_smile: