Search Unity

CustomEditor Accessing Private Fields --> Each Field Appears Repeated

Discussion in 'Scripting' started by DomingoGD, Jan 27, 2021.

  1. DomingoGD

    DomingoGD

    Joined:
    Jan 28, 2018
    Posts:
    16
    Hello!
    (Project files attached! Please check!) :)

    I have been trying to create a custom inspector (FancyFloatEditor) to be shown every time a object of a custom class (in this case FancyFloat Class) is declared as public in Monobehaviour Scripts (ex: FancyFloat_Test).

    The goal is that at some point I want to include private fields that only the inspector can read. Ideally I would make the inspector class friend of the FancyFloat class, but that doesn't exist in C#.

    So for what I see, we can still get private fields from inspector, but something rare happens to me, fields get repeated (found several time by inspector).




    upload_2021-1-27_16-51-17.png
    In the case of Public Field all works well:
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using System.Linq;
    3. using System.Reflection;
    4. using UnityEditor;
    5. using UnityEngine;
    6.  
    7. [CanEditMultipleObjects]
    8. [CustomEditor(typeof(UnityEngine.Object), true)]
    9. public class FancyFloatEditor : Editor
    10. {
    11.     public override void OnInspectorGUI()
    12.     {
    13.         serializedObject.Update();
    14.         SerializedProperty iterator = serializedObject.GetIterator();
    15.         bool enterChildren = true;
    16.         while (iterator.NextVisible(enterChildren))
    17.         {
    18.             if (iterator.type == "FancyFloat")
    19.             {
    20.                 object obj = iterator.serializedObject.targetObject;
    21.                 System.Type type = obj.GetType();
    22.                 FieldInfo f = type.GetField(iterator.name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
    23.                 if (f != null)
    24.                     obj = f.GetValue(obj);
    25.                 PropertyInfo p = type.GetProperty(iterator.name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
    26.                 if (p != null)
    27.                     obj = p.GetValue(obj, null);
    28.                 FancyFloat deserialized_FancyFloat = obj as FancyFloat;
    29.                 deserialized_FancyFloat.value = EditorGUILayout.FloatField(deserialized_FancyFloat.value, new GUILayoutOption[0]);
    30.                 deserialized_FancyFloat.number_decimals = EditorGUILayout.IntField(deserialized_FancyFloat.number_decimals, new GUILayoutOption[0]);
    31.  
    32.             }
    33.             else
    34.                 EditorGUILayout.PropertyField(iterator, true, new GUILayoutOption[0]);
    35.             enterChildren = false;
    36.         }
    37.         EditorGUI.BeginChangeCheck();
    38.         //base.OnInspectorGUI();
    39.         if (EditorGUI.EndChangeCheck())
    40.         {
    41.             string update_stuff = "here update edited properties";
    42.         }
    43.         serializedObject.ApplyModifiedProperties();
    44.     }
    45. }
    In the case of Private Field, not lucky here:
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using System.Linq;
    3. using System.Reflection;
    4. using UnityEditor;
    5. using UnityEngine;
    6.  
    7. [CanEditMultipleObjects]
    8. [CustomEditor(typeof(UnityEngine.Object), true)]
    9. public class FancyFloatEditor : Editor
    10. {
    11.     public override void OnInspectorGUI()
    12.     {
    13.         serializedObject.Update();
    14.         SerializedProperty iterator = serializedObject.GetIterator();
    15.         bool enterChildren = true;
    16.         while (iterator.NextVisible(enterChildren))
    17.         {
    18.             if (iterator.type == "FancyFloat")
    19.             {
    20.                 object obj = iterator.serializedObject.targetObject;
    21.                 System.Type type = obj.GetType();
    22.                 FieldInfo f = type.GetField(iterator.name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
    23.                 if (f != null)
    24.                     obj = f.GetValue(obj);
    25.                 PropertyInfo p = type.GetProperty(iterator.name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
    26.                 if (p != null)
    27.                     obj = p.GetValue(obj, null);
    28.                 FancyFloat deserialized_FancyFloat = obj as FancyFloat;
    29.                 deserialized_FancyFloat.value = EditorGUILayout.FloatField(deserialized_FancyFloat.value, new GUILayoutOption[0]);
    30.                 //(because is private, cannot do this, see below) deserialized_FancyFloat.number_decimals = EditorGUILayout.IntField(deserialized_FancyFloat.number_decimals, new GUILayoutOption[0]);
    31.  
    32.                 UnityEngine.Object fancyFloat_target = iterator.serializedObject.targetObject;
    33.                 SerializedObject fancyFloat_SO = new SerializedObject(fancyFloat_target);
    34.                 fancyFloat_SO.Update();
    35.                 SerializedProperty iterator_fancy_float = fancyFloat_SO.GetIterator();
    36.                 bool enterChildren_fancyFloat = true;
    37.                 while (iterator_fancy_float.NextVisible(enterChildren_fancyFloat))
    38.                 {
    39.                     if (iterator_fancy_float.name == "number_decimals")
    40.                         iterator_fancy_float.intValue = EditorGUILayout.IntField(iterator_fancy_float.intValue, new GUILayoutOption[0]);
    41.                 }
    42.             }
    43.             else
    44.                 EditorGUILayout.PropertyField(iterator, true, new GUILayoutOption[0]);
    45.             enterChildren = false;
    46.         }
    47.         EditorGUI.BeginChangeCheck();
    48.         //base.OnInspectorGUI();
    49.         if (EditorGUI.EndChangeCheck())
    50.         {
    51.             string update_stuff = "here update edited properties";
    52.         }
    53.         serializedObject.ApplyModifiedProperties();
    54.     }
    55. }
     

    Attached Files:

  2. DomingoGD

    DomingoGD

    Joined:
    Jan 28, 2018
    Posts:
    16
    Moving Up!

    Anyone has a hint?
     
  3. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,006
    You'Re doing almost everything wrong you can do wrong ^^. First of all you should never mix the new SerializedObject / SerializedProperty system with the old "direct" way (using target / targets).

    You marked your custom editor as "CanEditMultipleObjects" but none of your code supports it. This could result in massive data loss if not handled carefully or just in strange behaviour.

    You seem to have the wrong idea about what the SerializedObject and its SerializedProperty represents. A SerializedObject represents the serialized data of a "root" object. Root object are only classes derived from UnityEngine.Object (So things like Gameobjects, MonoBehaviours, ScriptableObjects, ...). All other serialized data is represented through SerializedProperties. A SerializedProperty can have child properties which is the case for your "FancyFloat" class. So the data inside your FancyFloat class are just nested SerializedProperties.

    The SerializedObject / SerializedProperty system takes care about multi object editing or when you edit prefabs, scene objects or assets. It takes care about creating undo actions and does take care that the data is serialized properly when ApplyModifiedProperties is called.

    You really should not create a custom editor in your case. There can only be one custom editor be active for one object. You register that editor for all UnityEngine.Objects. It would be ignored for those which have already a specialized editor. You should create a CustomPropertyDrawer for your FanceFloat class. That way it doesn't matter where your FancyFloat class is used, it simply works out of the box.
     
  4. DomingoGD

    DomingoGD

    Joined:
    Jan 28, 2018
    Posts:
    16
    Thanks a lot Bunny83! I just went straight to develop my goal and the first searches I made were using the CustomEditor, but you are right. Property Drawers seems the way to go.

    I did some tests with it and much better, except for the refresh rate. Totally a miss here not to have the option to flag RequiresConstantRepaint. Hope they add an option for this soon. The "forced" ones I found in the forum are not too realiable.
     
  5. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,006
    I'm a bit confused by this statement. A CustomEditor or PropertyDrawers are just responsible for displaying and editing the serialized data of the inspected object. While it's not meant as a debugging tool, it can be used for that while in play mode. In playmode the inspector should update instantly, during edit mode it will only update when an event happened.

    So in what exact situation do you need a refresh when the editor does not refresh the inspector?

    Also keep in mind that custom editors and property drawers can not change how and what data is serialized. They can only change how that data is presented in the inspector.

    I'm also not quite sure what's the point of that class ^^. So it looks like you have created a wrapper class for a normal float field that is paired with a decimals integer to probably specify how many decimal digits you want. Though how and where should that class be used? And why do you need a custom editor or property drawer for it?
     
  6. DomingoGD

    DomingoGD

    Joined:
    Jan 28, 2018
    Posts:
    16
    Custom Editors can use RequiresConstantRepaint() { return true; } to update constantly the inspector view, not only when events happens.

    This is very useful for my case, you will realize that if you try to display smooth changes in textures, animation curves, or any other visual information rather than a numeric/string field trough inspector, a huge and annoying lag is detected by your eyes.

    I couldn't find yet a good way to refresh in the property drawers though!