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 (
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
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
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
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
or as assets by extending
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
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
property which the custom build pipeline can set