A minimal, blazing-fast dependency injection framework for Unity.
Field injection only. No service locator. Factory-first creation.
Production-ready • Zero allocations • IL2CPP safe • UPM compatible
- Field injection only — Clean, simple approach with no property/method/constructor injection complexity
- Non‑recursive container — The container never auto-constructs dependency graphs
- Fail‑fast validation — Duplicate binds and missing dependencies throw immediately with clear error messages
- Factory‑first creation — Use
Factory<T>for plain objects andPrefabFactory<T>for Unity prefabs - Deterministic lifecycle —
BindNew<T>()guaranteesInitialize()is called after injection - High performance — Per‑type reflection cache, compiled injectors, and zero‑allocation prefab scanning
- Simple by default: Keep DI lean; push complexity into factories and composition
- Explicit composition: All bindings declared in
CompositionRootassets executed byDIContextbefore scene load - No service locator: You don't "get" instances from the container; you bind and inject via factories
- Strict wiring: If something isn't bound, that's a bug—fail early and clearly
Public APIs:
Bind<T>(instance)— Store an already constructed instance with no injection. Duplicate binds are illegalBindNew<T>()—new T()→ field inject →Initialize()if implemented → store. Throws on missing dependencies or duplicate typesBindFactory<T>()— Register aFactory<T>for creating injectable plain objectsBindFactory<T>(prefab)— Register aPrefabFactory<T>for creating injectable Unity prefabs
Internal APIs (used by factories only):
Inject(object)— Field‑inject dependencies into an objectInjectGameObject(GameObject, ...)— Field‑inject across a hierarchy; callsInitialize()on components that implement it
Important: Injection scans only fields declared on the concrete type (uses
BindingFlags.DeclaredOnly). Private fields declared in base classes are not injected.
- Create one or more
CompositionRootassets (ScriptableObjects) and implementCompose(Container container) - Create a single enabled
DIContextasset inResources/ - At startup (
BeforeSceneLoad), it constructs aContainerand runs allCompose(...)methods in dependency order
Factory — For plain objects (where T : IInitializable, new()):
Create()→new T()→ inject →Initialize()→ return
PrefabFactory — For prefabs (where T : Component, IInitializable):
Create(parent, name, position, rotation)→Instantiate(prefab)→ inject whole hierarchy →Initialize()components that implement it → returnTon the root
Pro tip: To guarantee injection before
Awake(), save DI'd prefabs inactive in your project. The factory inherits the inactive state, injects dependencies, then you can activate safely.
-
Add via Git URL:
- Open Window → Package Manager
- Click + → Add package from git URL
- Enter:
https://github.com/singh-ps/nano-di.git?path=Packages/NanoDI
-
Add via manifest.json:
{ "dependencies": { "com.singh-ps.nanodi": "https://github.com/singh-ps/nano-di.git?path=Packages/NanoDI" } }
- Download the latest release
- Extract and copy the
NanoDIfolder to your project'sPackages/directory
After installation, import the comprehensive GamePlay sample:
- Window → Package Manager → NanoDI → Samples → Import
After installation, create the DI context:
- Create a
Resources/folder in your project - Right-click → Create → NanoDI → DIContext
- Check enabled
- Add your
CompositionRootassets to the list
using NanoDI;
public class UIManager : IInitializable
{
public void Initialize() => Debug.Log("UI Manager initialized!");
public void ShowHUD() => Debug.Log("Showing HUD");
}
public class GameManager : IInitializable
{
[Inject] private UIManager _ui; // Field injection
public void Initialize()
{
_ui.ShowHUD(); // Dependencies are ready!
}
}[CreateAssetMenu(menuName = "Game/Composition Root")]
public class GameComposition : CompositionRoot
{
public override void Compose(Container container)
{
container.BindNew<UIManager>(); // Creates and injects UIManager
container.BindNew<GameManager>(); // Creates and injects GameManager
}
}- Right-click → Create → Game → Composition Root
- Open your
DIContextasset inResources/ - Add the
GameCompositionasset to the compositions list
Perfect for spawning enemies, projectiles, or any runtime objects:
public class Enemy : IInitializable
{
[Inject] private GameAnalytics _analytics;
public void Initialize()
{
_analytics.TrackEnemySpawned();
}
}
public class EnemySpawner : MonoBehaviour, IInitializable
{
[Inject] private Factory<Enemy> _enemyFactory;
public void Initialize() { }
public void SpawnEnemy()
{
var enemy = _enemyFactory.Create(); // Fully injected!
}
}
// In your CompositionRoot:
public override void Compose(Container container)
{
container.BindNew<GameAnalytics>();
container.BindFactory<Enemy>(); // Registers Factory<Enemy>
}Handle complex prefabs with full dependency injection:
public class PlayerController : MonoBehaviour, IInitializable
{
[Inject] private IInputProvider _input;
[Inject] private GameManager _gameManager;
public void Initialize()
{
Debug.Log("Player ready with all dependencies!");
}
}
public class GameComposition : CompositionRoot
{
[SerializeField] private PlayerController playerPrefab; // Assign in inspector
public override void Compose(Container container)
{
container.Bind<IInputProvider>(new KeyboardInput());
container.BindNew<GameManager>();
container.BindFactory(playerPrefab); // Registers PrefabFactory<PlayerController>
}
}
// Usage:
public class LevelManager : MonoBehaviour, IInitializable
{
[Inject] private PrefabFactory<PlayerController> _playerFactory;
public void Initialize()
{
var player = _playerFactory.Create(
parent: transform,
name: "Player",
position: Vector3.zero,
rotation: Quaternion.identity
);
// Player and ALL child components are injected!
}
}Use interfaces for flexible, testable code:
public interface IInputProvider
{
Vector2 GetMovementInput();
}
public class KeyboardInput : IInputProvider
{
public Vector2 GetMovementInput() =>
new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
}
public class TouchInput : IInputProvider
{
public Vector2 GetMovementInput() => /* touch logic */;
}
// In CompositionRoot - easy to swap implementations:
public override void Compose(Container container)
{
IInputProvider input = Application.isMobilePlatform
? new TouchInput()
: new KeyboardInput();
container.Bind(input); // Bind interface to implementation
}- Only fields marked with
[Inject]are injected - Field discovery is cached per type for optimal performance
BindNew<T>()always callsInitialize()after injection- Compiled injectors provide fast, IL2CPP-safe injection
- Duplicate binds throw
InvalidOperationException - Missing dependencies throw with clear error messages
- No service locator — no
Get<T>()/TryGet<T>()methods - No base class fields — only declared fields are injected
- Reflection cache: Field discovery is cached per type; subsequent injections skip attribute scans
- Compiled injection: Uses compiled delegates for lightning-fast repeat injections (IL2CPP‑safe)
- Zero-alloc prefabs:
PrefabFactoryusesGetComponentsInChildren(true, List<T>)with reused buffers - Minimal overhead: Container operations are optimized for game development performance requirements
This project is licensed under the MIT License - see the LICENSE file for details.