The Dumb Injector.
DumbInjector is a lightweight, home-grown Dependency Injection (DI) framework designed for Unity. It provides simple and fast injection for both scene-local and global objects, with minimal setup. Perfect for developers who want DI without the overhead of large frameworks.
It's like Reflex, but dumber.
graph LR
    %% Core Interfaces
    IDP["IDependencyProvider"]
    %% Attributes
    Inject["[Inject] Attribute"]
    Provide["[Provide] Attribute"]
    %% Connections
    IDP --> Provide
    Inject --> Injector["Injectors"]
    Provide --> Injector
    - Scene-local injection: via 
SceneContextInjector - Global singleton injection: Managed by 
GlobalContextInjector, allowing registration and resolution of dependencies that persist across scenes, suitable for additive scene workflows. - Attribute-based injection: 
[Inject]for fields, properties, and methods - Provider system: 
[Provide]methods inIDependencyProviderfor registering injectable services - Minimal boilerplate: lightweight and fast
 - Extensible core: The 
Mainassembly is the entry point for extension for your providers. - Usable injectors: 
Injectorsprefabs can be used as-is without being extended 
You can add DumbInjector to your Unity project in two ways:
git submodule add https://github.com/oultrox/DumbInjector.git Assets/DumbInjector- Open Window > Package Manager
 - Click the + button and choose Add package from git URL
 - Enter:
 
https://github.com/oultrox/DumbInjector.gitDumbInjector is scene-scoped, meaning each scene requires a single context injector to resolve dependencies correctly.
Use the editor menu to create a SceneContextInjector in your scene:
GameObject > DumbInjector > Create SceneContextInjector
This will automatically create a GameObject named SceneContextInjector and add the component. It will:
- Scan all root GameObjects in the scene for 
[Inject]attributes - Resolve dependencies from scene-local providers first
 - Fall back to the GlobalContextInjector if needed
 
For additive scene setups, use the editor menu to create a GlobalContextInjector:
GameObject > DumbInjector > Create GlobalContextInjector
This will create a GameObject named GlobalContextInjector and add the component. It will:
- Scan the scene for 
[Inject]attributes - Register all 
IDependencyProvideroutputs into the singletonBuilder - Inject dependencies into all scene objects.
 
💡 Tip: You can create prefabs of these contexts if you want to, you can just slap either
GlobalContextInjectororSceneContextInjectorin a GameObject. Remember: only 1 type of context is needed per scene.
When using a GlobalContextInjector in a global scene alongside other scene-local's SceneContextInjector, it is crucial that the global context is initialized first. This ensures that any dependencies registered globally are available to scene-local injectors.
You can achieve this in one of the following ways:
- Additive Scene Setup: Load the global scene additively before loading other scenes, ensuring its injector is active.
 - Set Global as Active Scene: Make the global scene the currently active scene before loading other scenes.
 - Scene Load Order: Make sure the global scene is the very first scene loaded in your game’s build settings.
 
⚠️ Note: The framework does not automatically manage execution order between global and scene-local injectors. This design gives you full control and flexibility over your initialization flow. Make sure the global injector is initialized before any scene-local injectors to ensure all dependencies are correctly resolved.
The DumbInjector framework works on a scene-scoped model:
- SceneContextInjector: lives in each scene and handles injections for objects within that scene.
 - GlobalContextInjector: lives in a persistent "Global" scene and provides cross-scene services that survive scene transitions.
 - This means each scene requires either of the two, or your owns solution extending the 
IInjectorinterface (I explain this later on). 
graph TD
    SceneA["Scene A (SceneContextInjector)"]
    AudioProvider["AudioProvider (IDependencyProvider)"]
    Player["PlayerController ([Inject] IAudioHandler)"]
    SceneA --> AudioProvider
    AudioProvider -->|Provides IAudioHandler| Player
    graph TD
    GlobalScene["Global Scene (GlobalContextInjector)"]
    GlobalAudio["GlobalAudioProvider (IDependencyProvider)"]
    SceneA["Scene A (SceneContextInjector)"]
    SceneB["Scene B (SceneContextInjector)"]
    PlayerA["PlayerController ([Inject] IAudioHandler)"]
    PlayerB["EnemyController ([Inject] IAudioHandler)"]
    GlobalScene --> GlobalAudio
    GlobalAudio -->|Provides IAudioHandler| SceneA
    GlobalAudio -->|Provides IAudioHandler| SceneB
    SceneA -->|Scene-local injection| PlayerA
    SceneB -->|Scene-local injection| PlayerB
    After you got your scenes setup, it's Dumb Injecting time!
Any component you want to inject must have a corresponding Provider that implements IDependencyProvider and exposes it via a [Provide] method. The injector will not magically create instances — it relies on providers to supply dependencies.
// Example Component Provider
using DumbInjector;
using UnityEngine;
public class PlayerProvider : MonoBehaviour, IDependencyProvider
{
    [SerializeField] PlayerHealth playerHealth;
    [Provide]
    IHasHealth ProvidePlayerHealth() => playerHealth;
}// Example Component Provider
using DumbInjector;
using UnityEngine;
public class AudioProvider: MonoBehaviour, IDependencyProvider
{
    [SerializeField] AudioLogger audioLogger;
    [Provide]
    IAudioHandler ProvideAudio() => audioLogger;
}Simply decorate fields, properties, or methods with [Inject]:
// Example attribute injection
public class PlayerController : MonoBehaviour
{
    [Inject] IHasHealth health;
    [Inject] IAudioHandler audio;
    private void Start()
    {
        health.TakeDamage(10);
        audio.PlaySound("spawn");
    }
}// Example method injection
public class Pistol : Weapon
{ 
    Sprite idlePistol;
    Sprite shotPistol;
    GameObject bulletHolePrefab;
    SpriteRenderer spriteRenderer;
    Vector3 firePosition;
    
    
    [Inject]
    public void InjectContainer(WeaponContainer weaponContainer)
    {
        idlePistol = weaponContainer.IdlePistol;
        shotPistol = weaponContainer.ShotPistol;
        bulletHolePrefab = weaponContainer.BulletHolePrefab;
    }
    
    private void Awake()
    {
        InitBehaviors();
    }No singleton handling is required in your consumers — everything is injected automatically, let the dumb injectors handle that for you.
There are three primary ways to trigger dependency injection in the system:
Each scene can have a SceneContextInjector (or a GlobalContextInjector in persistent scenes) that automatically handles injection for all existing objects when the scene loads. This ensures that dependencies are populated without manually attaching AutoInjector to every object.
How it works:
- The context injector scans all root 
GameObjectsin the scene. - Recursively visits all children.
 - Injects any 
[Inject]fields, properties, or methods. - Registers any 
IDependencyProviders it finds for later resolution. 
SceneContextInjector
public class SceneContextInjector : MonoBehaviour, IInjector
{
    readonly HashSet<Type> _injectableTypes = new();
    readonly Dictionary<Type, object> _sceneRegistry = new();
    bool _typesCached;
    private void Awake()
    {
        CacheInjectableTypes();
        InjectSceneObjects();
    }
   ... 
}Attach the AutoInjector component to any MonoBehaviour that requires dependencies. On Awake(), AutoInjector automatically resolves and injects all [Inject] fields and methods. It will first look for a scene context injector, and if none is found, it falls back to a Global context injector.
// With the AutoInjector component attached.
public class PlayerController : MonoBehaviour
{
    [Inject] private IAudioHandler audioHandler;
    private void OnEnable()
    {
        audioHandler.Play("SpawnSound");
    }
}- Add your 
MonoBehaviour(e.g.,PlayerController) to aGameObject. - Add the 
AutoInjectorcomponent to the sameGameObject. - The 
[Inject]fields and methods will be automatically populated beforeStart()executes. 
For objects already present in the scene or instantiated at runtime that cannot have AutoInjector attached, you can manually inject dependencies using a scene or global injector.
Example:
[SerializeField] SceneContextInjector _injector;
_injector.Inject(existingGameObject);This will scan the GameObject and all its children for [Inject] attributes. Dependencies are resolved and injected automatically.
Tip: For most use cases, AutoInjector is sufficient and preferred. Manual injection provides flexibility for complex scene setups or dynamic objects.
- You can inject through the 
IInjectorinterface if you want to work with a custom injector implementation without relying on the default scene or global injectors. - You can also use the 
Buildersingleton directly to create or manage your own injector and still resolve dependencies using the same[Inject]attributes. 
using System;
    /// <summary>
    /// Interface for all injectors.
    /// </summary>
    public interface IInjector
    {
        void Inject(object mb);
        object Resolve(Type t);
    }using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using DumbInjector;
/// <summary>
/// A simple custom injector for demonstration purposes.
/// </summary>
public class CustomInjector : IInjector
{
    readonly Dictionary<Type, object> _registry = new();
    const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
    /// <summary>
    /// Register an instance for a given type.
    /// </summary>
    public void Register<T>(T instance)
    {
        var type = typeof(T);
        if (_registry.ContainsKey(type))
        {
            throw new InvalidOperationException($"Type {type.Name} is already registered.");
        }
        _registry[type] = instance;
    }
    /// <summary>
    /// Inject dependencies into fields or properties marked with [Inject].
    /// </summary>
    public void Inject(object obj)
    {
        if (obj == null) return;
        
        // Inject fields
        foreach (var field in obj.GetType().GetFields(flags)
                     .Where(f => Attribute.IsDefined(f, typeof(InjectAttribute))))
        {
            var resolved = Resolve(field.FieldType);
            if (resolved != null) field.SetValue(obj, resolved);
        }
        // Inject properties
        foreach (var prop in obj.GetType().GetProperties(flags)
                     .Where(p => Attribute.IsDefined(p, typeof(InjectAttribute)) && p.CanWrite))
        {
            var resolved = Resolve(prop.PropertyType);
            if (resolved != null) prop.SetValue(obj, resolved);
        }
    }
    /// <summary>
    /// Resolve an instance for a given type.
    /// </summary>
    public object Resolve(Type t)
    {
        _registry.TryGetValue(t, out var instance);
        return instance;
    }
    /// <summary>
    /// Generic resolve helper.
    /// </summary>
    public T Resolve<T>()
    {
        return (T)Resolve(typeof(T));
    }
}// Example usage:
public class ExampleUsage
{
    public void Setup()
    {
        var injector = new MyCustomInjector();
        
        var player = new PlayerController();
        injector.Inject(player); // Dependencies injected automatically
    }
}Or you can do things like...
// Exampple Orchestrator
public class EnemyFactory
{
    IInjector _injector;
    public EnemyFactory(IInjector injector)
    {
        _injector = injector;
    }
    public GameObject CreateEnemy(GameObject prefab)
    {
        var instance = GameObject.Instantiate(prefab);
        // Example 1: Inject only the main component you know requires dependencies.
        var enemyComponent = instance.GetComponent<Enemy>();
        _injector.Inject(enemyComponent);
        // Example 2: Inject all MonoBehaviours in the prefab and its children.
        var allComponents = instance.GetComponentsInChildren<MonoBehaviour>(true);
        foreach (var comp in allComponents)
        {
            _injector.Inject(comp);
        }
    }
}Correct execution timing is critical for dependency injection to work reliably.
The SceneContextInjector runs before any Awake() methods of your scene’s MonoBehaviours.
This ensures that all [Inject] dependencies are already resolved when components initialize.
As a result, for most scene-scoped injections, you don’t need extra setup — it works out of the box.
Like previously mentioned, for dependencies that persist across multiple scenes (e.g., AudioProvider, PlayerController), injection requires manual handling.
In these cases, a GlobalContextInjector should be initialized as early as possible (e.g., via a bootstrap scene or [RuntimeInitializeOnLoadMethod]).
This guarantees that global providers are registered before new scenes are loaded, allowing injection to succeed even across additive scenes.
- GlobalContextInjector (optional, cross-scene) → Registers global providers early.
 - SceneContextInjector (per scene) → Resolves scene-local providers and applies injections.
 - MonoBehaviour.Awake() → Runs with all required dependencies already injected.
 - MonoBehaviour.Start() → Safe to use injected dependencies for gameplay logic.
 
DumbInjector leverages C# reflection to automatically discover and inject dependencies:
- [Inject] attributes: Applied to fields, properties, or methods. The injector scans each object’s members at runtime and sets them automatically.
 - [Provide] attributes: Used inside 
IDependencyProviderimplementations. These methods expose instances that can be injected elsewhere. - Scene-scoped reflection: Instead of scanning the entire project every time, the injector caches injectable types per scene. This ensures fast lookups and minimal runtime overhead.
 - Type caching optimization: The injector caches all injectable types (
[Inject]fields, properties, methods, and IDependencyProviders) once per scene or globally. This avoids repeated reflection scans at runtime, improving performance when injecting multiple objects. - Global fallback: SceneContextInjector resolves dependencies from local providers first, then falls back to the global injector if no local provider is available (and if there's any global context to begin with).
 
This approach allows you to write clean, decoupled code without manually wiring dependencies, while still being fully dynamic and flexible.
- Maybe implement C# constructor plain classes injections.
 - Better debugging tools.
 - Better editor tooling.
 
- 
Reflex – A lightweight dependency injection framework for Unity. Useful for understanding attribute-based injection and runtime resolution.
GitHub - 
Zenject – A popular DI framework for Unity supporting scene contexts, global containers, and automatic injection. Great inspiration for context-based injection patterns.
Zenject Documentation - 
Youtube Video – Demonstrates Unity dependency injection patterns and best practices:
 
