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: 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 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 golden rules I want to follow when I implement MonoBehaviours for GameObjects:
- MonoBehaviours should always operate within the GameObjects they are attached to.
- MonoBehaviours are reusable, 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.
- The behaviour of a GameObject should be extended adding MonoBehaviours instead of adding methods to a single MonoBehaviour.
The problem is, as many of you know already, that Unity does not really allow to follow most of these rules because of the way the dependencies must be solved. In fact what happens if my component needs to communicate with other classes? Let’s say there is a Level class and for some reasons this class 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.
- Level is a Singleton.
- 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
The first solution breaks my golden rules, which would be not a big issue since they are actually more guidelines for good design. Instead the real problem is the design of GameObject.Find. GameObject.Find is one of the example of how awkward the Unity Framework is for big project development.
What happens if someone in the 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 (break encapsulation, dependencies hiding, many issues to test the code through unit test, being bound to the implementation of a service instead of its abstraction), but with the hindsight of the practice: your code will become a big pile of crap after a while. This is because the use of Singletons does not involve any design constriction that, while apparently makes the coder life easier, will make it a hell later on, when the design of the classes becomes an incomprehensible blob of code without a structured flow. Projects developed by one of two coders couldn’t shown it 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 pretty soon.
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, that is 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 dependency manually through constructor or setter and using an Inversion Of Control container. I do not like to use 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.
Explaining what an Inversion of Control container is needs probably another article, so I will be simple with it. An IoC container is a…..container that contains the dependencies that must be injected. It is called IoC because the design is created in such a way that the objects are never created by the user (in fact you can also forget the use of the new keyword), but they are lazily created by the container when they are requested.
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 coded. The best ones out there are StrangeIoC and ZenInject. Google them, it’s worth it.