Home > Back-end >  Can I create a custom MonoBehaviour editor to edit a list from a ScriptableObject field?
Can I create a custom MonoBehaviour editor to edit a list from a ScriptableObject field?

Time:12-28

I have a custom editor for a MonoBehaviour to display a reorderable list of items.

public class MyComponent : MonoBehaviour
{
   public MyArrayElement[] myList;
}

public struct MyArrayElement
{
   public string firstField;
   public string secondField;  
}

[CustomEditor(typeof(MyComponent))]
public class MyComponentEditor : Editor
{
    private ReorderableList list;

    private void OnEnable()
    {
        SerializedProperty property = this.serializedObject.FindProperty("myList");
        this.list = new ReorderableList(this.serializedObject, property, true, true, true, true);
        list.drawElementCallback = DrawListItems;
    }

    public override void OnInspectorGUI()
    {
        serializedObject.Update();
        list.DoLayoutList();
        serializedObject.ApplyModifiedProperties();
    }

    void DrawListItems(Rect rect, int index, bool isActive, bool isFocused)
    {
        EditorGUI.PropertyField(
           new Rect(rect.x, rect.y, 100, EditorGUIUtility.singleLineHeight), 
           element.FindPropertyRelative("firstField"), 
           GUIContent.none);

        EditorGUI.PropertyField(
           new Rect(rect.x   150, rect.y, 100, EditorGUIUtility.singleLineHeight), 
           element.FindPropertyRelative("secondField"), 
           GUIContent.none);
    }
}

This works correctly. However, I would like to have the Inspector for every instance of this MonoBehaviour edit the same set of elements, so I created a ScriptableObject

public MyScriptableObject : ScriptableObject
{
   public MyArrayElement[] myList;  
}

And then replace MyComponent.myList with an instance of MyScriptableObject

public class MyComponent : MonoBehaviour
{       
   // Remove this
   // public MyArrayElement[] myList;

   // Add this
   public MyScriptableObject myScriptableObject;
}

Then I want to update my custom editor for the MonoBehaviour to show myScriptableObject.myList

I tried this, but the list is empty in the Inspector, even if the ScriptableObject's list is not empty

SerializedProperty property = this.serializedObject.FindProperty("myScriptableObject").FindPropertyRelative("myList");
this.list = new ReorderableList(this.serializedObject, property, true, true, true, true);

Is there a way to get my MonoBehaviours editor to let me edit the ScriptableObjects array?

CodePudding user response:

Yes of course there is! ;)

But first of all: Starting with Unity 2020 there is no need to use ReorderableList anymore except you want some very special behavior simce for List<T> and T[] this is now the default drawer anyway!

You might want to consider to rather implement a special CustomPropertyDrawer for your list elements (MyArrayElement) in general so it is actually displayed this way everywhere you expose it in the Inspector.

Such as e.g.

[CustomPropertyDrawer(typeof(MyArrayElement))]
public class MyArrayElementDrawer : PropertyDrawer
{
    public override int GetPropertyHeight ()
    {
        return EditorGUIUtility.singleLineHeight;
    }

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.BeginProperty(position, label, property);

        // Draw label
        position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);

        // Don't make child fields be indented
        var indent = EditorGUI.indentLevel;
        EditorGUI.indentLevel = 0;

        // Calculate rects
        var Rect1 = new Rect(position.x, position.y, 100, EditorGUIUtility.singleLineHeight);
        var Rect2 = new Rect(position.x   150, position.y, 100, EditorGUIUtility.singleLineHeight);

        EditorGUI.PropertyField(Rect1, property.FindPropertyRelative(nameof (MyArrayElement.firstField)), GUIContent.none);
        EditorGUI.PropertyField(Rect2, property.FindPropertyRelative(nameof (MyArrayElement.secondField)), GUIContent.none);

        EditorGUI.indentLevel = indent;

        EditorGUI.EndProperty();
    }
}

And then using FindPropertyRelative only works for [Serializable] classes/structs and their sub-fields.

Whenever you deal with a separate UnityEngine.Object reference (another MonoBehaviour, ScriptableObject, etc) what you need to do is go through the SerializedObject of the instance of that reference.

Like e.g.

var property = serializedObject.FindProperty(nameof(MyComponent.myScriptableObject));
EditorGUILayout.PropertyField(property, true);

if(property.objectReferenceValue)
{
    var so = new SerializedObject(property.objectReferenceValue);
    so.Update();
    
    var listProperty = so.FindProperty(nameof(MyScriptableObjevt.myList));

    EditorGUILayout.PropertyField(listProperty, true);

    so.ApplyModifiedProperties();
}

Note: Typing on smartphone but I hope the idea gets clear

  • Related