Skip to content
Development
Skill

/dependency-injection

Use when managing dependencies between systems — autoloads, service locators, @export injection, and scene injection patterns

From plugin
godot-prompter
54157 skills9 agents1 hook
Install
$ npx -y skills add jame581/GodotPrompter --skill dependency-injection --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/dependency-injection

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use when managing dependencies between systems — autoloads, service locators, @export injection, and scene injection patterns

SKILL.md

dependency-injection.SKILL.md
name: dependency-injection
description: Use when managing dependencies between systems — autoloads, service locators, @export injection, and scene injection patterns

Dependency Injection in Godot 4.3+

Patterns for wiring dependencies between systems so nodes stay loosely coupled, swappable, and testable. All examples target Godot 4.3+ with no deprecated APIs.

> **Related skills:** **godot-testing** for test-friendly architecture, **event-bus** for signal-based decoupling, **godot-project-setup** for autoload registration.

---

1. The Problem

Tight coupling makes code hard to test, extend, and swap. The most common form in Godot is reaching directly into a global autoload from everywhere in the codebase.

# BAD — tight coupling via direct autoload access scattered everywhere

# player.gd
func take_damage(amount: int) -> void:
    health -= amount
    AudioManager.play_sfx("hurt")          # hard dependency on AudioManager
    UIManager.update_health_bar(health)    # hard dependency on UIManager
    if health <= 0:
        GameState.record_death()           # hard dependency on GameState

# enemy.gd
func attack() -> void:
    AudioManager.play_sfx("attack")        # same AudioManager dependency again
// BAD — tight coupling via direct autoload / global access scattered everywhere

// Player.cs
public partial class Player : CharacterBody3D
{
    private int _health = 100;

    public void TakeDamage(int amount)
    {
        _health -= amount;
        GetNode<AudioManager>("/root/AudioManager").PlaySfx("hurt");        // hard dependency
        GetNode<UIManager>("/root/UIManager").UpdateHealthBar(_health);     // hard dependency
        if (_health <= 0)
            GetNode<GameState>("/root/GameState").RecordDeath();            // hard dependency
    }
}

// Enemy.cs
public partial class Enemy : CharacterBody3D
{
    public void Attack()
    {
        GetNode<AudioManager>("/root/AudioManager").PlaySfx("attack");      // same dependency again
    }
}

**Problems with this approach:**

  • Every node that calls `AudioManager` directly is coupled to its concrete implementation.
  • Swapping `AudioManager` for a different implementation requires changing every caller.
  • Unit-testing `Player` in isolation is impossible — `AudioManager`, `UIManager`, and `GameState` must all exist and be valid.
  • Autoload initialization order bugs silently break behaviour when scenes load.
  • Hidden dependencies make it hard to see what a class actually needs to function.

---

2. Approach Comparison

| Pattern | Complexity | Testability | Best For | |---|---|---|---| | **Autoloads** | Low | Low | Truly global singletons: audio, settings, platform services | | **@export Injection** | Low | High | Most nodes — wire deps in the editor, no runtime lookup needed | | **Service Locator** | Medium | Medium | Plugins, optional systems, swappable implementations at runtime | | **Scene Injection** | Low | High | Parent-to-child wiring: Level sets up Enemy, HUD sets up sub-panels |

---

3. Autoloads as Singletons

Register a script in **Project Settings → Autoload** for global access (`AudioManager.play_sfx(...)`, `GameState.score = 100`). Best for cross-cutting concerns: audio, save state, event bus, settings. Resist autoloading domain-specific systems (those should be scene-injected).

> See [references/autoloads.md](references/autoloads.md) for the full AudioManager example (SFX + crossfade music) in GDScript + C#.

---

4. @export Node Injection

Expose collaborator nodes as `@export var health_component: HealthComponent`, then wire in the Inspector or via parent scene. Lifecycle: `@export` properties are assigned BEFORE `_ready()`.

> See [references/export-injection.md](references/export-injection.md) for full `@export` patterns (GDScript + C#) and lifecycle notes.

---

5. Service Locator Pattern

A central registry autoload mapping `String` keys to service instances. Services register themselves at `_ready()`, deregister at `_exit_tree()`, consumers call `ServiceLocator.get(name)`. Useful when you want flexible runtime swap of implementations (testing, mods, A/B variants).

> See [references/service-locator.md](references/service-locator.md) for the full Service Locator (GDScript + C#) with typed helper methods.

---

6. Scene Injection

Parent scene loads its children, then in `_ready()` walks the tree assigning dependencies (`enemy.player = $Player`). Children declare `@export` properties but the parent — not the Inspector — sets them. Best for game-specific dependencies that change per level.

> See [references/scene-injection.md](references/scene-injection.md) for the parent-injects-children pattern (GDScript + C#).

---

7. Testing with Dependency Injection

Injecting fakes / test doubles is what makes nodes testable. For autoloads: mock-replace before the test scene loads. For `@export` injection: swap the export to a test double. For Service Locator: register a fake under the same key.

> See [references/testing-with-di.md](references/testing-with-di.md) for GUT-based test patterns showing each injection technique.

---

8. When to Use What

| Situation | Recommended Pattern | |---|---| | Service used by nearly every node in every scene | Autoload singleton | | Node needs 1–3 deps, scene is editor-authored | `@export` injection | | System is optional or swappable at runtime | Service Locator | | Parent scene constructs children and knows their needs | Scene injection | | Writing tests for a node with external dependencies | `@export` or property injection + stubs | | Plugin that must work in any project | Service Locator (self-registers, no assumptions) | | Two sibling nodes need the same dep | Let their parent hold it and inject downward |

**Quick decision guide:**

Does every scene in the project need it?
  YES → Autoload singleton
  NO  ↓

Is the dependency known at edit-time and wired in the Inspector?
  YES → @export injection
  NO
Read more
Ships withgodot-prompter

Agentic skills framework for Godot 4.x game development. Gives AI coding agents domain-specific expertise for GDScript and C# projects.

Get the whole plugin