Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Opt-in DI architecture using Reflex for Unity 6+. Covers container setup, scoping, injection, command pattern, update management, and class taxonomy. Use when ProjectConfig.yaml -> architecture_pattern is "di-first", or when the user asks about dependency injection, inversion of control, service architecture, or testable game code. Triggers on "set up DI", "add dependency injection", "create a service", "wire up dependencies", "make this testable", "decouple these systems", "add a command patter
.claude/skills/bilal140202-uw-dependency-injection/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 126% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 33% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 33% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 211% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 153% | 0% |
DI-first architecture for complex Unity projects. Only applies when ProjectConfig.yaml -> architecture_pattern: di-first. For simpler projects, use uw-scriptable-object-arch (SO-first) instead.
docs/ProjectConfig.yaml for:architecture_pattern — must be "di-first" for this skill to apply.di_framework — should be "reflex" (the default and recommended framework).mcp.unity_mcp — if true, call refresh_unity after creating files.docs/CODING_STANDARDS.md for async patterns (Awaitable + CancellationToken), access modifiers (internal for cross-class helpers within an asmdef), and class structure.docs/NAMING_CONVENTIONS.md for file/class naming..asmdef — create with uw-unity-feature-scaffold. Installers, Controllers, Services all live inside feature asmdefs.| SO-First (default) | DI-First | |---------------------|----------| | Small/medium scope | Large/complex scope | | Solo or small team | Team with testing culture | | Rapid prototyping | Production architecture | | Simple event-driven communication | Cross-feature orchestration via Commands | | ScriptableObject event channels | Interface-based injection + factories |
Both can coexist: DI containers can bind ScriptableObject data assets as singletons.
Install (OpenUPM): openupm install com.gustavopsantos.reflex Or UPM Git URL: https://github.com/gustavopsantos/reflex.git?path=/Assets/Reflex/
RootScope (app lifetime — singletons, core services)
+-- SceneScope (scene lifetime — per-scene services, controllers)
+-- Manual child scopes (gameplay round, level, etc.)Each feature module has one Installer that registers its bindings. The Installer lives inside the feature's .asmdef.
csharpusing Reflex.Core; using UnityEngine; namespace {RootNamespace}.Core { public class CoreInstaller : MonoBehaviour, IInstaller { [SerializeField] private AudioSettingsData _audioSettings; public void InstallBindings(ContainerBuilder builder) { // Services (singleton, app lifetime) builder.AddSingleton(typeof(IAudioService), typeof(AudioService)); builder.AddSingleton(typeof(IStateMachineService), typeof(StateMachineService)); // SO data (inject existing asset as value) builder.AddInstance(_audioSettings); } } }
| Method | What It Does | Lifetime | |--------|-------------|----------| | AddSingleton(type, impl) | Container creates one instance | App/Scene | | AddTransient(type, impl) | New instance each resolve | Per-resolve | | AddInstance(obj) | Register existing object (SO, config) | Singleton |
csharpusing Reflex.Attributes; namespace {RootNamespace}.Combat { public class ArrowController { [Inject] private readonly IAudioService _audio; [Inject] private readonly IArrowMovementController _movement; // OR constructor injection (preferred for non-MonoBehaviours): public ArrowController(IAudioService audio, IArrowMovementController movement) { _audio = audio; _movement = movement; } } }
Constructor injection is preferred for plain C# classes — it makes dependencies explicit and prevents forgetting to inject. Use [Inject] attribute injection for MonoBehaviours (which Unity constructs).
Always bind to interfaces, not concrete types. This enables mock injection for testing, makes dependencies explicit, and follows the Dependency Inversion Principle.
csharp// Correct — bind to interface builder.AddSingleton(typeof(IAudioService), typeof(AudioService)); // Wrong — binding concrete directly prevents mock injection builder.AddSingleton(typeof(AudioService));
Circular dependencies (A depends on B, B depends on A) cause infinite resolution loops. Prevent them with:
uw-scriptable-object-arch event channels.{RootNamespace}.Core.asmdef so both features depend on the interface, not on each other.If the container throws a circular dependency error, trace the dependency chain and break it at the point where a Command or event channel makes more sense than a direct reference.
Only one scene has a Start() method. All other initialization flows from there.
CoreScene loads -> RootScope binds -> CoreInitiator.Start()
-> Load GameScene (additively) -> SceneScope binds -> GameInitiator.Init()
-> Load GamePlayScene -> SceneScope binds -> GamePlayInitiator.Init()Rules:
Start() method — everything else is initialized via async InitEntryPoint().See references/class-taxonomy-and-commands.md for the full 9-class taxonomy, Command pattern, and centralized Update management.
Key rule: Commands are the ONLY classes allowed to cross feature boundaries. This prevents circular dependencies — each Command resolves its own references from the DI container at execution time.
When Feature A (Combat) needs to notify Feature B (Score) about a hit:
csharp// Lives in a shared Commands assembly or in the feature that initiates it public class OnHitScoredCommand : ICommand { [Inject] private readonly IScoreController _score; [Inject] private readonly IAudioService _audio; public void Execute() { _score.AddPoints(10); _audio.PlayOneShot(AudioClipType.Hit); } }
The Combat feature doesn't reference Score directly — it resolves and executes the Command, which the container wires up. Register in the installer: builder.AddTransient(typeof(OnHitScoredCommand));
DI and SO work together — SO holds data, DI manages services and wiring:
csharp// In installer: inject SO data asset into container [SerializeField] private WeaponDatabase _weaponDatabase; public void InstallBindings(ContainerBuilder builder) { builder.AddInstance(_weaponDatabase); // SO data available via [Inject] }
See uw-scriptable-object-arch for data container and event channel patterns that complement DI.
uw-unity-feature-scaffold to create feature modules with their own .asmdef — each feature gets its own Installer.uw-state-machine for game flow states, with states receiving dependencies via constructor injection.uw-unity-test-runner — interface-based DI makes testing easy: construct classes with mock dependencies, no container needed in tests.uw-scriptable-object-arch for game data assets that get injected into services via AddInstance.ProjectConfig.yaml -> architecture_pattern: di-first.[Inject] attribute for MonoBehaviours..asmdef (per NAMING_CONVENTIONS.md). Installers live inside the feature's asmdef.[SerializeField] private for Inspector-exposed fields on Installers and MonoBehaviours — never public fields.Awaitable with CancellationToken for async operations (per CODING_STANDARDS.md).Start().ProjectConfig.yaml -> mcp.unity_mcp is true, call refresh_unity after creating files.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-06 | pass→pass | 11,803 | 10,225 | -13% | 1 | 1 | 0% | 2,088 | 4,002 | +92% | 0 | 0 | — |
case-01 | fail→pass | 7,759 | 7,246 | -7% | 1 | 1 | 0% | 1,495 | 3,386 | +126% | 0 | 0 | — |
case-02 | fail→fail | 15,300 | 8,689 | -43% | 1 | 1 | 0% | 3,027 | 3,752 | +24% | 0 | 0 | — |
case-03 | pass→pass | 8,769 | 6,099 | -30% | 1 | 1 | 0% | 1,826 | 3,148 | +72% | 0 | 0 | — |
case-04 | pass→pass | 11,195 | 4,606 | -59% | 1 | 1 | 0% | 2,273 | 2,903 | +28% | 0 | 0 | — |
case-05 | pass→pass | 10,396 | 3,709 | -64% | 1 | 1 | 0% | 1,737 | 2,691 | +55% | 0 | 0 | — |
case-07 | fail→pass | 13,222 | 5,233 | -60% | 1 | 1 | 0% | 2,262 | 3,002 | +33% | 0 | 0 | — |
case-08 | fail→pass | 13,261 | 5,919 | -55% | 1 | 1 | 0% | 2,413 | 3,212 | +33% | 0 | 0 | — |
case-09 | pass→pass | 8,531 | 1,914 | -78% | 1 | 1 | 0% | 1,320 | 2,276 | +72% | 0 | 0 | — |
case-10 | pass→pass | 14,147 | 10,710 | -24% | 1 | 1 | 0% | 2,465 | 4,044 | +64% | 0 | 0 | — |
case-11 | pass→pass | 11,202 | 6,305 | -44% | 1 | 1 | 0% | 2,121 | 3,303 | +56% | 0 | 0 | — |
case-12 | fail→pass | 4,715 | 2,781 | -41% | 1 | 1 | 0% | 805 | 2,504 | +211% | 0 | 0 | — |
case-13 | fail→fail | 7,921 | 2,126 | -73% | 1 | 1 | 0% | 1,365 | 2,401 | +76% | 0 | 0 | — |
case-14 | pass→pass | 7,611 | 4,404 | -42% | 1 | 1 | 0% | 1,406 | 2,814 | +100% | 0 | 0 | — |
case-15 | pass→pass | 13,084 | 3,849 | -71% | 1 | 1 | 0% | 2,312 | 2,771 | +20% | 0 | 0 | — |
case-16 | fail→pass | 5,440 | 1,752 | -68% | 1 | 1 | 0% | 905 | 2,289 | +153% | 0 | 0 | — |
case-17 | fail→pass | 8,244 | 3,163 | -62% | 1 | 1 | 0% | 1,567 | 2,572 | +64% | 0 | 0 | — |
case-18 | fail→pass | 11,258 | 5,367 | -52% | 1 | 1 | 0% | 1,831 | 2,965 | +62% | 0 | 0 | — |
case-19 | pass→pass | 11,757 | 9,466 | -19% | 1 | 1 | 0% | 2,194 | 3,851 | +76% | 0 | 0 | — |
case-20 | pass→pass | 10,168 | 4,691 | -54% | 1 | 1 | 0% | 1,901 | 3,164 | +66% | 0 | 0 | — |
case-21 | pass→pass | 12,343 | 9,905 | -20% | 1 | 1 | 0% | 2,449 | 4,110 | +68% | 0 | 0 | — |
case-22 | pass→pass | 11,965 | 5,599 | -53% | 1 | 1 | 0% | 2,131 | 3,190 | +50% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +32 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.