1

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?

Ben Rubin
  • 6,909
  • 7
  • 35
  • 82

1 Answers1

2

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

derHugo
  • 83,094
  • 9
  • 75
  • 115
  • The list displays correctly and it works correctly with I view/update it in the Inspector when I have the `ScriptableObject` selected in the Project view. But when I select the `MonoBehaviour` in the scene that has the reference to the `ScriptableObject`'s list, I can't seem to edit any of my list elements in the Inspector. Each time I change a value, the value reverts after I tab out of the field. I CAN add/delete/reorder elements, but I can't edit individual elements. Do you know what's happening? – Ben Rubin Jan 01 '22 at 15:56
  • @BenRubin are you still using the `ReorderableList` (in that case it might be related to [this old question](https://stackoverflow.com/questions/54516221/how-to-select-elements-in-nested-reorderablelist-in-a-customeditor) of mine) or the mentioned property drawer? (honestly doing editor scripting is hard enough on the PC but on a phone .. ^^ ) – derHugo Jan 01 '22 at 18:22
  • No, I stopped using the `ReorderableList`. I'm using a `PropertyDrawer` with your code. It works perfectly when I'm editing the `ScriptableObject` directly. But when I'm editing the `MonoBehaviour`, it's like Unity doesn't recognize when fields within each element of the list are being updated, but it does recognize when the list itself is being updated. I added a `Console.Log` to see when `ApplyModifiedProperties` returns `true`, and it doesn't return `true` when fields of a list element are updated. – Ben Rubin Jan 01 '22 at 18:32