Custom inspector initializing array

I’m bashing my head on custom inspector array handling.

I have 4 arrays that I want to all be the same size. I figured it would be better to have a custom inspector that handled making sure they are all the same size for me. I have a property which is the max size. It can be between 1 and 9 (inclusive). I put in an increment button and decrement button. For the increment button I call InsertArrayElementAtIndex for each of the array; likewise on decrement button I call DeleteArrayElementAtIndex.

This works great if the arrays are already populated. If the arrays aren’t then no elements are added.

What I want to do is initialize these arrays to a size of 1 in the inspector. I can not figure out, however, how to assign a new array to these SerializedProperties.

There has to be a way to do it, since the default inspector will create/initialize an array when you increase the size. But I am unable to figure out how to do so.

Any help would be appreciated.

Argh, turns out it is dead simple. If I replace “InsertArrayElementAtIndex” with “arraySize += 1” and “DeleteArrayElementAtIndex” with “arraySize -= 1” then everything works great.

This was a hard one.

In fact, I discovered it myself, and just now I’ve read your solution - after banging my head for hours. :slight_smile:

I needed to add elements to an array, and naive as I am, I thought that InsertArrayElementAtIndex(int index) would be responsible for creation of new (empty) slots. I was getting:

Retrieving array element that was out of bounds
UnityEditor.SerializedProperty:InsertArrayElementAtIndex(Int32)

The thing is that SerializedProperty array isn’t documented as it should be.Also have overlooked the arraySize property, since I thought it is read only. If fact, it is also the setter.

So, to help the next victim of Unity’s serialized array, here are the operations available for manipulating it:

// ADDS the element to the end of the array
YourSerializedProperty.arraySize ++;

// REMOVES the element from the end of the array
YourSerializedProperty.arraySize --;

// INSERTS the element to the specified array index. 
// Note: The index has to be prepopulated, meaning this cannot be used to push to the end of the array.
YourSerializedProperty.InsertArrayElementAtIndex(int index);

// REMOVES the element from the specified array index, but not the last one.
YourSerializedProperty.DeleteArrayElementAtIndex(int index);

// SETS the value at the specified slot
YourSerializedProperty.GetArrayElementAtIndex(index).objectReferenceValue = value;

// GETS the value at the specified slot
return YourSerializedProperty.GetArrayElementAtIndex(index).objectReferenceValue;

Cheers! :slight_smile:

13 Likes

The “next victim” is happy you posted that code. Thank you!

1 Like

:slight_smile:

Edited.

Was googling my problem and found this thread.

I have a custom inspector script which manipulates a SerializedProperty array. What I noticed is that when I use DeleteArrayElementAtIndex it doesn’t actually delete an item but leaves a null at a specified index.

I added a second check which is triggered during the next Layout event which looks for null values and does the same DeleteArrayElementAtIndex on the first one. And this time array actually shrinks.

Has anyone experienced such behavior?

P.S. I also asked the question at SerializedProperty.DeleteArrayElementAtIndex leaves a null instead of removing an item. - Questions & Answers - Unity Discussions

Yes, I have also experienced this. But inside the PropertyDrawer it works ok.

You might want to check out the Custom List tutorial by CatlikeCoding.

Here it works normally, with only one line of code doing the actual deletion:

if (GUI.Button(position, deleteButtonContent, EditorStyles.miniButtonRight)) {
	property.DeleteArrayElementAtIndex(index);
}

The key thing might be the timing of calling the ApplyModifiedProperties method on the serialized object.

The funny thing that I ended up with this code

private void removeFromArray(SerializedProperty array, int index)
        {
            if (index != array.arraySize - 1)
            {
                array.GetArrayElementAtIndex(index).objectReferenceValue = array.GetArrayElementAtIndex(array.arraySize - 1).objectReferenceValue;
            }
            array.arraySize--;
        }

Have you tried this:

if (GUI.Button(position, deleteButtonContent, EditorStyles.miniButtonRight)) {
    property.DeleteArrayElementAtIndex(index);
    property.serializedObject.ApplyModifiedProperties();
}

Or:

if (GUI.Button(position, deleteButtonContent, EditorStyles.miniButtonRight)) {
    removedIndex = position;
}

… and then, outside of the loop:

property.DeleteArrayElementAtIndex(removedIndex );
property.serializedObject.ApplyModifiedProperties();

(it’s never healty to remove elements while inside the loop)

My code looks like this basically:

int gestureIndexToRemove = -1;
for (int i = 0; i < serializedGestures.arraySize; i++) {
  if (serializedGestures.GetArrayElementAtIndex(i).objectReferenceValue == null || GUILayout.Button("remove")) gestureIndexToRemove = i;
}
if (gestureIndexToRemove > -1) removeGestureAt(gestureIndexToRemove);
private void removeGestureAt(int index) {
  serializedGestures.DeleteArrayElementAtIndex(index);
}

So when I press “remove” button it goes to removeGestureAt but right in the next Layout event it encounters a null at this index and goes to removeGestureAt again. After the second call the element is finally removed.

I tried all the possible combinations of serializedObject.ApplyModifiedProperties() and EditorUtility.SetDirty(target) inside removeGestureAt function but nothing helped…

1 Like

Still wonder if it’s a bug o.O

Another ‘next victim’ here to say thanks.

If you care about item order in the list this works too:

list.GetArrayElementAtIndex(index).objectReferenceValue = null;
list.DeleteArrayElementAtIndex(index);

I know the thread is old, But I’m having the same problem, how does one do this:
OnGUI >> ButtonPress >> DeleteArrayAtElement
when I do it, it removes two values not one, obviously because the OnGUI draws two-times per frame, I’m using EditorWindow so it needs to be done inside the OnGUI

The latest victim of this poorly-documented class also thanks you for posting this. I beat my head against the wall for two hours before finally seeing the solution here.

Hi I know this is an old post but I think I am having the same problem. I have made an Inventory editor that works with 4 items, as soon as I add even just one more the 5th one does not work. The error points me to a serialized array in my code.
Inventory Editor code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

[CustomEditor (typeof(Inventory))]
public class InventoryEditor : Editor {

    private SerializedProperty itemImagesProperty;
    private SerializedProperty itemsProperty;
    private bool[] showItemSlots = new bool[Inventory.numItemSlots];
   
   

    private const string inventoryPropItemImagesName = "itemImages";
    private const string inventoryPropItemsName = "items";

    private void OnEnable()
    {
        itemImagesProperty = serializedObject.FindProperty(inventoryPropItemImagesName);
        itemsProperty = serializedObject.FindProperty(inventoryPropItemsName);


    }

    public override void OnInspectorGUI()
    {
        serializedObject.Update();

        for (int i= 0; i < Inventory.numItemSlots; i++)
        {
            ItemSlotGUI(i);
        }


        serializedObject.ApplyModifiedProperties();
    }

    private void ItemSlotGUI (int index)
    {
        EditorGUILayout.BeginVertical(GUI.skin.box);
        EditorGUI.indentLevel++;
        showItemSlots[index] = EditorGUILayout.Foldout(showItemSlots[index], "Item slot " + index);

        if (showItemSlots[index])
        {
            EditorGUILayout.PropertyField(itemImagesProperty.GetArrayElementAtIndex(index));
            EditorGUILayout.PropertyField(itemsProperty.GetArrayElementAtIndex(index));
        }

        EditorGUI.indentLevel--;

        EditorGUILayout.EndVertical();

    }
}

Inventory code:

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class Inventory : MonoBehaviour
{
    public Image[] itemImages = new Image[numItemSlots];
    public Item[] items = new Item[numItemSlots];
    public const int numItemSlots = 5;



    public void AddItem(Item itemToAdd)
    {
      
        int id = itemToAdd.id;
        if (items.Length <= id)
            return;
        if (items [id]== null)
        {
            items[id] = itemToAdd;
            itemImages[id].sprite = itemToAdd.sprite;
            itemImages[id].enabled = true;
            return;
        }


      
    }



   public void RemoveItem(Item itemToRemove)
    {
        for (int i = 0; i < items.Length; i++)
        {
            if (items[i] == itemToRemove)
            {
                items[i] = null;
                itemImages[i].sprite = null;
                itemImages[i].enabled = false;
                return;
            }
        }
    }
}

If you originally created your component when Inventory.numItemSlots was 4, then its itemImages[ ] and images[ ] arrays were created with 4 elements in that instance. Once Unity creates a component on a GameObject, it doesn’t resize them if you change the array size in your class. You can force Unity to reset them to their initializer values by selecting Reset from the gear menu. In your code, you can force the arrays to be the correct size by adding these lines:

public override void OnInspectorGUI()
    {
        serializedObject.Update();

        itemImagesProperty.arraySize = Inventory.numItemSlots;
        itemsProperty.arraySize = Inventory.numItemSlots;

        for (int i= 0; i < Inventory.numItemSlots; i++)
        ...
1 Like

This worked amazingly! Thank youu!

Glad I could help!