Search Unity

Draw Custom Property Drawers From an Editor, Inside an Editor Window

Discussion in 'Immediate Mode GUI (IMGUI)' started by BinaryCats, Jul 28, 2018.

  1. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    317
    Hello

    Apologies for the ridiculous title, but bare with me.

    I have a piece of data that has a custom property drawer:

    Code (CSharp):
    1. [System.Serializable]
    2.     public class MyData
    3.     {
    4.           public bool MyField;
    5.     }
    6.     [CustomPropertyDrawer(typeof(MyData))]
    7.     public class MyPropertyDrawer : PropertyDrawer
    8.     {
    9.         public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    10.         {
    11.             EditorGUI.LabelField(position, "Hello");
    12.         }
    13.     }
    It is important to note, that if the type of MyData is inside the EditorWindow, and drawn with EditorGUILayout.PropertyField it is drawn with the Custom Property Drawer (see below)

    I have lots of different scriptable objects types, deriving from one type. I want One editor window to draw some data which is specific to those types.

    I want to store how each type is drawn inside it's type, to keep the code modular and easy to expand in the future. Rather than handle all the drawing inside the one EditorWindow file.

    In the example MyData isn't the data directly stored in the ScriptableObjects, rather just a type of how the data is interpreted to be drawn / edited with.

    I also do not wish to clutter up my scriptable objects, with editor specific variables, in this example MyData.

    Code (CSharp):
    1. public class MyEditorWindow : UnityEditor.EditorWindow{
    2.     MyScriptableType MYType;
    3.     public MyData Data;
    4.     MyEditor myEditor;
    5.     void OnGUI()
    6.     {
    7.         EditorGUILayout.PropertyField( new SerializedObject(this).FindProperty("Data")); //Drawn correctly
    8.         MYType = EditorGUILayout.ObjectField(MYType, typeof(MySkinType),true) as MySkinType;
    9.         if (MYType)
    10.         {
    11.             if (myEditor == null)
    12.                 myEditor = Editor.CreateEditor(MYType, typeof(MyEditor)) as MyEditor;
    13.             myEditor.Draw(); //drawn incorrectly
    14.         }
    15.     }
    16. }
    17. public class MyEditor : Editor
    18. {
    19.     public MyData Data;
    20.     public void Draw()
    21.     {
    22.         EditorGUILayout.PropertyField(new SerializedObject(this).FindProperty("Data"), true);
    23.  
    24.     }
    25. }
    Here the first line of OnGUI draws the type MyData with the intended Custom Property drawer. However myEditor.Draw() draws it without the Custom Property Drawer (how it would be drawn if there was no override)


    upload_2018-7-28_18-25-30.png

    I have also tried having MyEditor Inherit from ScriptableObject (and getting the serialized object inside the draw) however that results in the same issue.

    Is what I am trying to do possible?
    Is there a better solution to what I am trying to do?

    Thanks in advance
     
    Last edited: Jul 29, 2018
    ModLunar likes this.
  2. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,317
    I can't see any reason why it should be drawing the property drawer of one but not the other. Even though your setup is weird, that shouldn't make a difference in how it draws... The only difference I can see is that the second one is 'including children'. Does removing that fix it?

    By the way, if it's the default inspector you want, then there's no need to create a custom Draw method. Simply call the created editor's OnInspectorGUI method and it will draw the default inspector (which should include custom property drawers!). You can also override the OnInspectorGUI method to add custom GUI if you so choose, without changing your EditorWindow code at all.

    I would also recommend you pass
    null
    to the CreateEditor method rather than
    typeof( MyEditor )
    , as that will automatically find and return the correct editor for the type (either a custom editor you've defined or the default editor).
     
  3. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    317
    @Madgvox Thank you for your reply

    Removing the IncludeChildren flag does not resolve the issue.

    Yes you are correct about the the OnInspectorGUI I had tried that, however I did experiment with having it's own unique draw function as I was unsure if drawing it in the OnInspectorGUI was causing issues.

    Finally, I do not wish to pass NULL into the create editor, as the MYeditor is not the way I want it to be drawn in the inspector window, so it lacks the [CustomEditor(typeof(MyScriptableType))] attribute
     
  4. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,317
    I'm not sure I'm following. You want to use a specific custom editor for all of the scriptable objects?
     
  5. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    317
    I want a specific custom editor, for my editor window which is not the editor in the inspector window, each of the scriptable objects will more than likely have their own editor which will be used in the window

    --edit
    I just noticed an issue with the MyEditor draw function, I have corrected this ( I am aware that newing up a new Serilised object every draw is not very good, I just quickly wrote it in)
     
  6. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,317
    Right... but that falls under the use-case of passing null into the method and letting unity choose the editor for you. If you create an editor and bind it to an SO type via the CustomEditor attribute then CreateEditor (with null) will choose that editor. You can even create a custom default fallback editor for whatever the common base class is for all the SOs.
     
  7. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    317
    I was under the impression that if I used the [CustomEditor(typeof(MyScriptableType))] attribute, that editor will be used in the inspector window? which I do not want
     
  8. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    317
    I have now found the issue with the code, The MyEditor was not in its own file, and as it derives from scriptableobject, that is a requirement for it to be serilized properly
     
  9. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,317
    Ah, I see what you're getting at. You'll need to create your own custom editor mapping logic to avoid a lot of hard-coding. What kind of use-case do you have where you need a different-but-equal editor in the inspector than in the editor window?
     
  10. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    317
    Its essentially animation data, lots of arrays with specific values coupled with timing. Its incredibly hard to hand edit these arrays and it should only really be edited by MyWindow. As MyWindow (and the scriptableObject's editors) can/will interpreted the data into a humanly readable/editable fashion.

    The logic for the custom editor mapping, is simply to have a virtual method in commonly derived BaseScriptableObjectType Draw(float Time)

    Code (CSharp):
    1. public class MyScriptableObjectType : ScriptableObject
    2. {
    3.     public MyTimeline timeline;//bunch of non human readable data
    4.  
    5.     MyEditor editor;
    6.  
    7.     public void Draw( float t)
    8.     {
    9.         if (editor == null)
    10.             editor = Editor.CreateEditor(this, typeof(MyEditor)) as MyEditor;
    11.         editor.Draw();
    12.     }
    13. }
    And so my Window code where I draw the editor will look like this:

    Code (CSharp):
    1. public class MyEditorWindow : UnityEditor.EditorWindow
    2. {
    3. float Time;
    4.     MyScriptableObjectType thing ;
    5.     // Add menu named "My Window" to the Window menu
    6.     [MenuItem("Window/My Window")]
    7.     static void Init()
    8.     {
    9.         // Get existing open window or if none, make a new one:
    10.         MyEditorWindow window = (MyEditorWindow)EditorWindow.GetWindow(typeof(MyEditorWindow));
    11.         window.Show();
    12.  
    13.     }
    14.     void OnGUI()
    15.     {
    16.         thing = EditorGUILayout.ObjectField(thing , typeof(MyScriptableObjectType), true) as MyScriptableObjectType;
    17.         if (thing )
    18.         {
    19.             thing .Draw(Time);
    20.         }
    21.     }
    22. }
    or simular
     
    hworld and Mark117 like this.
  11. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,317
    So it's not a problem to use CustomEditor then?
     
  12. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    317
    MyWidow encompasses more than just the MyEditor, there are an assortment of tools which Edit the scriptable object's data within MyWindow. Think Animator Controller, which can only be edited in the animator, and not inspector.

    I will use CustomEditor, but only to point people to use the Window
     
  13. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,317
    Then why use the Editor at all? You don't seem to be making use of any of its features.
     
  14. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    317
    As I said in the thread.
    • I don't want to clutter my scriptable object with editor only variables
    • I want the draw code to be modular, i.e. not have everything in MyEditorWindow
    The Editor's will poll it's targetObject for data to interpret

    I understand I'm not using Editor to its fullest, I am open to other suggestions
     
  15. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,317
    I would probably do this:

    It's not that difficult to reproduce the CustomEditor logic. Create your own attribute [MyCustomEditorAttribute] and generate a dictionary of type -> editor type on load. Tie the attribute to some SO base class that will act as your editor base. You can create new instances using ScriptableObject.CreateInstance. Inheriting from editor isn't worth it just to use
    target
    and
    serializedObject
    .
     
    BinaryCats likes this.
  16. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    317
    Ah I see thank you, I thought about using a ScriptableObject to hadle the drawing, but I did not realise there was an overhead(?) to using Editor

    Having thought about it I like the idea of [MyCustomEditorAttribute] to cache which DrawerScriptableObjects handle drawing of what types
     
  17. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,317
    It's not so much a technical overhead as a semantic overhead. The Editor class comes with certain assumptions about its use. Your use-case does not sufficiently overlap with the Editor class use-case, in my opinion. That's just what I would do in your shoes, though. There's nothing wrong technically with using Editor as your base class.
     
  18. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    317
    Okay thanks for the oversite.

    regarding building the dictionary, I assume I would have to reflect over types checking if they have the MyCustomEditorAttribute taged onto them?
     
  19. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,317
    Correct.