Zenject Unity



One useful design pattern in game development is the State design pattern. It can be used in various occasions from player input to enemy AI.

Long form, TL;DR below I'm currently developing a game using Zenject and designed my UI and game architecture very similar to a web app where there are IConversationRepository-Interfaces providing. With a bunch of projects in my mobile development career, mainly for startups, I can help you either with: - working on ongoing project: fixing bugs, implementing new features, propose solutions etc. create a new project from scratch, including: define business requirements, implement UI/UX per design, integrate required API, business logic, actively communicate with designers and backend. Zenject is a lightweight dependency injection framework built specifically to target Unity 3D.

We can manage all the states using a Finite State Machine. With a FSM we have distinct finite states that we can transition to, but having only one state enabled at any given time.

In our example we will implement a Game Manager, that controls our game states in Unity, using Zenject framework. Zenject is Dependency Injection Framework for Unity 3D.

Taken from the gitHub documentation:

Zenject is a lightweight dependency injection framework built specifically to target Unity 3D (however it can be used outside of Unity as well). It can be used to turn your application into a collection of loosely-coupled parts with highly segmented responsibilities. Zenject can then glue the parts together in many different configurations to allow you to easily write, re-use, refactor and test your code in a scalable and extremely flexible way.

For our example we will use Unity 5.6.0 and Zenject 5.1.0.
Zenject 5.2.0 has been recently released, so if you prefer you can use the newer version.
If you use an older version of Unity, the latest Zenject version might not be compatible, so you might need to download a previous version from the Releases Page.

Zenject Unity Tutorial

You can install Zenject using different methods. The easiest way is to download it from the Asset Store Page. The above version includes two sample games (Asteroids and SpaceFighter) to further explore Zenject framework.

For a cleaner project, we will download the unity Zenject 5.1.0. package from the Releases Page without the sample projects.

Now create a new unity project and import Zenject 5.1.0 package. Right click in the Hierarchy and select Zenject → SceneContext. Then a SceneContext gameObject will be created.

The SceneContext component is the entry point of the application, where Zenject set up all the various dependencies.

Now in the Assets folder create a Scripts folder and inside it an Installers and a GameManager folder. Right click inside the Installers folder and choose Create → Zenject → MonoInstaller and name it GameInstaller.

Add GameInstaller script in the SceneContext GameObject and add a reference to your SceneContext component, by adding a new row in the inspector of the “Installers” property (press the + button) and then dragging the GameInstaller GameObject to it.

An Installer declares all the dependencies used in your scene and their relationships with each other.

It’s a good time to save our scene, so we give our scene the FSM_Example name and we put it inside a Scenes folder for better organization.

We are going to set aside the GameInstaller for now and focus on the creation of our GameManager. Inside GameManager folder create a new C# script called GameManager.

Zenject

Fill it with the code below:

Don’t worry about the missing type/namespace errors. We will add all the missing dependencies in the following steps.

Normally a class that inherits from MonoBehavior does not have a constructor, but using the [Inject] attribute of the Zenject framework, we can inject our dependencies into our classes. There are a lot of ways to have these dependencies injected, but for our Construct method we are using what we call a Method Injection.

The core method is ChangeState which handles the state transitions. The way that we will implement the state change, is by using a Factory. Factories are used to create objects dynamically, but in our case we will use it to create a new state.

This lead us to the creation of the next script’ the GameStateFactory. Inside the GameManager folder create a new C# script called GameStateFactory. Include the following code:

If you remember the ChangeState method of the GameManager class calls the CreateState method of GameStateFactory which returns the requested game state entity.

In GameManager folder create a States folder and inside create a new C# script called GameStateEntity.

Include the following code:

GameStateEntity is abstract class that contains the virtual methods that will be overridden by our state classes. Thus GameStateEntity will play the role of the base class for the all the implemented states.

In our example we decided to implement Iinitializable, Itickable, Idisposable interfaces.

In order to understand how to inherit from the GameStateEntity class, we are going to create our first state. Inside the States folder create a new C# script called MenuState.

Add the following code:

We added some debug methods to figure out when the overridden methods are called.

The Initialize method will be called on startup and it’s a good place to put all the initialization logic before the MenuState is created.
When our state is created (by calling ChangeState(GameState.Menu) in GameManager) Start() is the first method that is called.
Use Tick() to perform per frame tasks.
Finally Dispose will be called when we change to a new state, when the scene changes, the app closes, or the composition root object is destroyed. This is useful when you want to clear unused resources or disable redundant functionality.

Similarly we will create our Gameplay state. Create the GameplayState script inside the States folder. The script will look like this:

It’s up to you to fill the Start and Tick methods with code that will be responsible managing the actual gameplay.

If you remember the GameState enum inside the GameStateFactory script, we also have two other states’ GameOver and Victory. You can add more states to your game, by expanding the GameState enumerator and the GameStateFactory class.

Now it’s time to create the GameOverState by adding a new a new C# script. In GameOverState script add the following:

As you can see we added some new elements to GameOverState, an AsyncProcessor and a Settings class.

Imagine that when you transition to a GameOver screen (or Victory), you want to move back to the Menu screen after an amount of time.
In our example the GameOverState is a normal C# class. Using Zenject, there is less of a need to make every class a MonoBehavior. But there are still occasions that we want to call a StartCoroutine to add asynchronous methods.

One suggested solution is to use a dedicated class and just call StartCoroutine on that instead. AsyncProcessor class will play that role.

In the Scripts folder create a Helper folder. Inside it create a new C# script called AsyncProcessor. You just have to put the following code:

Alternatively, if you don’t want to use the AsyncProcessor class, you can use UniRx or a coroutine library that implements a similar functionality (Look here for an example).

The Settings class has a waitingTime field, which represents the time to wait before moving to the Menu state. Instead of changing the float value using code or the Unity inspector we can use a ScriptableObjectInstaller, which has the advantage to change these values at runtime and have those changes persist when play mode is stopped.

Before creating our installers, we will add the our final state. In the States folder create the VictoryState script.

Add the following code in VictoryState:

If you did all the previous steps correctly you shouldn’t get any errors in Unity console.

We return to the GameInstaller script and fill it with the necessary bindings. In Zenject framework an Installer is a re-usable object, that contains a group of bindings.

The actual dependency mapping is achieved by adding bindings to a container. As a result the container knows how create all the object instances in your application, by recursively resolving all dependencies for a given object.

Add the following code inside the GameInstaller script:

To add bindings to an installer we have to override the InstallBindings method, which is called by whatever Context the installer has been added (in our example the SceneContext).

Let’s see the first statement of the InstallGameManager() method.

By calling:

we say that any class that take GameStateFactory as input will receive the same instance of the GameStateFactory.

In the next four lines we bind all the interfaces to the classes and the classes themselves by calling the BindInterfacesAndSelfTo shortcut.

Therefore

is equivalent to:

In

binding the Itickable interface will result Tick() being called like Update().

Finally in

it means that the Container will only ever instantiate one instance of the type MenuState type.

So it is recommended to use BindInterfacesAndSelfTo and BindInterfacesTo in order to group interface bindings and increase the code readability.

In the final four statements we bind all the all the factory dependencies. For example by using VictoryState.Factory all the dependencies for the VictoryState (like GameManager) will be filled in automatically.

Zenject Unity

In InstallMisc method we add the binding for the AsyncProcessor. The FromNewComponentOnNewGameObject is a construction method that creates a new empty gameObject and instantiates a new component of the given type (in our case an AsyncProcessor).

So when we transition to GameOverState (or VictoryState) for the first time, Zenject will create a new gameObject with an AsyncProcessor component, in order to be used by GameOverState (or VictoryState). AsSingle says that every class that requires a dependency of type AsyncProcessor (like GameOverState), will be given the same instance of type AsyncProcessor.

For our settings we will create a Scriptable Object Installer. Inside the Installers folder right click and select Create → Zenject → Scriptable Object Installer and name it GameSettingsInstaller. Inside the GameSettingsInstaller put the following code:

With the above code we can create a scriptable object installer and Zenject will bind the instances to the required types.

Zenject Unity Asset Store

Right click in the Installers folder and from the newly added menu select Create → Installers → GameSettingsInstaller.
Select the created GameSettingsInstaller.asset file and to the Inspector, put the value 2 for the Game Over State waiting time and 5 for the Victory State waiting time.

The last thing to do is add the GameSettingsInstaller scriptable object in our scene context.
Select the SceneContext gameObject and in the Scriptable Object Installer press the + button to add a new row on the list.
Drag and drop GameSettingsInstaller into that slot.

We are going to add some UI elements in our scene in order to test our GameManager functionality.

In the Hierarchy create a new gameObject called GameManager and add a GameManager component to it.
We will try to validate our scene by selecting Edit → Zenject → Validate Current Scene or simply pressing CTRL+SHIFT+V. What validation does is that it executes all installers for the current scene and iterate though the the object graphs and verify that all bindings can be found.

If we try to do this we will get the following error in Unity console:

ZenjectException: Unable to resolve type ‘FSM.GameManager.GameManager’ while building object with type ‘FSM.GameManager.States.GameOverState’. Object graph:

This means that GameManager MonoBehavior is not added to the Zenject Container, thus can’t be injected into other classes (like GameOverState or VictoryState).

One way to handle the Scene Bindings is by adding the ZenjectBinding component in the GameManager gameObject. In the Components array increase it’s size to 1 and drag and drop the GameManager Script. Then the GameManager script will look likes this:

Clear the Unity console messages and try to validate your scene again (by pressing CTRL+SHIFT+V). If everything will go as planned you will get the following message:

In the Hierarchy add a UI → Text and name it StateText. Clear it’s text value and place it in the following rect transform position if you like:

Drag and drop the StateText gameObject into the State Text field of our GameManager:

Add a UI Button, name it MenuButton and change the text value in it’s text component to Menu. You can place the MenuButton here:

Zenject Unity

With the same process add another UI Button, name it GameplayButton and change the text value in it’s text component to Gameplay. You can place the GameplayButton here:

Add another Button, name it GameOverButton and change the text value in it’s text component to GameOver. You can place the GameOverButton here:

Finally add a Button, name it VictoryButton and change the text value in it’s text component to Victory. You can place the VictoryButton here:

We will add a new Monobehavior to control the buttons behavior. Under the scripts folder create a UI folder, and inside a new C# script called StateChangeManager.
Add the following code in StateChangeManager:

Add the StateChangeManager script in the Canvas gameObject.

Now select the MenuButton gameObject and inside the Button component add a new item in the On Click() List. Select the Canvas game object and on the function list choose StateChangeManager → ChangeToMenu.

Repeat the same process to the remaining buttons.
In GameplayButton select StateChangeManager → ChangeToGameplay.
In GameOverButton select StateChangeManager → ChangeToGameOver.
In VictoryButton select StateChangeManager → ChangeToVictory.

Run the scene and press the buttons to change states.

By pressing the GameOver or the Victory buttons, we return to the Menu state after the time specified in GameSettingsInstaller scriptable object.

Now all you have to do is to extend the functionality of the FSM to cover your game requirements. Moreover it’s recommended to study Zenject documentation and experiment with the given sample games. Zenject framework has a learning curve, thus will take some time to understand and apply all the concepts.

You can download the complete project from here.

Resources:

1) State pattern
2) Game Programming Patterns – State
3) Finite-state machine
4) Game programming patterns in Unity with C# – State pattern
5) Unity download archive
6) Zenject – Dependency Injection Framework for Unity3D

Luna doesn't support DLL versions of plugins so it is required to include the C# source files inside your project.

This guide is for developers who are using Zenject in their Unity projects as DLLs.

  1. Download or clone the Zenject source code on GitHub.
  2. Move the Zenject source files located inside Zenject-master > UnityProject > Assets > Plugins > Zenject outside Zenject-master.

  1. You will receive some console errors that need resolving but don't worry, these are caused by DLL usages requests within the plugin.
    To clear the errors you will need to remove then replace Zenject > Source > Usage folder contents (containing the DLL) with the contents of Zenject-master > AssemblyBuild > Zenject-usage with is the source files.

Remove the DLL usage:


Move the source usage
  1. Once this is done you can remove the Zenject-master from your project. You can also remove optionalExtras from the Zenject folder.

  2. The final step will be an invisible build error caused in Luna. This error is caused by a specific script called CachedProvider.cs, and the pre-processor directives (#if and #endif), which can cause build errors in Luna.

To resolve the build compilation failure you should comment out all pre-processors from this script which will allow for a successful build.

Example:

readonly object _locker = new object();
// #else
// #endif

Zenject Tutorial

You may need to restart Unity as some errors might be present due to cache.