Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

[RELEASED] Zenject Dependency Injection Framework

Discussion in 'Assets and Asset Store' started by eventropy, Jun 3, 2014.

  1. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    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.

    Asset Store Link

    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
    * Ability to validate object graphs at editor time
     
  2. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Updated version to 1.08 (July 12 / 2014) which includes:
    - Several large performance improvements
    - Minor API improvements such as the way dependency identifiers are handled
     
  3. FREEZX

    FREEZX

    Joined:
    Apr 2, 2013
    Posts:
    60
    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.

    Thanks for making this, you rock!
     
  4. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Updated version to 1.10 which includes:
    - Support for nested containers (which means you can have a static container that contains objects that persist across scenes)
    - Support for non-generic binding using Type objects
    - Fixes to the handling of IDisposable
     
  5. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    No problem, glad you're enjoying it!
     
  6. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Updated to version 1.11 which includes:
    - Removed Fasterflect DLL library to keep Zenject nice and lightweight
    - Added custom inspector for composition root
     
  7. gm2008

    gm2008

    Joined:
    Aug 8, 2014
    Posts:
    19
    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:
    Code (CSharp):
    1. public class Tile : MonoBehaviour
    2. {
    3.     [Inject]
    4.     MapManager _mapManager;
    5.  
    6.     public void Start(){
    7.            // _mapManager is found to be null still!
    8.     }
    9. }
    If I set a breakpoint in Start() and check the value of _mapManager, it is null. What is wrong?
     
  8. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Are you creating your MonoBehaviour dynamically?

    If you are creating it dynamically then you will have to use the [PostInject] attribute with another method (not Start)

    Code (csharp):
    1.  
    2.     public class Tile : MonoBehaviour
    3.     {
    4.        [Inject]
    5.         MapManager _mapManager;
    6.  
    7.        [PostInject]
    8.        public void Initialize()
    9.        {
    10.                // _mapManager should be non null now
    11.        }
    12.     }
    13.  
    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.
     
  9. gm2008

    gm2008

    Joined:
    Aug 8, 2014
    Posts:
    19
    Wow, thank you very much for the quickly reply.

    My Tile objects are indeed dynamically created. I have changed my Start() to Initiailize() and added [PostInject]. It still does not work.:oops: 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()?
     
  10. gm2008

    gm2008

    Joined:
    Aug 8, 2014
    Posts:
    19
    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:
    Code (CSharp):
    1. public class TileFactory{
    2.     protected MapManager _mapManager ;
    3.     TileFactory( [Inject] MapManager mapManager ){
    4.         _mapManager = mapManager;
    5.     }
    6.     public Create(){
    7.          Tile newTile = instantiate(...);
    8.          newTile._mapManager = _mapManager;
    9.          return newTile;
    10.     }
    11. }
     
  11. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    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?
     
  12. gm2008

    gm2008

    Joined:
    Aug 8, 2014
    Posts:
    19
    Thanks for reply. I've tried with GameObjectInstantiator. It is not working either.

    I've traced into the codes:
    * In GameObjectInstantiator.Insantiate(), it calls InjectionHelper.InjectChildGameObjects();
    * Inside InjectChildGameObjects(), it calls InjectMonoBehaviour() for each child GameObject.

    So the issue is if what I am trying to property inject is not a MonoBehaviour, then it is not injected.

    Is there any other function provided to deal with all property injections including non-Monobehaviour objects?

    Thanks.
     
  13. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    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?
     
  14. gm2008

    gm2008

    Joined:
    Aug 8, 2014
    Posts:
    19
    Thanks a lot for your kind help.
    My installer:
    Code (CSharp):
    1. public class MyInstaller : MonoInstaller{
    2.     public override void InstallBindings(){
    3.         _container.Bind<IInitializable>().ToSingle<MapManager>();
    4.         _container.Bind<MapManager>().ToSingle ();
    5.     }
    6. }
    Codes of Monobehaivour subclass:
    Code (CSharp):
    1.  
    2. public class Tile : MonoBehaviour, IInitializable{
    3.      [Inject]
    4.      public static MapManager _mapManager;
    5.      [PostInject]
    6.      public void Initialize(){
    7.          if( null == _mapManager )
    8.              throw new UnassignedReferenceException("Dependency _mapManager is found null");
    9.       }
    10. }
    11.  
    Code of instantiating the Tiles:
    Code (CSharp):
    1. public class TileFactory: IInitializable{
    2.     protected GameObjectInstantiator _zenjectInstantiator;
    3.     public TileFactory ( [Inject]  GameObjectInstantiator zenjectInstantiator ){
    4.         _zenjectInstantiator = zenjectInstantiator;
    5.     }
    6.     public virtual void Initialize(){
    7.     }
    8.  
    9.     public virtual GameObject Create( params object[] args ){
    10.         Vector2 pos = (Vector2) args[0];
    11.         GameObject newTileObject = (GameObject) _zenjectInstantiator.Instantiate( groundTile,
    12.                                                         new Vector3(pos.x, height, pos.y),
    13.                                                         Quaternion.identity);
    14.         return newTileObject;
    15.     }
    16. }
    I have found the following issues:
    1. 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.
    2. 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.
    3. 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.
     
    Last edited: Aug 15, 2014
  15. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    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:

    Code (csharp):
    1.  
    2.  
    3. var obj = _zenjectInstantiator.Instantiate(prefab);
    4. obj.transform.position = ...
    5. obj.transform.rotation = ...
    6.  
    7.  
    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.
     
  16. gm2008

    gm2008

    Joined:
    Aug 8, 2014
    Posts:
    19
    It's working like a charm now!:D
    Two problems: 1. static member; 2. More argument passed to GameObjectInstantiator.Instantiate();

    Suggest you add these two issues to the manual so that other people do not fall in the same problems.
     
  17. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Good call - I will add a "Gotchas" section
     
  18. gm2008

    gm2008

    Joined:
    Aug 8, 2014
    Posts:
    19
    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".

    Is there any ready made dll to reference?

    Thanks!
     
  19. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    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: https://github.com/modesttree/Zenject) 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.
     
  20. gm2008

    gm2008

    Joined:
    Aug 8, 2014
    Posts:
    19
    Thank you very much for reply.

    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.
     
  21. gm2008

    gm2008

    Joined:
    Aug 8, 2014
    Posts:
    19
    Hmmm, still have some problem with running unit test although it passed compiling. I ran it from Unit Test pad, it said, "No suitable constructor was found."

    My test class is like this:
    Code (CSharp):
    1. namespace UnitTest
    2. {
    3.     [TestFixture()]
    4.     public class MapManagerTest
    5.     {
    6.         protected MapManager obj ;
    7.         Instantiator _zenjectInstantiator;
    8.         public MapManagerTest(Instantiator zenjectInstantiator){
    9.             _zenjectInstantiator = zenjectInstantiator;
    10.         }
    11.         [SetUp]
    12.         public void Init(){
    13.             obj = (MapManager) _zenjectInstantiator.Instantiate( typeof(MapManager) );
    14.         }
    15.         [Test()]
    16.         public void TestGenerateMap ()
    17.         {
    18.             obj.GenerateMap();
    19.             Assert.AreEqual (obj.map.Rank, 2);
    20.             Assert.AreEqual (obj.map.GetLength(0),10);
    21.             Assert.AreEqual (obj.map.GetLength(1),10);
    22.         }
    23.     }
    24. }
    25.  
    Only the MonoInstaller subclass I defined in the original project knows about the bindings. Obviously the Unit Test framework does not invoke the installer. I still do not understand how Zenject works with NUnit. Very appreciated if you could give some hints.
     
  22. gm2008

    gm2008

    Joined:
    Aug 8, 2014
    Posts:
    19
    I have solved it by simulating the test codes under Zenject\Extras\ZenjectUnitTests. I copied your class TestWidthContainer and have made a subclass of it as follows, which I am not sure whether it is the right way.

    Code (CSharp):
    1. namespace UnitTest
    2. {
    3.     [TestFixture()]
    4.     public class MapManagerTest: TestWithContainer
    5.     {
    6.         protected MapManager obj ;
    7.         [SetUp]
    8.         public override void Setup(){
    9.             base.Setup ();
    10.             obj = _container.Instantiate<MapManager>();
    11.         }
    12.         protected override void RegisterBindings()
    13.         {
    14.             _container.Bind<MapManager> ().ToSingle ();
    15.             _container.Bind<GameObjectInstantiator> ().ToSingle ();
    16.             _container.Bind<IDependencyRoot>().ToSingle<DependencyRootStandard>();
    17.          
    18.             _container.Bind<IInstaller>().ToSingle<StandardUnityInstaller>();
    19.         }
    20.      
    21.         [Test]
    22.         public void TestGenerateMap ()
    23.         {
    24.             obj.GenerateMap();
    25.             Assert.AreEqual (obj.map.Rank, 2);
    26.             Assert.AreEqual (obj.map.GetLength(0),10);
    27.         }
    28.     }
    29. }
    This works well for scene-independent classes. However, if there is any dependency of game object in-scene, it is not able to resolve it.

    I noticed in your documentation, you mentioned:
    Could you possibly provide an example of manually creating a off-scene CompositionRoot?

    Thanks a lot!
     
  23. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Sorry for the late response - We're in 'crunch mode' at work right now so my time is fairly limited.

    You are correct that testing code using NUnit only works with code that does not interface very much with Unity. Our solution to that is to have a bunch of NUnit tests where possible, and then test the unity-specific stuff separately. To be honest though we haven't figured out a really good way of doing automated testing of MonoBehaviour classes. However, in our case we can actually get away with quite a bit of testing in NUnit since almost all our code is just normal C# classes (not monobehaviours) so its less of an issue.

    At some point (post-crunch) I'd like to look into more options in that regard. I tried Unity's testing tools package briefly but couldn't really find a good way to integrate it into our workflow.

    The approach that I refer to there in the documentation is to simply create a new scene, with a new composition root, and a different set of installers from your main scene. We have all the functionality of our app split up into many different MonoInstallers, and we have each MonoInstaller saved as separate prefabs. So in our test scenes we just re-use the prefabs that contain the installers for the functionality we want to test, then include a new installer that sets up the test scene.

    Another useful thing that we've found is that you can 'decorate' your main scene with test code by simply having a test scene that calls ZenUtil.LoadScene and passes in a delegate that installs all your test classes. For example, you might have a scene that installs a bunch of hotkeys that you use for testing, but don't want to include in production.

    Hope that helps. In the future I plan to extend the documentation to include more topics like this when work slows down.
     
  24. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Also, if you use TestWithContainer you should be able to write your test the following way instead. Note that each [Test] method will have all its dependencies (eg. obj) re-injected to run each test clean.

    Code (csharp):
    1.  
    2.  
    3. namespace UnitTest
    4. {
    5.    [TestFixture()]
    6.    public class MapManagerTest : TestWithContainer
    7.    {
    8.        [Inject]
    9.        protected MapManager obj;
    10.  
    11.        protected override void RegisterBindings()
    12.        {
    13.             _container.Bind<MapManager> ().ToSingle ();
    14.             _container.Bind<GameObjectInstantiator> ().ToSingle ();
    15.             _container.Bind<IDependencyRoot>().ToSingle<DependencyRootStandard>();
    16.  
    17.             _container.Bind<IInstaller>().ToSingle<StandardUnityInstaller>();
    18.        }
    19.  
    20.        [Test]
    21.        public void TestGenerateMap ()
    22.        {
    23.             obj.GenerateMap();
    24.             Assert.AreEqual (obj.map.Rank, 2);
    25.             Assert.AreEqual (obj.map.GetLength(0),10);
    26.        }
    27.    }
    28. }
    29.  
    30.  
     
  25. gm2008

    gm2008

    Joined:
    Aug 8, 2014
    Posts:
    19
    Thanks a lot for reply.
    I think I have found the solution. Unity has published a test runner in Assets Store: Unity Test Tools. It has a Unit Test Runner, which allows our NUnit tests to be run in a Unity scene. Without this test runner, "new GamebObject()" will throw an exception. Now you can new GameObjects smoothly

    I have tried. It works pretty well. Just place the codes in a "Editor" subfolder within Assembly-Editor-CSharp.

    I have changed TestWithContainer into like this:
    Code (CSharp):
    1.     public class TestWithContainer
    2.     {
    3.         protected DiContainer container;
    4.         protected CompositionRoot compRoot;
    5.         [SetUp]
    6.         public virtual void Setup()
    7.         {
    8.             GameObject obj1 = new GameObject ();
    9.             compRoot = obj1.AddComponent<CompositionRoot>();
    10.             compRoot.InitContainer ();
    11.             container = compRoot.Container;
    12.          
    13.             RegisterBindings();
    14.          
    15.             container.InstallInstallers();
    16.             FieldsInjecter.Inject(container, this);
    17.         }
    18.      
    19.         protected virtual void RegisterBindings()
    20.         {
    21.         }
    22.      
    23.         [TearDown]
    24.         public virtual void Destroy()
    25.         {
    26.             container = null;
    27.         }
    28.     }
    I've made one change to the source code of Zenject:
    Change CompositionRoot.InitContainer() 's protection level to 'public'.
     
    Last edited: Aug 26, 2014
  26. zerophase

    zerophase

    Joined:
    Nov 5, 2013
    Posts:
    25
    This is awesome, a light weight dependency injection framework for Unity. I had to add the Inject tag above each constructor used in the current implementation of my game. The only thing is I have other situational constructors for handling adjustments to the positioning of the GameObjects of those classes. Is there any way to tell Zenject when to use the other constructors?

    Other than having to limit the current implementation to just using one of each Interface's constructor I'm getting a No dependency root found warning. After playing around with the code for a bit I don't know which class I still need to call. When InstallBindings is called from my base injection class, I do bind IInstaller to StandardUnityInstaller. Unlike in the example asteroid project I don't get a UnityKernal GameObject. Which step could I be missing when starting up the framework?
     
  27. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    That's great. I will have to try that myself in that case.

    Yes, currently you can only inject into one constructor. Other dependency injection frameworks try to intelligently choose a constructor based on what dependencies are available. This is definitely on the roadmap however (in fact there is already a github issue for it)

    If you would like to use some of the built in interfaces such as IInitializable or ITickable, then you will need to add the following line to one of your installers (in addition to binding IInstaller to StandardUnityInstaller). You can also define your own dependency root if you're not interested in using ITickable and IInitializable.

     
  28. zerophase

    zerophase

    Joined:
    Nov 5, 2013
    Posts:
    25
    Thanks, for the help. How the code's currently written I'd have to take the Unity specific parts out of Monobehaviours. I might do that anyways, eventually, to maintain testability on more of the code.

    I'll look into that issue and see if I can help out.
     
  29. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Absolutely - pull requests are welcome :)
     
  30. gm2008

    gm2008

    Joined:
    Aug 8, 2014
    Posts:
    19
    Hello, eventropy,
    I have encountered another problem: I have done field injection successfully with some classes, but found one of my classes in which a field marked [inject] before it is not injected as expected. This class is not a direct subclass of Monohehaviour. Its super-super-superclass is Monobehavour. I wonder if this kind of scenario is supported?
     
  31. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Can you post the code? Whether it's a direct sub class or not should not matter.
     
  32. gm2008

    gm2008

    Joined:
    Aug 8, 2014
    Posts:
    19
    Thanks for reply. It works now! :p I must have some compiler error with other codes, which made the dependency not able to resolve.

    I am now struggling with "ToGetter" binding.
    As illustrated in the code listing below, in GUIManager class I want to inject _CurrentDate from outside.This should refer to a SimpleStringModel type object owned by a singleton class TimeManager.

    I do not want to let GUIManager depend the whole class TimeManager. I wish to inject TimeManager.instance.curDate into GUIManager. I saw that Zenject provides a ToGetter binding, but haven't been able figure out how to use it. Could you please give some advice? Thanks!:p

    Code (CSharp):
    1. public class GUIManager : MonoBehaviour
    2. {
    3.     [Inject]
    4.     protected SimpleStringModel _CurrentDate;
    5. }
    6.  
    7. public class TimeManager :  IInitializable, ITickable
    8. {
    9.     // singleton structure:
    10.     static TimeManager instanceInternal = null;
    11.     public static TimeManager instance
    12.     {
    13.         get
    14.         {
    15.             if (instanceInternal == null)
    16.                 instanceInternal = new TimeManager();
    17.             return instanceInternal;
    18.         }
    19.     }
    20.  
    21.     public SimpleStringModel curDate;
    22.  
    23.     private TimeManager()
    24.     {
    25.         curDate = new SimpleStringModel("curDate");
    26.     }
    27. }
     
  33. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    One thing I notice is that you're using the singleton pattern in TimeManager. This shouldn't be necessary because Zenject provides the ability to do this for you

    Code (csharp):
    1.  
    2.     _container.Bind<IInitializable>().ToSingle<TimeManager>();
    3.     _container.Bind<ITickable>().ToSingle<TimeManager>();
    4.     // For anyone that wants a direct reference:
    5.     _container.Bind<TimeManager>().ToSingle();
    6.  
    Any time you use ToSingle<>, what that means is that the DiContainer will only ever instantiate one instance of the type given inside the ToSingle<>. So in this case, any classes that take ITickable, IInitializable, or TimeManager as inputs will receive the same instance.

    If you then want to inject the curData field into GUIManager you can do this:
    Code (csharp):
    1.  
    2.     _container.Bind<SimpleStringModel>().ToGetter<TimeManager>(x => x.curDate);
    3.  
     
  34. gm2008

    gm2008

    Joined:
    Aug 8, 2014
    Posts:
    19
    ToGetter works now.:D Thanks a lot! The problem was that I did not have a right understanding about the anonymous delegate grammar. It's actually simple.

    Now Zenject has met all my needs. Thanks for the great work!

    I have also replaced TimeManager class' Singleton design with Zenject's ToStingle(). Singleton is evil. Thanks for the advice.
     
  35. gm2008

    gm2008

    Joined:
    Aug 8, 2014
    Posts:
    19
    Sorry. One new issue: I've encountered difficulty when trying to resolve a system defined Dictionary class.
    Code (CSharp):
    1. public class Events
    2.     {
    3.         public delegate void EventDelegate<T> (T e) where T : IGameEvent;
    4.         public delegate void EventDelegate (IGameEvent e);
    5.         private Dictionary<System.Type, EventDelegate> m_Delegates ;
    6.  
    7.         public Events( Dictionary<System.Type, EventDelegate> delegates )
    8.         {
    9.             m_Delegates = delegates;
    10.         }
    11. }
    Code (CSharp):
    1. public class KennedyInstaller : MonoInstaller
    2.     {
    3.         public override void InstallBindings()
    4.         {
    5.             _container.Bind<Events>().ToSingle();
    6.             _container.Bind<Dictionary<System.Type, Events.EventDelegate>>().ToTransient();
    7.    ...
    8.    }
    The run time error message is:
    I ended up with using ToMethod(), and then new it by myself in the method. I wonder if there is any better way, or it should be just created directly in the original code via new() without involving Dependency Injection?:rolleyes:
     
    Last edited: Sep 10, 2014
  36. maxxa05

    maxxa05

    Joined:
    Nov 17, 2012
    Posts:
    92
    It's the first time I try to use such a system, but I'm not sure if it's suitable for my project, which is already advanced. First, the game loads a scene which is only a UI for the main menu. Then, when the player starts the game, it keeps the same UI GameObject with DontDestroyOnLoad and loads a scene with a playable character and some other managers that are singletons for now. Will it be possible, for example, for my UI to have my playable character or any other managers' instance injected, even if they are not created at the same time? I guess I could use a factory for my managers, but it seems like a somewhat sketchy solution if I need only one instance.

    I have some difficulty wrapping my head around the difference between transient and single too. what is the difference with their interactions with the [Inject] attributes. In fact, I'm not sure I understand the whole relation between the Bind function and the [Inject] attributes, so if someone could help me with this, that would be great!

    Edit: I think I just understood what I needed
     
    Last edited: Sep 18, 2014
  37. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Hey - sorry I missed the notification on your message apparently.

    Yes, currently Zenject does not really work very well when instantiating types with multiple constructors. There is an issue on the github describing this so hopefully one of us will address it at some point. In your case, I don't see an issue with newing it directly in the class. Using dependency injection doesn't mean that you should absolutely never use new(), just that you shouldn't use new() for volatile dependencies (dependencies likely to change) or dependencies that are behind an interface. For things like concrete system types I don't see a reason not to just new directly.

    Yeah it sounds a bit tricky since you're creating objects at different times and have some that persist across scenes. What might work for that case is to use a global container which is used as the parent container for each scene. That way you could have your UI persist across scenes and have dependencies within each scene get references to it. Search the github page for nested containers to see what I'm talking about

    Use ToSingle() for objects that should only ever be instantiated once. ToTransient() will create a new object each time it is injected into a class
     
  38. maxxa05

    maxxa05

    Joined:
    Nov 17, 2012
    Posts:
    92
    Thanks!

    For now I just use one container, if I try to use a nested container it gives me an assert when stopping the game. It seems that both containers try to dispose the same DisposablesHandler via their dependency root or something like that. Maybe I did something wrong while binding the StandardUnityInstaller or the Dependency Root in my containers, I didn't try for too long.
     
  39. gm2008

    gm2008

    Joined:
    Aug 8, 2014
    Posts:
    19
    Thanks for reply. You are genuinely modest. I think this DI framework is good enough. I am relying on it.

    I have another question today:
    I have two c# scripts MovingArmy and PathFinder attached to the same GameObject, and they both depends on MapManager as follows:
    Code (CSharp):
    1. public class MovingArmy: MonoBehaviour, IInitializable
    2. {
    3.         [Inject]
    4.         public MapManager _MapManager;
    5.         ...
    6.  
    Code (CSharp):
    1. public class PathFinder : MonoBehaviour
    2.     {
    3.         [Inject]
    4.         MapManager _MapManager;
    I have a builder class to instantiate MovingArmy using GameObjectInstantiator:
    Code (CSharp):
    1. public  class ArmyBuilder: ArmyBuilderBase
    2. {
    3.     protected GameObjectInstantiator _GameObjectInstantiator;
    4.     protected ArmyBase m_Army;
    5.      
    6.     public ArmyBuilder (  GameObjectInstantiator gameObjectInstantiator  )
    7.     {
    8.             _GameObjectInstantiator = gameObjectInstantiator;
    9.     }
    10.    
    11.     public override ArmyBuilderBase CreateArmy<T>( GameObject armyPrototype )
    12.     {
    13.         m_Army =  _GameObjectInstantiator.Instantiate<T>( armyPrototype );
    14.         ....
    15.     }
    16.  
    I have called CreateArmy() like this:
    Code (CSharp):
    1. _ArmyBuilder.CreateArmy<MovingArmy>( _Settings.ArmyPrototype )
    The problem is that PathFinder's dependency MapManager is found not injected. Could you please advice on this? Thanks a lot.
     
  40. zee_ola05

    zee_ola05

    Joined:
    Feb 2, 2014
    Posts:
    166
    Hi! Can you help me with this problem? I'm trying to test DI with instantiated gameobject/monobehaviours.

    I have this code:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using ModestTree.Zenject;
    4.  
    5. public class Tester : MonoBehaviour
    6. {
    7.     [Inject]
    8.     public ITest test;
    9.  
    10.     [PostInject]
    11.     void PostStart()
    12.     {
    13.         // the concrete class for ITest only logs "Test Successful!"
    14.         // when Test() is called.
    15.         test.Test ();
    16.     }
    17. }
    18.  
    why is my PostStart not being called? I did put a [PostInject]
     
  41. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Are you using Zenject.GameObjectInstantiator or GameObject.Instantiate directly? You will need to use the former if you want it to be injected properly
     
  42. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    I have a feeling that you have moved on from your problem given my late reply but in case you haven't: Can you post the binding your are using for MapManager ?
     
  43. zee_ola05

    zee_ola05

    Joined:
    Feb 2, 2014
    Posts:
    166
    Yea, I was using GameObject.Instantiate.

    Problem now is, I don't know how to use Zenject.GameObjectInstantiator. :(

    Can you give me a sample?
     
  44. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Sure. You just need to inject the GameObjectInstantiator and then you call create, and give as the generic argument the monobehaviour that you want to retrieve on the prefab:

    Code (csharp):
    1.  
    2.     public class MonoBehaviourSomewhereOnPrefab : MonoBehaviour
    3.     {
    4.         ...
    5.     }
    6.  
    7.     public class Foo : MonoBehaviour
    8.     {
    9.         [Inject]
    10.         GameObjectInstantiator _instantiator;
    11.  
    12.         [SerializeField]
    13.         GameObject _prefab;
    14.  
    15.         MonoBehaviourSomewhereOnPrefab _instance;
    16.  
    17.         void Start()
    18.         {
    19.             _instance = _instantiator.Create<MonoBehaviourSomewhereOnPrefab>(_prefab);
    20.         }
    21.     }
    22.  
     
  45. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Zenject has been updated to version 1.12 which includes the following changes:
    • Added support for Global Composition Root to allow project-wide installers/bindings
    • Added Rebind<> method which can be used to override any previous bindings
    • Changed Factories to use strongly typed parameters by default. Also added ability to pass in null values as arguments as well as multiple instances of the same type
    • Renamed _container to Container in the installers
    • Added DiContainer.ToSingleMonoBehaviour method
    • Removed some boiler plate code that was previously required. (eg. you no longer need to include the StandardUnityInstaller, or declare a dependency root)
    • Added IFixedTickable class to support unity FixedUpdate method
    Note that this is a breaking update so will result in a few compiler errors if you have already started a project with a previous version.
     
    Last edited: Oct 17, 2014
  46. zee_ola05

    zee_ola05

    Joined:
    Feb 2, 2014
    Posts:
    166
    Hi! Is there a way to inject into instantiated Monobehaviours without using GameObjectInstantiator? I want my script to not depend on Zenject as much as possible. Also, some assets from the Asset Store usually use GameObject.Instantiate.
     
  47. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    You can avoid using GameObjectInstantiator by using Game Object Factories (more info here). This is more or less just a simple interface that returns a game object. This approach is also nice because it will include your class as part of object graph validation.

    If you want to not depend on Zenject entirely, you would need some way to hook into calls to GameObject.Instantiate. As far as I know there's no way to do that but if that's not the case I'd love to hear it.
     
  48. gm2008

    gm2008

    Joined:
    Aug 8, 2014
    Posts:
    19
    Hello, Eventropy,
    I am now trying to use "Unity Serializer" from Asset Store to save Unity games. You know, when deserializing, it creates objects according to saved file. This does not work straight with Zenject. I need to modify Unity Serializer to make it work.
    Do you have any experience in this? Could you possibly give some advice? Thanks!
    I am trying to define a class of type ICreateObject for anything to be instantiated by Zenject, but have not figured out how to make Container available.
     
  49. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Updated Zenject to v1.17.

    This update includes mostly minor tweaks / bug fixes but also one new feature: Scene Decorators! Which can be used to add functionality to a scene from another scene without actually changing the scene directly, which can be useful particularly for testing (see documentation for details).
     
  50. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,298
    I'm kinda having trouble figuring out how to best use injection in MonoBehaviours. Let's say I've got a MonoBehaviour for a UI component like a health bar, and I want to reference a player stats class, and I want to avoid having a PlayerStats singleton. Currently I'm just including a field like "[Inject] protected PlayerStats stats", but this feels like it's basically the same thing as just referencing a singleton and has some of the same problems, like hiding the dependency in the code. It feels like things like this should use constructor injection, but you can't use that with MonoBehaviours. Is it really "okay" to just put injected fields all over the place, or is there a better way to structure this?