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.