The Unity Inspector is a great tool for combing and configuring various components into powerful game objects with complex behaviors, all with little to no additional coding.
Interfaces are a useful architecture mechanism enabling developers to design good contracts between modules without having to expose implementation details.
Background #
As part of my current project I’m currently modifying the way “tools” are implemented. Tools are objects that can be equipped on a controller. In my old system, tools are special objects that have their own RPC mechanism that is essentially hard coded. My new tool system is based on my game’s “prop” system. Props are far more dynamic: they can be picked up by players, placed in buildings, left on the field, or placed in a player’s inventory. Depending on the current state of a prop the route for RPC messages can be different.
Because of this I implemented an “IRPCProvider” interface. This interface provides methods for performing remote procedural calls, and is implemented on various different components that actually handle sending and receiving the messages. This has been working out very well, but eventually I ran into a problem.
Problem #
The Unity Editor does not expose interface type variables in the inspector. The Unity d0ocumentation specifies only the following types can be serialized by Unity (and therefore exposed in the inspector):
- All classes inheriting from
UnityEngine.Object
, for exampleGameObject
,Component
,MonoBehaviour
,Texture2D
,AnimationClip
. - All basic data types like
int
,string
,float
,bool
- Some built-in types like
Vector2
,Vector3
,Vector4
,Quaternion
,Matrix4x4
,Color
,Rect
,LayerMask
. - Arrays of a serializable type
- List of a serializable type
- Enums
- Structs
In my search for a solution one suggestion is to use an abstract class that implements MonoBehaviour instead of using an interface. This might work for some situations, but it certainly has some drawbacks. Besides the fact that abstract classes and interfaces are fundamentally different things, one big drawback is in C# a class may inherit from only one parent class, so this won’t work if one wants to do this for multiple interfaces.
Solution #
However there is a simple solution to this problem: rather than using the actual interface type, store a UnityEngine.Component reference. Then use a C# property to automatically cast that variable back into the actual interface type. Finally, access the variable only through the property.
[SerializeField]
private Component _rpcProvider;
public IRPCProvider RPCProvider {
get {
#if UNITY_EDITOR
//Ensure the type is an IRPCProvider
if(_rpcProvider!=null && !(_rpcProvider is IRPCProvider)) {
Debug.LogError("RPCProvider must be an IRPCProvider");
}
#endif
return _rpcProvider as IRPCProvider;
}
set {
if(value is Component)
_rpcProvider = value as Component;
else {
Debug.LogError("RPCProvider must be a UnityEngine.Component");
}
}
}
However this is not a perfect solution. The main issue is the _rpcProvider variable can now store a reference to any component. The inspector will not enforce that it must have the IRPCProvider type. To help catch this I perform a check within the RPCProvider property, but this will only be called at runtime. With a little bit of work one could just create a new custom editor to provide this editor-time check automatically. Perhaps with a custom attribute that would look like this:
[SerializeFieldType(typeof(IRPCProvider))]
[SerializeField]
private Component _rpcProvider;
But for now this will work well enough for my project.