Search Unity

C# Interface won't show in inspector

Discussion in 'Scripting' started by tshauli, Feb 4, 2016.

  1. tshauli

    tshauli

    Joined:
    Feb 4, 2016
    Posts:
    4
    I have an interface that several classes will inherit from

    Code (CSharp):
    1. public interface RatioVar
    2. {
    3.     float GetVarRatio();
    4. }
    5. public class HealthScript : MonoBehaviour, RatioVar{...}
    6. public class StaminaScript : MonoBehaviour, RatioVar {...}
    Another class takes the interface as a public variable, so it can be changed in the inspector

    Code (CSharp):
    1. public class RatioUIBar : MonoBehaviour {
    2.     public RatioVar varToDisplay;
    3. }
    but because it's an interface it won't show it in the inspector.
    Is there a good way to show it in the inspector without creating an editor script or changing "public RatioVar" to "public Object" or "public Transform"?
     
  2. Teravisor

    Teravisor

    Joined:
    Dec 29, 2014
    Posts:
    654
    Last edited: Feb 4, 2016
    Lohaz and Kiwasi like this.
  3. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Unity doesn't handle interfaces gracefully in the inspector at all. There are several options. The simplest is to expose a Component and then check in OnValidate if the component is the right type.
     
  4. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
  5. Dantus

    Dantus

    Joined:
    Oct 21, 2009
    Posts:
    5,667
    The simplest one is definitely not the one that should be used at all!
    Type safety was a huge achievement, a precious gift in the short history of programming. Let's use and not abuse it :)
     
  6. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    It's essentially type safe. Something like the following.

    Code (CSharp):
    1. [SerialiseFeild]
    2. private GameObject inspectorObject;
    3. private IMyInterface myInterface;
    4.  
    5. void OnValidate ()
    6. {
    7.     myInterface = inspectorObject.GetComponent<IMyInterface>();
    8.     if(myInterface == null) inspectorObject = null;
    9. }
    The other option is to build a custom property drawer using reflection. It's more involved, but valid if you are going to do this all the time across multiple classes.
     
  7. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    That's literally what @lordofduct suggested in my thread. Simplest version here:

    Code (CSharp):
    1. [System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false)]
    2.     public class ComponentTypeRestrictionAttribute : SPPropertyAttribute
    3.     {
    4.         public System.Type InheritsFromType;
    5.         public bool HideTypeDropDown;
    6.         public ComponentTypeRestrictionAttribute(System.Type inheritsFromType)
    7.         {
    8.             this.InheritsFromType = inheritsFromType;
    9.         }
    10.     }
     
  8. Dantus

    Dantus

    Joined:
    Oct 21, 2009
    Posts:
    5,667
    @BoredMormon, that looks interesting! It is still pretty much a hack in my opinion as there are also cases where it won't work. The last time I worked with OnValidate, I had the issue that it didn't work when the inspector was in debug mode. Are you sure that it is assigned at runtime too, since the interface isn't serialized and OnValidate is only called in the editor?
     
  9. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    There might be some things you have to deal with to make it work, it's been a long time since I needed to serialise an interface in the inspector. It's painful enough that I found other ways to solve the use cases.
     
  10. tshauli

    tshauli

    Joined:
    Feb 4, 2016
    Posts:
    4
    Thanks for all the quick replies.
    I looked at the solution given to @Nigey but I think adding all of @lordofduct spacepuppy-unity-framework looks like a lot code for my tiny (tiny) game. And I am kind of uncertain to how it will work on newer unity versions (but that can be said on just about anything).

    @BoredMormon OnValidate() solution works but only for the first component. I have more than one so I changed it a bit:
    Code (CSharp):
    1. public class RatioUIBar : MonoBehaviour {
    2.     public MonoBehaviour varToDisplayMono;
    3.     private iRatioVar varToDisplay;
    4.  
    5.     void OnValidate ()
    6.     {
    7.             if (varToDisplayMono is iRatioVar)
    8.             {
    9.                 varToDisplay = (iRatioVar)varToDisplayMono;
    10.             }
    11.             else
    12.             {
    13.                 varToDisplay = null;
    14.                 varToDisplayMono = null;
    15.             }
    16.     }
    17.    
    18. }
    19.  
    With this code you can take the exact component, not just the first one. The downside is you can't just drag the gameobject you need the specific component.
     
  11. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    Oh, if you want the thing I gave nigey, it can be pulled out of the framework pretty easily.

    I'm assuming nigey did the same.
     
  12. alibatiste

    alibatiste

    Joined:
    Oct 25, 2013
    Posts:
    25
    This can be resolved with [SerializedField,HideInInspector]
     
  13. Vesnican

    Vesnican

    Joined:
    Aug 26, 2013
    Posts:
    11
    Another solution could be through serializable object:

    Code (CSharp):
    1.  
    2.     public class InterfaceProvider<T>
    3.     {
    4.         [SerializeField]
    5.         private GameObject _interfaceObject;
    6.  
    7.         private T _interface;
    8.  
    9.         public T Value
    10.         {
    11.             get
    12.             {
    13.                 if (_interface == null)
    14.                 {
    15.                     _interface = _interfaceObject.GetComponent<T>();
    16.                 }
    17.                 return _interface;
    18.             }
    19.         }
    20.     }
    Unfortunately template classes cannot be serialized so we need to hide it inside generic class:

    Code (CSharp):
    1.     [Serializable] public class MyInterfaceProvider : InterfaceProvider<IInterface> { }
    And finally we can use it in our class:

    Code (CSharp):
    1. public class TestClass : MonoBehaviour
    2. {
    3.         [SerializeField] private MyInterfaceProvider _interfaceProvider;
    4. }