Learn how to tackle complicated binding scenarios using Zenject’s Sub-Containers (or Nested Containers).
I know I haven’t shown it too much in the series, but Zenject is great at handling large projects and complex scenarios.
But so far I’ve only covered simple use cases, each using a single context and a single installer.
In this video, we’re going to look at a slightly more advanced topic called sub-containers, also known nested containers.
The funny thing is we’ve actually been using them all along without even knowing it.
You see, the Scene Context, which is required in every scene that uses Zenject, is actually a sub-container of the Project Context.
The Project Context is a global context from which all other containers inherit their bindings.
This is because bindings are inherited by the children and grandchildren of any given container.
Children of the Scene Context come in many forms –
But first, how about a formal definition?
A sub-container is any container that is the child of another container.
By default, a sub-container’s bindings are only visible to itself and its children.
Sub-containers inherit the bindings of their parents, and grandparents.
And are typically used to abstract away a related group of dependencies.
That last part is important, because it sets the stage for a design pattern that goes hand in hand with sub-containers, called the facade pattern.
The facade pattern is a design pattern in which an object, known as the facade, provides a simplified interface for a complex system made up of many interworking parts.
I like the car analogy.
The facade is the ignition switch.
The complex system is everything that goes on under the hood (literally) in order to start your engine when you turn the key.
There are a few ways to define a sub-container.
What you’re looking at here is the simplest method.
In layman’s terms, we’re creating a binding statement that resolves an instance of the Greeter class via a sub-container that is defined using the method called InstallGreeter.
Install Greeter defines a binding for Greeter, as well as a binding for the string “Hello World”, which is only visible to that sub-container.
Defining sub-containers like this may serve well as an example, but you’d be hard pressed to find something like this in production code.
Let’s turn to a more practical example so we can examine a more common type of sub-container.
So this is an open world, exploration and survival, space MMO that I’m working on.
Oh did I mention it also has crafting?
Well, it will eventually, this is just the alpha version, of course.
And being in alpha, it currently only has a single ship.
I think I’m gonna to need to add at least 1 or 2 more before I officially announce the beta.
But before we go creating an entire squadron, let’s analyze what we already have in the scene.
So, first of all, we have a GameObject called “Ship” that has a `Ship` component attached to it.
Nested beneath this is the ship’s model and a canvas that contains a `GameObject` with a `Nameplate` component attached to it.
Finally, at the top of our scene hierarchy, we of course have our scene context, which already has an Installer, called “game installer”, attached to it.
Let’s dive into the code, starting with the Ship class.
Right off the bat, the `Inject` attribute tells us a lot about this class.
`Ship` depends on an instance of `HealthController`, which seems to be a subsystem that’s responsible for handling everything related to a ship’s health.
This is made apparent through `Ship`’s `Heal` and `Damage` methods, which do nothing more than delegate their own responsibilities to the `HealthController`.
Remember the Facade pattern?
Welp, this is basically it.
Except imagine that `Ship` had many public methods like `Heal` and `Damage` that merely delegated their responsibilities to a larger set of subsystems like `HealthController`.
`HealthController` is a plain old C# class, so it doesn’t need the `Inject` attribute.
We can, however, take a look at it’s constructor to get an idea about what it does.
It looks like `HealthController` requires an instance of `Nameplate`, as well as an integer that represents the starting health.
Taking a quick look at the `Heal` and `Damage` methods reveals that health is represented, and kept track of, by a private member variable called “current health”
`Heal` and `Damage` also make calls to `_nameplate.SetHeatlhGauge`, which presumably updates the UI.
If you’re keeping tally, that’s 3 dependencies that need to be filled so far.
Let’s hop on over to `GameInstaller`.
And there they are, `HealthController`, which is injected into `Ship`, followed `Nameplate` and a hard coded `Integer`, which are injected into `HealthController`.
Great, now that we’re up to speed, we can start adding more ships, right?
Well, let me slow my roll for second here.
We have a little bit of a problem that you may have already caught.
`HealthController` is injected as a singleton, which means each ship will receive the same instance.
And, on top of that, `Nameplate` is resolved via a lookup on the hierarchy.
If we had another ship, which’ll add its own nameplate to the hierarchy, Zenject won’t know which one to use.
In fact, let’s give it a shot so you can see what happens.
“Provider returned multiple instances when only one was expected while resolving type ‘Nameplate’…”
Each Ship has its own instance of `Nameplate` attached to it.
So how the heck are we supposed to perform these bindings?
Pfft c’mon, you already know, the video has “sub-containers” in the title for Pete’s sake.
Let’s move all of our ship related bindings to a sub-container.
And for clarity, I’m going to also give `Ship` it’s own `Installer`.
I’m going to call it `ShipInstaller`.
Hah, no surprise there, right?
Now we’re going to add it to our ship prefab via the `GameObjectContext` component.
This is just like the `SceneContext`, except that bindings defined in the installers attached to a `GameObjectContext` will only be visible to other classes in the same context, namely the components nested within.
This allows us to define singleton and hierarchy lookup bindings without worrying about any sort of cross contamination.
We can create as many ships as we want, and they’ll each be injected with their own copies of `HealthContainer` and `Nameplate`.
I’m going to just cut and paste the bindings from `GameInstaller`.
You can think of it like moving these bindings from a larger context, that spans the entire scene, to a smaller context, that spans only a subset of the scene.
Now, let’s create some ships –
I’ll give each one a different starting health –
And there you have it!
Now, with all of this in place, I’m going to show you something pretty cool that we can do now.
Check out this class called `ShipController`.
The `Inject` attribute tells us that it expects to be given a list of `Ship` objects.
It’s OnGUI method exposes two buttons that allow us to both damage and heal the ships in this list.
So, what sort of binding do we need to add in order to fulfill this dependency?
It’s probably a lot easier than you think!
All we need to is to add a `ZenjectBinding` component to the ship’s `GameObject`, reference the `Ship` component, and set the context to the `SceneContext`.
And just like that, all of these ships are injected into the `ShipController` as a list.
At this point you may be thinking to yourself, what if I need to generate these ships dynamically at runtime?
Well, in the next video in this series I’ll be covering factories, which allow you to do just that.
For just $5 on Patreon, you’ll get access to all of the code used in this video, and you’ll be supporting the creation of content like this.
If you enjoyed this Unity tutorial, please leave a like and comment letting me know what you think.
And for more content just like this, please subscribe with notifications on.