Better ScriptableObjects Inspector-Editing (Editor Tool)

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 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

7 Likes

Didn’t have a chance to try it until now, but seems to work perfectly! Definitely recommend others give it a try, it’s simple and speeds up workflow quite a bit, just put it in your project and you’re good to go. We use scriptable objects quite heavily so this is going to be very helpful.

@TheVastBernie I would recommend attaching a zip of those two script files in their .cs format to make it a bit simpler for people to try it out!

Thanks for the suggestion, I added a unitypackage for easier import.
Also I fixed a few quirks with multi-object editing of MonoBehaviours and a bug causing labels to draw twice.
I added everything under the main post

Hey, after using this for a while I founding one lacking issue with it. The ScriptableObject’s “OnValidate()” isn’t propagated to the script its being displayed in. So when you modify its values you don’t get that callback to make things update to those changes.

My solution was to add:

MonoBehaviour mono = property.serializedObject.targetObject as MonoBehaviour;
            if(mono != null){
                    mono.Invoke("OnValidate", 0);
            }

Just before the final “EditorGUI.indentLevel–;” line. This works for the most part, though it calls OnValidate a bunch of times on each change. If you can think of a better option would love to hear it!

(Had to using Invoke instead of SendMessage because SendMessage requires the Mono script to be “ExecuteInEditMode” for it to work out of PlayMode, otherwise you get an exception.)

This is really cool, thanks.

I added the following script so that I can use the inspector for scriptable object assets from the Project menu. Without it, I was getting an error.

Aborting```

ScriptableObjectEditor.cs

```csharp
using UnityEngine;
using UnityEditor;

/// https://discussions.unity.com/t/671670
///see ScriptableObjectDrawer
[CanEditMultipleObjects]
[CustomEditor(typeof(ScriptableObject), true)]
public class ScriptableObjectEditor : Editor
{
}

This is absolutely brilliant!

This is amazing… I would have paid for it on the Asset Store, but I’m very glad you shared it! Thank you.

EDIT: I found out that with an array of ScriptableObjects, clicking on any of the “expand” arrows actually expand all ScriptableObjects of the array, displaying all of them at the end of the list. Weird behavior, that I have no idea how to solve…

@TheVastBernie I assume this is the bug you were working around? http://answers.unity.com/answers/1422999/view.html

Also, the layout events seem not to work on CustomPropertyDrawers on a PropertyAttribute.

My ideal scenario would be to use something like below, with the attribute determining if the ScriptableObject is inlined.

[InlineScriptableObject] public PlayerBaseStats Stats;

It seems that there have been multiple attempts at creating this feature, but none are quite perfect.

2 Likes

@Invertex I found a more performant way to call OnValidate. I need this since my OnValidate generates a mesh and doing this multiple times each frame causes my editor to run at 15fps.
I added:

var mono = property.serializedObject.targetObject as MonoBehaviour;
            if(property.objectReferenceValue is IEmbeddable so && mono != null)
                so.InvokeOnValidate(mono, "OnValidate");

just before the indentLevel-- line and made sure my Scriptable Object implemented the Interface:

public interface IEmbeddable
{
    public void InvokeOnValidate(MonoBehaviour mono, string methodName);
}

like this:

private void OnValidate()
    {
        if (_monoOnNextValidate != null)
            _monoOnNextValidate.Invoke(_nameOnNextValidate, 0);
    }

    private MonoBehaviour _monoOnNextValidate;
    private string _nameOnNextValidate;
    public void InvokeOnValidate(MonoBehaviour mono, string methodName)
    {
        _monoOnNextValidate = mono;
        _nameOnNextValidate = methodName;
    }

It’s a half-decent workaround but it works if you have to prevent OnValidate from being called multiple times. So if you can, go for the previous solution.
Hope it helps!