- Home /
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.
Answer by Bunny83 · Jul 29, 2015 at 12:19 PM
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 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[i] = 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[i];
}
}
}
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.
Thank you. Thank you so much. It actually worked. I used the first method. :)
The second solution is better as it lets you create a true 2D array plus lets you manage its serialized form. Thanks for mentioning it! PS: I knew about ISerializationCallbackReceiver
Thanks a lot for providing multiple solutions. If I were making something more scalable I'd dig deeper into option two, but seeing as its such a small part of my project that uses a couple 2D arrays, I really only needed the quick and dirty jagged array. Looking at your solution. I'm surprised I didn't think to do this, but I just wanted to say thanks and that there are still people using this on serious projects multiple years later.
Answer by Xarbrough · Dec 21, 2017 at 01:36 AM
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);
}
}
Answer by Eldoir · Dec 20, 2017 at 04:35 PM
Hello there, I just came across this question today at work and solved it,
I made a GitHub for this: https://github.com/Eldoir/2DArrayEditor
Hope you'll like it :)
Your answer
Follow this Question
Related Questions
Prefabs aren't saving with Undo.RecordObject 4 Answers
Getting a target's gameobject in custom editor 1 Answer
Generic CustomEditor 0 Answers
Events in custom inspector 1 Answer
Handles not displaying 1 Answer