Skip to content
cccskills
FreePublished on GitHub

uw-dependency-injection

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 pattern", "cross-feature communication", or architecture discussions involving testability, decoupling, and service layers. Only activates when ProjectConfig.yaml -> architecture_pattern is "di-first". For simpler projects, use uw-scriptable-object-arch instead.

The English summary is being prepared. Showing the original description.

Works withUnity
See how to install

Files included (2)

  • SKILL.md9.2 KB
  • references/class-taxonomy-and-commands.md3.0 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Dependency Injection (Reflex)

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.

Before You Start

  1. Read 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.
  2. Read docs/CODING_STANDARDS.md for async patterns (Awaitable + CancellationToken), access modifiers (internal for cross-class helpers within an asmdef), and class structure.
  3. Read docs/NAMING_CONVENTIONS.md for file/class naming.
  4. Ensure each feature module has its own .asmdef — create with uw-unity-feature-scaffold. Installers, Controllers, Services all live inside feature asmdefs.

When to Use DI vs SO-First

SO-First (default)DI-First
Small/medium scopeLarge/complex scope
Solo or small teamTeam with testing culture
Rapid prototypingProduction architecture
Simple event-driven communicationCross-feature orchestration via Commands
ScriptableObject event channelsInterface-based injection + factories

Both can coexist: DI containers can bind ScriptableObject data assets as singletons.

Reflex Setup

Install (OpenUPM): openupm install com.gustavopsantos.reflex Or UPM Git URL: https://github.com/gustavopsantos/reflex.git?path=/Assets/Reflex/

Container Hierarchy

RootScope (app lifetime — singletons, core services)
  +-- SceneScope (scene lifetime — per-scene services, controllers)
        +-- Manual child scopes (gameplay round, level, etc.)

Installer Pattern

Each feature module has one Installer that registers its bindings. The Installer lives inside the feature's .asmdef.

using 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);
        }
    }
}

Binding Types

MethodWhat It DoesLifetime
AddSingleton(type, impl)Container creates one instanceApp/Scene
AddTransient(type, impl)New instance each resolvePer-resolve
AddInstance(obj)Register existing object (SO, config)Singleton

Injection

using 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).

Interface-First Rule

Always bind to interfaces, not concrete types. This enables mock injection for testing, makes dependencies explicit, and follows the Dependency Inversion Principle.

// Correct — bind to interface
builder.AddSingleton(typeof(IAudioService), typeof(AudioService));

// Wrong — binding concrete directly prevents mock injection
builder.AddSingleton(typeof(AudioService));

Circular Dependency Prevention

Circular dependencies (A depends on B, B depends on A) cause infinite resolution loops. Prevent them with:

  1. Commands — if two features need to communicate, use a Command that resolves its deps at execution time instead of at construction time.
  2. Events / ScriptableObject channels — decouple with uw-scriptable-object-arch event channels.
  3. Interfaces in a shared assembly — extract the interface into {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.

App Lifecycle / Single Entry Point (SEP)

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:

  • CoreScene is always the first scene (set in Build Settings index 0, or via editor DefaultSceneSelector).
  • Each scene has one Installer (bindings) and one Initiator (entry/exit points).
  • Only CoreInitiator has a Start() method — everything else is initialized via async InitEntryPoint().

Class Taxonomy, Commands & Update Management

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.

Cross-Feature Command Example

When Feature A (Combat) needs to notify Feature B (Score) about a hit:

// 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));

Integration with ScriptableObject Data

DI and SO work together — SO holds data, DI manages services and wiring:

// 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.

After Setup

  • Scaffold features: Use uw-unity-feature-scaffold to create feature modules with their own .asmdef — each feature gets its own Installer.
  • State management: Use uw-state-machine for game flow states, with states receiving dependencies via constructor injection.
  • Write tests: Use uw-unity-test-runner — interface-based DI makes testing easy: construct classes with mock dependencies, no container needed in tests.
  • SO data: Use uw-scriptable-object-arch for game data assets that get injected into services via AddInstance.

Rules

  • Only applies when ProjectConfig.yaml -> architecture_pattern: di-first.
  • Always bind to interfaces, not concrete types (interface-first rule).
  • Constructor injection preferred for plain C# classes. [Inject] attribute for MonoBehaviours.
  • Commands are the ONLY classes allowed to cross feature boundaries.
  • Each feature module needs its own .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.
  • Use Awaitable with CancellationToken for async operations (per CODING_STANDARDS.md).
  • CoreScene is Build Settings index 0. Only CoreInitiator has Start().
  • Break circular dependencies with Commands, events, or shared interface assemblies — never with direct cross-feature references.
  • If ProjectConfig.yaml -> mcp.unity_mcp is true, call refresh_unity after creating files.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

adopt

Free

Brownfield onboarding — audits existing project artifacts for template format compliance (not just existence), classifies gaps by impact, and produces a numbered migration plan. Run this when joining an in-progress project or upgrading from an older template version. Distinct from /project-stage-detect (which checks what exists) — this checks whether what exists will actually work with the template's skills.

The English summary is being prepared. Showing the original description.

IdoCohen560/claude-unity-game-studio22Updated Jul 8, 2026

Creates an Architecture Decision Record (ADR) documenting a significant technical decision, its context, alternatives considered, and consequences. Every major technical choice should have an ADR.

The English summary is being prepared. Showing the original description.

IdoCohen560/claude-unity-game-studio22Updated Jul 8, 2026

Validates completeness and consistency of the project architecture against all GDDs. Builds a traceability matrix mapping every GDD technical requirement to ADRs, identifies coverage gaps, detects cross-ADR conflicts, verifies engine compatibility consistency across all decisions, and produces a PASS/CONCERNS/FAIL verdict. The architecture equivalent of /design-review.

The English summary is being prepared. Showing the original description.

IdoCohen560/claude-unity-game-studio22Updated Jul 8, 2026

Guided, section-by-section Art Bible authoring. Creates the visual identity specification that gates all asset production. Run after /brainstorm is approved and before /map-systems or any GDD authoring begins.

The English summary is being prepared. Showing the original description.

IdoCohen560/claude-unity-game-studio22Updated Jul 8, 2026

Audits game assets for compliance with naming conventions, file size budgets, format standards, and pipeline requirements. Identifies orphaned assets, missing references, and standard violations.

The English summary is being prepared. Showing the original description.

IdoCohen560/claude-unity-game-studio22Updated Jul 8, 2026

Generate per-asset visual specifications and AI generation prompts from GDDs, level docs, or character profiles. Produces structured spec files and updates the master asset manifest. Run after art bible and GDD/level design are approved, before production begins.

The English summary is being prepared. Showing the original description.

IdoCohen560/claude-unity-game-studio22Updated Jul 8, 2026

See all skills by IdoCohen560

Report a problem with this skill