How do I expose a list of interfaces in the inspector?

This shouldn’t be that hard. After looking through many post of often outdated and/or opinionated discussions about the topic of exposing interfaces in the inspector, I haven’t gotten anywhere…

I am looking to have a list of interfaces and be able to assign objects to the list from the inspector. I am open to using a custom editor to solve this. Is this possible or should I just be creating a list of GameObjects and checking each one implements the interface?

The code so far:

public class InstructionSet : MonoBehaviour
{
    public List<ICompletableInstruction> Instructions;

    public bool IsComplete
    {
        get
        {
            return Instructions.All(i => i.IsComplete == true);
        }
    }
}

public interface ICompletableInstruction
{
    bool IsComplete { get; }
    bool CheckCompletion();
}

Thanks for any help.

1 Like

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.

So, I was trying to do the same thing and found this post, and Xarbrough’s answer (thanks) made it clear it’s not possible.

So for anybody else trying something similar, I was able to come up with at least a convenient approach to the strategy of maintaining a list of generic items and checking if they implement the interface later.

For example, if you have the OP’s example interface:

public interface ICompletableInstruction {
     bool IsComplete { get; }
     bool CheckCompletion ();
     void DoSomething (); // I added this for an example.
}

You can create a generic list (the challenge is choosing an appropriate base type, that’s your call):

public List<Component> Instructions;

And then you can do this if you want to e.g. call DoSomething on every member of the list:

Instructions.ForEach(c => (c as ICompletableInstruction)?.DoSomething());

And that will do the interface check on the fly and skip over items that don’t implement that interface or are just null. See C# null-conditional operator for more info.

For the equivalent of the OP’s example, you can use the C# null-coalescing operator:

return Instructions.All(o => ((o as ICompletableInstruction)?.IsComplete ?? true) == true);

Where you can choose ?? true if you want incompatible objects to evaluate to true there, or ?? false if you want them to be false, your call.

A small improvement to Xarbrough’s solution. OnGUI must also check whether
mouse is over its object field, otherwise it will break assigning references to other fields.

[CustomPropertyDrawer(typeof(TypeConstraintAttribute))]
public class TypeConstraintDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        if (property.propertyType != SerializedPropertyType.ObjectReference)
        {
            // Also check that the user only uses the attribute on a GameObject or Component
            // because we need to call GetComponent
            Debug.LogError(string.Format("{0} - {1}: This drawer must be used only on Object types",
                property.serializedObject.targetObject.GetType().ToString(), property.displayName));
            return;
        }

        var constraint = attribute as TypeConstraintAttribute;

        Event evt = Event.current;
        if (DragAndDrop.objectReferences.Length > 0 && position.Contains(evt.mousePosition))
        {
            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 (evt.type == EventType.DragExited)
                    Debug.LogError(string.Format("Object assigned to '{0}' must implement interface '{1}'", property.name, constraint.Type));
            }
        }

        property.objectReferenceValue = EditorGUI.ObjectField(position, label, property.objectReferenceValue, typeof(GameObject), true);

        //If a value was set through other means(e.g.ObjectPicker)
        if (property.objectReferenceValue != null)
        {
            // Check if the interface is present.
            Component go = property.objectReferenceValue as Component;
            if (go != null && go.GetComponent(constraint.Type) == null)
            {
                // Clean out invalid references.
                property.objectReferenceValue = null;
                Debug.LogError(string.Format("Object assigned to '{0}' must implement interface '{1}'", property.name, constraint.Type));
            }
        }
    }
}

This is an alternative solution that works pretty well.

Just write a function for List of Components that validates the all the components have the interface you want. You would still have to typecast the interface when you want to call it in your script, but at least you would be certain they have it.

Example

[SerializeField]
[ValidateInput("IsIPowerable", "the component must extend from IPowerable")]
private List<Component> powerables;
private bool IsIPowerable(List<Component> components)
{
    bool didRemove = false;
    for (int i = 0; i < components.Count; i++)
    {
        if(components[i] != null && !(components[i] is IPowerable))
        {
            components.RemoveAt(i);
            didRemove = true;
        }
    }
    return !didRemove;
}

This tool is very powerful and I use it in all my projects. Never have to think about writing custom editors again.

Here’s my more versatile generic SerializableInterface solution.

It’s just a single file which contains the wrapper class as well as the property drawer. It works not only with components but also with ScriptableObjects which implement that interface. You can drag a gameobject onto the field and it would automatically pick up the component that implements this interface. If multiple components on that gameobject implement that interface, it would display a drop down list for you to select which one you want to reference. At runtime it also caches a strongly typed variable for easy access.

I also made a SerializableMonoScript implementation that allows you to “store” a MonoScript / System.Type of a component or ScriptableObject in a field that can be used later to add that component type or create an instance of that class.

Here’s a working solution using the above method on GitHub. Includes Demo vid and example. Super lightweight- just add 2 files to your project.

https://github.com/TheDudeFromCI/Unity-Interface-Support