Search Unity

Unable to mark ObjectField as dirty in custom editor.

Discussion in 'Scripting' started by blablaalb, Oct 24, 2019.

  1. blablaalb

    blablaalb

    Joined:
    Oct 28, 2015
    Posts:
    53
    I need to expose an interface in the inspector panel. In order to do so I tried to write a custom Inspector which exposes a MonoBehaviour property, check if assigned MonoBehaviour class implements the interface, and if it does, then the custom editor casts the assigned MonoBehaviour script to the interface type and assigns it to the serialized variable:
    Code (CSharp):
    1. public class PowerupEditor : Editor
    2. {
    3.     private MonoBehaviour _powerableMB;    // MonoBehaviour script which implements IPowerable.
    4.  
    5.     public override void OnInspectorGUI()
    6.     {
    7.         DrawDefaultInspector();
    8.         DrawPowerableMB();
    9.  
    10.         if (_powerableMB != null)
    11.         {
    12.             IPowerable iPowerable;
    13.             if (ImplementsIPowerable(out iPowerable))
    14.             {
    15.                 SetValue(iPowerable);
    16.             }
    17.             else
    18.             {
    19.             }
    20.         }
    21.     }
    22.  
    23.     private bool ImplementsIPowerable(out IPowerable powerable)
    24.     {
    25.         powerable = null;
    26.         if (_powerableMB is IPowerable)
    27.         {
    28.             powerable = _powerableMB as IPowerable;
    29.             return true;
    30.         }
    31.  
    32.         return false;
    33.     }
    34.  
    35.     private void SetValue(object value)
    36.     {
    37.         FistPowerup fistPowerup = target as FistPowerup;
    38.         Type t = typeof(FistPowerup);
    39.         var field = t.GetField("_powerable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
    40.         field.SetValue(fistPowerup, value);
    41.     }
    42.  
    43.     private void DrawPowerableMB()
    44.     {
    45.         Undo.RecordObject(target, "Value changed");
    46.         EditorGUI.BeginChangeCheck();
    47.         _powerableMB = EditorGUILayout.ObjectField("IPowerable", _powerableMB, typeof(MonoBehaviour), true) as MonoBehaviour;
    48.         if (EditorGUI.EndChangeCheck())
    49.         {
    50.             Debug.Log("Dirty");
    51.             EditorUtility.SetDirty(target);
    52.             EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
    53.         }
    54.     }
    55. }
    56.  
    57.  
    58. public interface IPowerable
    59. {
    60.     //...
    61. }
    The problem being is that I'm unable to mark the inspected script dirty after assigning a value to the _powerableMB, i.e. this piece of code doesn't work:
    Code (CSharp):
    1.     private void DrawPowerableMB()
    2.     {
    3.         Undo.RecordObject(target, "Value changed");
    4.         EditorGUI.BeginChangeCheck();
    5.         _powerableMB = EditorGUILayout.ObjectField("IPowerable", _powerableMB, typeof(MonoBehaviour), true) as MonoBehaviour;
    6.         if (EditorGUI.EndChangeCheck())
    7.         {
    8.             Debug.Log("Dirty");
    9.             EditorUtility.SetDirty(target);
    10.             EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
    11.         }
    12.     }
    The "dirty" is being print on the Console but the _powerableMB is not marked as dirty thus resets to nothing every time I deselect and select the game object in the Hieararchy. I even tried to assign it via SerializedProperty, but it didn't worked either:
    Code (CSharp):
    1.     private void DrawPowerableMB()
    2.     {
    3.         SerializedProperty property = serializedObject.FindProperty("_powerable");
    4.         Undo.RecordObject(target, "Value changed");
    5.         EditorGUI.BeginChangeCheck();
    6.         _powerableMB = EditorGUILayout.ObjectField("IPowerable", _powerableMB, typeof(MonoBehaviour), true) as MonoBehaviour;
    7.         if (EditorGUI.EndChangeCheck())
    8.         {
    9.             Debug.Log("Dirty");
    10.             property.objectReferenceValue = _powerableMB;
    11.             serializedObject.Update();
    12.             EditorUtility.SetDirty(target);
    13.             EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
    14.         }
    15.     }
     
  2. WarmedxMints

    WarmedxMints

    Joined:
    Feb 6, 2017
    Posts:
    1,035
    Rather than working around those issues, why not just implement a method in the interface which returns the value type you want and have the mono behaviour class return a value stored in it as unity can serialize that.
     
  3. blablaalb

    blablaalb

    Joined:
    Oct 28, 2015
    Posts:
    53
    What method you suggest me to declare inside interface,?I didn't get.. I want to expose the interface in the inspector panel so I could assign scripts that implement that interface.
     
    Last edited: Oct 24, 2019
  4. Dr-Nick

    Dr-Nick

    Joined:
    Sep 9, 2013
    Posts:
    25
    I'm having the same issue running in Unity 2019.3. Without looking a work around solution using the default inspector, its a bit of a bummer that this is an issue.
     
  5. Dr-Nick

    Dr-Nick

    Joined:
    Sep 9, 2013
    Posts:
    25
    To follow up on this issue, I found the issue of the object field not saving was because the gameobject was a prefab instance. I submitted a bug report with my issue and got a response about another step required where you need to call
    PrefabUtility.RecordPrefabInstancePropertyModifications() on the modified component. This is how it could look like.

    Code (CSharp):
    1.     public override void OnInspectorGUI()
    2.     {
    3.         var target = (CustomComponent)EditorGUILayout.ObjectField("Target", ExampleCache.ComponentTarget, typeof(CustomComponent), true);
    4.         if (target != ExampleCache.ComponentTarget)
    5.         {
    6.             Undo.RecordObject(ExampleCache, "Update target");          
    7.             ExampleCache.ComponentTarget = target;
    8.             PrefabUtility.RecordPrefabInstancePropertyModifications(ExampleCache);
    9.         }
    10.     }
     
    nik_d and blablaalb like this.