user1105430
user1105430

Reputation: 1399

How to create multidimensional array in Unity Inspector?

How does one create an enum multidimensional array in Unity Inspector and make it serializable so I can call it from a different script?

public enum colors {red, blue, green, yellow, cyan, white, purple};
public int rows = 7;
public int column = 4;
public colors[,] blockColors;

private void Awake() {
    blockColors = new colors[rows, column];
}

For me to manually type all 28 colors in the script is time consuming, especially, when I have to do this for hundreds of levels. Is there a way to create a table in the Inspector to make workflow faster?

enter image description here

I tried making blockColors a [Serializefield] but it doesn't work. I've never tried coding a chart for the inspector before. Can someone please direct me to a tutorial that can help me understand how to code a chart like in the picture?

Upvotes: 5

Views: 35632

Answers (5)

Andreas
Andreas

Reputation: 1

For anyone stumbling over this: This has also been discussed here: https://answers.unity.com/questions/1485842/serialize-custom-multidimensional-array-from-inspe.html The tl;dr is that two dimensional arrays just don't work and you're better off either flattening the array and just display it two dimensional or nest both dimensions in separate classes.

Upvotes: 0

zambari
zambari

Reputation: 5035

You need to create a custom editor (or more specifically CustomPropertDrawer if you want to re-use it for other components

The only non-obvious part required to create a table like that is forcing the elements to lay out the way you want. One way is manually handling position Rect's given you by Unity, but there is a much simple (albeit a bit less flexible) way, just wrap your elements in horizontal/vertical layout combos. The intuitive approach would be to wrap your elements in

GUILayout.BeginHorizontal();
{
  // your elements line 1
}
GUILayout.EndHorizontal(); 
GUILayout.BeginHorizontal();
{
  // your elements line 2 and so on
}
GUILayout.EndHorizontal(); 

but it has a downside - autolayout will only take widts of elements in a current line, but if element size varies this will break vertical alingment. A solution is to wrap each column in a layout first, and than use the horizontal layout to combine vertical strips, it goes like this

GUILayout.BeginHorizontal();
{
  GUILayout.BeginVertical();
  {
   // your elements column 1
  }
 GUILayout.EndVertical();
 GUILayout.BeginVertical();
 { 
   // your elements column 2
 }  
 GUILayout.EndVertical();
}
GUILayout.EndHorizontal(); 

Brackets are just for clarity, they do nothing. I hope this helps

Upvotes: 1

robbytfry
robbytfry

Reputation: 1

Thanks to all the excellent answers above.

For anyone having trouble persisting their custom arrays, one issue is that multidimensional arrays are not serializable. Possible workarounds include implementing your 2D array as a jagged array (an array whose elements are arrays) or creating a wrapper class that is serializable itself.

As endrik exe suggested, you'll also want to prompt Unity to save changes to your object using EditorUtility.SetDirty(). Adding the following to the end of OnInspectorGUI() should do the trick:

if (GUI.changed) {
    Undo.RecordObject(levels, "Edit levels");
    EditorUtility.SetDirty(levels);
}

Upvotes: 0

user1105430
user1105430

Reputation: 1399

Thanks to all the answers provided I have come up with this solution:

enter image description here

Levels.cs

using UnityEngine;

public enum BlockColors {blank, red, blue, green, yellow, cyan, white, purple};

[System.Serializable] public class level {
    #if UNITY_EDITOR
    [HideInInspector] public bool showBoard;
    #endif
    public int rows = 9;
    public int column = 9;
    public BlockColors [,] board = new BlockColors [columns, rows];
}


public class Levels : MonoBehaviour {

    public Level[] allLevels;

}

Editor/LevelEditor.cs

using UnityEngine;
using UnityEditor;


[CustomEditor(typeof(Levels))]
public class LevelEditor : Editor {

    public bool showLevels = true;

    public override void OnInspectorGUI() {
        Levels levels = (Levels)target;
        EditorGUILayout.Space ();

        showLevels = EditorGUILayout.Foldout (showLevels, "Levels ("+levels.allLevels.Length+")");
        if (showLevels) {
            EditorGUI.indentLevel++;
            for (ushort i = 0; i < levels.allLevels.Length; i++) {

                levels.allLevels[i].showBoard = EditorGUILayout.Foldout(levels.allLevels[i].showBoard, "Board");
                if (levels.allLevels [i].showBoard) {

                    EditorGUI.indentLevel = 0;

                    GUIStyle tableStyle = new GUIStyle ("box");
                    tableStyle.padding = new RectOffset (10, 10, 10, 10);
                    tableStyle.margin.left = 32;

                    GUIStyle headerColumnStyle = new GUIStyle ();
                    headerColumnStyle.fixedWidth = 35;

                    GUIStyle columnStyle = new GUIStyle ();
                    columnStyle.fixedWidth = 65;

                    GUIStyle rowStyle = new GUIStyle ();
                    rowStyle.fixedHeight = 25;

                    GUIStyle rowHeaderStyle = new GUIStyle ();
                    rowHeaderStyle.fixedWidth = columnStyle.fixedWidth - 1;

                    GUIStyle columnHeaderStyle = new GUIStyle ();
                    columnHeaderStyle.fixedWidth = 30;
                    columnHeaderStyle.fixedHeight = 25.5f;

                    GUIStyle columnLabelStyle = new GUIStyle ();
                    columnLabelStyle.fixedWidth = rowHeaderStyle.fixedWidth - 6;
                    columnLabelStyle.alignment = TextAnchor.MiddleCenter;
                    columnLabelStyle.fontStyle = FontStyle.Bold;

                    GUIStyle cornerLabelStyle = new GUIStyle ();
                    cornerLabelStyle.fixedWidth = 42;
                    cornerLabelStyle.alignment = TextAnchor.MiddleRight;
                    cornerLabelStyle.fontStyle = FontStyle.BoldAndItalic;
                    cornerLabelStyle.fontSize = 14;
                    cornerLabelStyle.padding.top = -5;

                    GUIStyle rowLabelStyle = new GUIStyle ();
                    rowLabelStyle.fixedWidth = 25;
                    rowLabelStyle.alignment = TextAnchor.MiddleRight;
                    rowLabelStyle.fontStyle = FontStyle.Bold;

                    GUIStyle enumStyle = new GUIStyle ("popup");
                    rowStyle.fixedWidth = 65;

                    EditorGUILayout.BeginHorizontal (tableStyle);
                    for (int x = -1; x < levels.allLevels [i].columns; x++) {
                        EditorGUILayout.BeginVertical ((x == -1) ? headerColumnStyle : columnStyle);
                        for (int y = -1; y < levels.allLevels [i].rows; y++) {
                            if (x == -1 && y == -1) {
                                EditorGUILayout.BeginVertical (rowHeaderStyle);
                                EditorGUILayout.LabelField ("[X,Y]", cornerLabelStyle);
                                EditorGUILayout.EndHorizontal ();
                            } else if (x == -1) {
                                EditorGUILayout.BeginVertical (columnHeaderStyle);
                                EditorGUILayout.LabelField (y.ToString (), rowLabelStyle);
                                EditorGUILayout.EndHorizontal ();
                            } else if (y == -1) {
                                EditorGUILayout.BeginVertical (rowHeaderStyle);
                                EditorGUILayout.LabelField (x.ToString (), columnLabelStyle);
                                EditorGUILayout.EndHorizontal ();
                            }

                            if (x >= 0 && y >= 0) {
                                EditorGUILayout.BeginHorizontal (rowStyle);
                                levels.allLevels [i].board [x, y] = (BlockColors)EditorGUILayout.EnumPopup (levels.allLevels [i].board [x, y], enumStyle);
                                EditorGUILayout.EndHorizontal ();
                            }
                        }
                        EditorGUILayout.EndVertical ();
                    }
                    EditorGUILayout.EndHorizontal ();

                }

            }
        }
    }
}

My main problem now is it won't serialize. Any change I made to the level will automatically reset on Play. How can I serialize the custom array setup from the Inspector?

Upvotes: 3

Endrik
Endrik

Reputation: 606

You can build a CustomEditor script for your class, and then display your multidimensional array using GUI, just like you code normal OnGUI event.

Here's a simple pseudo code

Loop for every Rows
   Divide inspector width with columns length;
   Loop for every Columns
       Render Custom Field with dividen width;
   End Loop
   Incrase posY for new rows ++;
End Loop

Here are some links that will help you get started

https://docs.unity3d.com/Manual/editor-CustomEditors.html

https://unity3d.com/learn/tutorials/topics/interface-essentials/building-custom-inspector

Upvotes: 1

Related Questions