Search Unity

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

Discussion in 'Scripting' started by alexfeature, May 21, 2010.

Thread Status:
Not open for further replies.
  1. alexfeature

    alexfeature

    Joined:
    Apr 1, 2010
    Posts:
    132
    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 :
    Code (csharp):
    1.  
    2. ...
    3. public List<object> Lights;
    4. ...
    5.  
    but this doesn't, I cannot see the field in the editor:

    Code (csharp):
    1.  
    2. ...
    3. // Where ILight is an Interface defined elsewhere in the code
    4. public List<ILight> Lights;
    5. ...
    6.  
    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<CSwitch> 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<ISwitch> 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.
     
    IARI likes this.
  2. rsx

    rsx

    Joined:
    Nov 4, 2009
    Posts:
    87
    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
    Code (csharp):
    1. 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.
     
  3. alexfeature

    alexfeature

    Joined:
    Apr 1, 2010
    Posts:
    132
    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 :) if that.
     
  4. AngryAnt

    AngryAnt

    Keyboard Operator

    Joined:
    Oct 25, 2005
    Posts:
    3,045
    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:

    Code (csharp):
    1. [Assets/MyScript.cs]:
    2. using UnityEngine;
    3.  
    4. public class MyScript : MonoBehaviour
    5. {
    6.    public ILight m_Light;
    7.    public Transform m_TheTransform;
    8.    public Rigidbody m_LaserBlastingRocketOfDoom_prefab;
    9. }
    10.  
    11. [Assets/Editor/MyScriptInspector.cs]:
    12. using UnityEngine;
    13. using UnityEditor;
    14.  
    15. [CustomEditor (typeof (MyScript))]
    16. public class MyScriptInspector : Editor
    17. {
    18.    void OnInspectorGUI ()
    19.    {
    20.       MyScript script = (MyScript)target;
    21.       script.m_Light = EditorGUILayout.ObjectField ("Light", script.m_Light, typeof (ILight));
    22.       DrawDefaultInspector ();
    23.    }
    24. }
     
    Lohaz and n8dev_yt like this.
  5. alexfeature

    alexfeature

    Joined:
    Apr 1, 2010
    Posts:
    132
    :D 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:
     
  6. tomvds

    tomvds

    Joined:
    Oct 10, 2008
    Posts:
    1,028
    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.
     
  7. corey.stpierre

    corey.stpierre

    Joined:
    Nov 18, 2008
    Posts:
    79
    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:

    Code (csharp):
    1.   Object obj = EditorGUILayout.ObjectField("Light", (Object)script.m_Light, typeof(ILight), true);
    2.       script.m_Light = obj as ILight;
    I've implemented this solution in Unity 3.4.2 and I'm getting this error:

    Code (csharp):
    1.     [Assets/MyScript.cs]:
    2.     using UnityEngine;
    3.      
    4.     public class MyScript : MonoBehaviour
    5.     {
    6.        public ILight m_Light;
    7.        public Transform m_TheTransform;
    8.        public Rigidbody m_LaserBlastingRocketOfDoom_prefab;
    9.     }
    10.      
    11.     [Assets/Editor/MyScriptInspector.cs]:
    12.     using UnityEngine;
    13.     using UnityEditor;
    14.      
    15.     [CustomEditor (typeof (MyScript))]
    16.     public class MyScriptInspector : Editor
    17.     {
    18.        void OnInspectorGUI ()
    19.        {
    20.           MyScript script = (MyScript)target;
    21.           script.m_Light = EditorGUILayout.ObjectField ("Light", script.m_Light, typeof (ILight));
    22.           DrawDefaultInspector ();
    23.        }
    24.     }
    "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?
     
    Last edited: Feb 26, 2012
  8. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    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).
     
  9. Glockenbeat

    Glockenbeat

    Joined:
    Apr 24, 2012
    Posts:
    670
    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.
     
  10. Roland1234

    Roland1234

    Joined:
    Nov 21, 2012
    Posts:
    190
    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.
     
    Last edited: Nov 15, 2013
  11. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    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
     
    MapuHoB likes this.
  12. Jeppe-Andreasen

    Jeppe-Andreasen

    Joined:
    Dec 3, 2013
    Posts:
    3
    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<ISteerable>().ToArray();
    }
     
  13. starikcetin

    starikcetin

    Joined:
    Dec 7, 2017
    Posts:
    340
    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.
     
  14. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    856
    I definitely agree with you
    I am so confused why it is not added
     
  15. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    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.
     
    Last edited: Feb 3, 2018
    guneyozsan and mahdiii like this.
  16. Pablomon

    Pablomon

    Joined:
    Mar 9, 2015
    Posts:
    47
    I sometimes store the reference to a gameobject like
    Code (CSharp):
    1. public Gameobject interfacedgo
    and then I check wether it has the required interface in OnValidate callback like
    Code (CSharp):
    1. void OnValidate ()
    2. {
    3.   if (interfacedgo != null)
    4.   {
    5.      Isomething s = interfacedgo.GetComponent<Isomething>();
    6.      if ( s == null ) interfacedgo = null;
    7.   }
    8. }
    9.  
    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
    Code (CSharp):
    1. [Interface(typeof(Isomething))] public Isomething s
    ?
     
    Lohaz, Nick62 and AAye like this.
  17. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    You can always make your own PropertyDrawer and PropertyAttribute for that case.
     
  18. jariwake

    jariwake

    Joined:
    Jun 2, 2017
    Posts:
    100
    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..
     
  19. jhina

    jhina

    Joined:
    Apr 6, 2015
    Posts:
    2
    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:

    Code (CSharp):
    1. void OnValidate ()
    2. {
    3.   if (!(interfacedgo is Isomething))
    4.   {
    5.      interfacedgo = null;
    6.   }
    7. }
    8.  
    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.
     
    lanfort likes this.
  20. wallabe

    wallabe

    Joined:
    Oct 6, 2017
    Posts:
    6
    For future reference, I did this and it works. Unity 2020.3



    warmth.cs implements the IPlayerStat interface

    then in the CircularStat code:

    Code (CSharp):
    1. public Object statObject;
    2. IPlayerStat statScript;
    3.  
    4.     private void Awake()
    5.     {
    6.         statScript = (IPlayerStat)statObject;
    7.     }
     
  21. Pablomon

    Pablomon

    Joined:
    Mar 9, 2015
    Posts:
    47
    Yes that works.
    But then you can't be sure that the statObject you injected in the inspector is in fact of the type IPlayerState. Also it breaks the selection 'only type of' tool ( the bullseye to the right of the object name )

    BTW, now that onvalidate works no longer this thread needs an update.

    Anyone?
     
Thread Status:
Not open for further replies.