Unity Editor UI: Static Array Editor

ยท

4 min read

Context

I stumbled into a situation where I needed to expose an array in a MonoBehaviour component, but I'd like for that array's size to stay fixed. But by default, Unity always renders arrays as resizeable: Unity_XHMN6JOhkb.gif

Very cool!... But not what we want.

In C#, arrays can be allocated to be of a fixed size, but this is still dynamic memory. You can re-allocate memory and move the values of your array to a new one:

class TestArraysClass
{
    static void Main()
    {
        // Declare a single-dimensional array of 5 integers.
        int[] array1 = new int[5];

        // Declare and set array element values.
        int[] array2 = new int[] { 1, 3, 5, 7, 9 };

        // Alternative syntax.
        int[] array3 = { 1, 2, 3, 4, 5, 6 };

        // Declare a two dimensional array.
        int[,] multiDimensionalArray1 = new int[2, 3];

        // Declare and set array element values.
        int[,] multiDimensionalArray2 = { { 1, 2, 3 }, { 4, 5, 6 } };

        // Declare a jagged array.
        int[][] jaggedArray = new int[6][];

        // Set the values of the first array in the jagged array structure.
        jaggedArray[0] = new int[4] { 1, 2, 3, 4 };
    }
}

From C#'s own documentation on arrays. All of these arrays can be resized or re-allocated at run-time.

This is unlike C++'s static arrays, that are blocks of static memory whose size must be determined at compile time, before the program runs:

int staticArray[5] = { 16, 2, 77, 40, 12071 };

So how can we bypass this behavior and render a fixed size amount on the Inspector GUI?

Here's the sample component that I'll be using in this post:

public class StaticArrayTest : MonoBehaviour
{
    public int[] staticArray = { 0, 0, 0, 0 };

    // Start is called before the first frame update
    void Start()
    {
    }

    // Update is called once per frame
    void Update()
    {
    }
}

Custom Editors for the Win!!

A quick online search on how to do this brought me to this thread on the Unity forum. And the advice given here is to basically to either declare however many separate variables needed, or use a custom editor. I prefer going with the latter, since I'd like to still use the array.

So we need to create our own kind of editor. Fortunately, Unity provides lots of features to easily do this very task, we can create our own custom editors!

[CustomEditor(typeof(StaticArrayTest))]
public class StaticArrayTestEditor : Editor
{
    public override void OnInspectorGUI()
    {
        if(target is not StaticArrayTest staticArrayTest)
        {
            return;
        }

        for(int i = 0; i < staticArrayTest.staticArray.Length; ++i)
        {
            EditorGUILayout.BeginHorizontal();

                EditorGUILayout.LabelField($"Field #{i + 1}");

                int currentNumber = staticArrayTest.staticArray[i];
                staticArrayTest.staticArray[i] = EditorGUILayout.IntField(currentNumber);

            EditorGUILayout.EndHorizontal();
        }
    }
}

Note: Remember to please place custom editor scripts inside of an Assets/Editor folder. Use the Unity environment to your advantage, and the special folder names.

And look at that! With a few lines of code, we now can modify the values in our array, but without the ability to resize it: Unity_bI651fLNNZ.gif

What about other fields?!

This is all well and good, but what if you'd like to have that static array and other additional fields? Imagine you modify your component to contain these new fields:

public class StaticArrayTest : MonoBehaviour
{
    public int[] staticArray = { 0, 0, 0, 0 };

    public float floatValue = 0f;

    public GameObject referenceToAgo = null;
    ....
}

Aaaaand nope. Since we're still using our own custom editor, it's only rendering the staticArray, and nothing else.

image.png

erm.... not good.

Does this mean that we have to now write custom editor rendering code for every additional field?!

Fortunately, no. We can easily adjust our StaticArrayTest to first hide the static array (trust me, this will make sense soon):

public class StaticArrayTest : MonoBehaviour
{
    [HideInInspector]
    public int[] staticArray = { 0, 0, 0, 0 };

    ...

}

And now, let's modify the StaticArrayTestEditor to use a mix of our own editor rendering code, and Unity's own default inspector GUI rendering:

[CustomEditor(typeof(StaticArrayTest))]
public class StaticArrayTestEditor : Editor
{
    public override void OnInspectorGUI()
    {
        ...

        for(int i = 0; i < staticArrayTest.staticArray.Length; ++i)
        {
            ...
        }

        EditorGUILayout.LabelField("Switching to Unity's default inspector GUI...");

        base.OnInspectorGUI();
    }
}

And there ya go! Now you can mix in whatever other custom rendering you'd like to apply for other fields and also take advantage of Unity's own features, by calling the base class's own OnInspectorGUI() function: Unity_8H6s9aUM61.gif

Note: You can also switch the order in which you render custom and default behavior.

Remember that since we applied the [HideInInspector] attribute to our staticArray field, the editor ignores rendering this field. We're basically telling it: "Don't worry about this one, I'll handle it."

Please let me know what you think!

  • Have you used this technique in the past?
  • How else have you modified the editor GUI rendering logic?

๐ŸŽ‰๐ŸŽ‰๐ŸŽ‰Happy coding!!๐ŸŽ‰๐ŸŽ‰๐ŸŽ‰

References

ย