To me, engineering is for the most part about getting the desired result with the tools that we have available. It is not about theory, not about being exceptionally clever, it is about creating a system with the available resources and constraints that are present. It is also not about cobbling it up together, it is about advancing your craftsmanship and improving your skills and work ethic with regards to the big picture.

This may not seem related to the topic of this post, or it might…

Interfaces are preferable to base classes because a class in C# can derive from only one base class, but can implement many interfaces. If the architecture calls for base classes, or we want some shared implementation, or some shared Unity-serialized fields, then they are fine. On the other hand, interface is like a contract that a class must implement, so they can be used across class hierarchies. The problem with interfaces in Unity is that they are not serialized. Let’s set up code like this:

public interface IImplementable {
    float GetMagicNumber();
}
public class Implementation1 : MonoBehaviour, IImplementable {
    public float GetMagicNumber() {
        return 2.71828f;
    }
}

public class Implementation2 : MonoBehaviour, IImplementable {
    public float GetMagicNumber() {
        return 3.14f;
    }
}

public class Consumer : MonoBehaviour {
    public IImplementable magicNumberProvider;
}

Let’s say we have 2 objects in the inspector, one called Implementer  and the other Consumer. Implementer has Implementation1 and Implementation2 scripts attached and consumer has Consumer.cs script added.

In the inspector of Consumer we will not see anything exposed, there is no field to drag the script to. In this case, a base class may be preferable if we really want that field, but may not be possible due to the rest of the inheritance related architecture in the system (one of the base classes on the implementers may already be taken). However, all is not lost.  GetComponent<Interface>() will still work. This means we can do something like this on the Consumer.cs:

public class Consumer : MonoBehaviour {
    public GameObject implementer;

    private IImplementable _magicNumberProvider;

    void Start() {
        _magicNumberProvider = implementer.GetComponent<IImplementable>();
        Debug.Log(_magicNumberProvider.GetMagicNumber());
    }
}

 This has a couple of side effects:

1.       We don’t get the type-safe drag’n’drop field in the inspector. Instead, we get the general GameObject type field, and from that we extract the implementing component. This means we now should do some error checking, as I will show below. This is a rough equivalent of taking an array of Objects and casting them to proper type in general C# programming.

2.       If there are multiple implementations of the interface, the first one will be selected. There is the same risk when doing GetComponent<>, but I would say the risk is actually increased here because we may expect it less.

So, this code will look a bit better:

public class Consumer : MonoBehaviour {
    public GameObject implementer;

    private IImplementable _magicNumberProvider;

    void Start() {
        IImplementable[] implementers = implementer.GetComponents<IImplementable>();
        if(implementers.Length != 1) throw new System.ArgumentException("Implementer object has non-existent or ambiguous IImplementable implementation.");
        _magicNumberProvider = implementers[0];
        Debug.Log(_magicNumberProvider.GetMagicNumber());
    }
}

Our example setup will now not work, as we have two implementers on the Implementer object. Once there is only one implementer, the exception will not be thrown and the code will work as expected.

Notice that I do actual pre-emptive error throwing instead of exception swallowing (that’s right).


If you're finding this article helpful, consider our asset Dialogical on the Unity Asset store for your game dialogues.

 

Log In:




Comments (0)