Dependency Injector =================== The dependency injector brings an easy-to-use IoC container to Unity. It lets you configure the lifetime of services and provides them to any components that need them. Services are game objects that store shared data, coordinate other game objects or monitor the game - anything that contains code but does not have a physical presence in your scene. Using a dependency injector makes it easier to write code that can be isolated for unit testing and turns the nightmare of managing connections between components and service into a complete non-issue. Overview -------- Even the simple games have different scripts interacting with each other. You may notice that the are two classes of components: /actors/ containing the logic for individual game objects and /services/ managing the overall state of the game and providing information to the actors. ![The game of Pong](pong.png) In a "Pong", there would likely be a `ScoreKeepingService` which stored the score of each player (neither the `Ball` nor the `Paddle` *technically* owns the score): The `Ball` and the two `Paddle` instances would be the actors. The ball would likely access the `ScoreKeepingService` to inform it when it has touched the left or right screen border. Now someone has to create the `ScoreKeepingService`. Also, the `Ball` has to connect to the `ScoreKeepingService` somehow. If your game consists only of a single scene, you can get away with directly assigning the `ScoreKeepingService` to the `Ball`. But if there are multiple scenes, things quickly get tricky: * The `ScoreKeepingService` has to stay alive across scene changes. * The `Ball` doesn't, however, and has to look for the `ScoreKeepingService`. * Said `ScoreKeepingService` might exist from the previous scene, but if you run just the current scene in the editor, it might also have to be created first. As the game grows, you end up with dozens of cases like this. Sometimes the services should be recreated in the next scene, sometimes they have to survive scene changes. And sometimes they need to be destroyed when the player starts a new game, but not when the player enters different scenes. Is there a way to generalize this and avoid managing the lifetime of these services by hand? There is. It's called /dependency injection/! How it Works ------------ Let's return to our example of the /Pong/ game. We might have the following components interacting with each other in the game (if you're new to UML, each box is a class derived from MonoBehaviour and the arrows indicate relationship; minus signs in front of methods and fields indicate that they're private, plus signs that they're public). +-------------------------------------+ +-------------------------------------+ | ScoreKeepingService | | ScoreDisplay | +-------------------------------------+ +-------------------------------------+ | + LeftScore : int |<-------| + OnGUI() | | + RightScore : int | | - scoreKeeper : ScoreKeepingService | | - scoreKeeper : ScoreKeepingService | +-------------------------------------+ +-------------------------------------+ ^ | | +-------------------------------------+ +-------------------------------------+ | Ball | | Paddle | +-------------------------------------+ +-------------------------------------+ | + Update() | | + Update() | | - scoreKeeper : ScoreKeepingService | +-------------------------------------+ +-------------------------------------+ So when a ball gets added to the scene, it somehow has to obtain the `ScoreKeepingService` (or create a new one). How does it go about this with a dependency injector? See for yourself: public abstract class BallController : ScriptComponent { protected void Inject(ScoreKeepingService scoreKeeper) { this.scoreKeeper = scoreKeeper; } private ScoreKeepingService scoreKeeper; } Instead of `MonoBehaviour`, the `BallController` derives from the `ScriptComponent` class, thereby stating that it wants to access services via the dependency injector. If a class deriving from `ScriptComponent` defines a method named `Inject()`, the dependency injector will call it, providing it with any services specified in its parameters. So if the `BallController` defines its `Inject()` method like this: public abstract class BallController : ScriptComponent { protected void Inject( ScoreKeepingService scoreKeeper, ScreenShakingService shaker ) { this.scoreKeeper = scoreKeeper; this.shaker = shaker; } private ScoreKeepingService scoreKeeper; private ScreenShakingService shaker; } Then the dependency injector would look for the screen shaking services (or create a new one) and provide it to the `BallController`. It's as simple as that. When your component comes to life, whatever services its `Inject()` method lists in its parameters will be looked up or created. This is mostly equivalent to the concept of /constructor injection/, except that, due to Unity's design, a method named `Inject()` takes on the role of the constructor. Service Lifetime ---------------- Besides making services available to game objects, the dependency injector also lets you define the lifetime of a service by placing it in one of three scopes: * `Scope.Scene` - Services in this scope will be destroy when the scene gets unloaded. It is mostly useful for short-lived things like camera managers, enemy coordinators or ambient audio sources. * `Scope.Global` - Services in this scope remain alive for as long as the game runs. That means they will be created once and then remain available in the main menu, during gameplay, when the credits roll, everywhere. * `Scope.Session` - This is a special scope that means the services will remain available for one gameplay session. A gameplay session is defined as what happens between loading or starting a new game until the player ends the game by winning, losing or selecting the "Quit to Main Menu" option. Since there's no way for the service container to figure out when the session ends (the main menu scene looks no different to a typical level from a code perspective), you have to kill the session-scoped services yourself at the appropriate time. To do this, you can either place the `SessionKiller` component in a scene or call `ServiceContainer.Kill(Scope.Session)` yourself. To declare the scope in which a service exists, use the `Scope` attribute. For example, here's a service that would get killed once the player moves to another scene: [ServiceScope(Scope.Scene)] public class EnemyCoordinator : Service { public const int MaximumAttackingEnemyCount = 3; public bool CanAttack { get {} } public void RegisterAttacker(Enemy enemy) {} public void UnregisterAttacker(Enemy enemy) {} } Here's another service that would stay around from the first time it is requested until your game is closed: [ServiceScope(Scope.Global)] public class TranslationProvider : Service { protected override void Awake() { base.Awake(); loadTranslatedStrings(); } public string GetTranslation(string id, string default) {} private void loadTranslatedStrings() {} } Scene Layout ------------ If you're curious about where your services are stored, the dependency injector creates a new node named `Services` at the root of your scene. It will have three nested nodes containing the services organized by scope: ![Services in the Global Scope](global-services.png) ![Services in the Scene Scope](scene-services.png) ![Services in the Session Scope](session-services.png) Sessions -------- As mentioned before in the chapter "Service Lifetime", a session is defined as what happens between the player starting loading a saved game or starting a new game until he stops playing (or loads another saved game / starts over). So a service that takes care of the player's inventory or stats (so that, for example, a `PlayerController`, `InventoryScreen` and `SaveManager` can access it) would be ideally suited for the /session/ scope. When the player ends his playing session (for example, after the credits roll, or by returning to the main menu), you need to kill all session-scoped services. This can be done by calling `ServiceContainer.Kill(Scope.Session)`. If there's a scene in which, when it is entered, the session is guaranteed to be over (for example, your game might only show its main menu when the player has stopped playing and there's no "Return to Game" option in your main menu), you can also add the `SessionKiller` component in a game object of your main menu scene. It simply kills the session as soon as the scene loads. What This is Not ---------------- This is not a tool for novice programmers to just push their dependency management worries aside. You will still have to think about how components interact with each other, come up with a reasonable structure and be aware of this structure when writing components. This also is not a full-blown dependency injector. There is no runtime configuration (only intrusive base classes and attributes) and basic binding. You also cannot tell the dependency injector to bind an interface to an implementation, it only binds concrete classes. It's fit well into Unity's architecturally-challenged programming model but doesn't try to change it.