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. Dismiss Notice

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,716
    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,500
    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,500
    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,716
    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,500
    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:
    288
    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.