I thought about how to pass an interface with SerializeField [MonoBehaviour is also OK]

5 minute read

Unity’s SerializeField is useful, but you can’t pass an interface in the usual way.
With this, even if you make an interface, it will be passed directly to the class, which is sad.
In this article, I’ll show you how to force an interface with SerializeField!

  • This is Qiita’s first post!
    Originally it was written on my personal blog, but I decided to write niche content on Qiita.
    We will continue to move articles to Qiita from now on, so thank you.

Thing you want to do

It has some components such as:


public class SomeComponent : MonoBehaviour
{
    public void SomeMethod()
    {
        print("SomeMethod has been executed!");
    }
    
    //~~~~~~There are various other functions below
    //・
    //・
    //・
}

Suppose you want to pass this component to another component through the Inspector and only call __SomeMethod () __.
However, you don’t want to pass it in a state where you can access all other functions even though you only execute __SomeMethod (), right? __
So, create the following interface …

public interface ISomeMethodInvokable
{
    void SomeMethod();
}

Let the component implement it …

public class SomeComponent : MonoBehaviour, ISomeMethodInvokable { /*abridgement*/ }

I want to pass a component reference through an interface.

public class SomeMethodInvoker : MonoBehaviour
{
    [SerializeField]
    ISomeMethodInvokable m_someMethodInvokable;

    private void Start() 
    {
        m_someMethodInvokable.SomeMethod();
    }
}

However, as you all know, this cannot be done in the usual way.
Then what should we do? If you search, the following two methods will come out.

“How to pass Interface in Inspector” that appears when you search

There are two main ways to pass an interface from the Inspector by searching.

–How to use SerializeReference attribute
–How to use the paid asset “Odin”

How to use the SerializeReference attribute

The first is to use the SerializeReference attribute.

It seems that interfaces and abstract classes can be serialized by using this SerializeReference attribute instead of the SerializeField attribute.
It is not possible to serialize a class (that is, a component) that inherits from __MonoBehaviour. __

This time, the purpose is to pass the interface implemented in the component, so this is a crap.

How to use the paid asset “Odin”

The second method is to use the paid asset “Odin”.

If you use the paid asset “Odin”, you can pass a class that inherits MonoBehaviour from the __Inspector via the interface __ That’s right.
In other words, __ If you buy this asset, all the problems will be solved. __

However, the price is about $ 55. Well, it’s slightly expensive …

Both are subtle …

So both are subtle.
As a result of thinking that I could manage to realize it on my own, I created the following method.

“How to pass Interface in Inspector” that I thought about by myself

No matter what you do in the usual way, you can’t pass an interface from the Inspector.

But when you think about the purpose in the first place, what you want to do is not “pass the interface in the inspector” but __ “get the component accessed through the interface” __, right?
Then, why not wrap the component that implements the __ interface and pass a class that is exposed only in the interface type? I came up with the idea of __.

The procedure is shown below.

Create a class that wraps GameObject and returns only Interface

Create a generic class like this:


[Serializable]
public class SerializeInterface<TInterface>
{
    [SerializeField]
    private GameObject m_gameobject;

    private TInterface m_interface;

    public TInterface Interface
    {
        get
        {
            if(m_interface == null)
            {
                m_interface = m_gameobject.GetComponent<TInterface>();
            }
            return m_interface;
        }
    }
}

The private field of GameObject type with SerializeField is the part that is actually associated with the inspector.

Since the interface cannot be passed in SerializeField, you will end up passing GameObject, but it is this interface type that is exposed to the outside of the __ class because the interface is specified by generics. It becomes _. __The game object itself is hidden inside, isn’t it?

Pass the interface from the inspector using the created class

Now, let’s apply SerializeField to the class created earlier and actually pass the interface from the inspector.

Unfortunately, the generics class is ignored even if it is SerializeField. Therefore, define a non-generic version of the class as an inner class and apply SerializeField to that class.


public class SomeMethodInvoker : MonoBehaviour
{
    [SerializeField]
    SerializeISomeMethodInvokable m_someMethodInvokable;

    [Serializable]
    private class SerializeISomeMethodInvokable : SerializeInterface<ISomeMethodInvokable> { }
}

Again, the SerializeInterface class we created earlier has a GameObject inside, but only the __ interface is exposed to the outside __. Therefore, the user’s code can only access the GameObject passed in the SerializeField from the __ interface __.

Let’s actually access the interface.


public class SomeMethodInvoker : MonoBehaviour
{
    [SerializeField]
    SerializeISomeMethodInvokable m_someMethodInvokable;

    private void Start() 
    {
        m_someMethodInvokable.Interface.SomeMethod();
    }

    [Serializable]
    private class SerializeISomeMethodInvokable : SerializeInterface<ISomeMethodInvokable> { }
}

You are accessing the interface within the Start method. You can only access the inside from the Interface property, so you will always access it through the interface __.
I didn’t pass the interface itself in a SerializeField, but I think the original purpose of __ “accessing through the interface” was achieved.

Try using

Now, let’s actually attach it to the GameObject and pass the interface from the inspector.

First, prepare the side that receives the interface.
When I attach SomeMethodInvoker to the GameObject, it looks like this:

image.png

GameObject can be specified.

Now, let’s attach a component (GameObject to which is attached) that implements the ISomeMethodInvokable interface to this field.

image-1.png

Attached. I will try it.

image-2.png

I was able to properly access members of another component through the interface!

If you do not implement the specified interface

If you pass and run a component that does not implement the specified interface, you will get an error similar to the following:

image-3-1024x54.png

This is because null is returned in the part of GetComponent of the interface in the SerializeInterface class.
You can display the following accurate error message with the following code.


[Serializable]
public class SerializeInterface<TInterface>
{
    [SerializeField]
    private GameObject m_gameobject;

    private TInterface m_interface;

    public TInterface Interface
    {
        get
        {
            if(m_interface == null)
            {
                m_interface = m_gameobject.GetComponent<TInterface>();
                if (m_interface == null)
                {
                    throw new Exception($"GameObject\"{m_gameobject.name}\"Is{typeof(TInterface).Name}I haven't attached a component that implements");
                }
            }
            return m_interface;
        }
    }
}

image-5-1024x52.png

It’s a more accurate error and easier to understand!

Summary

From the above, I found that I can pass the interface in the inspector by myself, though it is troublesome!

However, you have to define the non-generic version of __SerializeInterface while crying every time, and don't drop it with an exception __ I want you to play the interface not implemented in the inspector in the first place __ etc. I'm dissatisfied, but I'd like to do my best with this for the time being. Thank you until the end!