Unity won't even let me draw my interface property drawer? WTF?!?! Y U No support interfaces????

I was hoping I’d get to write a property drawer that would allow me to assign interface fields from the inspector.

SerializeInterfaceAttribute.cs

using UnityEngine;

public class SerializeInterfaceAttribute : PropertyAttribute
{
	private System.Type type;
	public System.Type Type { get { return type; } }
	public SerializeInterfaceAttribute(System.Type type)
	{
		this.type = type;
	}
}

SerializeInterfaceDrawer.cs

using UnityEngine;
using UnityEditor;
using System.Linq;

[CustomPropertyDrawer(typeof(SerializeInterfaceAttribute))]
public class SerializeInterfaceDrawer : PropertyDrawer
{
	public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
	{
		// get the type
		var sid = attribute as SerializeInterfaceAttribute;
		System.Type iType = sid.Type;

		// find all the implementors
		string[] dlls = Utils.GetProjectDlls(d => !d.Contains("Editor"));
		System.Type[] validTypes = iType.GetAllConcreteChildren(dlls);

		// make a MB object field
		EditorGUI.BeginChangeCheck();
		MonoBehaviour field = EditorGUI.ObjectField(position, iType.ToString(), property.objectReferenceValue, typeof(MonoBehaviour), true) as MonoBehaviour;
		if (EditorGUI.EndChangeCheck()) {
			// if a proper type is not assigned throw an exception
			if (!field.GetType().IsSubclassOf(iType)) {
				throw new UnityException("Invalid type assignment. Type must be of " + iType);
			}
			property.objectReferenceValue = field;
		}
	}
}

Utils.cs

public static class Utils
{
	public static string[] GetProjectDlls(Func<string, bool> predicate)
	{
		string[] dlls = Directory.GetFiles("Library\\ScriptAssemblies", "*.dll");
		List<string> validDlls = new List<string>();
		foreach(var dll in dlls)
			if (predicate(dll))
				validDlls.Add(dll);
		return validDlls.ToArray();
	}
}

OtherExtensions.cs

public static class OtherExtensions
{
	public static Type[] GetAllConcreteChildren(this Type type, Assembly asm = null)
	{
		if (asm == null) asm = Assembly.GetExecutingAssembly();
		var types = asm.GetTypes();
		return types.Where(t => t.IsSubclassOf(type) && !t.IsAbstract).ToArray();
	}
	public static Type[] GetAllConcreteChildren(this Type type, string[] dlls)
	{
		List<Type> allTypes = new List<Type>();
		foreach (var dll in dlls) {
			var types = type.GetAllConcreteChildren(Assembly.LoadFile(dll));
			foreach (var t in types) {
				allTypes.Add(t);
			}
		}
		return allTypes.ToArray();
	}
}

Usage:

(better to be placed in separate files)

public interface IMyInterface { }

public class A : MonoBehaviour, IMyInterface { }

public class B : MonoBehaviour, IMyInterface { }

public class TestMB : MonoBehaviour
{
   [SerializeInterface(typeof(IMyInterface))
   IMyInterface iField;
}

What should happen, is that you’ll get a MB object field for iField, if you assign a MB that is not of type IMyInterface, an exception will get thrown, otherwise the value will be assigned.

But guess what?!

The OnGUI of the drawer ISN’T EVEN EXECUTING!

No debug logs, no breakpoints, it just won’t budge!

If you notice, I could pass any type, not just an interface, so if we modify our code a bit:

public abstract class Abstract : MonoBehaviour { }

public class A : Abstract { }

public class B : Abstract { }

public class TestMB : MonoBehaviour
{
   [SerializeInterface(typeof(Abstract))
   Abstract iField;
}

It will actually work as expected.

I have no words to express, other than the Unity serialization is retarded!

I made this attempt cause I saw this which kind of encouraged me to try to make my own way to get an interface field visible somehow…

I wonder how he did it.

Anyway, question is, is there possibly any way to just, get the drawer OnGUI to execute? and why isn’t it getting executed? And, if that won’t work, how is it possible to do what I want, in a modular, reusable and plug-n-play manner?

Thanks for any help.

Hi vexe, I got your e-mail - thanks for linking to my thread, I appreciate it.

Taking a quick look at your code and narrowing it down to your specific question regarding why your property drawer’s OnGUI method isn’t even getting executed I can tell you that, as you’ve likely deduced yourself already, Unity simply does not serialize interfaces or fields of an interface type (even explicitly decorating the field with the [SerializeField] attribute will do nothing). Unity will serialize primitives, certain Unity-defined derived types (like Component, MonoBehaviour, ScriptableObject, etc), as well as classes decorated with the [Serializable] attribute, and maybe some others types I’m not aware of, but that’s basically it. And if Unity doesn’t serialize something, then as far as the editor is concerned it almost doesn’t exist - property drawers will not get invoked for it and even if you could somehow manage it, any assignment made to a non-serialized field will be lost between sessions and entering/exiting play mode.

However, we do have enough to come up with a generic solution to expose assignable (and serialized) “interface fields” in the inspector if you are willing to put in some work and dance within some Unity-enforced constraints. I’ll describe to you the approach I’ve taken in my asset, maybe it’ll help you or someone else figure something better out in the future (although I honestly think this is as good as it gets the way Unity currently does things):

Basically, what I have is an abstract generic container class which contains a Component (which MonoBehaviours end up deriving from) field and a property of the generic type which will cast the Component to the generic type when accessed (casting only once and remembering the result during runtime). The reason for making it abstract is to require a class to derive from it since raw generics are not serialized by Unity (I know, right?) but if you explicitly resolve the generic types in a deriving class decorated with the [Serializable] attribute, then Unity will indeed serialize it. So that gives us the core functionality.

Now that we have something Unity can serialize we can write a property drawer for it that will get invoked and pretty much do whatever we want to display and assign it - just remember that Unity will only “remember” the Component field. That’s the key here - we technically can’t serialize an actual interface field, but if the thing implementing it is serializable, well we can serialize that and cast it to our interface when we need to via the generic property. I’ve also added the logic necessary to be able to assign anything that implements the interface in code, as not being able to kinda defeats the whole point of an interface, but this is limited to runtime assignment in code only. I’ve found this to be perfectly acceptable as the only thing I’m likely to even assign in the editor would be a MonoBehaviour anyways (how would you drag and drop anything else in the editor, right?).

So that’s the basic idea, there’s quite a bit more work necessary (and some that’s just nice to make it easier to use - like a custom selection list showing all implementing MonoBehaviours in the scene) but it mostly presents itself logically once you have the core concept. I wouldn’t mind helping out if you have more specific questions, but I would of course encourage people to simply buy the asset (it’s only $5) which would give you access to the code itself.

Good coding to you!