[Editor Tool] Better ScriptableObject Inspector-Editing

Hi, guys,
so, I think a lot of you have been using ScriptableObjects with Unity. I myself have been using them not only for things like items or other types of asset databases, but also for ways to store gameplay relevant stats that can easily be edited by designers and makes rapid iteration even easier. One thing that crops up a lot in our production are scriptables with a few parameters that define members like “playerHealth”, “playerDamage” or things like that. Jumping from the player inspector to the scriptableObject, changing the property, jumping back to the player, maybe accessing another scriptable with movement data, changing this object, testing the changes, jumping back to the various scriptables, making alterations, iterating, and so on. It can get quite tiring, altering multiple scriptables at once.

One thing that would help a lot, would be to display the scriptables member variables within the components inspector gui. And since we found no easy accessible solution for that, we made one ourselves.

Our two simple editor scripts turn what would previously be found in two inspectors

(Player Object)


(Scriptable Object)

Into an easy to manage and edit, single foldout inspector:

(Player and Scriptable Object combined)

If any of you want to try it out or offer feedback, please check out the code below.

Simply place those two scripts inside the editor folder:

ScriptableObjectDrawer.cs

using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer(typeof(ScriptableObject), true)]
public class ScriptableObjectDrawer : PropertyDrawer
{
    // Cached scriptable object editor
    private Editor editor = null;

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        // Draw label
        EditorGUI.PropertyField(position, property, label, true);
     
        // Draw foldout arrow
        if (property.objectReferenceValue != null)
        {
            property.isExpanded = EditorGUI.Foldout(position, property.isExpanded, GUIContent.none);
        }

        // Draw foldout properties
        if (property.isExpanded)
        {
            // Make child fields be indented
            EditorGUI.indentLevel++;
         
            // Draw object properties
            if (!editor)
                Editor.CreateCachedEditor(property.objectReferenceValue, null, ref editor);
            editor.OnInspectorGUI();
         
            // Set indent back to what it was
            EditorGUI.indentLevel--;
        }
    }
}

MonoBehaviourEditor.cs

using UnityEngine;
using UnityEditor;

[CanEditMultipleObjects]
[CustomEditor(typeof(MonoBehaviour), true)]
public class MonoBehaviourEditor : Editor
{
}

This one is mandatory since without it, the custom property drawer will throw errors. You need a custom editor class of the component utilising a ScriptableObject. So we just create a dummy editor, that can be used for every MonoBehaviour. With this empty implementation it doesn’t alter anything, it just removes Unitys property drawing bug.

Please try it out, I’m open for feedback and suggestions. Other than that, I hope this was useful and enjoy!

Edit: For easier inporting, here is a link to a unitypackage
https://www.dropbox.com/s/rccxlbjgrys77c9/ScriptableObjectEditorFoldout.unitypackage

20 Likes

Nice work. Only issue I’m running into, is when I’m editing a ScriptableObject array inside of another ScriptableObject. The elements seem to overlap. For instance if I have 2 elements and expand the first element, it shows the second element fields as well.

1 Like

Yeah, this seems to be an quirk of the unity array property drawer. It always happens when editing array elements. I have not found a way around that. However, if anyone figures something out, I’d be happy to add it to the script.

In other news, I’ve made a small unitypackage to make import easier. It’s under the main post

Thanks for the advice, isExpanded seems to work quite well. And it removes the dictionary which I didn’t like in the first place :slight_smile:

The array issue however cannot be solved by simply expanding the property height. The function changes the height of the actual property, meaning the ScriptableObject field and not the additional child fields that are drawn below. I basically draw my ‘default’ property and add the properties of the ScriptableObject below it. Arrays and their elements however seem to be drawn as one single property. Thus, all array elements are drawn first and the additional properties of the ScriptableObject are then drawn below. Below the array in this case and not below the actual array element.

Not sure if you can overcome this limitation, but in most cases it seems to work fine :wink:

Great tool. If you want to edit SO inside SO you should create an additional script, save it as ScriptableObjectEditor.cs

under Editor and copy paste the following code

using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer(typeof(ScriptableObject), true)]
public class ScriptableObjectDrawer : PropertyDrawer
{
    // Static foldout dictionary
    private static Dictionary<System.Type, bool> foldoutByType = new Dictionary<System.Type, bool>();

    // Cached scriptable object editor
    private Editor editor = null;

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        // Draw label
        EditorGUI.PropertyField(position, property, label, true);
       
        // Draw foldout arrow
        bool foldout = false;
        if (property.objectReferenceValue != null)
        {
            // Store foldout values in a dictionary per object type
            bool foldoutExists = foldoutByType.TryGetValue(property.objectReferenceValue.GetType(), out foldout);
            foldout = EditorGUI.Foldout(position, foldout, GUIContent.none);
            if (foldoutExists)
                foldoutByType[property.objectReferenceValue.GetType()] = foldout;
            else
                foldoutByType.Add(property.objectReferenceValue.GetType(), foldout);
        }

        // Draw foldout properties
        if (foldout)
        {
            // Make child fields be indented
            EditorGUI.indentLevel++;
           
            // Draw object properties
            if (!editor)
                Editor.CreateCachedEditor(property.objectReferenceValue, null, ref editor);
            editor.OnInspectorGUI();
           
            // Set indent back to what it was
            EditorGUI.indentLevel--;
        }
    }
}
1 Like

This is very useful. However, I’m getting errors with KeyCode enums. Anyone know how to fix?

Very helpful and it works perfectly within a component. However SO generate errors when unfolding nested SO references:

ArgumentException: Getting control 4's position in a group with only 4 controls when doing Repaint

I tried casing the OnInspectorGUI with an even type test but that old trick doesn’t work in this case. What’s the fix?

Here is the animated version of what happens:

Also the background could be darker to improve readability but OnInspectorGUI doesn’t return a rect so I don’t know what to feed GUI.Box(rect, null) so how do I do that?
And unfolding nested SO in an array could display below the element instead of at the end of the array, I’ve tried a few things to do that but again, unsuccesful, any idea?

Here is a mockup of what I want to do:

3331540--259713--eyizIJB.png

fix a bug: when the property that contains the SO is empty it would spit out errors

[CustomPropertyDrawer(typeof(ScriptableObject), true)]
public class ScriptableObjectDrawer : PropertyDrawer
{
    // Cached scriptable object editor
    private Editor editor = null;

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        // Draw label
        EditorGUI.PropertyField(position, property, label, true);

        // Draw foldout arrow
        if (property.objectReferenceValue != null)
        {
            property.isExpanded = EditorGUI.Foldout(position, property.isExpanded, GUIContent.none);
        }

        // Draw foldout properties
        if (property.isExpanded)
        {
            // Make child fields be indented
            EditorGUI.indentLevel++;

            if (!editor)
                Editor.CreateCachedEditor(property.objectReferenceValue, null, ref editor);

            // Draw object properties
            if (editor) // catches empty property
                editor.OnInspectorGUI();
   
            // Set indent back to what it was
            EditorGUI.indentLevel--;
        }
    }
}

I haven’t tried this, but the problem might be fixed by replacing the “dummy editor” MonoBehaviourEditor.cs with:

UnityObjectEditor.cs

using UnityEngine;
using UnityEditor;

[CanEditMultipleObjects]
[CustomEditor(typeof(UnityEngine.Object), true)]
public class UnityObjectEditor : Editor
{
}

This editor should fix the error in both MonoBehaviour and ScriptableObject

You genius, it works.
Now the problem I’m seeing is when you change a SO from withing a mono, that SO doesn’t register as dirty by collab, maybe it’s another collab bug but I think it lacks some apply thingy somewhere.
I know that there is somewhere in the humongous editor api, a way to auto detect change and mark dirty. Let’s see if I can fix that…

Here we go.

[CustomPropertyDrawer(typeof(ScriptableObject), true)]
public class ScriptableObjectDrawer : PropertyDrawer
{
    // Cached scriptable object editor
    private Editor editor = null;

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        // Draw label
        EditorGUI.PropertyField(position, property, label, true);

        // Draw foldout arrow
        if (property.objectReferenceValue != null)
        {
            property.isExpanded = EditorGUI.Foldout(position, property.isExpanded, GUIContent.none);
        }

        // Draw foldout properties
        if (property.isExpanded)
        {
            // Make child fields be indented
            EditorGUI.indentLevel++;

            if (!editor)
                Editor.CreateCachedEditor(property.objectReferenceValue, null, ref editor);

            // Draw object properties
            EditorGUI.BeginChangeCheck();
            if (editor) // catch for empty property
                editor.OnInspectorGUI();
            if (EditorGUI.EndChangeCheck())
                property.serializedObject.ApplyModifiedProperties();
   
            // Set indent back to what it was
            EditorGUI.indentLevel--;
        }
    }
}
1 Like

So after being reached out too, I decided to create a fleshed out and optimized solution for this.

3332137--259791--27f20e19e686a06bf952f68de473c06a.png

Draws in Lists and Arrays

Optimized, doesn’t use GUILayout

Customizable Spacing on the Inside and Outside of the border
Allows Nesting (but not nesting of parent objects, causes stack overflow)

Thanks to @laurentlavigne I had an idea to put backgrounds in the property drawer that layer on top of each other to clearly show children. This works brilliantly and I created 3 themes that you can change easily using an enum, HelpBox, Darken, Lighten and None.

3332137--259786--90715bed61b26a0e671960a4a9b884c2.png

3332137--259787--90b45ff46da58ec2ca427a2054646d13.png

3332137--259788--486bbdd2fb6b183982a8de3df18f2dd5.png

To use it, put the ExpandableAttribute on a field that you want to be expandable:

    [ExpandableAttribute]
    public ScriptableObject child;

or:

    [Expandable]
    public ScriptableObject child;

And it will now appear as an expandable field. This also works on MonoBehaviour references, as you can see below I am referencing the Main Camera and I am getting all of the camera fields below.

The code for this solution is shown below. I will also be uploading this to the Asset Store at some point so Redistribute Privately so that I can fix the bugs. The Asset Store will allow me to publish updates to fix “self-nesting” issues, as there are currently issues with StackOverflowExceptions.

using System;
using UnityEngine;

#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEditor;
#endif

/// <summary>
/// Use this property on a ScriptableObject type to allow the editors drawing the field to draw an expandable
/// area that allows for changing the values on the object without having to change editor.
/// </summary>
public class ExpandableAttribute : PropertyAttribute
{
    public ExpandableAttribute ()
    {

    }
}

#if UNITY_EDITOR
/// <summary>
/// Draws the property field for any field marked with ExpandableAttribute.
/// </summary>
[CustomPropertyDrawer (typeof (ExpandableAttribute), true)]
public class ExpandableAttributeDrawer : PropertyDrawer
{
    // Use the following area to change the style of the expandable ScriptableObject drawers;
    #region Style Setup
    private enum BackgroundStyles
    {
        None,
        HelpBox,
        Darken,
        Lighten
    }

    /// <summary>
    /// Whether the default editor Script field should be shown.
    /// </summary>
    private static bool SHOW_SCRIPT_FIELD = false;

    /// <summary>
    /// The spacing on the inside of the background rect.
    /// </summary>
    private static float INNER_SPACING = 6.0f;

    /// <summary>
    /// The spacing on the outside of the background rect.
    /// </summary>
    private static float OUTER_SPACING = 4.0f;

    /// <summary>
    /// The style the background uses.
    /// </summary>
    private static BackgroundStyles BACKGROUND_STYLE = BackgroundStyles.HelpBox;

    /// <summary>
    /// The colour that is used to darken the background.
    /// </summary>
    private static Color DARKEN_COLOUR = new Color (0.0f, 0.0f, 0.0f, 0.2f);

    /// <summary>
    /// The colour that is used to lighten the background.
    /// </summary>
    private static Color LIGHTEN_COLOUR = new Color (1.0f, 1.0f, 1.0f, 0.2f);
    #endregion

    /// <summary>
    /// Cached editor reference.
    /// </summary>
    private Editor editor = null;

    public override float GetPropertyHeight (SerializedProperty property, GUIContent label)
    {
        float totalHeight = 0.0f;

        totalHeight += EditorGUIUtility.singleLineHeight;

        if (property.objectReferenceValue == null)
            return totalHeight;

        if (!property.isExpanded)
            return totalHeight;

        if (editor == null)
            Editor.CreateCachedEditor (property.objectReferenceValue, null, ref editor);

        if (editor == null)
            return totalHeight;
       
        SerializedProperty field = editor.serializedObject.GetIterator ();

        field.NextVisible (true);

        if (SHOW_SCRIPT_FIELD)
        {
            totalHeight += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
        }

        while (field.NextVisible (true))
        {
            totalHeight += EditorGUI.GetPropertyHeight (field, true) + EditorGUIUtility.standardVerticalSpacing;
        }

        totalHeight += INNER_SPACING * 2;
        totalHeight += OUTER_SPACING * 2;

        return totalHeight;
    }

    public override void OnGUI (Rect position, SerializedProperty property, GUIContent label)
    {
        Rect fieldRect = new Rect (position);
        fieldRect.height = EditorGUIUtility.singleLineHeight;

        EditorGUI.PropertyField (fieldRect, property, label, true);

        if (property.objectReferenceValue == null)
        {
            Debug.Log ("It's secretly null");
            return;
        }
       
        property.isExpanded = EditorGUI.Foldout (fieldRect, property.isExpanded, GUIContent.none, true);

        if (!property.isExpanded)
            return;

        if (editor == null)
            Editor.CreateCachedEditor (property.objectReferenceValue, null, ref editor);

        if (editor == null)
        {
            Debug.Log ("Couldn't fetch editor");
            return;
        }
       
       
        #region Format Field Rects
        List<Rect> propertyRects = new List<Rect> ();
        Rect marchingRect = new Rect (fieldRect);

        Rect bodyRect = new Rect (fieldRect);
        bodyRect.xMin += EditorGUI.indentLevel * 18;
        bodyRect.yMin += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing
            + OUTER_SPACING;
       
        SerializedProperty field = editor.serializedObject.GetIterator ();
        field.NextVisible (true);

        marchingRect.y += INNER_SPACING + OUTER_SPACING;

        if (SHOW_SCRIPT_FIELD)
        {
            propertyRects.Add (marchingRect);
            marchingRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
        }

        while (field.NextVisible (true))
        {
            marchingRect.y += marchingRect.height + EditorGUIUtility.standardVerticalSpacing;
            marchingRect.height = EditorGUI.GetPropertyHeight (field, true);
            propertyRects.Add (marchingRect);
        }

        marchingRect.y += INNER_SPACING;

        bodyRect.yMax = marchingRect.yMax;
        #endregion

        DrawBackground (bodyRect);

        #region Draw Fields
        EditorGUI.indentLevel++;

        int index = 0;
        field = editor.serializedObject.GetIterator ();
        field.NextVisible (true);

        if (SHOW_SCRIPT_FIELD)
        {
            //Show the disabled script field
            EditorGUI.BeginDisabledGroup (true);
            EditorGUI.PropertyField (propertyRects[index], field, true);
            EditorGUI.EndDisabledGroup ();
            index++;
        }

        //Replacement for "editor.OnInspectorGUI ();" so we have more control on how we draw the editor
        while (field.NextVisible (true))
        {
            try
            {
                EditorGUI.PropertyField (propertyRects[index], field, true);
            }
            catch (StackOverflowException)
            {
                field.objectReferenceValue = null;
                Debug.LogError ("Detected self-nesting cauisng a StackOverflowException, avoid using the same " +
                    "object iside a nested structure.");
            }

            index++;
        }

        EditorGUI.indentLevel--;
        #endregion
    }

    /// <summary>
    /// Draws the Background
    /// </summary>
    /// <param name="rect">The Rect where the background is drawn.</param>
    private void DrawBackground (Rect rect)
    {
        switch (BACKGROUND_STYLE) {

        case BackgroundStyles.HelpBox:
            EditorGUI.HelpBox (rect, "", MessageType.None);
            break;

        case BackgroundStyles.Darken:
            EditorGUI.DrawRect (rect, DARKEN_COLOUR);
            break;

        case BackgroundStyles.Lighten:
            EditorGUI.DrawRect (rect, LIGHTEN_COLOUR);
            break;
        }
    }
}

/// <summary>
/// Required for the fetching of a default editor on MonoBehaviour objects.
/// </summary>
[CanEditMultipleObjects]
[CustomEditor(typeof(MonoBehaviour), true)]
public class MonoBehaviourEditor : Editor { }

/// <summary>
/// Required for the fetching of a default editor on ScriptableObject objects.
/// </summary>
[CanEditMultipleObjects]
[CustomEditor(typeof(ScriptableObject), true)]
public class ScriptableObjectEditor : Editor { }
#endif
5 Likes

Really helps with readability but there are four issues, three of which have to do with nested [Expandable], I think it’s expanding recursively:

  1. Overlap issue in the following condition:
[CreateAssetMenu]
public class Construct : ScriptableObject {
    public int price;

    [Expandable]
    public List<Construct> ingredients;

    public List<Elemental> elementals;

    [System.Serializable]
    public class PropertySubjectValue
    {
        [Expandable]
        public Property property;
        public Object subject;
        public float value;
    }
    public List<PropertySubjectValue> propertyValues;

    [System.Serializable]
    public class ActionActionPair
    {
        public Construct actor;
        [Expandable]
        public Action resultingAction;
    }
    public List<ActionActionPair> actorActions;
}

and in a mono using the dark theme

  1. it seems to double expand…

  2. it doesn’t handle this scenario well

// break down into smaller components
[CreateAssetMenu(menuName= "Actions/Mine")]
public class ActionMine : Action{
    [Tooltip("unit per seconds")]
    public float efficiency =2.3f;
}
  1. in a mono, it shows the collection size even when the collection label is folded

2 of those issues (1 and 3) are because you are using another PropertyDrawer to draw the property, which is a condition which I didn’t design for. Unfortunately, this cannot be fixed without breaking the encapsulation of the PropertyDrawer.

Issue number 4 I think is caused because I used “field.NextVisible (true)” instead of “field.NextVisible (false)”, so I changed that and now it’s fixed.

I also forgot to remove some debugs, which I have also done.

The indent size was also off, so I fixed that.

Issue number 1 is absolutely impossible to fix whilst keeping it optimized using the Rects without breaking encapsulation.

Issue number 3 could be fixed by fetching the editors width and using that instead of the Rect you are passing it with the PropertyDrawer your using, but that would break other uses for the property drawer.

Here is the amended code:

using System;
using UnityEngine;

#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEditor;
#endif

/// <summary>
/// Use this property on a ScriptableObject type to allow the editors drawing the field to draw an expandable
/// area that allows for changing the values on the object without having to change editor.
/// </summary>
public class ExpandableAttribute : PropertyAttribute
{
    public ExpandableAttribute ()
    {

    }
}

#if UNITY_EDITOR
/// <summary>
/// Draws the property field for any field marked with ExpandableAttribute.
/// </summary>
[CustomPropertyDrawer (typeof (ExpandableAttribute), true)]
public class ExpandableAttributeDrawer : PropertyDrawer
{
    // Use the following area to change the style of the expandable ScriptableObject drawers;
    #region Style Setup
    private enum BackgroundStyles
    {
        None,
        HelpBox,
        Darken,
        Lighten
    }

    /// <summary>
    /// Whether the default editor Script field should be shown.
    /// </summary>
    private static bool SHOW_SCRIPT_FIELD = false;

    /// <summary>
    /// The spacing on the inside of the background rect.
    /// </summary>
    private static float INNER_SPACING = 6.0f;

    /// <summary>
    /// The spacing on the outside of the background rect.
    /// </summary>
    private static float OUTER_SPACING = 4.0f;

    /// <summary>
    /// The style the background uses.
    /// </summary>
    private static BackgroundStyles BACKGROUND_STYLE = BackgroundStyles.HelpBox;

    /// <summary>
    /// The colour that is used to darken the background.
    /// </summary>
    private static Color DARKEN_COLOUR = new Color (0.0f, 0.0f, 0.0f, 0.2f);

    /// <summary>
    /// The colour that is used to lighten the background.
    /// </summary>
    private static Color LIGHTEN_COLOUR = new Color (1.0f, 1.0f, 1.0f, 0.2f);
    #endregion

    /// <summary>
    /// Cached editor reference.
    /// </summary>
    private Editor editor = null;

    public override float GetPropertyHeight (SerializedProperty property, GUIContent label)
    {
        float totalHeight = 0.0f;

        totalHeight += EditorGUIUtility.singleLineHeight;

        if (property.objectReferenceValue == null)
            return totalHeight;

        if (!property.isExpanded)
            return totalHeight;

        if (editor == null)
            Editor.CreateCachedEditor (property.objectReferenceValue, null, ref editor);

        if (editor == null)
            return totalHeight;
       
        SerializedProperty field = editor.serializedObject.GetIterator ();

        field.NextVisible (true);

        if (SHOW_SCRIPT_FIELD)
        {
            totalHeight += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
        }

        while (field.NextVisible (false))
        {
            totalHeight += EditorGUI.GetPropertyHeight (field, true) + EditorGUIUtility.standardVerticalSpacing;
        }

        totalHeight += INNER_SPACING * 2;
        totalHeight += OUTER_SPACING * 2;

        return totalHeight;
    }

    public override void OnGUI (Rect position, SerializedProperty property, GUIContent label)
    {
        Rect fieldRect = new Rect (position);
        fieldRect.height = EditorGUIUtility.singleLineHeight;

        EditorGUI.PropertyField (fieldRect, property, label, true);

        if (property.objectReferenceValue == null)
            return;
       
        property.isExpanded = EditorGUI.Foldout (fieldRect, property.isExpanded, GUIContent.none, true);

        if (!property.isExpanded)
            return;

        if (editor == null)
            Editor.CreateCachedEditor (property.objectReferenceValue, null, ref editor);

        if (editor == null)
            return;
       
       
        #region Format Field Rects
        List<Rect> propertyRects = new List<Rect> ();
        Rect marchingRect = new Rect (fieldRect);

        Rect bodyRect = new Rect (fieldRect);
        bodyRect.xMin += EditorGUI.indentLevel * 14;
        bodyRect.yMin += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing
            + OUTER_SPACING;
       
        SerializedProperty field = editor.serializedObject.GetIterator ();
        field.NextVisible (true);

        marchingRect.y += INNER_SPACING + OUTER_SPACING;

        if (SHOW_SCRIPT_FIELD)
        {
            propertyRects.Add (marchingRect);
            marchingRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
        }

        while (field.NextVisible (false))
        {
            marchingRect.y += marchingRect.height + EditorGUIUtility.standardVerticalSpacing;
            marchingRect.height = EditorGUI.GetPropertyHeight (field, true);
            propertyRects.Add (marchingRect);
        }

        marchingRect.y += INNER_SPACING;

        bodyRect.yMax = marchingRect.yMax;
        #endregion

        DrawBackground (bodyRect);

        #region Draw Fields
        EditorGUI.indentLevel++;

        int index = 0;
        field = editor.serializedObject.GetIterator ();
        field.NextVisible (true);

        if (SHOW_SCRIPT_FIELD)
        {
            //Show the disabled script field
            EditorGUI.BeginDisabledGroup (true);
            EditorGUI.PropertyField (propertyRects[index], field, true);
            EditorGUI.EndDisabledGroup ();
            index++;
        }

        //Replacement for "editor.OnInspectorGUI ();" so we have more control on how we draw the editor
        while (field.NextVisible (false))
        {
            try
            {
                EditorGUI.PropertyField (propertyRects[index], field, true);
            }
            catch (StackOverflowException)
            {
                field.objectReferenceValue = null;
                Debug.LogError ("Detected self-nesting cauisng a StackOverflowException, avoid using the same " +
                    "object iside a nested structure.");
            }

            index++;
        }

        EditorGUI.indentLevel--;
        #endregion
    }

    /// <summary>
    /// Draws the Background
    /// </summary>
    /// <param name="rect">The Rect where the background is drawn.</param>
    private void DrawBackground (Rect rect)
    {
        switch (BACKGROUND_STYLE) {

        case BackgroundStyles.HelpBox:
            EditorGUI.HelpBox (rect, "", MessageType.None);
            break;

        case BackgroundStyles.Darken:
            EditorGUI.DrawRect (rect, DARKEN_COLOUR);
            break;

        case BackgroundStyles.Lighten:
            EditorGUI.DrawRect (rect, LIGHTEN_COLOUR);
            break;
        }
    }
}

/// <summary>
/// Required for the fetching of a default editor on MonoBehaviour objects.
/// </summary>
[CanEditMultipleObjects]
[CustomEditor(typeof(MonoBehaviour), true)]
public class MonoBehaviourEditor : Editor { }

/// <summary>
/// Required for the fetching of a default editor on ScriptableObject objects.
/// </summary>
[CanEditMultipleObjects]
[CustomEditor(typeof(ScriptableObject), true)]
public class ScriptableObjectEditor : Editor { }
#endif
3 Likes

I use encapsulation, could these be fixed by using GUILayout instead of rect? In your email you mentioned that it slows things down a lot, it’s alright because in-play deep expansion is used for debug purposes anyway, for example “this tree isn’t producing any apple, I can see if the production list isn’t being updated”

which uses?
Maybe a [Compact] attribute can be created to bring the field closer to the label, in which case this can be hacked around.

public class Compact : PropertyAttribute {public Compact (){}}
[CustomPropertyDrawer (typeof (Compact), true)]
public class CompactDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        Rect rect = new Rect (position);
        rect.height = EditorGUIUtility.singleLineHeight;

        EditorGUI.PropertyField (rect, property, GUIContent.none);
    }
}

I couldn’ figure out how to to move the label closer to the field, this is meh-ok for now

Yes, GUILayout would fix that - but it would also not allow it to draw in lists properly.

The currently you use EditorGUI.PropertyField (rect, prop) concerning issue 3, if I instead of using the rect that is passed to the property expand that to the size of the inspector.

This would involve using reflection to get the Inspector window and then use thats dimensions instead of the ones used to draw the property.

Then you will just have to add EditorGUI.GetPropertyHeight () to your property that you are using.

This would work in lists - but it wouldn’t work if you wanted to draw multiple of them as children. In this case you would have to rewrite the script.

That’s a real bummer.
Can you make the expanded property display at the end of the list like in @TheVastBernie 's version?

Yes, it’s possible. That’s what I mean’t by GUILayout not drawing into lists properly.

It would be very easy to do the GUILayout version and probably be about half the amount of code.

I’d still always recommend you edit the property drawer you are currently using to include the rect property drawer but GUILayout is a lot easier to impliment.

I reverted back to @TheVastBernie version and just added an Area
It’s good enough

using System;
using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer(typeof(ScriptableObject), true)]
public class ScriptableObjectDrawer : PropertyDrawer
{
    // Cached scriptable object editor
    Editor editor = null;

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        // Draw label
        EditorGUI.PropertyField(position, property, label, true);

        // Draw foldout arrow
        if (property.objectReferenceValue != null)
        {
            property.isExpanded = EditorGUI.Foldout(position, property.isExpanded, GUIContent.none);
        }

        // Draw foldout properties
        if (property.isExpanded)
        {
            // Make child fields be indented
            EditorGUI.indentLevel++;

            // background
            GUILayout.BeginVertical("box");

            if (!editor)
                Editor.CreateCachedEditor(property.objectReferenceValue, null, ref editor);

            // Draw object properties
            EditorGUI.BeginChangeCheck();
            if (editor) // catch empty property
            {
                editor.OnInspectorGUI ();
            }
            if (EditorGUI.EndChangeCheck())
                property.serializedObject.ApplyModifiedProperties();

            GUILayout.EndVertical ();

            // Set indent back to what it was
            EditorGUI.indentLevel--;
        }
    }
}

[CanEditMultipleObjects]
[CustomEditor(typeof(UnityEngine.Object), true)]
public class UnityObjectEditor : Editor
{
}
2 Likes