Dependency injection

2025/12/27

Type
Learning Resource
Format
Glossary Article
Version
General
Subject Tags
Code
Assets
All else
Copyright 2016-2026, GDQuest
Created
2026/02/16
Updated
2025/12/27

Dependency injection

Dependency injection is a pattern where an object receives the data it needs to function (its dependencies) from outside, rather than creating them itself. Instead of a class creating its own dependencies, you pass them in, typically through the _init() constructor method or a custom setup() method.

In game development, this pattern is common because game entities often need to reference other objects, resources, or signals. Rather than having each object find or create what it needs, you pass references around. This is already a common practice in Godot, even if we usually don't call it "dependency injection."

Let's use a common example: in your game you have different characters that the player can control with a heads-up display showing health, stamina, and other stats. Instead of having the UI search for the player character in the scene tree, you can inject the player reference into the UI when you set it up.

Here's a health bar. It has a setup() function that expects a signal:

class_name HealthBar extends ProgressBar

func setup(health_changed: Signal) -> void:
	health_changed.connect(_on_health_changed)

func _on_health_changed(new_health: int) -> void:
	var tween := create_tween()
	tween.tween_property(self, "value", new_health, 0.3)

The player script has a signal that emits when the character's health changes:

class_name Player extends CharacterBody2D

signal health_changed(new_health: int)

var health := 100: set = set_health

func set_health(new_health: int) -> void:
	var old_health: int = health
	health = new_health
	health_changed.emit(health)

In practice, you might have a third script that holds references to both the player and the UI, and calls the setup function to connect them:

class_name Game extends Node

@onready var player: Player = %Player
@onready var health_bar: HealthBar = %HealthBar

func _ready() -> void:
	health_bar.setup(player.health_changed)

This way, the main game script (or whatever you call it) wires up the dependencies. The HealthBar doesn't need to know where the player is or how to find it, and the Player doesn't need to know about the UI. This is just one example; usually in a solo game with a single playable character, you can directly make the UI a child of the player scene and skip this extra step.

You may hear people say that dependency injection helps to decouple different parts of your code. Because, in the UI's example, without the signal, you may directly update the health bar from the player code and create a hard, explicit link between the UI and the player.

But actually, the health bar still depends on the player through its health_changed signal, plus the health bar only exists to display the player's health. So, there's still an irreducible dependency between the two parts of the code. Plus, in this example, the game script has references to both the player and the UI!

When you use a dependency injection, you are explicitly stating that one part of your code relies on another part. Even if that other part is passed in from the outside, the dependency still exists. It's just obfuscated a bit.

Nathan

Founder and teacher at GDQuest
What would it look like without dependency injection?

Without dependency injection, the UI either has to create a copy of the player character or it searches for it by itself.

Creating a new copy of the character doesn't make sense in this case, so instead it would search for the player node with code like this:

class_name HealthBar extends Control

var player: Player = null

func _ready() -> void:
	var result: Array = get_tree().current_scene.find_children("*", "Player", true, false)
	if not results.is_empty():
		player = result.front() as Player

	if player != null:
		player.health_changed.connect(_on_health_changed)


func _on_health_changed(new_health: int) -> void:
	# ...

The find_children() method is just one reliable way to find the player regardless of its name or position in the scene tree; you could also find the player node by saving a reference in a singleton, using node groups, or using get_node() with a hardcoded path.

In any case, while this works in smaller games, in a team you'll generally want to avoid this pattern to make the UI easier to replace and the flow of dependencies clearer to your teammates.

Become an Indie Gamedev with GDQuest!

Don't stop here. Step-by-step tutorials are fun but they only take you so far.

Try one of our proven study programs to become an independent Gamedev truly capable of realizing the games you’ve always wanted to make.

Nathan

Founder and teacher at GDQuest
  • Starter Kit
  • Learn Gamedev from Zero
Check out GDSchool

You're welcome in our little community

Get help from peers and pros on GDQuest's Discord server!

20,000 membersJoin Server

Contribute to GDQuest's Free Library

There are multiple ways you can join our effort to create free and open source gamedev resources that are accessible to everyone!