I’ll try to give a high-level recap:
Interfaces are currently not serializable by Unity and therefore don’t show in the inspector. It is not likely for Unity to add support soon (maybe ever).
In many cases, you can use a abstract MonoBehaviour base class to essentially provide the same functionality, unless you require a class to implement several interfaces (more than one base class).
Unity’s GetComponent calls find Interfaces, so it’s a valid strategy to only serialize references to GameObjects und check for the existence of a specific interface, e.g. an IDamageable interface which is queried OnTriggerEnter.
Interfaces do not have any connection to the object they are implemented on, this is way Unity doesn’t serialize them. We can create this connection ourselves, as mentioned, by serializing the reference to an actual object, that Unity can handle (everything that inherits from UnityEngine.Object, mainly GameObject, MonoBehaviour and ScriptableObject) and then checking if the interface is still implemented.
There are multiple open-source and commercial solutions online. I can’t recommend any, but I assume they all work very similarly. For plain C# classes you could also think about using a reflection-based approach by storing a string representation of the class and interface and then using the Activator class from the .NET framework to create instances when you need them. Existing solutions probably use one of the two approaches. The main differences come in how the editor/inspector is designed. In any case, interfaces are not actually serialized (even though some products claim), instead, other related data like GameObjects and strings are serialized, but the custom editor provides a mechanism to prevent user error when assigning values, etc.
You can write a simple usable version by creating your own ObjectField which checks for the presence of an interface on the object before allowing to save it.
Here is a very rough example:
using UnityEditor;
using UnityEngine;
public class StoreInterfaceReference : MonoBehaviour, MyInterface
{
[TypeConstraint(typeof(MyInterface))]
public GameObject myReference;
public void Do()
{
}
}
public interface MyInterface
{
void Do();
}
public class TypeConstraintAttribute : PropertyAttribute
{
private System.Type type;
public TypeConstraintAttribute(System.Type type)
{
this.type = type;
}
public System.Type Type
{
get { return type; }
}
}
[CustomPropertyDrawer(typeof(TypeConstraintAttribute))]
public class TypeConstraintDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (property.propertyType != SerializedPropertyType.ObjectReference)
{
// Show error
// Also check that the user only uses the attribute on a GameObject or Component
// because we need to call GetComponent
}
var constraint = attribute as TypeConstraintAttribute;
if (DragAndDrop.objectReferences.Length > 0)
{
var draggedObject = DragAndDrop.objectReferences[0] as GameObject;
// Prevent dragging of an object that doesn't contain the interface type.
if (draggedObject == null || (draggedObject != null && draggedObject.GetComponent(constraint.Type) == null))
DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
}
// If a value was set through other means (e.g. ObjectPicker)
if(property.objectReferenceValue != null)
{
// Check if the interface is present.
GameObject go = property.objectReferenceValue as GameObject;
if (go != null && go.GetComponent(constraint.Type) == null)
{
// Clean out invalid references.
property.objectReferenceValue = null;
}
}
property.objectReferenceValue = EditorGUI.ObjectField(position, label, property.objectReferenceValue, typeof(GameObject), true);
}
}
You can see, it’s not too difficult to create a convenient system which let’s designers only drag interface references to GameObject fields, but it can get quite complex, if you want the system to work in every situation, there are quite a few edge-cases in my experience.
Additionally, instead of using a GameObject and GetComponent, you can also create a wrapper type and custom editor to automatically call GetComponent and maybe cache it for you.