Note: I published the new Svelto.IoC container http://www.sebaslab.com/svelto-inversion-of-control-container/
Unity is quite a good game development tool, but, although I like most of its features, the code framework is awkward to use on big projects where code maintainability is of fundamental importance.
The considerations in this article do not really apply to simple projects or projects developed by one or two persons. For these specific cases, Unity is good enough. The arguments in this article apply to medium-big projects developed by a medium-big size team.
I will not talk about all the flaws I found, but I will focus on one specifically: how to inject dependencies inside Unity entities. A dependency is defined as an object needed by another object to execute its code. If a class A needs the instance of a class B to execute its code, B is a dependency for A. When B is passed into A, we say that the dependency B is injected into A.
Unity is based on the concept of “Entity Framework”. An Entity Framework brings many benefits: pushes the users to favor composition over inheritance, keeps the classes small and clean focusing on single responsibilities, promotes modularity. This is in a perfect world.
In Unity, the Entites are called GameObjects and the Components are based on MonoBehaviour implementations. Personally I have some rules I follow when I implement MonoBehaviours for GameObjects:
- MonoBehaviours must operate within the GameObjects they are attached to.
- MonoBehaviours are reusable between GameObjects , single responsibility classes.
- GameObjects should not be created without a view, like a mesh or a colliders or something that is really an entity in the game.
- MonoBehaviours can know other components on the same GameObject using GetComponent, however they cannot know MonoBehaviours from other GameObjects.
- The behaviour of a GameObject should be extended adding MonoBehaviours instead of adding methods to a single MonoBehaviour.
Unfortunately Unity does not allow to follow most of these rules because of the way dependencies must be solved. In fact what happens if a component needs to communicate with other objects? Let’s say there is a Level object and for some reasons this object must know the monsters that are still alive. When a monster dies, how is it possible to communicate the fact to the Level object?
Currently there are 3 simple solutions to this problem:
- Level is a Monobehaviour created inside a GameObject that has no view. All the monsters will look for the Level class using the GameObject.Find call inside the Start or Awake functions and inject themselves into the Level. When the monster dies, will communicate it to the Level through one of its public methods.
- Level is a Singleton and it’s used directly inside the monster Monobehaviour.
- Like 1, but Level is found through Object.FindObjectOfType.
albeit, I reckon there are problems in following all the three possible solutions:
What’s wrong with GameObject.Find
Beside being quite a slow function, GameObject.Find is one of the example of how awkward the Unity Framework is for big projects development. What happens if someone in a team decides to rename the GameObject? Should GameObjects renames or deletions been forbidden? GameObject.Find can lead to several run-time errors that cannot be caught in compiling time. This scenario could be really hard to manage when dozens and dozens of GameObjects are searched through this function.
What’s wrong with the Singleton
Singleton is a controversial argument since the pattern has been invented. I am personally against the use of Singleton. However if you ask me why I am against the Singleton, I will not answer you with the usual answers (they break encapsulation, hide dependencies, make impossible to write proper unit tests, bind the code to the implementation of a service instead of its abstraction, make refactoring really awkward), but with the hindsight of the practice: your code will become unmanageable after a short term. This because the use of Singletons comes without any design constriction that, while apparently makes the coder life easier, will make it a hell later on, when the code becomes an incomprehensible blob without a structured flow. Projects developed by one of two coders wouldn’t show the problem at first or ever (for example, if the project doesn’t need to be maintained over time), but mid-size projects will pay the consequences of wild design when it’s probably too late for refactoring.
What’s wrong with the Object.FindObjectOfType
How should the Level object be injected inside the Monster class then? There is actually a third solution: calling the function Object.FindObjectOfType. However, what is FindObjectOfType? Object.FindObjectOfType could be seen as a wrong implementation of the Service Locator Pattern, with the difference that it is not possible to abstract the implementation of the service from its interface. This is another problem of Unity framework, Unity seems to hate the concept of interface. Interface is the most powerful concept at the core of every well designed code. Instead to push the coders to use interfaces, Unity pushes the coder to uses Monobehaviour, even when the use of Monobehaviour is not necessary.
There are 3 ways to resolve dependencies: using the Service Locator pattern, injecting dependencies manually through constructors/setters or automatically with an IoC container and, obviously, using Singletons. Disregarding Singletons, I do not like the Service Locator Pattern because the SLP itself is a singleton (or a static class) and this could lead to some severe limitations compared to the IoC container solution. However using an IoC container in Unity is not simple because Unity does not specify a place where the application can be initialized.
Another article is needed to explain what an IoC container is, therefore I will give a first simple definition: An IoC container is a…container that creates and contains the dependencies that must be injected inside the application objects. It is called Inversion Of Control because the design is created in such a way that the objects are never created by the user, but they are lazily created by the container when they are needed.
There are several IoC containers out there, a lot written in c# as well, but practically no one works in Unity and most of all, they are damn complicated*. For these reasons I decided to create an IoC container for Unity trying to keep it as simple as possible. Actually creating a basic IoC container is pretty straightforward and everybody can do it, my implementation has just a few tweaks that makes it simple to use with Unity projects.
Conclusion (for this article)
The second part of this article (including source code) is now available here: http://blog.sebaslab.com/ioc-container-for-unity3d-part-2
*Since when I first wrote this article, new IoC containers have been developed. The best ones out there are StrangeIoC, ZenInject and Adic. Google them, it’s worth it.