Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

How to use System.Serializable with Serialized Property

Discussion in 'Scripting' started by LukasO, Apr 25, 2016.

  1. LukasO

    LukasO

    Joined:
    May 23, 2013
    Posts:
    115
    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

    Code (CSharp):
    1. [System.Serializable]
    2. public class PrizeEntriesForLevel
    3. {
    4.     public int levelNumber;
    5.  
    6.     [SerializeField]
    7.     PrizeEntry[] prizeEntries;
    8.  
    9.     public PrizeEntriesForLevel(int levelNumber, PrizeEntry[] newPrizeEntries)
    10.     {
    11.         this.levelNumber = levelNumber;
    12.         prizeEntries = newPrizeEntries;
    13.     }
    14. }
    15.  
    16. [System.Serializable]
    17. public class PrizeEntry
    18. {
    19.     public int starsRequired = 0;
    20.     public string prizeName;
    21.  
    22.     public PrizeEntry(int newStars, string prize)
    23.     {
    24.         starsRequired = newStars;
    25.         prizeName = prize;
    26.     }
    27.  
    28. }
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using UnityEditor;
    4. using Google.GData.Spreadsheets;
    5. using System.Collections.Generic;
    6.  
    7. [CustomEditor(typeof(PrizeManager), true)]
    8. public class PrizeManagerEditor : Editor {
    9.  
    10.     public override void OnInspectorGUI()
    11.     {
    12.         base.OnInspectorGUI();
    13.  
    14.         if (GUILayout.Button("Update"))
    15.         {
    16.             UpdatePrizesForLevel(serializedObject.FindProperty("spreadsheetID").stringValue);
    17.         }
    18.  
    19.     }
    20.  
    21.     void UpdatePrizesForLevel(string spreadsheetID)
    22.     {
    23.         ListFeed test = LoadGoogleSpreadsheet.GetSpreadsheet(spreadsheetID);
    24.         List<PrizeEntriesForLevel> prizeEntriesForAllLevels = new List<PrizeEntriesForLevel>();
    25.         List<PrizeEntry> prizeEntriesForIndividualLevel = new List<PrizeEntry>();
    26.  
    27.         foreach (ListEntry row in test.Entries)
    28.         {
    29.             if (!string.IsNullOrEmpty( row.Title.Text))
    30.             {              
    31.                 int levelNumber = int.Parse(row.Title.Text);
    32.  
    33.                 for (int j = 1; j < row.Elements.Count; j++)
    34.                 {
    35.                     if(!string.IsNullOrEmpty(row.Elements[j].Value))
    36.                     {
    37.                         PrizeEntry newPrizeEntry = new PrizeEntry(j, row.Elements[j].Value);
    38.                         prizeEntriesForIndividualLevel.Add(newPrizeEntry);
    39.                     }
    40.                 }
    41.  
    42.                 PrizeEntriesForLevel newEntryForLevel = new PrizeEntriesForLevel(levelNumber, prizeEntriesForIndividualLevel.ToArray());
    43.                 prizeEntriesForAllLevels.Add(newEntryForLevel);
    44.                
    45.              }
    46.         }
    47.  
    48.         SerializedProperty property = serializedObject.FindProperty("prizeEntries");
    49.  
    50.         property.ClearArray();
    51.  
    52.         property.arraySize = prizeEntriesForAllLevels.Count;
    53.  
    54.         for (int i = 0; i < prizeEntriesForAllLevels.Count; i++)
    55.         {
    56.             property.InsertArrayElementAtIndex(i);
    57.             SerializedProperty prop = property.GetArrayElementAtIndex(i);
    58.             // I'd like to assign the prop value here but can't!
    59.         }
    60.  
    61.         serializedObject.ApplyModifiedProperties();
    62.  
    63.  
    64.  
    65.  
    66.     }
    67. }
     
  2. skalev

    skalev

    Joined:
    Feb 16, 2012
    Posts:
    264
    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:

    Code (CSharp):
    1. for (int i = 0; i < prizeEntriesForAllLevels.Count; i++)
    2.         {
    3.             SerializedProperty prop = property.GetArrayElementAtIndex(i);
    4.             prop.FindPropertyRelative("starsRequired").intValue = prizeEntriesForAllLevels[i].starsRequired;
    5.             prop.FindPropertyRelative("prizeName").stringValue = prizeEntriesForAllLevels[i].prizeName;
    6.         }
    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.
     
  3. LukasO

    LukasO

    Joined:
    May 23, 2013
    Posts:
    115
    Great! Thanks for that. It ended up quite longwinded but here's my solution:

    Code (CSharp):
    1.         SerializedProperty property = serializedObject.FindProperty("prizeEntries");
    2.  
    3.         property.ClearArray();
    4.  
    5.         property.arraySize = prizeEntriesForAllLevels.Count;
    6.  
    7.         for (int i = 0; i < prizeEntriesForAllLevels.Count; i++)
    8.         {
    9.             //property.InsertArrayElementAtIndex(0);
    10.             SerializedProperty prop = property.GetArrayElementAtIndex(i);
    11.             prop.FindPropertyRelative("levelNumber").intValue = prizeEntriesForAllLevels[i].levelNumber;
    12.  
    13.             SerializedProperty prizeEntries = prop.FindPropertyRelative("prizeEntries");
    14.             for(int j = 0; j < prizeEntriesForAllLevels[i].prizeEntries.Length; j++)
    15.             {
    16.                 prizeEntries.ClearArray();
    17.                 prizeEntries.arraySize = prizeEntriesForAllLevels[i].prizeEntries.Length;
    18.                 //prizeEntries.InsertArrayElementAtIndex(0);
    19.                 SerializedProperty arrayElement = prizeEntries.GetArrayElementAtIndex(j);
    20.                 arrayElement.FindPropertyRelative("starsRequired").intValue = prizeEntriesForAllLevels[i].prizeEntries[j].starsRequired;
    21.                 arrayElement.FindPropertyRelative("prizeName").stringValue = prizeEntriesForAllLevels[i].prizeEntries[j].prizeName;
    22.             }
    23.         }
    24.