Assigning Interface in Unity Inspector

How many times were you interested in assigning Interfaces into the Inspector in Unity? I was pretty often, but Unity doesn’t support that by default.

So if we can program, why don’t we add that by ourselves?

Ready? ?

Let’s start with the problem ?

As I already mentioned, Unity doesn’t support displaying C# interfaces in the Inspector. They probably have a good reason for that, but at some point, you fall in love with them, and you want to have them everywhere! ?

Unfortunately, we can’t just create a field with interface type…

using UnityEngine;

public class ExampleScript : MonoBehaviour
{
    public IExampleInterface interfaceReference;
}
You can’t assign a reference to the interface

Workaround ?

So because of that, we need to use what Unity is giving us (serialized fields) and make them work as interfaces.

First of all, the base class for all of the objects that can be referenced is “Object” class. We will use it as a base for our references in the Inspector.

using UnityEngine;

public class ExampleScript : MonoBehaviour
{
    public Object objectReference;
}
You can assign Unity Objects in Inspector!

Interfaces ?‍?

To make this idea work, we need three things: Interface, Attribute, and Drawer.

Let’s start with the Interface.

/// <summary>
/// Empty interface to mark valid reference.
/// </summary>
public interface IExampleInterface { }

Yeah, I know it’s pretty empty, but for our example purpose, it’s sufficient.

Next on our list is the Attribute. Recently, I made a post about creating ReadOnly attribute if you’re interested.

using UnityEngine;

/// <summary>
/// Attribute that require implementation of the provided interface.
/// </summary>
public class RequireInterfaceAttribute : PropertyAttribute
{
    // Interface type.
    public System.Type requiredType { get; private set; }

    /// <summary>
    /// Requiring implementation of the <see cref="T:RequireInterfaceAttribute"/> interface.
    /// </summary>
    /// <param name="type">Interface type.</param>
    public RequireInterfaceAttribute(System.Type type)
    {
        this.requiredType = type;
    }
}

This attribute will have a type of Interface that it’s looking for.

And finally, we can get to the Drawer. This one will be a little bit more complex.

using UnityEngine;
using UnityEditor;
using System.Linq;

/// <summary>
/// Drawer for the RequireInterface attribute.
/// </summary>
[CustomPropertyDrawer(typeof(RequireInterfaceAttribute))]
public class RequireInterfaceDrawer : PropertyDrawer
{
    /// <summary>
    /// Overrides GUI drawing for the attribute.
    /// </summary>
    /// <param name="position">Position.</param>
    /// <param name="property">Property.</param>
    /// <param name="label">Label.</param>
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        // Check if this is reference type property.
        if (property.propertyType == SerializedPropertyType.ObjectReference)
        {
            // Get attribute parameters.
            var requiredAttribute = this.attribute as RequireInterfaceAttribute;

            // Begin drawing property field.
            EditorGUI.BeginProperty(position, label, property);

            // Draw property field.
            property.objectReferenceValue = EditorGUI.ObjectField(position, label, property.objectReferenceValue, requiredAttribute.requiredType, true);

            // Finish drawing property field.
            EditorGUI.EndProperty();
        }
        else
        {
            // If field is not reference, show error message.
            // Save previous color and change GUI to red.
            var previousColor = GUI.color;
            GUI.color = Color.red;

            // Display label with error message.
            EditorGUI.LabelField(position, label, new GUIContent("Property is not a reference type"));

            // Revert color change.
            GUI.color = previousColor;
        }
    }
}

Our Attribute can only work with the reference type of properties. Otherwise, we will display a small error message in the Inspector.

Great! Now I think we have everything that we needed to make it work!

Example ?

So let’s get to the example of how to use our Attribute and Interface!

We basically have two options.

First, a simple one with the attribute and a reference.

using UnityEngine;

/// <summary>
/// This class contain example how to use RequireInterface attribute.
/// </summary>
public class ExampleScript : MonoBehaviour
{
    /// <summary>
    /// Working example of the RequireInterface attribute.
    /// </summary>
    [RequireInterface(typeof(IExampleInterface))]
    public Object simpleExample;
}

Second, with private reference and the property with the type of our Interface.

using UnityEngine;

/// <summary>
/// This class contain example how to use RequireInterface attribute.
/// </summary>
public class ExampleScript : MonoBehaviour
{
    /// <summary>
    /// Working example of the RequireInterface attribute.
    /// </summary>
    [SerializeField]
    [RequireInterface(typeof(IExampleInterface))]
    private Object _complexExample;
    public IExampleInterface ComplexExample => _complexExample as IExampleInterface;
}

The choice is yours! ?

But with this approach, you can assign components and scriptable objects!

Proof in action!

If you find this useful, let me know in the comment section below or share it with your friend! I would really appreciate that! ❤️

And if you are interested in getting emails when I release new posts sign up for the newsletter!

You can also check the whole project at my public repository. ?

See you next time! ?

4.6 19 votes
Article Rating
Subscribe
Notify of
guest
20 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Miyavi
Miyavi
4 years ago

Hi! I’m testing this with Unity 2019.3.0f6 (Personal) and it literally won’t let me assign anything to the public interfaces Objects in the inspector.

I’ve tried making my own test interface, but it’s the very same thing.

I see the reference in the Example GameObject to the ValidReference script correctly assigned, but as soon as I detach it, I’m unable to assign it again. The same happens if I try to reassign it even when it’s already assigned.

PS: This is from the downloaded repo from Bitbucket.

Kendall
Kendall
Reply to  Patryk Galach
4 years ago

Hi Patryk, would you be able to give an example of this or possibly update the post (since it may be a common question)? I’ve been digging around with this for a bit and so far haven’t managed any progress (due to my semi-limited understanding of types).

TMT
TMT
Reply to  Kendall
4 years ago

I was able to get it to work by modifying the following code:

property.objectReferenceValue = EditorGUI.ObjectField(position, label, property.objectReferenceValue, requiredAttribute.requiredType, true);

to this:

UnityEngine.Object obj = EditorGUI.ObjectField(position, label, property.objectReferenceValue, typeof(UnityEngine.Object), true);

if(obj is GameObject g) property.objectReferenceValue = g.GetComponent(requiredAttribute.requiredType);
Joel
Joel
Reply to  TMT
3 years ago

Thank you for that. I’ve notice that after this change I couldn’t select scripts on the same GameObject so I’ve used both options (not sure how to post code):

Screenshot 2021-03-10 120854.png
Tyler
Tyler
Reply to  Joel
3 years ago

You are a god, this worked for me!

Marco
Marco
Reply to  Joel
3 years ago

this worked for me:

            var reference = EditorGUI.ObjectField(position, label, property.objectReferenceValue, typeof(UnityEngine.Object), true);
            if (reference is null) {
                var obj = EditorGUI.ObjectField(position, label, property.objectReferenceValue, typeof(Object), true);
                if (obj is GameObject gO) {
                    reference = gO.GetComponent(requiredAttribute.requiredType);
                }
            }
            property.objectReferenceValue = reference;
Michael
Michael
Reply to  Marco
1 year ago
                // Draw property field.
                var reference = EditorGUI.ObjectField(position, label, property.objectReferenceValue, typeof(Object), true);
                if (GUI.changed && reference is GameObject @object)
                {
                    reference = @object.GetComponent(requiredAttribute.requiredType);
                }
                property.objectReferenceValue = reference;
Andru
Andru
Reply to  Marco
1 year ago

In Unity 2022 reference is not null but game object. I am uncertain if is only me, or constant, but it was easy to bug fix. Working version:

var reference = EditorGUI.ObjectField(position, label, property.objectReferenceValue, typeof(UnityEngine.Object), true);
if (reference is null) {
    var obj = EditorGUI.ObjectField(position, label, property.objectReferenceValue, typeof(System.Object), true);
    if (obj is GameObject gO) {
        reference = gO.GetComponent(requiredAttribute.requiredType);
    }
}else
if (reference is GameObject) {
    if (reference is GameObject gO) {
        reference = gO.GetComponent(requiredAttribute.requiredType);
    }
}
property.objectReferenceValue = reference;
GDK
GDK
Reply to  Andru
1 year ago

Where did you add this code?

Ian
Ian
3 years ago

Is there a way to use this in a stand-alone build? The ReqiredInterface is dependent on the UnityEditor being loaded.

Ian
Ian
Reply to  Patryk Galach
3 years ago

I figured it out shortly after commenting, but with the comment approval system I couldn’t see how to edit my pending comment.

I’d just made the mistake of putting all of the files in game project, when actually the RequiredInterfaceDrawer file needs to be in the editor project.

Sven Akelian
Sven Akelian
3 years ago

Great article thanks.
Any idea on how to handle list fields?

JRG
JRG
3 years ago

Hi, thanks a lot for this, super useful in what I’m doing. I would like to ask, how would you specify the “set” parameter in the property for the private declaration in the example? I’m currently using a separate method in order to set the value of _complexExample from an external script.

Thanks!

Danil
Danil
3 years ago

Just get my love♥

Oddlord
Oddlord
1 year ago

That’s amazing, it works beautifully!
I’ve been looking a lot for a solution like this one, thanks!

Do you know how I could modify the attribute and drawer code to make them work with arrays/lists of interfaces?

Cheers!

Michael
Michael
1 year ago

Hello! I have a question: what should happen in order to display the message “Property is not a reference type”?

I was unable to reproduce this use case…

Michael
Michael
1 year ago

It is wonderful! I don’t know how but it works for List<> Thanks a lot!

20
0
Would love your thoughts, please comment.x
()
x