How to use System.Serializable with Serialized Property

Hi there,

I’m making a custom editor for my class and I’m struggling with how to use Serialized Properties correctly with System.Serializable classes? Basically I can’t assign the object reference to the classes I create in my UpdatePrizesForLevel function without declaring a class that derived from Object. Is there a way around this? Thanks

[System.Serializable]
public class PrizeEntriesForLevel
{
    public int levelNumber;

    [SerializeField]
    PrizeEntry[] prizeEntries;

    public PrizeEntriesForLevel(int levelNumber, PrizeEntry[] newPrizeEntries)
    {
        this.levelNumber = levelNumber;
        prizeEntries = newPrizeEntries;
    }
}

[System.Serializable]
public class PrizeEntry
{
    public int starsRequired = 0;
    public string prizeName;

    public PrizeEntry(int newStars, string prize)
    {
        starsRequired = newStars;
        prizeName = prize;
    }

}
using UnityEngine;
using System.Collections;
using UnityEditor;
using Google.GData.Spreadsheets;
using System.Collections.Generic;

[CustomEditor(typeof(PrizeManager), true)]
public class PrizeManagerEditor : Editor {

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        if (GUILayout.Button("Update"))
        {
            UpdatePrizesForLevel(serializedObject.FindProperty("spreadsheetID").stringValue);
        }

    }

    void UpdatePrizesForLevel(string spreadsheetID)
    {
        ListFeed test = LoadGoogleSpreadsheet.GetSpreadsheet(spreadsheetID);
        List<PrizeEntriesForLevel> prizeEntriesForAllLevels = new List<PrizeEntriesForLevel>();
        List<PrizeEntry> prizeEntriesForIndividualLevel = new List<PrizeEntry>();

        foreach (ListEntry row in test.Entries)
        {
            if (!string.IsNullOrEmpty( row.Title.Text))
            {               
                int levelNumber = int.Parse(row.Title.Text);

                for (int j = 1; j < row.Elements.Count; j++)
                {
                    if(!string.IsNullOrEmpty(row.Elements[j].Value))
                    {
                        PrizeEntry newPrizeEntry = new PrizeEntry(j, row.Elements[j].Value);
                        prizeEntriesForIndividualLevel.Add(newPrizeEntry);
                    }
                }

                PrizeEntriesForLevel newEntryForLevel = new PrizeEntriesForLevel(levelNumber, prizeEntriesForIndividualLevel.ToArray());
                prizeEntriesForAllLevels.Add(newEntryForLevel);
               
             }
        }

        SerializedProperty property = serializedObject.FindProperty("prizeEntries");

        property.ClearArray();

        property.arraySize = prizeEntriesForAllLevels.Count;

        for (int i = 0; i < prizeEntriesForAllLevels.Count; i++)
        {
            property.InsertArrayElementAtIndex(i);
            SerializedProperty prop = property.GetArrayElementAtIndex(i);
            // I'd like to assign the prop value here but can't!
        }

        serializedObject.ApplyModifiedProperties();




    }
}

Unfortunately, you can’t do it in this way, i.e. adding just an instance you created to the list. This is an annoying part of the API that many have struggled with. However, there is a way around it, which is to fill the values of the child property with the values of your created list. so , your code will look like this (typed from memory, but the idea is the same)

Starting at line 54 of your code:

for (int i = 0; i < prizeEntriesForAllLevels.Count; i++)
        {
            SerializedProperty prop = property.GetArrayElementAtIndex(i);
            prop.FindPropertyRelative("starsRequired").intValue = prizeEntriesForAllLevels[i].starsRequired;
            prop.FindPropertyRelative("prizeName").stringValue = prizeEntriesForAllLevels[i].prizeName;
        }

The reason behind it is that Unity hides the internal instance of the serialized list, but when you change the arraySize property, it will create a new instance to fill up the array, so it isn’t null. All you have to do, is fill up the data.

Also, note that in the way you are doing this, your array will be double the size, since InsertArrayElement() will CREATE an array element at index, but since you have already resized your array, you will add to the array in question. So you can just remove the call to property.InsertArrayElementAtIndex(), the element will already be there.

Great! Thanks for that. It ended up quite longwinded but here’s my solution:

        SerializedProperty property = serializedObject.FindProperty("prizeEntries");

        property.ClearArray();

        property.arraySize = prizeEntriesForAllLevels.Count;

        for (int i = 0; i < prizeEntriesForAllLevels.Count; i++)
        {
            //property.InsertArrayElementAtIndex(0);
            SerializedProperty prop = property.GetArrayElementAtIndex(i);
            prop.FindPropertyRelative("levelNumber").intValue = prizeEntriesForAllLevels[i].levelNumber;

            SerializedProperty prizeEntries = prop.FindPropertyRelative("prizeEntries");
            for(int j = 0; j < prizeEntriesForAllLevels[i].prizeEntries.Length; j++)
            {
                prizeEntries.ClearArray();
                prizeEntries.arraySize = prizeEntriesForAllLevels[i].prizeEntries.Length;
                //prizeEntries.InsertArrayElementAtIndex(0);
                SerializedProperty arrayElement = prizeEntries.GetArrayElementAtIndex(j);
                arrayElement.FindPropertyRelative("starsRequired").intValue = prizeEntriesForAllLevels[i].prizeEntries[j].starsRequired;
                arrayElement.FindPropertyRelative("prizeName").stringValue = prizeEntriesForAllLevels[i].prizeEntries[j].prizeName;
            }
        }