Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

Working with arrays as SerializedProperty(ies) in editor script

Discussion in 'Scripting' started by guavaman, Jul 20, 2011.

  1. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    3,865
    I can't find a way to access the contents of an array which is a SerializedProperty.
    Specifically:

    Code (csharp):
    1.  
    2. // .... in MyScript ... //
    3. public var myIntArray : int[] = [1, 2, 3, 4];
    4.  
    5. // ... in another script ... ///
    6. var so : SerializedObject = new SerializedObject(MyScript);
    7. var sp : SerializedProperty = so.FindProperty("myIntArray"); // serialized property of an int array
    8.  
    9. // In an effort to figure out how this works, I tried:
    10. Debug.Log(sp.propertyType.ToString()); // value = Generic
    11. Debug.Log(sp.hasChildren.ToString()); // value = True
    12. Debug.Log(sp.hasVisibleChildren.ToString()); // value = True
    13. Debug.Log(sp.type.ToString(); // value = vector ????
    14.  
    The returned SerializedPropertyType "Generic" is not a listed type in SeralizedPropertyType. The returned value for sp.type "vector" (not vector2 or vector3) is also confusing.

    I can't figure out how to access the values in the array from the SerializedProperty. Am I understanding this incorrectly? Do I have to so.FindProperty(myIntArray[#]) for each element in the array separately? If so, why is it not giving me an error when I do it for the whole array?

    Edit: Forgot to ask, hasChildren and hasVisibleChildren are true. Not even sure what this is referring to. Docs for these has no information at all. How do I access these children?

    Thanks!
     
    Last edited: Jul 20, 2011
  2. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    3,865
    Okay, so even if I try to get the size or elements of my array as SerializedProperty like this it fails (crashes)

    Code (csharp):
    1.  
    2. sp = so.FindProperty("myIntArray.length");
    3. sp = so.FindProperty("myIntArray[0]");
    4.  
    5. // Any attempt to access data in sp after this crashes unity
    6. Debug.Log(sp.intValue); // CRASH
    7.  
    SerializeField article states:

    They specifically mention arrays being serializable more than one time on that page. Or course this is the case with public arrays in the inspector -- the data gets saved, and one can override array values in the inspector for prefabs. However there appears to be no way of accessing arrays through SerializedProperty even though the inspector clearly does this when editing arrays.

    Heck even SerializedPropertyType contains a SerializedPropertyType.ArraySize value, so obviously some sort of array handling exists within SerializedObject and SerializedProperty. Is it just undocumented?

    Useless info:
    I'm working on a prefab property editor which will let me change certain values in hundreds of prefabs (the master prefabs, not the instances in the scene) via a spreadsheed like view. If I just simply edit the values directly in the prefabs, all instances of those prefabs in the scene will not inherit the new changes. However, if I change the values through SerializedProperty, all instanced prefabs in the world automatically update values that haven't been previously overridden which is the desired effect. However, not being able to edit arrays like this pretty much kills the effectiveness of this tool as I rely on arrays quite heavily.
     
    Last edited: Jul 20, 2011
  3. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    3,865
    Last edited: Jul 20, 2011
  4. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    3,865
    Okay, I found a way. Not the nicest, but whatever:

    Code (csharp):
    1.  
    2. private var propertyName : String = "propertyName";
    3. private var so : SerializedObject = new SerializedObject(target);
    4.  
    5. function OnGUI() {
    6.   // Since OnGUI runs continually, I need to "rewind" the index in the SerializedProperty each time back to the named property's index
    7.  
    8.   var sp : SerializedProperty = so.FindProperty(propertyName); // Get SP again each time to "rewind" it to proper starting index. If you don't, Next() will continue off from the last position
    9.  
    10.   var arrayLength : int = 0;
    11.   var count : int = 0;
    12.   var arrayElementCount : int = 0;
    13.  
    14.   while(true) {
    15.     // Iteration 0  1 do not contain the data we need. This was verified by Debug.Log(sp.propertyType) through every iteration to find where the array length and starting indices were stored.
    16.    
    17.     if(count == 2) { // Iteration 2: This should be the size of the array.
    18.      
    19.       arrayLength = sp.intValue; // get array size
    20.      
    21.     } else if(count > 2) { // Iteration 2+: The remaining should be the array elements
    22.      
    23.       // Do something wonderful with the array here... in my case, create a GUI.IntField
    24.      
    25.       arrayElementCount++; // Increment array element count so we know when to stop. sp.CountRemaining() will not help us because it gives the count of all remaining properties in the entire SO, not the children of this SP.
    26.  
    27.       if(arrayElementCount == arrayLength) // we got the last array element
    28.         break; // stop iterating, we're done
    29.     }
    30.    sp.Next(true); // Step to the next property in the SerializedObject including children
    31.     count++;
    32.   }
    33. }
    Well anyway, it's messy and could be cleaned up. It's kinda slow too having to do all this iteration with Next(), but whatever.

    Ain't it great talkin to yourself?
     
    Last edited: Jul 30, 2011
    Walbert-Schmitz likes this.
  5. VoxelBoy

    VoxelBoy

    Joined:
    Nov 7, 2008
    Posts:
    213
    Thank you for posting your findings! I was just battling with this very same issue and your code has helped me tremendously.
     
  6. Jake-L

    Jake-L

    Joined:
    Oct 17, 2009
    Posts:
    397
    I did it this way:

    Code (csharp):
    1. void ArrayGUI(SerializedObject obj,string name)
    2.     {
    3.         int no = obj.FindProperty(name + ".Array.size").intValue;
    4.         EditorGUI.indentLevel = 3;
    5.         int c = EditorGUILayout.IntField("Size", no);
    6.         if (c != no)
    7.             obj.FindProperty(name + ".Array.size").intValue = c;
    8.  
    9.         for (int i=0;i<no;i++) {
    10.             var prop = obj.FindProperty(string.Format("{0}.Array.data[{1}]", name, i));
    11.             EditorGUILayout.PropertyField(prop);
    12.         }
    13.     }
     
    vedram likes this.
  7. minimatt32

    minimatt32

    Joined:
    Jul 8, 2012
    Posts:
    1
    Thanks Jake that was just what I needed!

    For anyone else looking for a solution.
    I made a small adjustment as you could only increase the size of the array or list.
    The code below will increase and decrease the size.
    All I changed was the for loop and some text display.

    Code (csharp):
    1.  
    2. void ArrayGUI(SerializedObject obj, string name)
    3.     {
    4.         int size = obj.FindProperty(name + ".Array.size").intValue;
    5.  
    6.         int newSize = EditorGUILayout.IntField(name + " Size", size);
    7.        
    8.         if (newSize != size)
    9.             obj.FindProperty(name + ".Array.size").intValue = newSize;
    10.        
    11.         EditorGUI.indentLevel = 3;
    12.  
    13.         for (int i=0;i<newSize;i++)
    14.         {
    15.             var prop = obj.FindProperty(string.Format("{0}.Array.data[{1}]", name, i));
    16.             EditorGUILayout.PropertyField(prop);
    17.         }
    18.     }
     
    idbrii likes this.
  8. spectre1989

    spectre1989

    Joined:
    Oct 6, 2009
    Posts:
    107
    Necropost but whatever. You can also use:
    Code (csharp):
    1. public void InsertArrayElementAtIndex( int index );
    2. public bool MoveArrayElement( int srcIndex, int dstIndex );
    3. public SerializedProperty GetArrayElementAtIndex( int index );
    You can set the value in the array index by writing to the SerializedProperty returned by GetArrayElementAtIndex.
     
    vedram likes this.
  9. Anxo

    Anxo

    Joined:
    Jan 7, 2014
    Posts:
    11
    Sorry for the noob question but the way Guavaman wrote it in the GUI function I can understand as it would display it in the editor but you guys wrote your code in a separate function "ArrayGUI" How would you implement it so that it would show the array in the editor GUI?

     
  10. Anxo

    Anxo

    Joined:
    Jan 7, 2014
    Posts:
    11
    Ah it is brillant. So to use it one just has to do this.

    Code (csharp):
    1.  
    2. public void OnEnable(){
    3.         CVGO = GameObject.FindWithTag ("GameController");
    4.         so = new SerializedObject (CVGO.GetComponent (typeof(CharacterChanger)));
    5.         }
    6.  
    To set it up and then call it in the "OnInspectorGUI" like this.

    Code (csharp):
    1.  
    2. ArrayGUI (so, "characterNames");
    3.  
    4.  
    But now the problem I am having is that when I change the array in editor, it defaults back after I hit play. So my changes do not take effect.
     
  11. elhispano

    elhispano

    Joined:
    Jan 23, 2012
    Posts:
    52
    Small improvement using the SerializedProperty of the array var instead of strings.

    Code (CSharp):
    1.  
    2. void ArrayGUI(SerializedObject obj, SerializedProperty _property)
    3.     {
    4.         int size = _property.arraySize;
    5.      
    6.         int newSize = EditorGUILayout.IntField(_property.name + " Size", size);
    7.      
    8.         if (newSize != size)
    9.         {
    10.             _property.arraySize = newSize;
    11.         }
    12.      
    13.         EditorGUI.indentLevel = 3;
    14.      
    15.         for (int i=0;i<newSize;i++)
    16.         {
    17.             var prop = _property.GetArrayElementAtIndex(i);
    18.             EditorGUILayout.PropertyField(prop);
    19.         }
    20.     }
    21.  
     
  12. ThomLaurent

    ThomLaurent

    Joined:
    Sep 27, 2014
    Posts:
    4
    Why not simply get the `arraySize` by its `serializedProperty`? :

    Code (CSharp):
    1. void ArrayGUI (SerializedProperty property) {
    2.     SerializedProperty arraySizeProp = property.FindPropertyRelative("Array.size");
    3.     EditorGUILayout.PropertyField(arraySizeProp);
    4.  
    5.     EditorGUI.indentLevel ++;
    6.  
    7.     for (int i = 0; i < arraySizeProp.intValue; i++) {
    8.         EditorGUILayout.PropertyField(property.GetArrayElementAtIndex(i));
    9.     }
    10.  
    11.     EditorGUI.indentLevel --;
    12. }
     
  13. zero_null

    zero_null

    Joined:
    Mar 11, 2014
    Posts:
    157
  14. BerengerVolumiq

    BerengerVolumiq

    Joined:
    Feb 6, 2017
    Posts:
    10
    Another improvement, using a foldout to get the same look as other arrays

    Code (CSharp):
    1.  
    2.     private void DrawPropertyArray(SerializedProperty property, ref bool fold)
    3.     {
    4.         fold = EditorGUILayout.Foldout(fold, property.displayName);
    5.         if (fold)
    6.         {
    7.             SerializedProperty arraySizeProp = property.FindPropertyRelative("Array.size");
    8.             EditorGUILayout.PropertyField(arraySizeProp);
    9.  
    10.             EditorGUI.indentLevel++;
    11.  
    12.             for (int i = 0; i < arraySizeProp.intValue; i++)
    13.             {                EditorGUILayout.PropertyField(property.GetArrayElementAtIndex(i));
    14.             }
    15.  
    16.             EditorGUI.indentLevel--;
    17.         }
    18.     }
     
    radiatoryang and ftejada like this.