I've written some code that allows Unity3D's inspector to display fields that conform to an interface. Unity has some quirks about their inspector, so as a preface they are listed here:
- If you add a
[Serializable]
attribute to a class, Unity's Inspector will attempt to show all public fields inside that class. - Any class extending
Monobehaviour
automatically has the[Serializable]
attribute - Unity's inspector will attempt to display any private field with the
[SerializeField]
attribute. - Unity's inspector will not attempt to display generic types or interfaces, with the exception of
List<T>
, which is hard-coded. - Unity's inspector will not attempt to display properties. A common workaround is to have a private backing field for your property with
[SerializeField]
attached. Setters won't be called on the value set in the inspector. It's typically only set pre-compilation time, although a developer can modify values in the inspector during runtime. Currently it is acceptable to me to only take the initial value, although if anybody has a simple and efficient way to update successfully, I'd be happy to hear it. - Unity has a
PropertyDrawer
class you can extend to control how a type is displayed in the inspector. ThePropertyDrawer
for an interface or generic type will be ignored.
The code
UnityInterfaceHelper.cs
[Serializable]
public class UnityInterfaceHelperBase
{
[Tooltip("The component that is of the type required.")]
[SerializeField]
public Component target;
}
[Serializable]
public class UnityInterfaceHelper<TInterface> where TInterface : class
{
public TInterface TargetAsInterface
{
get
{
if (targetAsInterface == null)
{
targetAsInterface = target as TInterface;
}
return targetAsInterface;
}
set
{
if (targetAsInterface != value)
{
targetAsInterface = value;
if (value as Component != null)
{
target = targetAsInterface as Component;
}
}
}
}
[Tooltip("The component that is of the type required.")]
[SerializeField]
private Component target;
private TInterface targetAsInterface;
public static implicit operator UnityInterfaceHelper<TInterface>(UnityInterfaceHelperBase b)
{
return new UnityInterfaceHelper<TInterface>()
{
target = b.target
};
}
public static implicit operator UnityInterfaceHelperBase(UnityInterfaceHelper<TInterface> b)
{
return new UnityInterfaceHelperBase()
{
target = b.target
};
}
}
UnityInterfaceHelperPropertyDrawer.cs
[CustomPropertyDrawer(typeof(UnityInterfaceHelperBase), true)]
public class UnityInterfaceHelperPropertyDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
label = EditorGUI.BeginProperty(position, label, property);
position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
EditorGUI.PropertyField(position, property.FindPropertyRelative("target"), GUIContent.none);
EditorGUI.EndProperty();
}
}
Typical usage
[SerializeField]
private UnityInterfaceHelperBase itemComparable;
public IComparable ItemComparable
{
get { return ((UnityInterfaceHelper<IComparable>)itemComparable).TargetAsInterface; }
set { ((UnityInterfaceHelper<IComparable>)landedComparable).TargetAsInterface = value; }
}
public void CompareItems()
{
if(ItemComparable.CompareTo("Hello") == 0)
{
Debug.Log("Hello world!");
}
}
I'm looking to reduce the amount of code I have to repeat for every property, it's already down quite a way, but I'm hoping to make it as simple as possible, but any other comments on the code are welcome too.
-
\$\begingroup\$ If there's no other idea about this, maybe do some code generation, possibly via T4? \$\endgroup\$ferada– ferada2014年10月11日 00:23:47 +00:00Commented Oct 11, 2014 at 0:23
-
\$\begingroup\$ Check out the Full Inspector for Unity by Sient. No need to re-invent the wheel. I believe there is a trial version as well. The source code might also be available if you purchase it. jacobdufault.github.io/fullinspector \$\endgroup\$Steve– Steve2014年11月04日 16:53:08 +00:00Commented Nov 4, 2014 at 16:53
-
\$\begingroup\$ I like it, but it has a number of issues that I think are dealbreakers: 1) Having to make all classes inherit from their own Monobehaviour subclass, that's a lot of renaming, 2) Having to use a custom serialization toolkit to get non-Moonobehaviour types serialized. I do like their extra features on top, but they're not what I'm looking for (and not free either). \$\endgroup\$Nick Udell– Nick Udell2014年11月04日 17:01:18 +00:00Commented Nov 4, 2014 at 17:01
1 Answer 1
Possible bug:
If you get the value, targetAsInterface
is initialized. One can then set the value to something that is not a Component
. Like null
.
If you then get the value again, you'll get the old value of target
recasted to TInterface
. Seems to me that this violates what get
and set
are supposed to do.
-
\$\begingroup\$ Looks like you're correct. Perhaps I need to implement
PropertyChanged
notifications or something similar. \$\endgroup\$Nick Udell– Nick Udell2014年10月17日 08:40:01 +00:00Commented Oct 17, 2014 at 8:40