If you’re familiar with Strange IoC, Zenject is similar to that except it is more of a pure DI framework. It takes inspiration from well known existing DI frameworks such as Ninject (which also inspired the name) - the difference here being that it’s designed to specifically target Unity.
Zenject can be used to turn the code base of your Unity 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.
Tested on the following platforms:
PC/Mac/Linux, iOS, Android, and Webplayer
For more information, as well as a general introduction to Dependency Injection, and a complete tutorial on each feature, see the git hub page here.
Features
Injection into normal C# classes or MonoBehaviours
Constructor injection (can tag constructor if there are multiple)
Field injection
Property injection
Conditional Binding Including Named injections (string, enum, etc.)
Optional Dependencies
Support For Building Dynamic Object Graphs At Runtime
Auto-Mocking using the Moq library
Injection across different Unity scenes
Ability to print entire object graph as a UML image automatically
I love the direction the framework is going.
I tried to wrap my head around StrangeIoC, but it had too many features and the documentation didn’t feel too clear, what i needed was to just be able to inject objects, nothing more. So i found this framework, and the example was simple and totally made sense, so i got into a habbit of using the framework with almost every project since.
Hello, Thanks for the initiative in creating Zenject as well as the continued good work. I am trying to apply Zenject into a game project.
I have encountered a problem. I wonder if you could kindly give some advice. I am working well with constructor injection for non-MonoBehaviour classes, but I have failed to get Property Injection work with subclasses of MonoBehaviour. My code is like this:
public class Tile : MonoBehaviour
{
[Inject]
MapManager _mapManager;
public void Start(){
// _mapManager is found to be null still!
}
}
If I set a breakpoint in Start() and check the value of _mapManager, it is null. What is wrong?
If you are creating it dynamically then you will have to use the [PostInject] attribute with another method (not Start)
public class Tile : MonoBehaviour
{
[Inject]
MapManager _mapManager;
[PostInject]
public void Initialize()
{
// _mapManager should be non null now
}
}
This is because when objects are created dynamically, Unity executes Awake() and Start() immediately, so Zenject doesn’t have a chance to inject dependencies into it.
My Tile objects are indeed dynamically created. I have changed my Start() to Initiailize() and added [PostInject]. It still does not work. I have another function Tile.TakeTile(). When I set on breakpoint in TakeTile() and another breakpoint in Initialize(), it turned out that the TakeTile() breakpoint is triggered first.
I think the issue is not about to do what after injection but that injection has not got chance to be done yet.
Is there any possibility to have something like setter injection so that I can call the setter function manually at the beginning of Start()?
I think for the moment I am going to do the injection manually this way:
Since the Monobehaviour subclass object is instantiated by my own codes dynamically, I know where to inject the dependencies as soon as it is instantiated.
I’ll make a TileFactory class which is not subclass of Monobehaviour. It has MapManager injected through constructor. Make a setter method in Tile class to inject MapManager manually. TileFactory.create() will call the setter as soon as the Tile object is instantiated:
public class TileFactory{
protected MapManager _mapManager ;
TileFactory( [Inject] MapManager mapManager ){
_mapManager = mapManager;
}
public Create(){
Tile newTile = instantiate(...);
newTile._mapManager = _mapManager;
return newTile;
}
}
How are you instantiating your new GameObject? If you want Zenject to inject dependencies into it then you have to use a Zenject class such as GameObjectInstantiator. Could that be the issue?
GameObjectInstantiator injects all dependencies - not just MonoBehaviours. So that should work.
Can you show me the code you’re using in your installer to bind your objects as well as the code you’re using in the monobehaviour and the code you’re using to instantiate it?
public class MyInstaller : MonoInstaller{
public override void InstallBindings(){
_container.Bind<IInitializable>().ToSingle<MapManager>();
_container.Bind<MapManager>().ToSingle ();
}
}
Codes of Monobehaivour subclass:
public class Tile : MonoBehaviour, IInitializable{
[Inject]
public static MapManager _mapManager;
[PostInject]
public void Initialize(){
if( null == _mapManager )
throw new UnassignedReferenceException("Dependency _mapManager is found null");
}
}
Code of instantiating the Tiles:
public class TileFactory: IInitializable{
protected GameObjectInstantiator _zenjectInstantiator;
public TileFactory ( [Inject] GameObjectInstantiator zenjectInstantiator ){
_zenjectInstantiator = zenjectInstantiator;
}
public virtual void Initialize(){
}
public virtual GameObject Create( params object[] args ){
Vector2 pos = (Vector2) args[0];
GameObject newTileObject = (GameObject) _zenjectInstantiator.Instantiate( groundTile,
new Vector3(pos.x, height, pos.y),
Quaternion.identity);
return newTileObject;
}
}
I have found the following issues:
If I add [PostInject] before Tile class’ Initialize(), then it is called so early that it is invoked even before those at the front of Initializables list of my installer; If I do not use [PostInject], then Tile.Initialize() is not called at all. I find no suitable method to Bind IInitializable to Tile either.
I find your code of GameObjectInstantiator class uses the arguments after the 1st one of method Instantiate() for other uses. However, I am using the type of Object.Instantiate() function with a signature that uses 3 arguments. This is a conflict.
I have another method in Tile class trying to use injected object _mapManager, but found it is null. This method is invoked by the Initialize() method of another class. If my method of instantiating Tile objects is right, I guess this might be a bug in your code. All injections should have been done before any Initialize() method is called.
Not a complaint, but hope raising these will help you to improve Zenject as well. Thanks.
I see the problem. MapManager should not be declared as static. Zenject doesn’t inject static members.
Something minor I noticed here is that you don’t need to use [Inject] attribute in constructor parameters. (Constructor parameters are always injected). I’m assuming here that you removed the Vector3 and Quaternion inject fields in the tile class?
I normally only use [PostInject] for classes that are created dynamically after startup. I prefer to use IInitializable for start-up because you have control over the order that objects get initialized in (with PostInject the init method would always be called in the order of construction).
Since your object is created dynamically it won’t work with IInitializable. Also, any class that derives from IInitializable also has to be bound to it in your installer for its Init method to be called.
If [PostInject] is too early then you may want to call initialize() manually from another class at whatever time you want. By the way, [PostInject] is called immediately after the object is instantiated so will be called wherever you’re calling Create() on your factory
Ohhh I see why you added those arguments now. The version of Instantiate in GameObjectInstantiator does not include params for position and rotation (unlike unity’s normal Instantiate). If you want to configure these then I recommend something like this instead:
var obj = _zenjectInstantiator.Instantiate(prefab);
obj.transform.position = ...
obj.transform.rotation = ...
Absolutely! I’m always interested to hear criticisms or usability issues people are having. I think the issue is the use of the keyword static but if not let me know.
Hello, Another question:
I am trying to use Zenject when doing standalone c# class unit test. These classes are not based on UnityEngine.
However, I got many compiler errors if I check the option “Build project in MonoDevelop” in Tools menu’s Options dialogbox, then choose “Unity-> Debugger”.
Yes, I do the same thing in my project. There is a way to build an assembly for Zenject for use outside of Unity. If you get Zenject from the git repo (here: GitHub - modesttree/Zenject: Dependency Injection Framework for Unity3D) there is an “AssemblyBuild” directory. However it uses Visual Studio to do the build so that may or may not work for you. It does link in the UnityEngine.dll but as long as you don’t use classes that depend on Unity such as CompositionRoot or GameObjectInstantiator then it works fine.
After having some food, I revolved it. Actually, just I missed several .cs files under root Zenject folder. It works as a dll now. I produced it right from MonoDevelop.
I followed the guide in this page. I created a c# Library project and “Added Existing Folder” etc. Now I have the ZenjectLib.dll so that I can reference it from my game project.