/dependency-injection
Use when managing dependencies between systems — autoloads, service locators, @export injection, and scene injection patterns
$ npx -y skills add jame581/GodotPrompter --skill dependency-injection --agent claude-codeHow 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.mdname: 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
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
Agentic skills framework for Godot 4.x game development. Gives AI coding agents domain-specific expertise for GDScript and C# projects.
Other skills on godot-prompter.
- /authoring-godot-prompter-skills
Use when writing or editing a SKILL.md or an agent definition in this repo — required frontmatter, section ordering, and the GDScript-then-C# example convention.
Open skill - /releasing-godot-prompter
Use when cutting a GodotPrompter release or bumping its version — the version-bump sequence, tag-triggered workflow, and the marketplace manifests that must follow.
Open skill - /2d-essentials
Use when working with 2D-specific systems — TileMaps, parallax scrolling, 2D lights and shadows, canvas layers, particles 2D, custom drawing, and 2D meshes in Godot 4.3+
Open skill - /3d-essentials
Use when working with 3D-specific systems — materials, lighting, shadows, environment, global illumination, fog, LOD, occlusion culling, and decals in Godot 4.3+
Open skill - /ability-system
Use when building character abilities — Resource-based abilities with cost/cooldown/cast, buffs/debuffs, stat modifiers, gameplay tags, and HUD binding
Open skill - /addon-development
Use when creating Godot editor plugins — EditorPlugin, @tool scripts, custom inspectors, and dock panels
Open skill

