rootpanthera
rootpanthera

Reputation: 2771

Serialize the field from Scriptable in another Scriptable

I have found an interesting "issue" in UnityEditor regarding Scriptable objects. Below is simple example:

I have scriptable class ScriptableExpressionBulder that holds some list and a float field. Then I have another class ScriptableExpressionCreator that holds a LIST of scriptable objects mentioned previously. The code looks like this:

public class ScriptableExpressionBuilder : ScriptableObject
        {
           public List<MultipleExpressionBuilder> multipleExpressionBuilder;
           public float delay;
        }



public class ScriptableExpressionCreator : ScriptableObject {

        public List<ScriptableExpressionBuilder> List;

}

When creating ScriptableExpressionBuilder the fields from (MultipleExpressionBuilder) comes up nicely but it also adds a second field "delay" at the end (which is perfect to this point).

Now for the interesting part:

The second class ScriptableExpressionCreator holds a list of previous scriptables but it doesn't serialize the "delay" field in this current scriptable. The only field that is serialized is a Scriptable class.

Is there a way to serialize the field from ScriptableExpressionBuilder in another Scriptable that holds a list of these scriptables so I can basically set the delay from where this expressions are called. I could however populate the field in the inspector of original scriptable, but the wouldnt be reusable since each delay is different.

Upvotes: 0

Views: 708

Answers (1)

derHugo
derHugo

Reputation: 90580

You had the question deleted but I voted to reopen it since I already had started to write the solution and finished just when you deleted it and I think this now does what you want and gives you a good start point for further extending it where needed ;)

That's not really an issue, however, not possible built-in.

For ScriptableObject (and anything else that inherits from UnityEngine.Object) the Inspector by default draws an object field. If you want to draw something else you would need a custom editor script. There is not really away around customizing the Inspector in order to expose fiels of other UnityEngine.Object references in the Inspector of another one.

It could look somewhat like e.g. (for now assuming you only want the delay field to be exposed additionally)

#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;

[CustomEditor(typeof(ScriptableExpressionCreator))]
public class ScriptableExpressionCreatorEditor : Editor
{
    private SerializedProperty _list;

    // I know ... but that's your fault for naming th field List :P
    private ReorderableList _listList;

    // For Edito this is called when the object gains focus and the Inspector is loaded for this scriptableobject instance
    private void OnEnable()
    {
        // Find and link the serialized field called "List"
        _list = serializedObject.FindProperty(nameof(ScriptableExpressionCreator.List));

        // Initialize and configure the ReorderableList to draw the referenced elements in the way we want
        _listList = new ReorderableList(serializedObject, _list, true, true, true, true)
        {
            // How is te list header drawn?
            drawHeaderCallback = rect => EditorGUI.LabelField(rect, _list.displayName),

            // how should each element be drawn?
            drawElementCallback = (rect, index, active, focused) =>
            {
                // get the current element
                var element = _list.GetArrayElementAtIndex(index);

                // draw the default object reference field with the height of a single line without the label
                EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), element,  GUIContent.none);

                // if no object is referenced do nothing more
                if (!element.objectReferenceValue) return;

                // move one line lower
                rect.y += EditorGUIUtility.singleLineHeight;

                // get a serializedObject of the reference
                var elementsSerializedObject = new SerializedObject(element.objectReferenceValue);

                // same as in our own OnInspectorGUI method below loads the current values
                elementsSerializedObject.Update();

                // for now assuming you only want the delay field
                var delay = elementsSerializedObject.FindProperty(nameof(ScriptableExpressionBuilder.delay));

                // draw the delay field
                EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), delay);

                // same as in our own OnInspectorGUI method below writes back changed values and handles undo/redo marking dirty etc
                elementsSerializedObject.ApplyModifiedProperties();
            },

            // how do we get the height of one element?
            elementHeightCallback = index =>
            {
                // get the current element
                var element = _list.GetArrayElementAtIndex(index);

                // if nothing is referenced we only need a single line
                if (!element.objectReferenceValue)
                {
                    return EditorGUIUtility.singleLineHeight;
                }

                // otherwise we need two lines, one for the object field and one for the delay field
                return EditorGUIUtility.singleLineHeight * 2;
            }
        };
    }

    public override void OnInspectorGUI()
    {
        // draw the script field
        DrawScriptField();

        // loads current values into the serialized version
        serializedObject.Update();

        // draw the list according to the settings above
        _listList.DoLayoutList();

        // writes bac any changed values into the actual instance and handles undo/redo marking dirty etc
        serializedObject.ApplyModifiedProperties();
    }

    // Draws the default script field on the top the Inspector
    private void DrawScriptField()
    {
        // The script field is disabled since nobody is supposed to evr change it
        EditorGUI.BeginDisabledGroup(true);
        EditorGUILayout.ObjectField("Script", MonoScript.FromScriptableObject((ScriptableExpressionCreator)target), typeof(ScriptableExpressionCreator), false);
        EditorGUI.EndDisabledGroup();

        // leave a little space between the script field and the actual inspector conten
        EditorGUILayout.Space();
    }
}
#endif

For now this would look like this

enter image description here

Upvotes: 1

Related Questions