Dependency injection in Unity with Zenject

Dependency injection in Unity projects is slightly complicated by the fact that we do not have full control over the initialization of the engine. Unity instantiates it’s various object types (GameObject, Component, MonoBehaviour, ScriptableObject, Material, etc) when scenes and assets are loaded. Put simply… the objects exist before we can do anything with them.

More often than not I see developers implementing singletons and using static state so that their components can interact with one another. Whilst this seems like an easy solution for sharing and exposing state and behavior across a game; it introduces a high degree of coupling which tends to bite a little further down the road.

Take for example a game that has a static singleton GameManager implementation that is used to access the player; track health, score, game rules, etc. The game is composed of many highly reusable components. You then decide that you want to add some sort of special secondary game mode. So you create a GameManagerSpecial (also a static singleton) with the special gameplay logic. You stick a scene together and realize that things are acting up… all of the highly reusable components are all hard-wired to directly access the original GameManager.Instance property!

I’ve seen a number of existing code bases where there are branching statements everywhere selecting between the game modes to workaround this situation. When I’ve asked the developers why they did this they’ve said that it was a quick and dirty workaround. Some simple structural changes would have made the code more SOLID and averted the need for the developer to mass modify their code to cater for having different game modes.

Fortunately there are a number of frameworks that help to overcome these problems. Some of the frameworks bring along a lot of their own architecture whilst others are much more bare bones allowing you to use your own architecture. I’ve tried several of the frameworks (commercial and open source) and have found Zenject to be the best approach for my projects.

Zenject is purely a dependency injection framework that doesn’t dictate any sort of architecture. Whilst Zenject can be used in regular .NET applications; it has many features that are designed exclusively to overcome some Unity-specific problems. Zenject is able to inject scene components when scenes are loaded or prefabs when instantiated using Zenject’s API (rather than Unity’s Instantiate function).

This is fantastic because it frees you to structure your classes much more freely. Rather than making excessive usage of static state and the various GameObject.Find type functions, it becomes very easy to inject services, factories, etc directly into the components that need them. For instance, it becomes easier to use design patterns like MVC, MVP, MVVM with UGUI.

Implementations are bound and further defined to control instance lifetimes inside custom installer implementations. These can be implemented as components by extending MonoInstaller or as assets by extending ScriptableObjectInstaller.

For instance, let’s suppose that you want to inject an interstitial provider:

this.Container.Bind<IInterstitialProvider>()
    .To<ChartboostInterstitialProvider>()
    .AsSingle()
    .WithArguments(this.chartboostOptions);

Of course it is possible to inject different implementations of IInterstitialProvider on a per platform basis. It is also possible to use use different options based upon the platform; for instance,

#if UNITY_ANDROID
    // Use 'Chartboost' implementation with options for 'Android' platform.
    this.Container.Bind<IInterstitialProvider>()
        .To<ChartboostInterstitialProvider>()
        .AsSingle()
        .WithArguments(this.chartboostOptionsAndroid);
#elif UNITY_IOS
    // Use 'Chartboost' implementation with options for 'Apple' platform.
    this.Container.Bind<IInterstitialProvider>()
        .To<ChartboostInterstitialProvider>()
        .AsSingle()
        .WithArguments(this.chartboostOptionsApple);
#else
    // Assume a "null" implementation for all other platforms.
    this.Container.Bind<IInterstitialProvider>()
        .To<NullInterstitialProvider>()
        .AsSingle();
#endif

New target platforms can be supported by wiring up new configurations of the game’s various services.

Unity makes it easy to detect if we’re on, for example, Android or iOS but it isn’t so easy to detect if we’re currently building for the Google Play Store or Amazon Kindle since both of these are Android platforms. If you have a separate installer configuration asset for these two stores then the building of the final .apk can easily be automated by extending the editor with “Build for Google Play Store” and “Build for Amazon Kindle” options.

For example, you could use the Unity build pipelines API (or a visual tool like uTomate) to set the active project configuration; and then trigger the desired build. To do this I have a “ConfigurationSelector.asset” that exposes a single SelectedConfiguration property which the custom build pipeline can set