Search Unity

Serializing property by custom editor does not work

Discussion in 'Immediate Mode GUI (IMGUI)' started by maltakereuz, Sep 12, 2017.

  1. maltakereuz

    maltakereuz

    Joined:
    Mar 29, 2015
    Posts:
    53
    I made a custom editor for my component, with some properties, using IMGUI. It works fine, but if I change some values by default editor, they will be painted bold in inspector and will be serialized. If i using my custom editor, changes will be lost and reverted to prefab's defaults. How can i have both at same time: serializing and custom editor?

    Here is my Compoment:

    Code (CSharp):
    1. [System.Serializable]
    2. public class ArrayModifier : MonoBehaviour {
    3.  
    4.     // i have tryed public, it does not solves serializing problem
    5.     [SerializeField]
    6.     int repeat = 1;
    7.     [SerializeField]
    8.     float offsetX = 3;
    9.     [SerializeField]
    10.     float offsetY = 0;
    11.     [SerializeField]
    12.     bool rotateOffset = false;
    13.     ...
    14.  
    15.  
    16.     public int Repeat {
    17.         get {
    18.             return repeat;
    19.         }
    20.         set {
    21.             bool needsRefresh = !MathLvichka.Eq(value, repeat);
    22.             repeat = value;
    23.  
    24.             if (needsRefresh) {
    25.                 Refresh();
    26.             }
    27.         }
    28.     }
    29.  
    30.     public float OffsetX {
    31.         get {
    32.             return offsetX;
    33.         }
    34.         set {
    35.             bool needsRefresh = !MathLvichka.Eq(value, offsetX);
    36.             offsetX = value;
    37.  
    38.             if (needsRefresh) {
    39.                 Refresh();
    40.             }
    41.         }
    42.     }
    43.  
    44.     public float OffsetY {
    45.         get {
    46.             return offsetY;
    47.         }
    48.         set {
    49.             bool needsRefresh = !MathLvichka.Eq(value, offsetY);
    50.             offsetY = value;
    51.  
    52.             if (needsRefresh) {
    53.                 Refresh();
    54.             }
    55.         }
    56.     }
    57.    
    58.     etc.
    59.     ...
    60.  
    61. }

    Here is my Custom Editor:
    Code (CSharp):
    1.  
    2. [CustomEditor(typeof(ArrayModifier))]
    3. public class ArrayModifierEditor : Editor {
    4.     public override void OnInspectorGUI() {
    5.         ArrayModifier arr = (ArrayModifier) target;
    6.         DrawDefaultInspector();
    7.      
    8.         EditorGUILayout.TextArea("", GUI.skin.horizontalSlider);
    9.      
    10.         arr.Repeat = EditorGUILayout.IntField("Count", arr.Repeat);
    11.         arr.OffsetX = EditorGUILayout.FloatField("Offset X", arr.OffsetX);
    12.         arr.OffsetY = EditorGUILayout.FloatField("Offset Y", arr.OffsetY);
    13.         arr.RotateOffset = EditorGUILayout.Toggle("Rotate Offset", arr.RotateOffset);
    14.         ...
    15.  
    16.         if (GUILayout.Button("Refresh")) {
    17.             arr.Refresh();
    18.         }
    19.  
    20.     }
    21. }
    upd: I have searched through forum for topics like this, the solutions for properties are custom editor and [SerializeField]. Hence I use both, it is probably something wrong with my custom editor.
     
  2. PsyKaw

    PsyKaw

    Joined:
    Aug 16, 2012
    Posts:
    102
    You should look at this link: https://docs.unity3d.com/Manual/editor-CustomEditors.html.

    You must use SerializedProperty instead of target. It will be something like that:

    Code (CSharp):
    1. [CustomEditor(typeof(ArrayModifier))]
    2. [CanEditMultipleObjects]
    3. public class ArrayModifierEditor : Editor
    4. {
    5.     SerializedProperty repeatProperty;
    6.  
    7.     void OnEnable()
    8.     {
    9.         repeatProperty = serializedObject.FindProperty("repeat");
    10.     }
    11.  
    12.     public override void OnInspectorGUI()
    13.     {
    14.         serializedObject.Update();
    15.         EditorGUILayout.PropertyField(repeatProperty);
    16.         serializedObject.ApplyModifiedProperties();
    17.     }
    18. }
     
  3. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,282
    You need to tell Unity that the object will be changed, you do this with Undo.RecordObject
    Also you should check in your properties for the same value being assigned else you will do a refresh when nothing has changed.

    Code (CSharp):
    1.  
    2. using UnityEditor;
    3. using UnityEngine;
    4.  
    5. [CustomEditor(typeof(ArrayModifier))]
    6. public class ArrayModifierEditor : Editor
    7. {
    8.     public override void OnInspectorGUI()
    9.     {
    10.         ArrayModifier arr = (ArrayModifier)target;
    11.         DrawDefaultInspector();
    12.  
    13.         EditorGUILayout.TextArea("", GUI.skin.horizontalSlider);
    14.  
    15.         EditorGUI.BeginChangeCheck();
    16.         int repeat = EditorGUILayout.IntField("Count", arr.Repeat);
    17.         float offsetX = EditorGUILayout.FloatField("Offset X", arr.OffsetX);
    18.         float offSetY = EditorGUILayout.FloatField("Offset Y", arr.OffsetY);
    19.  
    20.         if (EditorGUI.EndChangeCheck())
    21.         {
    22.             Undo.RecordObject(target, "Changed Array Modifier");
    23.             arr.Repeat = repeat;
    24.             arr.OffsetX = offsetX;
    25.             arr.OffsetY = offSetY;
    26.         }
    27.  
    28.         if (GUILayout.Button("Refresh"))
    29.         {
    30.             //arr.Refresh();
    31.         }
    32.  
    33.     }
    34. }

    However its much better to use SerialiedProperties instead as they manage all the undo stuff for you.

    Code (CSharp):
    1.  
    2. using UnityEditor;
    3. using UnityEngine;
    4.  
    5. [CustomEditor(typeof(ArrayModifier))]
    6. public class ArrayModifierEditor : Editor
    7. {
    8.     SerializedProperty m_Repeat;
    9.     SerializedProperty m_OffsetX;
    10.  
    11.     void OnEnable()
    12.     {
    13.         m_Repeat = serializedObject.FindProperty("repeat");
    14.         m_OffsetX = serializedObject.FindProperty("offsetX");
    15.     }
    16.  
    17.     public override void OnInspectorGUI()
    18.     {
    19.         serializedObject.Update();
    20.  
    21.         EditorGUI.BeginChangeCheck();
    22.         EditorGUILayout.PropertyField(m_Repeat);
    23.         EditorGUILayout.PropertyField(m_OffsetX);
    24.         if (EditorGUI.EndChangeCheck())
    25.         {
    26.            // arr.Refresh();
    27.         }
    28.  
    29.         serializedObject.ApplyModifiedProperties();
    30.     }
    31. }
     
    aybeone and PsyKaw like this.
  4. Johannski

    Johannski

    Joined:
    Jan 25, 2014
    Posts:
    826
    I guess you could use EditorGUI.BeginChangeCheck and EditorGUI.EndChageCheck and in the changed block Undo.RecordObject. The proper way to do it however is different: You should work with the serializedObject like in the documentation:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. [CustomEditor(typeof(LookAtPoint))]
    4. [CanEditMultipleObjects]
    5. public class LookAtPointEditor : Editor
    6. {
    7.     SerializedProperty lookAtPoint;
    8.  
    9.     void OnEnable()
    10.     {
    11.         lookAtPoint = serializedObject.FindProperty("lookAtPoint");
    12.     }
    13.     public override void OnInspectorGUI()
    14.     {
    15.         serializedObject.Update();
    16.         EditorGUILayout.PropertyField(lookAtPoint);
    17.         serializedObject.ApplyModifiedProperties();
    18.     }
    19. }
    This way you can edit multiple objects without having to code any extra handling.

    €dit: Dang it really? I should refresh the page after having the thread open for too long ^^
     
  5. maltakereuz

    maltakereuz

    Joined:
    Mar 29, 2015
    Posts:
    53
    Thank you very much, SerializedProperty solution works just great. Except multiple object editing. Somehow it works only for the last object in my case. Here is my Editor code now:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections.Generic;
    3. using UnityEditor;
    4.  
    5. namespace Lvichki {
    6.     [CustomEditor(typeof(ArrayModifier))]
    7.     [CanEditMultipleObjects]
    8.     public class ArrayModifierEditor : Editor {
    9.      
    10.         List<SerializedProperty> props = new List<SerializedProperty>();
    11.  
    12.         static readonly List<string> prop_names = new List<string> {
    13.             "repeat",
    14.             "offsetX",
    15.             "offsetY",
    16.             "rotateOffset",
    17.             "orderInLayerProp",
    18.             "relativeOffset",
    19.             "noAutoRefresh",
    20.             "cropLastObject",
    21.             "keepPrefabs",
    22.         };
    23.  
    24.         void OnEnable() {
    25.             props.Clear();
    26.  
    27.             foreach (var prop_name in prop_names) {
    28.                 var prop = serializedObject.FindProperty(prop_name);
    29.                 if (prop != null) {
    30.                     props.Add(prop);
    31.                 } else {
    32.                     LogLvichka.Error("Prop " + prop_name + " is not found.");
    33.                 }
    34.  
    35.             }
    36.          
    37.         }
    38.  
    39.         public override void OnInspectorGUI() {
    40.             ArrayModifier arr = (ArrayModifier)target;
    41.  
    42.             serializedObject.Update();
    43.  
    44.             EditorGUI.BeginChangeCheck();
    45.  
    46.             foreach (var prop in props) {
    47.                 EditorGUILayout.PropertyField(prop);
    48.             }
    49.  
    50.             serializedObject.ApplyModifiedProperties();
    51.  
    52.             if (EditorGUI.EndChangeCheck()) {
    53.                 arr.Refresh();
    54.             }
    55.  
    56.             EditorGUILayout.TextArea("", GUI.skin.horizontalSlider);
    57.  
    58.             if (GUILayout.Button("Refresh")) {
    59.                 arr.Refresh();
    60.             }
    61.             if (GUILayout.Button("Delete")) {
    62.                 arr.DestroyArrayClones();
    63.             }
    64.             if (GUILayout.Button("Create on same level")) {
    65.                 LogLvichka.Error("Create on same level ist ausgebaut. Neu implementieren, einfach folder nach oben rauspacken, 2 code paths ist zu verwirrend");
    66.             }
    67.  
    68.         }
    69.     }
    70. }
    71.  
    P.S. One thing i found a little bit strange, that serializedObject.FindProperty(prop_name); uses lower-case property name. So to find property "OffsetX" i need to use "offsetX". I know it's breaking of contract, but pure technically i could have two props on my object: Hello and hello. What's then?

    Nothing about this in docs: https://docs.unity3d.com/ScriptReference/SerializedObject.FindProperty.html
     
  6. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,282
    The property name needs to have the same case as the variable. Having 2 properties with the same name but different casing works. To support multi edit you need to add the attribute CanEditMultipleObjects.
    This will work fine with PropertyFields, if you want to use none property versions then you need to manage the multi edit yourself like so https://docs.unity3d.com/ScriptReference/EditorGUI-showMixedValue.html
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. [System.Serializable]
    4. public class Example : MonoBehaviour
    5. {
    6.     public int value1;
    7.     public float Value1;
    8. }
    Code (CSharp):
    1. using UnityEditor;
    2.  
    3. [CanEditMultipleObjects]
    4. [CustomEditor(typeof(Example))]
    5. public class ExampleEditor : Editor
    6. {
    7.     SerializedProperty m_ValueInt;
    8.     SerializedProperty m_ValueFloat;
    9.  
    10.     void OnEnable()
    11.     {
    12.         m_ValueInt = serializedObject.FindProperty("value1");
    13.         m_ValueFloat = serializedObject.FindProperty("Value1");
    14.     }
    15.  
    16.     public override void OnInspectorGUI()
    17.     {
    18.         serializedObject.Update();
    19.  
    20.         EditorGUILayout.PropertyField(m_ValueInt);
    21.         EditorGUILayout.PropertyField(m_ValueFloat);
    22.         serializedObject.ApplyModifiedProperties();
    23.     }
    24. }