Lightweight dependency injection framework for Unity almost free of bindings designed to simplify and streamline the process of writing code.
- In Package Manager press
+
, selectAdd package from git URL
and pastehttps://github.com/natpuncher/bindlessdi.git
- Or find the
manifest.json
file in thePackages
folder of your project and add the following line to dependencies section:
{
"dependencies": {
"com.npg.bindlessdi": "https://github.com/natpuncher/bindlessdi.git",
},
}
- Initializing
- Injecting
- Bind Instance
- Instantiation Policy
- Factory
- Unity Objects
- Unity Events
- Optimizations
- Tests
To initialize Bindlessdi call Container.Initialize()
from entry point of the game.
public class EntryPoint : MonoBehaviour
{
private void Start()
{
var container = Container.Initialize();
var myGame = container.Resolve<MyGame>();
myGame.Play();
}
}
Bindlessdi only supports constructor injection.
public class MyGame
{
private readonly MyController _myController;
public MyGame(MyController controller)
{
_myController = controller;
}
}
In most cases Bindlessdi will guess implementation by itself without any bindings.
public class MyController : IController
{
}
public class MyGame
{
private readonly IController _controller;
public MyGame(IController controller)
{
_controller = controller;
}
}
If there are several of implementations for specific interface, intended implementation should be defined by calling container.BindImplementation<IInterface, Implementation>()
.
public class EntryPoint : MonoBehaviour
{
private void Start()
{
var container = Container.Initialize();
container.BindImplementation<IBar, Qux>();
var myGame = container.Resolve<MyGame>();
myGame.Play();
}
}
public class Foo : IFoo
{
}
public class Bar : IBar
{
}
public class Qux : IBar
{
}
public class MyGame
{
public MyGame(IFoo foo, IBar bar)
{
// bar is Qux
}
}
Already instanced objects could be added to container by calling container.BindInstance(instance)
.
Its also possible to add a bunch of them using container.BindInstances(instances)
.
public class EntryPoint : MonoBehaviour
{
private void Start()
{
var container = Container.Initialize();
var myController = new MyController();
container.BindInstance(myController);
var guns = new IGun[] { new FireGun(), new ColdGun() };
container.BindInstances(guns);
var myGame = container.Resolve<MyGame>();
myGame.Play();
}
}
public class MyGame
{
public MyGame(MyController myController, FireGun fireGun, ColdGun coldGun)
{
}
}
Bindlessdi resolves everything as single instance by default, but it can be changed.
- By changing default initialization policy to
InstantiationPolicy.Transient
, so every resolve will create a new instance.
public class EntryPoint : MonoBehaviour
{
private void Start()
{
var container = Container.Initialize();
container.DefaultInstantiationPolicy = InstantiationPolicy.Transient;
var myGame = container.Resolve<MyGame>();
myGame.Play();
var myGame2 = container.Resolve<MyGame>();
myGame2.Play();
// myGame != myGame2
}
}
- By registering instantiation policy for concrete type, so every resolve of this type will create a new instance.
public class EntryPoint : MonoBehaviour
{
private void Start()
{
var container = Container.Initialize();
container.RegisterInstantiationPolicy<MyGame>(InstantiationPolicy.Transient);
var myGame = container.Resolve<MyGame>();
var myGame2 = container.Resolve<MyGame>();
// myGame != myGame2
}
}
- By registering instantiation policy for an interface or a base type, so every resolve of a type implementing it will get this policy override.
public class EntryPoint : MonoBehaviour
{
private void Start()
{
var container = Container.Initialize();
container.DefaultInstantiationPolicy = InstantiationPolicy.Single;
container.RegisterInstantiationPolicy<ITrigger>(InstantiationPolicy.Transient);
container.RegisterInstantiationPolicy<TriggerA>(InstantiationPolicy.Single);
var a1 = container.Resolve<TriggerA>();
var a2 = container.Resolve<TriggerA>();
// a1 == a2
var b1 = container.Resolve<TriggerB>();
var b2 = container.Resolve<TriggerB>();
// b1 != b2
}
}
- By passing instantiation policy to
container.Resolve()
method, so only this call will create a new instance.
public class EntryPoint : MonoBehaviour
{
private void Start()
{
var container = Container.Initialize();
var myGame = container.Resolve<MyGame>();
var myGame2 = container.Resolve<MyGame>(InstantiationPolicy.Transient);
var myGame3 = container.Resolve<MyGame>(InstantiationPolicy.Transient);
var myGame4 = container.Resolve<MyGame>();
// myGame != myGame2 != myGame3
// myGame == myGame4
}
}
To create instances on demand use Factories for a specific interface.
Just add IFactory<MyInterface>
as a constructor argument and call Resolve
with concrete type when needed.
It is also possible to override instantiation policy on resolve.
There is no need to bind Factories, they will be resolved automatically.
public interface IBullet
{
}
public class FireBullet : IBullet
{
}
public class ColdBullet : IBullet
{
}
public class Gun
{
private readonly IFactory<IBullet> _factory;
public Gun(IFactory<IBullet> factory)
{
_factory = factory;
}
public IBullet Fire()
{
return _factory.Resolve<FireBullet>(InstantiationPolicy.Transient);
}
public IBullet Cold()
{
return _factory.Resolve<ColdBullet>(InstantiationPolicy.Transient);
}
}
- Create an implementation of
SceneContext
class
public class MyGameSceneContext : SceneContext
{
public override IEnumerable<Object> GetObjects()
{
return new Object[] { };
}
}
-
Create a GameObject in the root of the scene and attach the
SceneContext
implementation script to it -
Add
[SerializeField]
private fields for links to Components from scene, Prefabs or Scriptable Object assets and return it fromGetObjects()
method
public class MyGameSceneContext : SceneContext
{
[SerializeField] private Camera _camera;
[SerializeField] private MyHudView _hudView;
[SerializeField] private BulletView _bulletPrefab;
[SerializeField] private MyScriptableObjectConfig _config;
public override IEnumerable<Object> GetObjects()
{
return new Object[] { _camera, _hudView, _bulletPrefab, _config };
}
}
- Get
UnityObjectContainer
class as a constructor argument and receive objects by callingunityObjectContainer.TryGetObject(out TObject object)
method
public class MyGame
{
private readonly UnityObjectContainer _unityObjectContainer;
public MyGame(UnityObjectContainer unityObjectContainer)
{
_unityObjectContainer = unityObjectContainer;
}
public void Play()
{
if (!_unityObjectContainer.TryGetObject(out MyHudView hudView) ||
!_unityObjectContainer.TryGetObject(out MyScriptableObjectConfig config))
{
return;
}
hudView.Show(config);
}
}
Implement ITickable
, IFixedTickable
, ILateTickable
, IPausable
, IDisposable
to handle Unity Events. There is no need to bind these interfaces to a class, once instance will be resolved - Unity Events will be passed to it.
Unity Update => ITickable
Unity FixedUpdate => IFixedTickable
Unity LateUpdate => ILateTickable
Unity Pause => IPausable
Unity Application.Quit => IDisposable
public class MyGame : ITickable, IDisposable
{
public void Tick()
{
// called on Unity Update
}
public void Dispose()
{
// called on Application.Quit
}
}
This functionality could be turned off by passing false
to Container initialization
public class EntryPoint : MonoBehaviour
{
private void Start()
{
var container = Container.Initialize(false);
var myGame = container.Resolve<MyGame>();
myGame.Play();
}
}
public class MyGame : ITickable, IDisposable
{
public void Tick()
{
// not called
}
public void Dispose()
{
// not called
}
}
By default, Bindlessdi will search for certain types and cache their implementations whenever it needs to guess them, but this process can be optimized.
- By calling
Container.WarmupImplementationCache
behind the loading screen of your game. This will force Bindlessdi to collect and cache all needed information in the right time if you want to use Bindlessdi without bindings. - Or by binding all of your implementations by calling
Container.BindImplementation
, so there will be no need for Bindlessdi to find implementations by itself.
To use Bindlessdi in tests call Container.Initialize(false)
in the begining of a test and container.Dispose
in the end.
public class MyTests
{
[Test]
public void TestResolve()
{
var container = Container.Initialize(false);
var a = container.Resolve<A>();
var b = container.Resolve<B>();
var c = container.Resolve<C>();
var d = container.Resolve<D>();
Assert.True(a.B == b);
Assert.True(a.C == b.C);
Assert.True(a.C == c);
Assert.True(b.C == c);
Assert.True(b.D == c.D);
Assert.True(b.D == d);
container.Dispose();
}
}