Using abstractions and interfaces with Unity3D

oo1Unity3D includes a component architecture paradigm. This allows us to attach code as classes that derive from MonoBehaviour to our GameObjects and treat them as script components.

With Unity3D GameObjects can communicate with scripts via SendMessage by just addressing the name of particular methods. We can also get the reference to the script with GetComponent. This brings us the safety of type checking but makes us rely on a concrete script reference which results in tight coupling (this can actually not be the case if the class inside the script inherits from some kind of abstraction, more on this later).

Besides the Unity3D component mojo we also have more classical approaches in the OO department that may suit our needs depending on the scenario.

One of them is to use an observer pattern based system, such as the one found in the Unity3D wiki, which is a fastest and cleaner alternative to Unity’s SendMessage. This solution is suitable if we wanted to communicate with a number of GameObjects without having to manage concrete references for each one.

We also have the alternative of using abstract references (abstract classes and interfaces). With these we can make our code less coupled and we can plug reusable logic without relying on concrete classes.

The following example shows how can you combine this approach with Unity’s component based nature.

All the code and a working example can be found in this GitHub repository.

Abstracting SHUMP entities

If you try to categorize the most used type of logic in a SHUMP you may agree with me if I point “movement patterns”. Any type of projectiles, enemies and power ups will move not always in a linear fashion. You may have diagonal, senoidal or saw tooth movement patterns and you are likely to combine them to make complex sequences that can for example fit a boss encounter.

Let’s start by creating a suitable interface for our movement algorithms.

Now let’s show two movement patterns that implement this interface.

Neither the interface nor the movement logic need to be added to GameObjects since they don’t inherit from MonoBehaviour.

For this example our moving entity will be missiles, so let’s create a base class for them.

This script has to be attached to a GameObject as a component. From here we can create a collection of prefabs to set up different missile visuals such as the model or particle systems.

Let’s keep abstracting entities for the sake of reusability and create a weapon interface.

Now let’s get concrete, I give you the laser cannon.

This way of structuring our code gives us the following benefits.

  • We can reuse movement logic, just as we did in BaseMissileBehavior we can plug this logic onto any moving entity. This way of using interfaces is known as strategy pattern.
  • We can encapsulate abstract entities (missiles) inside concrete entities (weapons). This let’s us choose at which level we can assign attributes (like damage or fire delay in this example). In this case note that the overall configuration (which missile we use and which movement we plug inside them) takes place at the weapon level via its constructor.
  • We can ask another class to give us instances of predefined weapons by supplying a name (preferably enum) or id. This class could query a data source (e.g. XML or SQLite) for the specific configuration of the given weapon. This is an application of the factory pattern. You can apply the same idea to entities like enemies (tip: if you are continuously creating entities with a low life span add to your factory object pooling to avoid performance penalty).

And finally this is how an entity could make use of a weapon via an interface.

This is a good example since you can see how easy it is to switch concrete classes based on its interface during runtime.

You can use this approach to create a movement pattern based of multiple movement behaviors as we discussed early. You just have to cycle through a collection of movement implementations every x time which is something easily doable with coroutines.

GetComponent and abstractions

Actually GetComponent can fetch a script component by its superclass or interface implementation. I will show you an example of a scenario where you could use this, but first here you have some useful extension methods.

Let’s picture a typical mouse driven action rpg. You may click on a door and you player will move to its proximity and the door will open. You could create a ISOpenable interface with an Open method. Since different doors may have a different opening logic (sliding, rotating etc) you could reference any type of door by its interface.

To make this work you could check for every mouse click if the mouse is hovering a GameObject that implements ISOpenable and call a GetInterface<ISOpenable>().Open();

You could also use this approach to attack enemies with something like GetInterface<ISDamageable>().ApplyDamage(playerDamage); and so on.

Going further with abstractions

The art of plugging concrete classes on to abstractions is called dependency injection, and relies in the idea of inversion of control which is one of the core techniques of maintainable OO code.

If you want to go further in the practice of adding OO spice to your Unity3D development I recommend taking a look at StrangeIoC which is a framework that gives us a variety of tools, being one of them a cleaner mechanism to manage injections.

Acerca de

Ver todas las entradas de

5 Responses

  1. Víctor Barceló dice

    Unity3D serialization does not apply for this case, you’d have to use a programmatically mechanism like a factory to retrieve configuration and state.

    I don’t understand the rest of your points, interfaces and abstract classes can be used as a substitution for script components so exposing fields via the inspector to configure the classes that inherit/implement it’s not what I meant.

    Also I don’t know what the matter with inheritance and polymorphism in Unity is. If you are talking about prefab inheritance, again, we are referring to different things.

    I think that components and OO fit nicely together and that Unity3D developers that rely heavily on components may be losing sight of how convenient is to sometimes favor OO to help them with the development of complex projects.

    • Justice dice

      “Also I don’t know what the matter with inheritance and polymorphism in Unity is”
      That is exactly the problem. You can see more here, I found the feature request I previously mentioned:

      The request links to a post which explains the problems, but I’ll try to give a simple example.
      Let’s say you have a player, and he can have multiple weapons. You want him to be able to use a shotgun and a laser beam gun, and all kinds of different guns. Every gun has different tweakable parameters that you need to set up for optimal game play experience.

      a) You decide to use IWeapon interface.

      How will you tweak your weapons until you find the optimal parameters? You need to either spend a lot of time writing custom property drawers/ editor windows etc. so you could expose those parameters in Unity. Or you can make a mono behaviour whose sole purpose is to expose those parameters so you can tweak them.
      Or you need to change those parameters in code, wait for it to recompile, exit play mode, enter play mode again, try it out, repeat until satisfied with your parameters.

      You found your optimal parameters. What if you want to have two different laser guns, that are exactly the same but with different parameters? You’d have to make another class LaserGun2 with different parameters because your parameters are constants (even if they are not declared const, but normal variables you have no way to remember their values).

      How will you determine which weapons the player starts with? You will do it in code. What if you want different types of player “classes” or different starting weapons for every difficulty? You’ll have a lot of if/switch cases in your code just to determine the weapons the player starts with, because you can’t populate the weapon list at edit time, it has to be populated at runtime.

      The only time interfaces are viable is when you need something that will be created at runtime, and you have no need for it at edit time but has to adopt different behaviours depending on certain, other parameters.

      b) If you decided to use MonoBehaviours for your guns instead. You can make an abstract class inheriting MonoBheaviour.

      You create your laser gun and shotgun classes like before, but this time you need to assign them to a game object. But when you do, you can see and tweak your parameters. You can change which weapons your player starts with. You can make prefabs both from your guns and from your player’s starting weapon arsenal.

      You don’t need to exit play mode, you can change and tweak until you are perfectly satisfied and then make a prefab out of your weapon – saving that set up forever.

      Once when you are back in edit mode, you can still tweak it, and the values you changed will be there when you click play. No need to change your code. No need to write extra code. All those benefits just because you decided to make MonoBehaviours instead of regular classes/interfaces.

      The problems I talk about are not interface specific, the same applies to regular classes that do not extend ScriptableObject/MonoBehaviour. All references to them will be serialized which means you will get multiple copies of the same object. If your reference type is of a base type – only variables your base type has will be serialized. You will completely lose your derived type! List in which you put your weapons will be empty on recompile/exiting/entering play mode, etc.

      I still use regular classes/interfaces but only for things that are manually saved in external text/xml files and recreated from them at runtime, and, ofc, not editable at runtime.

      • Víctor Barceló dice

        I completely agree in that the advantages of the inspector are huge when prototyping and tweaking values. I also agree in that the approach for your “b)” example is the best solution for most scenarios.
        Thou in those I’d still see desirable using interfaces for the sake of reusable logic. For example in the IMovement interface.

        • Justice dice

          If it’s something you are going to create and use at runtime, have no need to adjust parameters or serialize, by all means, use them, they are great OO paradigm. Just not all that useful in Unity.

  2. Justice dice

    All that is fine and dandy but interfaces in Unity are only usable if you are creating them at runtime (as you seem to be doing) which is for most purposes impractical and it is better to just create an abstract class.
    Unity, by itself, does not expose fields that are type of interface, so you will not be able to edit parameters of your interface implementations. Not only that, but reference to your interface will not be serialized which means it will not be remembered between sessions and entering/exiting playmode.