Exposing fields with Interface type (C#) [SOLVED]

Hi Guys,

I had trouble properly wording the title of this post so if I messed it up I do apologize.
My problem is that when I expose a field with type of some Interface the Editor doesn’t display it in the properties list of an object to which this script is assigned.

So, this works just fine :

...
public List<object> Lights;
...

but this doesn’t, I cannot see the field in the editor:

...
// Where ILight is an Interface defined elsewhere in the code
public List<ILight> Lights;
...

It’s a disadvantage that the second method doesn’t work. In order to get the desired effect I use a CLight (class) in place of ILight but that makes implementation a bit dirty. Since I may have an object that implements ILight and ISwitch at the same time I don’t want to have CLightWithSwitch inherit from CLight just so that we could assign it to another object’s properties in the editor.

If this is a limitation of some sort then let me tell you what I’m after and maybe you could recommend a better approach to solving this issue altogether.

I have a simple switch class CSwitch (which would have been an Interface if not for the problem above) that lets me toggle an objects state to On or Off. It has a SetState() and GetState() methods exposed. I also have a class CLightWithSwitch that inherits CLight (this actually controls the light) which in turn inherits CSwitch. I attach CLightWithSwitch to a prefab that has a Light (unity light that is) as one of its children.
So, when the player clicks on the object in game I simply call the CLightWithSwitch.SetState(somestate) and the prefab reacts accordingly (i.e turns the light on or off).

Now this works fine if I have just the one prefab I want to affect. But there are cases when there is a light switch in the room that should set states of 10 light prefabs or more. So for this case I made a CSwitchRelay which inherits CSwitch but adds a collection property (as per the problem described above) List Switches. Its SetState() method is also modified to iterate the list and set the state of all items to the desired state.

CSwitchRelay is then assigned to the actual light switch object in the scene. I then click on the Switches property in the editor and select what lights I want to affect with this SwitchRelay. So when an object representing a light switch is clicked in game all of the lights assigned to the Lights property go On or Off.

So, my original intention was to use ISwitch interfaces so that any object that needs to be turned on or off in the game could just implement it and become listed in the selection box of any CSwitchRelay’s Switches property. But like I said above, I then can’t get the editor to show me the List property.
I don’t like the current implementation because its very cumbersome for no reason. Not being able to expose fields of Interface type makes the implementation ugly and requires me to resort to inheritance when I shouldn’t have to.

I hope this makes sense. And if anyone has any insights into this issue I would really appreciate some pointers.

1 Like

To be honest you sort of hurt my head :wink:, but without completely tunning you in, I think you want to specify a list of all the lights you want to turn on or off, right?

Since you want to setup which lights to affect, why not just use

public Transform[] lights;

You can set it to as many as you want and drag and drop the instances in the scene directly on to this. It should be a really fast implementation.

Otherwise, if you have a lot of lights, why not use a naming convention and just search the scene, when the game starts, for lights with a suffix that match the switch’s name?

I don’t know enough about your work-flows to say if this helps, but I hope it does.

I agree there are times when I wish I could do more complex things with the inspector. Hopefully the above works and you can do more than wish, heh.

No, not exactly. It could be any sort of entity really. It will in most cases be a prefab with multiple components that will be affected. The Light thing was just an example to make it clearer :slight_smile: if that.

Unfortunately the default Inspector serializer does not handle interfaces at all. Aside from your abstract class hack, one possible solution would be to implement a custom inspector to handle this for you.

Something like this:

[Assets/MyScript.cs]:
using UnityEngine;

public class MyScript : MonoBehaviour
{
   public ILight m_Light;
   public Transform m_TheTransform;
   public Rigidbody m_LaserBlastingRocketOfDoom_prefab;
}

[Assets/Editor/MyScriptInspector.cs]:
using UnityEngine;
using UnityEditor;

[CustomEditor (typeof (MyScript))]
public class MyScriptInspector : Editor
{
   void OnInspectorGUI ()
   {
      MyScript script = (MyScript)target;
      script.m_Light = EditorGUILayout.ObjectField ("Light", script.m_Light, typeof (ILight));
      DrawDefaultInspector ();
   }
}
2 Likes

:smile: awesome. Thanks a lot Emil.

I hate hacks and you solution lets me use interfaces the way binary gods intended.

PS: Path and Behave are absolutely amazing btw :wink:

While interfaces can be quite handy, they don’t combine well with Unity. Not only by not showing up in the inspector, but also for not being compatible with functions like GetComponent<>().

It may be possible, however, to make it work for your case by implementing a custom inspector for the class containing the List. (Edit: as was posted while i wrote this).

Personally I would not use interfaces for your specific problem, though. I would probably prefer composition, by making a generic Switch Monobehaviour and having all classes that implement switch functionality monitor the status of their corresponding switch object. That way, your array is simply a list of Switch behaviours, each of which is blissfully unaware of whatever it controls.

UPDATE: I solved the error by explicitly casting to Object and since the API has been updated since the original post, I added the boolean value for allowSceneObjects. Like so:

  Object obj = EditorGUILayout.ObjectField("Light", (Object)script.m_Light, typeof(ILight), true);
      script.m_Light = obj as ILight;

I’ve implemented this solution in Unity 3.4.2 and I’m getting this error:

    [Assets/MyScript.cs]:
    using UnityEngine;
     
    public class MyScript : MonoBehaviour
    {
       public ILight m_Light;
       public Transform m_TheTransform;
       public Rigidbody m_LaserBlastingRocketOfDoom_prefab;
    }
     
    [Assets/Editor/MyScriptInspector.cs]:
    using UnityEngine;
    using UnityEditor;
     
    [CustomEditor (typeof (MyScript))]
    public class MyScriptInspector : Editor
    {
       void OnInspectorGUI ()
       {
          MyScript script = (MyScript)target;
          script.m_Light = EditorGUILayout.ObjectField ("Light", script.m_Light, typeof (ILight));
          DrawDefaultInspector ();
       }
    }

“error CS1502: The best overloaded method match for `UnityEditor.EditorGUILayout.ObjectField(string, UnityEngine.Object, System.Type, params UnityEngine.GUILayoutOption[ ])’ has some invalid arguments”

which leads to this error:

“error CS1503: Argument #2' cannot convert IMyInterface’ expression to type `UnityEngine.Object’”

How can you pass the interface member as the 2nd parameter, if the 2nd parameter needs to be a Unity Object?

To anyone else trying to do this (make Unity obey C# interfaces in a simple, easy way):

The code in this thread WILL NOT WORK. It seems Unity has changed the implementation under the hood (it causes crashes deep inside Unity’s code if the object is ever null - and there seems no way out. Also, Unity refuses to accept things that implement the C# interface: if you replace the typeof() with a class type, it works, but if you use the interface type: Unity blocks all assigment).

Also, Unity editor flashes warnings that the functions here are now “obsolete”. If you follow the docs links and try to use the new versions, those really don’t work with C# interfaces - lots of crashes, and the documentation for the methods is missing :(.

TL;DR - don’t even bother using C# interfaces. It may be good practice for writing source code, but Unity’s support for this is very poor right now. Instead, go with the Abstract Class hack (don’t use interfaces, use abstract classes) - they work perfectly (even if it’s harder for people to read your code).

Funny, I stumbled upon this very same thing a couple of minutes ago and actually searched the boards in this exact minute. I can confirm your findings. Too bad. Being able to use interface could be a nice feature for upcoming versions.

For those that are interested in exposing interface fields that you can assign inside the editor:

Using Unity 4.2.1 - you can essentially create a generic container that casts a Component type to your target interface with a custom property drawer that integrates into the editor to handle assignments and serialization like you’d expect and not have to constrain yourself to deriving from an abstract class when you don’t have to. I’ve taken this approach and have released my IUnified Asset if anyone is interested.

You do have to subclass the generic container for it to be serialized properly, but you don’t have to define a custom editor to explicitly expose the interface field each time you have a script that needs to. More info in this thread.

Roland’s solution is great, but sometimes it’s not convenient to wrap things. My approach in VFW was to actually have a custom serialization and drawing system that provides polymorphic serialization and proper exposure to interfaces/abstract types. An added benefit to having a better serialization system is that you could handle pretty much everything else that Unity can’t (properties, dictionaries, delegates etc) LINK

1 Like

I’ve made a little workaround to do something equivalent in my code.

First declare the array of type Transform ( or GameObject )

public Transform[ ] ControlledParts; // this can be assigned in the editor
private ISteerable[ ] _steeredParts; // this is the interface list we are looking to get

void Start()
{
_steeredParts = ControlledParts.SelectMany(t => t.GetComponents(typeof(Component))).OfType().ToArray();
}

It’s been 7 years…

Interfaces are one of the most powerful features of C#. Don’t force people to either pay for inspector overhaul assets or to use tricks which look bad, and work even worse; just to be able to use standard features of the scripting language.

10 Likes

I definitely agree with you
I am so confused why it is not added

It’s not added because Unity only treats UnityEngine.Objects as reference types in the editor (so they can be drag-and-dropped), everything else is treated like structs. There are many many many reasons for this- drag-and-dropping objects like references (GameObjects, MonoBehaviours, and ScriptableObjects), and being able to define them in-place (everything else) are mutually exclusive scenarios- you can’t do both. Interfaces are not inherently derived from UnityEngine.Object, obviously, so the inspector has absolutely no way of knowing what you want to fill that reference with- MonoBehaviours that implement that interface, GameObjects that have MonoBehaviours that implement that interface, or non-UnityEngine.Object types (structs) that implement that interface.

The former two cases are definitely possible as default behaviours, and maybe it SHOULD support that, but it wouldn’t be self-explanatory to use and it’s easy enough to store MonoBehaviour and GameObject references already. The last case meanwhile is impossible, because it would need to create (not drag and drop, but create in place) an object of an unknown type that implements the interface. How could it know what type to use, and how would it enforce that selection?

However, Unity gave us custom editors and property drawers. By using these, it’s absolutely possible to implement interfaces however you like. You can make it so that you can drag-and-drop MonoBehaviours that implement the interface into it, you can make it so that you can drag-and-drop GameObjects with MonoBehavarious that implement the interface into it, or you can maybe even make it so that you can choose a specific non-UnityEngine.Object type that implements the interface from a drop-down list, then have a new struct-like object auto-generated there to play with. You can possibly even support all 3 all at once, if you really want to put some effort into it.

Only you can know how your use-case and how you want to implement this- if you want to do it yourself, you can (I did it with the first two options, quite easily), and if you don’t want to do it yourself there are extensions on the UAS, or solutions posted around the support forum, that will do it for you. I posted my own solution in a thread not more than two weeks ago.

2 Likes

I sometimes store the reference to a gameobject like

public Gameobject interfacedgo

and then I check wether it has the required interface in OnValidate callback like

void OnValidate ()
{
  if (interfacedgo != null)
  {
     Isomething s = interfacedgo.GetComponent<Isomething>();
     if ( s == null ) interfacedgo = null;
  }
}

It is a hack, and maybe not much sorter than implementing your own editor.

Couldn’t Unity provide a tag so we could do something like

[Interface(typeof(Isomething))] public Isomething s

?

3 Likes

You can always make your own PropertyDrawer and PropertyAttribute for that case.

I also think it would be very handy if you could have public variables that are type of some interface be visible in the inspector in Unity (without any hacks). I guess it has to do with how interfaces are not “aware” of the objects that implement them, but I was wondering if Unity has any plans to make it possible to expose interfaces in inspector? Or do we just have to live with how it is…

I like this solution but according to the original intent of the thread it makes slightly more sense to make interfacedgo be of type “Component” and do:

void OnValidate ()
{
  if (!(interfacedgo is Isomething))
  {
     interfacedgo = null;
  }
}

Of course, that means you need to drag a Component in the inspector rather than a GameObject but the intent is slightly closer to original intent.

1 Like

For future reference, I did this and it works. Unity 2020.3

warmth.cs implements the IPlayerStat interface

then in the CircularStat code:

public Object statObject;
IPlayerStat statScript;

    private void Awake()
    {
        statScript = (IPlayerStat)statObject;
    }