Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 13 Next capture
2021 2022 2023
1 capture
13 Jun 22 - 13 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
3
Question by Rafes · Apr 08, 2014 at 04:27 PM · editorlistserializedpropertymulti-edit

Using Generic List with serializedProperty Inspector

I haven't found a simple answer and example for working with generic lists. Say I have a class with public List things and I want to make my inspector work with multiple objects selected in the Editor.

[I'll update this example with any replies]

 public class CompoundTargetTrackerInspector : Editor
 {
     private SerializedProperty things
         
     private void OnEnable()
     {
         this.thingsProperty = this.serializedObject.FindProperty("things");
     }
 
     public override void OnInspectorGUI()
     {
         this.serializedObject.Update();
         
         // 1. How do I cast back to a generic list?
         List<Something> things = (List<Something>)this.thingsProperty;  // <--Doesn't work

         // 2. How do I iterate over the contents as "Something" types
         bool clicked = GUILayout.Button("Click Me");
         if (clicked)
         {
             for  or foreach ?
             {
                 ...???...

                 Debug.Log(thing.name);
             }
         }
         
         serializedObject.ApplyModifiedProperties();
     }
 }
Comment
Add comment
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

3 Replies

· Add your reply
  • Sort: 
avatar image
21
Best Answer

Answer by guavaman · Apr 09, 2014 at 09:08 AM

this.thingsProperty is not the List variable. Its returning a SerializedProperty object. You will have to get the contents of your list from the SerializedProperty object. Generally, SerializedProperty will store your data in one of its many data fields based on the type of the data. For example, bool data values are stored in the SerializedProperty.boolValue field. There are other types for ints, floats, etc. References to objects are stored in .objectReferenceValue So the basic way of working with SerializedProperties is to get the property, then get the value from the appropriate field depending on the data type you're expecting.

SerializedProperty stores arrays in a special way (and it just treats a list as an array). It doesn't just store the array as an object which you can easily grab and work with. Instead, it stores a bunch of data for the array in various fields. Basically, you test if the field is an array with isArray, then step through the fields with SerializedProperty.Next(true) to get to the parts of the array like the length, and finally the data fields. Once you get the data, you still have to know its type and access it through SerializedProperty.boolValue or whatever type it is.

Here is an example of one to get the data from an array/list in a SerialzedObject:

 [CustomEditor(typeof(MyTestType))]
 public class SOListtest : Editor {
 
     private SerializedProperty thingsProperty;
 
     private void OnEnable() {
         this.thingsProperty = this.serializedObject.FindProperty("_things");
     }
     
     public override void OnInspectorGUI() {
         this.serializedObject.Update();
 
         SerializedProperty sp = thingsProperty.Copy(); // copy so we don't iterate the original
 
         if(sp.isArray) {
             int arrayLength = 0;
 
             sp.Next(true); // skip generic field
             sp.Next(true); // advance to array size field
 
             // Get the array size
             arrayLength = sp.intValue;
 
             sp.Next(true); // advance to first array index
 
             // Write values to list
             List<int> values = new List<int>(arrayLength);
             int lastIndex = arrayLength - 1;
             for(int i = 0; i < arrayLength; i++) {
                 values.Add(sp.intValue); // copy the value to the list
                 if(i < lastIndex) sp.Next(false); // advance without drilling into children
             }
 
             // iterate over the list displaying the contents
             for(int i = 0; i < values.Count; i++) {
                 EditorGUILayout.LabelField(i + " = " + values[i]);
             }
         }
 
         this.DrawDefaultInspector(); // show the default inspector so we can set some values
     }
 }
 

And here is the MonoBehaviour that the inspector inspects:

 public class MyTestType : MonoBehaviour {
 
     public List<int> things {
         get {
             return _things;
         }
         set {
             _things = value;
         }
     }
 
     [SerializeField]
     private List<int> _things;
 
 }

FYI, there are also other methods you can use to access and work with arrays in SerializedProperty:

SerializedProperty.ClearArray, SerializedProperty.DeleteArrayElementAtIndex, SerializedProperty.GetArrayElementAtIndex, SerializedProperty.InsertArrayElementAtIndex, SerializedProperty.MoveArrayElement

Additionally, you can directly access the contents of an array via SerializedObject.FindProperty by passing it a string to the path using this syntax:

"fieldName.Array.data[0].fieldName"

Below are some methods I made to help with working with SerializedProperties. You can see how arrays are stored and how to iterate through all the properties to discover the structure.

   public static void LogProperties(SerializedObject so, bool includeChildren = true) {
       // Shows all the properties in the serialized object with name and type
       // You can use this to learn the structure
       so.Update();
       SerializedProperty propertyLogger = so.GetIterator();
       while(true) {
           Debug.Log("name = " + propertyLogger.name + " type = " + propertyLogger.type);
           if(!propertyLogger.Next(includeChildren)) break;
       }
   }

   // variablePath may have a structure like this:
   // "meshData.Array.data[0].vertexColors"
   // So it uses FindProperty to get data from a specific field in an object array
   public static void SetSerializedProperty(UnityEngine.Object obj, string variablePath, object variableValue) {
         SerializedObject so = new SerializedObject(obj);
         SerializedProperty sp = so.FindProperty(variablePath);
         if(sp == null) {
             Debug.Log("Error setting serialized property! Variable path: \"" + variablePath + "\" not found in object!");
             return;
         }
         
         so.Update(); // refresh the data
         
         //SerializedPropertyType type = sp.propertyType; // get the property type
         System.Type valueType = variableValue.GetType(); // get the type of the incoming value
         
         if(sp.isArray && valueType != typeof(string)) { // serialized property is an array, except string which is also an array
             // assume the incoming value is also an array
             if(!WriteSerializedArray(sp, variableValue)) return; // write the array
         } else { // not an array
             if(!WriteSerialzedProperty(sp, variableValue)) return; // write the value to the property
         }
         
         so.ApplyModifiedProperties(); // apply the changes
     }
         
     private static bool WriteSerialzedProperty(SerializedProperty sp, object variableValue) {
         // Type the property and fill with new value
         SerializedPropertyType type = sp.propertyType; // get the property type
         
         if(type == SerializedPropertyType.Integer) {
             int it = (int)variableValue;
             if(sp.intValue != it) {
                 sp.intValue = it;
             }
         } else if(type == SerializedPropertyType.Boolean) {
             bool b = (bool)variableValue;
             if(sp.boolValue != b) {
                 sp.boolValue = b;
             }
         } else if(type == SerializedPropertyType.Float) {
             float f = (float)variableValue;
             if(sp.floatValue != f) {
                 sp.floatValue = f;
             }
         } else if(type == SerializedPropertyType.String) {
             string s = (string)variableValue;
             if(sp.stringValue != s) {
                 sp.stringValue = s;
             }
         } else if(type == SerializedPropertyType.Color) {
             Color c = (Color)variableValue;
             if(sp.colorValue != c) {
                 sp.colorValue = c;
             }
         } else if(type == SerializedPropertyType.ObjectReference) {
             Object o = (Object)variableValue;
             if(sp.objectReferenceValue != o) {
                 sp.objectReferenceValue = o;
             }
         } else if(type == SerializedPropertyType.LayerMask) {
             int lm = (int)variableValue;
             if(sp.intValue != lm) {
                 sp.intValue = lm;
             }
         } else if(type == SerializedPropertyType.Enum) {
             int en = (int)variableValue;
             if(sp.enumValueIndex != en) {
                 sp.enumValueIndex = en;
             }
         } else if(type == SerializedPropertyType.Vector2) {
             Vector2 v2 = (Vector2)variableValue;
             if(sp.vector2Value != v2) {
                 sp.vector2Value = v2;
             }
         } else if(type == SerializedPropertyType.Vector3) {
             Vector3 v3 = (Vector3)variableValue;
             if(sp.vector3Value != v3) {
                 sp.vector3Value = v3;
             }
         } else if(type == SerializedPropertyType.Rect) {
             Rect r = (Rect)variableValue;
             if(sp.rectValue != r) {
                 sp.rectValue = r;
             }
         } else if(type == SerializedPropertyType.ArraySize) {
             int aSize = (int)variableValue;
             if(sp.intValue != aSize) {
                 sp.intValue = aSize;
             }
         } else if(type == SerializedPropertyType.Character) {
             int ch = (int)variableValue;
             if(sp.intValue != ch) {
                 sp.intValue = ch;
             }
         } else if(type == SerializedPropertyType.AnimationCurve) {
             AnimationCurve ac = (AnimationCurve)variableValue;
             if(sp.animationCurveValue != ac) {
                 sp.animationCurveValue = ac;
             }
         } else if(type == SerializedPropertyType.Bounds) {
             Bounds bounds = (Bounds)variableValue;
             if(sp.boundsValue != bounds) {
                 sp.boundsValue = bounds;
             }
         } else {
             Debug.Log("Unsupported SerializedPropertyType \"" + type.ToString() + " encoutered!");
             return false;
         }
         return true;
     }
     
     private static bool WriteSerializedArray(SerializedProperty sp, object arrayObject) {
         System.Array[] array = (System.Array[])arrayObject; // cast to array
         
         sp.Next(true); // skip generic field
         sp.Next(true); // advance to array size field
         
         // Set the array size
         if(!WriteSerialzedProperty(sp, array.Length)) return false;
         
         sp.Next(true); // advance to first array index
         
         // Write values to array
         int lastIndex = array.Length - 1;
         for(int i = 0; i < array.Length; i++) {
             if(!WriteSerialzedProperty(sp, array[i])) return false; // write the value to the property
             if(i < lastIndex) sp.Next(false); // advance without drilling into children            }
         
         return true;
     }

     // A way to see everything a SerializedProperty object contains in case you don't
     // know what type is stored.
     public static void LogAllValues(SerializedProperty serializedProperty) {
         Debug.Log("PROPERTY: name = " + serializedProperty.name + " type = " + serializedProperty.type);
         Debug.Log("animationCurveValue = " + serializedProperty.animationCurveValue);
         Debug.Log("arraySize = " + serializedProperty.arraySize);
         Debug.Log("boolValue = " + serializedProperty.boolValue);
         Debug.Log("boundsValue = " + serializedProperty.boundsValue);
         Debug.Log("colorValue = " + serializedProperty.colorValue);
         Debug.Log("depth = " + serializedProperty.depth);
         Debug.Log("editable = " + serializedProperty.editable);
         Debug.Log("enumNames = " + serializedProperty.enumNames);
         Debug.Log("enumValueIndex = " + serializedProperty.enumValueIndex);
         Debug.Log("floatValue = " + serializedProperty.floatValue);
         Debug.Log("hasChildren = " + serializedProperty.hasChildren);
         Debug.Log("hasMultipleDifferentValues = " + serializedProperty.hasMultipleDifferentValues);
         Debug.Log("hasVisibleChildren = " + serializedProperty.hasVisibleChildren);
         Debug.Log("intValue = " + serializedProperty.intValue);
         Debug.Log("isAnimated = " + serializedProperty.isAnimated);
         Debug.Log("isArray = " + serializedProperty.isArray);
         Debug.Log("isExpanded = " + serializedProperty.isExpanded);
         Debug.Log("isInstantiatedPrefab = " + serializedProperty.isInstantiatedPrefab);
         Debug.Log("name = " + serializedProperty.name);
         Debug.Log("objectReferenceInstanceIDValue = " + serializedProperty.objectReferenceInstanceIDValue);
         Debug.Log("objectReferenceValue = " + serializedProperty.objectReferenceValue);
         Debug.Log("prefabOverride = " + serializedProperty.prefabOverride);
         Debug.Log("propertyPath = " + serializedProperty.propertyPath);
         Debug.Log("propertyType = " + serializedProperty.propertyType);
         Debug.Log("quaternionValue = " + serializedProperty.quaternionValue);
         Debug.Log("rectValue = " + serializedProperty.rectValue);
         Debug.Log("serializedObject = " + serializedProperty.serializedObject);
         Debug.Log("stringValue = " + serializedProperty.stringValue);
         Debug.Log("tooltip = " + serializedProperty.tooltip);
         Debug.Log("type = " + serializedProperty.type);
         Debug.Log("vector2Value = " + serializedProperty.vector2Value);
         Debug.Log("vector3Value = " + serializedProperty.vector3Value);
     }
 


Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Rafes · Apr 29, 2014 at 02:42 PM 0
Share

This information is extremely valuable. Could I request one addition? It would be very helpful to see a function that takes a SerializedProperty (representing a List, so an Array) that returns a List. Or maybe it would be easier to populate a List referenced so the signature includes a List and just does a Clear() and AddRange() perhaps? WDYT?

I'll edit my question to be more explicit too. When I posted it I wasn't even sure what I was asking. After refitting most of our inspectors I have a better grasp of things, but I still couldn't figure out the Array internal workings.

avatar image
4

Answer by numberkruncher · Apr 12, 2014 at 10:27 PM

I do not fully understand your question, but I have put together a few examples which I thought might be helpful for you:

  • The default list editor with multi-editing and undo support.

  • Modifying the data directly using Undo.RecordObjects.

  • The default text field with multi-editing and undo support.

  • Converting existing editor GUI controls into multi-editing versions.

Also, if you really need to you can enumerate arrays using SerializedProperty:

  • yourProp.arraySize

  • yourProp.GetArrayElementAtIndex

  • yourProp.InsertArrayElementAtIndex

  • yourProp.DeleteArrayElementAtIndex

  • yourProp.MoveArrayElement

    Example Behaviour:

    using UnityEngine;

    using System.Collections.Generic;

    public class Example : MonoBehaviour {

       public List<string> things = new List<string>();
         public string label1 = "";
         public string label2 = "";
     
     }
    
    

    Example Editor:

    using UnityEngine; using UnityEditor;

    [CustomEditor(typeof(Example)), CanEditMultipleObjects] public class ExampleEditor : Editor {

       private SerializedProperty thingsProp;
         private SerializedProperty label1Prop;
         private SerializedProperty label2Prop;
     
         private void OnEnable() {
             thingsProp = serializedObject.FindProperty("things");
             label1Prop = serializedObject.FindProperty("label1");
             label2Prop = serializedObject.FindProperty("label2");
         }
     
         public override void OnInspectorGUI() {
             serializedObject.Update();
     
             // Editable list of things.
             EditorGUILayout.PropertyField(thingsProp, new GUIContent("Things"), true);
     
             if (GUILayout.Button("Append Item")) {
                 // Undo/redo support for changes made to behaviour fields.
                 Undo.RecordObjects(targets, "Append Item");
     
                 // You can loop through list as normal here!
                 foreach (Example target in targets) {
                     target.things.Add("Appended!");
    
                     // You can use either `foreach` or `for`.
                     foreach (var thing in target.things)
                         Debug.Log(thing);
    
                     // Any changes made to the array will be undoable.
                 }
             }
     
             // Multi-object editable label (the easy way):
             EditorGUILayout.PropertyField(label1Prop, new GUIContent("Label 1"));
     
             // Multi-object editable label (the manual way):
             EditorGUI.showMixedValue = label2Prop.hasMultipleDifferentValues;
             EditorGUI.BeginChangeCheck();
             string newValue = EditorGUILayout.TextField("Label 2", label2Prop.stringValue);
             if (EditorGUI.EndChangeCheck())
                 label2Prop.stringValue = newValue;
             EditorGUI.showMixedValue = false;
     
             serializedObject.ApplyModifiedProperties();
         }
     
     }
    
    

    Aside:

You might be interested in my reorderable list control. Whilst I haven't tested it for multi-object editing, I suspect that it could be used (perhaps with some minor alterations):

https://bitbucket.org/rotorz/reorderable-list-editor-field-for-unity

I hope that this helps a little anyhow. Feel free to follow up with comments :)

Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Rafes · Apr 28, 2014 at 03:51 PM 0
Share

Thanks for the effort but the question is actually about multi-object editing of custom List fields. Which after learning a lot more really means getting in and out of the SerializedProperty to handle changes. In other words, using a serialized property for display and then converting data back to a generic List for handling (reading the changes) and perhaps even setting further changes back to the SerializedProperty (writing the changes).

avatar image
3

Answer by Fornoreason1000 · Jan 17, 2015 at 11:08 AM

For those who are interested there is an array handler class in UnityEditorInternal namespace. However there is ZERO docs on it , mostly because UnityDevs don't want us to use it or something.

Inside the namespace there is a class called ReOrderableList which handles your array for example

 using System.Collections.Generic;
 using UnityEngine
 [System.Serializable]
 class ClassThatHasAList {
     [SerializeField]
     List<int> myIntList = new List<int>();
 }


We've already seen how hard using editor script when dealing with Generic Types, its made worse with the way their handled in Serialized Property.

   using UnityEngine;
     using UnityEditor;
     using UnityEditorInternal; 
     using System.Collections;
     using System.Collection.Generic;
     
     [CustomEditor("ClassThatHasAList")]
     class ListIntEditor : Editor {
     
     ReorderableList list;
     
     public void OnEnable() {
 
     list = new ReorderableList(serializedObject, 
                     serializedObject.FindProperty("myIntList"),
                     true,
                     true,
                     true,
                     true);
         
     }   
     
     public override void OnInspectorGUI () {
     
     list.DoLayoutList();
     
     }
 }

This should Give You something Similar you see in the Event Trigger Component, It also has several call backs you can use Lambda expressions to customize how it is drawn and behaves.

I found some more info on them here: http://va.lent.in/unity-make-your-lists-functional-with-reorderablelist/

Hope it Helps

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

26 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

A node in a childnode? 1 Answer

Editor: How to do PropertyField for List elements? 4 Answers

Null SerializedProperty SerializedObject upon Removal from list 1 Answer

Display Custom Inspectors for each class in a List<> 1 Answer

Help with array/lists manipulation in Editor script 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges