Problem
It is not possible to assign an interface-type field in the inspector. The field does not show up.
public interface ICharacterData { ... }
[CreateAssetMenu]
public class CharacterData : ScriptableObject, ICharacterData { ... }
public class Demo : MonoBehaviour
{
[SerializeField] private CharacterData data1; // works
[SerializeField] private ICharacterData data2; // does not work
}
Why are interface-type fields not serializable?
Unity’s serialization system needs to know the type of the serialized field, because it will do something different depending on the type. Basic types, structs, enums, built-in types, and serializable types gets serialized by value, similar to how JsonUtility would serialize them. Anything derived from UnityEngine.Object (including ScriptableObject, MonoBehaviour, and any asset in the project) gets serialized by reference instead, i.e. like this:
data1: {fileID: 11400000, guid: 961cd3964a6354efb9ad01cbfae84d63, type: 2}
In order for the serialization system to determine whether to serialize a field by value or by reference it needs to know whether the type is derived from UnityEngine.Object or not.
In our example, ICharacterData could be anything. We could have a serializable class implementing the interface. We could also have a ScriptableObject asset implementing it. The serialization system can not know which case we’re dealing with and the inspector can not know whether to show an object field or a value field.
But interface-type fields are serializable!
Since Unity 2019.3 interface-type fields are serializable. Instead of the SerializeField attribute, we have to use the SerializeReference attribute.
Problem solved, right? …
public class Demo : MonoBehaviour
{
[SerializeField] private CharacterData data1; // works
[SerializeReference] private ICharacterData data2; // still does not work
}
Contrary to what the name of the attribute implies, SerializeReference serializes the field by value, not by reference.
It does solve an important problem related to serializing polymorphic types by value, but it does not solve our problem.
Just use ScriptableObject!
The documentation suggests that we should use ScriptableObject here. That’s a great suggestion. In fact, we are using ScriptableObject here. We just want to use an interface as the field type for it.
Why though? Why are we being so stubborn? Why not use the concrete type instead?
What is so special about interfaces?
Remember the diamond problem? It leads to tricky ambiguities. That’s why C# only allows single inheritance. In C# a class can not inherit from multiple base classes at the same time. It can, however, implement multiple interfaces at the same time. That’s because interfaces don’t have the diamond problem. Interfaces have superpowers.
Programming in C# means programming with interfaces. If you don’t do that then you’re seriously limiting the kinds of inheritance patterns that can be used. Chances are you won’t notice the problem until the codebase is very large, because concrete types and abstract base classes go a long way. Until they don’t. At which point the only way forward is to build a parallel hierarchy of interfaces and replacing all uses of concrete types with their respective interface type.
Except doing so breaks serialized fields.
I can give you multiple examples of real world codebases of real games where using interfaces became a necessity, but those examples are all big, very specific, and hard to understand without context. For now, please just take my word for it: interfaces are important. Being able to refer to a type by interface is important. Other programmers reading this might want to confirm.
What can we do about it?
public class Demo : MonoBehaviour
{
[SerializeField, Restrict(typeof(ICharacterData))]
private UnityEngine.Object _data; // works
public ICharacterData data => _data as ICharacterData;
}
For our field we want Unity to show an object field in the inspector. So we might as well use UnityEngine.Object as the type. However, we only want to be able to assign objects that implement our interface. We can restrict what can be assigned to the object field by implementing a custom attribute and property drawer.
That essentially solves our problem. It’s a bit clunky and the extra get-only property with cast smells bad, but it works. Without help from Unity, this is as good as it gets.
Feature request
public class Demo : MonoBehaviour
{
[SerializeField] private ICharacterData data; // this should just work
}
Unity can serialize references to assets. In fact, Unity can serialize references to assets via base type. Assigning a CharacterData asset to a field of type UnityEngine.Object via the inspector works just fine. Unity serializes the reference to the asset and on deserialization Unity looks up the concrete asset by reference (fileId, guid, type) and assigns it to the field.
I don’t see any reason why it would be impossible to do the same for interface-type fields.
It wouldn’t even require a new attribute. Just use SerializeField. The semantics are unambiguous:
- If the type is an interface or inherited from UnityEngine.Object, serialize by reference.
- In all other cases, serialize by value.
It wouldn’t even clash with SerializeReference. Users who want to have their interface-typed field serialized by value can still do so.
TL;DR: Please support serializing interface-type fields the same as UnityEngine.Object type fields.