Search Unity

Serialize lists that save values within the editor

Discussion in 'Scripting' started by djfunkey, Jul 22, 2014.

  1. djfunkey

    djfunkey

    Joined:
    Jul 16, 2012
    Posts:
    201
    So I am trying to serialize a list object so the values within the custom editor stay the same the whole time. But I am having trouble working the logistics of it out :(

    Here is the base code:
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6. [System.Serializable]
    7. public class ObjectiveClass {
    8.     public string title = "";
    9.     public string description = "";
    10.     public bool show = true;
    11. }
    12.  
    13. [AddComponentMenu("Objective Manager"), ExecuteInEditMode]
    14. public class ObjectiveManager : MonoBehaviour {
    15.  
    16.     [SerializeField]
    17.     public List<ObjectiveClass>oClass = new List<ObjectiveClass>();
    18. }
    19.  
    Here is the editor code:
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using System.Collections;
    5.  
    6. [CustomEditor(typeof(ObjectiveManager))]
    7. public class ObjectiveManager_Editor : Editor {
    8.  
    9.     ObjectiveManager om;
    10.  
    11.     void OnEnable() {
    12.         om = target as ObjectiveManager;
    13.     }
    14.  
    15.     public override void OnInspectorGUI() {
    16.         //UI
    17.         foreach (ObjectiveClass oc in om.oClass) {
    18.             oc.show = EditorGUILayout.Foldout(oc.show, oc.title.ToString());
    19.             if (co.show) {
    20.                co.title = EditorGUILayout.TextField("Title", co.title);
    21.                co.description = EditorGUILayout.TextField("description", co.description);
    22.             }
    23.         }
    24.     }
    25. }
    26.  
     
  2. smitchell

    smitchell

    Joined:
    Mar 12, 2012
    Posts:
    702
    I always use this:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using System;
    4. using System.Collections;
    5.  
    6. public static class EditorList {
    7.  
    8.     [Flags]
    9.     public enum EditorListOption {
    10.         None = 0,
    11.         ListSize = 1,
    12.         ListLabel = 2,
    13.         Default = ListSize | ListLabel
    14.     }
    15.  
    16.     public static void Show (SerializedProperty list, EditorListOption options = EditorListOption.Default) {
    17.         bool showListLabel = (options & EditorListOption.ListLabel) != 0, showListSize = (options & EditorListOption.ListSize) != 0;
    18.      
    19.         if (showListLabel) {
    20.             EditorGUILayout.PropertyField(list);
    21.             EditorGUI.indentLevel += 1;
    22.         }
    23.  
    24.         if (!showListLabel || list.isExpanded) {
    25.             if (showListSize)
    26.                 EditorGUILayout.PropertyField(list.FindPropertyRelative("Array.size"));
    27.  
    28.             for (int i = 0; i < list.arraySize; i++)
    29.                 EditorGUILayout.PropertyField(list.GetArrayElementAtIndex(i));
    30.         }
    31.  
    32.         if (showListLabel)
    33.             EditorGUI.indentLevel -= 1;
    34.     }
    35. }
    Then just in your inspector GUI
    Code (CSharp):
    1. serializedObject.Update();
    2. EditorList.Show(serializedObject.FindProperty("oClass"));
    3. serializedObject.ApplyModifiedProperties();
     
  3. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    953
    SerializedProperty is generally the way to go since you get undo/redo for free!

    Otherwise you will need to explicitly mark your object as dirty:
    Code (csharp):
    1. public override void OnInspectorGUI() {
    2.     EditorGUI.BeginChangeCheck();
    3.  
    4.     //UI
    5.     foreach (ObjectiveClass oc in om.oClass) {
    6.  
    7.         oc.show = EditorGUILayout.Foldout(oc.show, oc.title.ToString());
    8.         if (co.show) {
    9.            co.title = EditorGUILayout.TextField("Title", co.title);
    10.            co.description = EditorGUILayout.TextField("description", co.description);
    11.         }
    12.     }
    13.  
    14.     if (EditorGUI.EndChangeCheck())
    15.         EditorUtility.SetDirty(target);
    16. }
    You might find my reorderable list control useful:
    https://bitbucket.org/rotorz/reorderable-list-editor-field-for-unity

    With this you could define a custom property drawer for "ObjectiveClass" and then just use the SerializedObject overload of the reorderable list:
    Code (csharp):
    1. [CustomPropertyDrawer(typeof(ObjectiveClass))]
    2. public class ObjectiveClassPropertyDrawer : PropertyDrawer {
    3.     // See Unity docs...
    4. }
    5.  
    6. private SerializedProperty listProperty;
    7.  
    8. private void OnEnable() {
    9.     listProperty = serializedObject.FindProperty("oClass");
    10. }
    11.  
    12. public override void OnInspectorGUI() {
    13.     serializedObject.Update();
    14.  
    15.     ReorderableListGUI.Title("Objective List");
    16.     ReorderableListGUI.ListField(listProperty);
    17.  
    18.     serializedObject.ApplyModifiedProperties();
    19. }
    Above code not tested, just proof of concept.
     
    Last edited: Jul 22, 2014
  4. djfunkey

    djfunkey

    Joined:
    Jul 16, 2012
    Posts:
    201
    A problem relating to this which I am also having is an error that pops up
     
  5. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    953
    There are several possible locations for this error... you have the line number, 18 which will pinpoint the exact culprit for you.
     
  6. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Or I could offer a shameless plug for the Advanced Inspector... So you would never need to write a custom editor again. :p
     
  7. djfunkey

    djfunkey

    Joined:
    Jul 16, 2012
    Posts:
    201
    The culprit line is on line 18 as you said. Its the same line of code as the one in the original post above I made above.
     
  8. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    953
    Okay, so stick the following line above the culprit to confirm which reference is null:
    Code (csharp):
    1. if (oc == null) Debug.Log("oc is null!");
     
  9. leo-carneiro

    leo-carneiro

    Unity Technologies

    Joined:
    Sep 1, 2011
    Posts:
    49
    I recommend removing the CustomEditor for now.
    Make sure your Serialization is working properly, from the code above it looks ok.
    After that you start on the CustomEditor. As @numberkruncher mentioned, you propabbly want to use SerializedProperties to write your CustomEditor since it will give you Undo functionality and also multi-object selection.
     
  10. djfunkey

    djfunkey

    Joined:
    Jul 16, 2012
    Posts:
    201
    I have searched for a few days over the internet on serialization inside an editor script, that incorporates foreach loops. But I haven't really found anything, and the stuff I do find is scarcely documented. To be honest i dont really know how to go about using seralization within the script, I have just never needed to use it in an editor script. So the basic understanding I have come across of what I need to do is...

    Use SerializedObject on the om.oClass
    then Use SerializedProperty for all the values inside om.oClass?
     
  11. IsGreen

    IsGreen

    Joined:
    Jan 17, 2014
    Posts:
    206
    List, Hashtable and Dictionary .NET objects are dynamic memory elements.

    To serialize in Unity Inspector, you need a Array (fixed length element).

    I created a Table and ListTable Class: [CodeSource]Table and ListTable editable from Inspector, similar to Dictionary.

    These classes use array elements to create a list or tables. You can create database game , or list of objects from Inspector before running a game, or view objects list at runtime.
     
  12. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    Unity explicitly supports serialization of Lists since 2.6. Check out the documentation for [SerializeField] and also this official blog post.
     
  13. IsGreen

    IsGreen

    Joined:
    Jan 17, 2014
    Posts:
    206
    You've tried before?

    You can view at runtime, but not modify in edit mode from Inspector.

    You need create a list from a array, for editing from the inspector.
     
  14. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    I use Lists all the time, and modified one from the Inspector in Edit mode literally just a moment ago. As far as the Inspector is concerned they're essentially a drop-in replacement for an array.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6. public class ListTester : MonoBehaviour {
    7.  
    8.    [SerializeField]
    9.    private List<int> testList;
    10.  
    11. }
    12.  
    If you drop that on a GameObject, does it work? I wonder if you're not running into issues with one of the other restrictions imposed by Unity's serialization system.
     
  15. IsGreen

    IsGreen

    Joined:
    Jan 17, 2014
    Posts:
    206
    I pay first, so that your code will work.

    For free I do not think nobody works.

    Luck.
     
  16. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Lists works just fine. They are serialized as if they were arrays.

    And mine look cooler than yours. :p
     
  17. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    Not sure what you mean here, but I wrote that snippet on my home PC where I still use Unity Free.
     
  18. DerDicke

    DerDicke

    Joined:
    Jun 30, 2015
    Posts:
    292
    Had the same problem, which lead to days of coding ugly code and bad design.
    Now I revisited the problem and pieces magically fell into place.
    This works like a charm:

    In your script:
    Code (CSharp):
    1.  
    2. [System.Serializable]
    3. public class GO
    4. {
    5.     public string key; // key and name
    6.     public GameObject go;
    7. }
    8.  
    9. [SerializeField]
    10. List<GO> gos = new List<GO>();
    In your Editor:
    Code (CSharp):
    1.     int lineHeight = 16;
    2.     public override void OnInspectorGUI()
    3.     {
    4.         serializedObject.Update();
    5.         BlackBoard bb = (BlackBoard)target;
    6.         //------------------------------- gos----------------------------------//
    7.         var gosProp = serializedObject.FindProperty("gos"); // the list
    8.  
    9.         GUILayout.BeginHorizontal();
    10.         EditorGUILayout.LabelField("Add New GameObj");
    11.         if (GUILayout.Button("+", GUILayout.Height(lineHeight), GUILayout.Width(20)))
    12.         {
    13.             bb.AddNew();
    14.         }
    15.         GUILayout.EndHorizontal();
    16.  
    17.         ShowGOList(gosProp);
    18.         serializedObject.ApplyModifiedProperties();
    19.     }
    20.  
    21.     void ShowGOList(SerializedProperty list)
    22.     {
    23.         //
    24.         for (int i = 0; i < list.arraySize; i++)
    25.         {
    26.             EditorGUILayout.BeginHorizontal();
    27.  
    28.             var goProp = list.GetArrayElementAtIndex(i);
    29.             var keyProp = goProp.FindPropertyRelative("key");
    30.             var gogoProp = goProp.FindPropertyRelative("go");
    31.  
    32.             keyProp.stringValue = EditorGUILayout.TextField(keyProp.stringValue);
    33.  
    34.             gogoProp.objectReferenceValue = EditorGUILayout.ObjectField(gogoProp.objectReferenceValue, typeof(GameObject), true) as GameObject;
    35.  
    36.             if (GUILayout.Button("-", GUILayout.Height(lineHeight), GUILayout.Width(20)))
    37.             {
    38.                 list.DeleteArrayElementAtIndex(i);
    39.                 i -= 1;
    40.             }
    41.  
    42.             EditorGUILayout.EndHorizontal();
    43.  
    44.         }
    45.     }
    46.  
    Looks like (I left the 'More Information' out of code):
    Capture.JPG

    Keypoints are:
    In Editor, treat your List<> as if it is an Array (e.g. use list.DeleteArrayElementAtIndex(i);).
    Don't change properties in List directly (aside from creating new Objs). Use FindPropertyRelative() instead and change the property.
     
    Underliinez likes this.