but I must be missing something since I get errors!
I have an interface ISwitchableObject. Objects like lamps implement the interace. Then I have a Switch object that has a reference to an instance of an ISwitchableObject like this:
public class Switch : MonoBehaviour
{
public ISwitchableObject _switchable;
}
I want to expose the _switchable field in the inspector so I can drag drop lamps and such to it. I've tried doing the below but it gives me errors (says I have invalid arguments). What am I doing wrong?
Thanks
[CustomEditor (typeof (Switch))]
public class SwitchInspector : Editor
{
void OnInspectorGUI ()
{
Switch s = (Switch)target;
s._switchable = EditorGUILayout.ObjectField ("switchable", s._switchable, typeof (ISwitchableObject));
DrawDefaultInspector ();
}
}
Unity, by itself, does not expose fields that are of an interface type. It is possible to manually enable this functionality by implementing a custom inspector each time as Mike 3 has pointed out, but even then the reference would not be serialized (“remembered” between sessions and entering/exiting playmode).
It is possible however to create a serializable container object that wraps around a Component field (which is serialized) and casts to the desired interface type through a generic property. And with the introduction of custom property drawers into Unity, you can effectively expose a serialized interface field in your scripts without having to write a custom inspector / property drawer each time.
One way to do it is using a Component variable for what is displayed in the inspector, and a second variable of the interface that is set using the first on Start or Awake. Using the OnValidate method, you can have it check if the Component variable is of the interface, i.e. if (component is interface). If it fails that, you have it set the component to null.
Example
public Component movement;
protected iMovement motor;
protected void OnValidate()
{
if (!(movement is iMovement))
movement = null;
}
protected override void Awake()
{
if (movement != null)
motor = (iMovement)movement;
}
This is not a perfect solution. You have to set the component to the exact component you want to use.
I don't think there is any way to do it by interface alone (I've run through all the methods involving generics and the like in my head and you'd end up making a custom inspector for each type that way) - you can't pass an interface to something requiring a UnityEngine.Object
The only thing I can think of is making an abstract base class which inherits from both UnityEngine.Object and ISwitchableObject. This then lets you add either monobehaviour scripts, or very basic classes which aren't attached to gameobjects
Note:
The solution on the page doesn't compile, with the same error you're getting.
public interface IDoable {
void DoIt();
}
public class Doer : MonoBehaviour, IDoable {
public void DoIt();
}
public class DoableContainer : MonoBehaviour {
public IDoable doable;
}
just do
public abstract class ADoable : MonoBehaviour{
virtual void DoIt();
}
public class Doer : ADoable {
public void DoIt();
}
public class DoableContainer : MonoBehaviour {
public ADoable doable;
}
Your Doer can only implement one “interface”, and all your implementation of Doable have to inherit from the same class (here MonoBehaviour), but it works, is displayed, and is saved with little additional cost;
Also not a perfect solution, but may be useful to someone.
You can add a [RequireComponent(typeof(MyInterface))] to your component and then get the component on Start:
[RequireComponent(typeof(MyInterface))]
public class Example : MonoBehaviour
{
private MyInterface interface;
void Start()
{
interface = GetComponent<MyInterface>();
}
}
This way you somewhat ensure that some component will implement the interface.
Although this solution is not good if you have multiple components implementing this interface on the same Game Object. But in my opinion it is better than just simply getting the component on Start, gives a bit more context on what the component requires and what it does.