- Home /
How to make a List that can store anything that implements an interface?
I recently hit a dilemma. I want to make something like a file explorer, where I want to show/store a List
of ScriptableObjects
that implements something like a "file" interface. I already figured out most of the parts. I already have this code for the container script:
public class FileContainer : MonoBehaviour, IFileContainer
......
[SerializeField]
List<FileItem> _contents;
public List<FileItem> contents {
get{ return _contents; }
set{ _contents = contents; }
}
And have a ScriptableObject
which looks something like this:
public class BlahFile : ScriptableObject, ..., IFileItem
If I use the interface directly as the type of List on FileContainer, the List's variable doesn't show up on the editor. Then if I use a class that implements the interface, then I can't add any other object because I can only use that specific class.
What is the cleanest way to implement what I want to do? I tried to use an abstract class for the ScriptableObject
but it creates a "multiple classes" error so I didn't go with it.
Answer by rickymanalo · Nov 03, 2021 at 01:35 PM
After some thinking, I found a solution that's kinda similar to @Bunny83's first suggestion that makes sense for me. What I did is I made a class FileItem
that implements IFile
that also extends ScriptableObject
. Then I made every field on the class a SerializeField
. So now BlahFile
extends FileItem
, I think this could work for my use case.
Answer by Bunny83 · Nov 02, 2021 at 09:27 AM
I think you're lost between different concerns here. Using a List of an interface type works just fine in C# and you can Add and use those as expected in code. However you will not get any editor support for lists of interfaces. At least it seems that's your main issue:
If I use the interface directly as the type of List on FileContainer, the List's variable doesn't show up on the editor.
References to assets or generally to UnityEngine.Object derived objects can only be serialized when the field type is derived from UnityEngine.Object.
Note that there are some more or less hacky ways around that, but all require quite a bit of boilerplate and editor code. One way is to actually store a List / array of UnityEngine.Object in the class. That way it could reference any asset. In addition you would have another List of your interface type and use the ISerializationCallbackReceiver to actually fill / synchronise your Lists. Of course in order to reject any invalid references you may use a PropertyDrawer that takes care of the filtering. Though the synchronising code should be careful as well since it's possible that you added for example a scriptable object that used to have the interface but later you removed the interface. So the Object array would still hold the reference but it no longer has the interface so the cast would fail.
Another approach that could work is to use unity's SerializeReference feature. It allows to serialize anything and does even support interfaces. However those references can't be across assets. So it only supports custom serializable classes which are stored inside that MonoBehaviour or ScriptableObject that contains your List. For those it actually supports inheritance and polymorphism. So it may be possible to create wrapper classes that implement your interface and store the actual reference to the asset in a private serialized field that is of type UnityEngine.Object. This has several issues though. First of all fields with SerializeReference don't have editor support out-of-the-box. So you have to handle the creation of those wrappers in your editor code. Also the actual casting from the serialized value to the interface type is still required.
Personally I think the first approach would be simpler. It could even be packed into a custom "InterfaceList" type that does handle all this conversion stuff internally.
Answer by Llama_w_2Ls · Nov 02, 2021 at 08:10 AM
Have your abstract class inherit from scriptable object. Then have your child classes inherit from the abstract class. They will inherit the properties of a scriptable object and the base class, essentially allowing for multiple inheritance whilst still allowing scripts to show in the editor.
Answer by Arycama · Nov 02, 2021 at 08:52 AM
You can make IFileContainer and IFileItem inherit from a common interface, eg IFileData, and then have a list of IFileData.