Can a ScriptableObject contain a List of ScriptableObjects?

I have a ScriptableObject (“monster defn list”) which contains a list of objects (“monster defns”) - when those contained objects are [Serializable], everything works fine; but if I instead make them ScriptableObjects, then the Inspector complains with “mismatched type” when viewing them. Should this work? Note that I’m on the 5.4 beta at the moment.

To see the issue; the following code creates a new Editor Window that can be opened with Control+Shift+m - click the “create list” button and then click the “create defn” button a few times. Select the created asset (in /Assets/MonsterDefnList) in the inspector, and notice that the defns look fine (as they are currently [serializable]). now delete that asset, and uncomment the first line of the code below and repeat; now the inspector will instead tell you “Type Mismatch” for those defns.

// #define UseScriptableObject
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;

#if UseScriptableObject
public class MonsterDefn : ScriptableObject
#else
[System.Serializable]
public class MonsterDefn
#endif
{
	public int MaxHP;
}

public class MonsterDefnList : ScriptableObject
{
	public List<MonsterDefn> MonsterDefns;
}

public class MonsterDefnEditor : EditorWindow
{
	public MonsterDefnList monsterDefnList;

	[MenuItem("Window/Monster Defn Editor  %#m")]
	static void Init()
	{
		EditorWindow.GetWindow(typeof(MonsterDefnEditor));
	}

	void OnEnable()
	{
		monsterDefnList = AssetDatabase.LoadAssetAtPath("Assets/MonsterDefnList.asset",
							typeof(MonsterDefnList)) as MonsterDefnList;
	}

	void OnGUI()
	{
		if (GUILayout.Button("Create New MonsterDefn List")) {
			monsterDefnList = ScriptableObject.CreateInstance<MonsterDefnList>();
			monsterDefnList.MonsterDefns = new List<MonsterDefn>();
			AssetDatabase.CreateAsset(monsterDefnList, "Assets/MonsterDefnList.asset");
			AssetDatabase.SaveAssets();
		}

		if (GUILayout.Button ("Add MonsterDefn")) {
#if UseScriptableObject
			MonsterDefn newMonsterDefn = ScriptableObject.CreateInstance<MonsterDefn>();
#else
			MonsterDefn newMonsterDefn = new MonsterDefn ();
#endif
			monsterDefnList.MonsterDefns.Add (newMonsterDefn);
		}

		if (GUI.changed) EditorUtility.SetDirty(monsterDefnList);
	}
}

Yes, it is supported, however it won’t work the way you use it. ScriptableObjects need to be explicitly saved as asset. They can’t be serialized along with another ScriptableObject like “normal” serializable classes can. References to ScriptableObjects are actually serialized as asset references. That’s why each ScriptableObject need to be an asset in your project.

Keep in mind that you can use AssetDatabase.AddObjectToAsset to add multiple assets into the same asset file. However due to the way Unity stores multiple assets in the same file it can happen that one of your “child” assets become the “main asset”.

On a related note, I believe I figured out what’s causing the parent/child relationship issue as well as a workaround. It actually has to do with the naming of the asset file vs. the name of the subassets (!). I’ve logged a bug with Unity, but here’s a simple test case that (a) repros the issue and (b) shows how to work around it:

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

// note to viewer: Unity needs this to be in a separate file, otherwise it'll
// fail to load; included in this file just for repro purposes.
public class MyItemDefn : ScriptableObject 
{
}

public class MyItemDefnList : ScriptableObject 
{
    public List<MyItemDefn> Defns;
}

public class MyItemDefnEditor : EditorWindow
{
    MyItemDefnList itemDefnList1;
    MyItemDefnList itemDefnList2;

    [MenuItem("Window/MyItemDefn Editor %#m")]
    static void Init()
    {
        EditorWindow.GetWindow<MyItemDefnEditor>();
    } 

    void OnGUI()
    {
        if (GUILayout.Button ("Run Test")) {            

            // The bug: When adding a ScriptableObject as a child of a ScriptableObject,
            //   the inspector can get confused about which is the parent and which
            //   is the child, and display the containing object as one of the children
            //
            // Repro'ed in: Unity 5.4.0b12 (beta), but it sounds like this has been
            //   around for a while.
            //
            // The reason this sometimes happens: If the filename of the main asset is
            //   alphabetically after the name of a child asset, then the inspector
            //   misidentifies which is which and the child asset appears as the main
            //   asset (+vice versa).
            //
            // To workaround: Name your main/containing asset's file something
            //   alphabetically before all child assets.  e.g.: append with "aaa"
            //

            // This one will fail; the main asset appears as the last asset in the container
            itemDefnList1 = ScriptableObject.CreateInstance<MyItemDefnList>();
            itemDefnList1.Defns = new List<MyItemDefn> ();
            createTestAsset ("zzzTestAsset", itemDefnList1);

            // This one will succeed; the main asset will appear as the parent asset
            itemDefnList2 = ScriptableObject.CreateInstance<MyItemDefnList>();
            itemDefnList2.Defns = new List<MyItemDefn> ();
            createTestAsset("aaaTestAsset", itemDefnList2);

            AssetDatabase.SaveAssets();
        }

        if (GUI.changed) {
            EditorUtility.SetDirty (itemDefnList1);
            EditorUtility.SetDirty (itemDefnList2);
        }
    }

    void createTestAsset(string assetName, MyItemDefnList itemDefnList)
    {
        // Save the container asset with the specified name
        string path = AssetDatabase.GenerateUniqueAssetPath ("Assets/"+assetName+".asset");
        AssetDatabase.CreateAsset(itemDefnList, path);

        // Create some test items and add them to the container asset.
        for (int i = 0; i < 5; i++)
        {
            MyItemDefn newMyItemDefn = ScriptableObject.CreateInstance<MyItemDefn> ();
            newMyItemDefn.name = "jjj " + i;
            itemDefnList.Defns.Add (newMyItemDefn);
            AssetDatabase.AddObjectToAsset (newMyItemDefn, itemDefnList);
        }
    }
}

Pretty crazy, and a fun one to track down =). Thanks again to @bunny83!