Course contentsShow
← Unity Game Development
Lesson 438 of 8287. Advanced Game ArchitectureFree lesson

Decoupling Systems via Dependency Injection Patterns

You will implement manual dependency injection to pass required services into your classes, removing hard-coded references between disparate game systems.

In this lesson, you will learn how to replace rigid, hard-coded dependencies with manual dependency injection to create modular, testable game systems.

The Problem: Tight Coupling

In Unity, it is common to see code like this:

public class PlayerHealth : MonoBehaviour 
{
    void Die() 
    {
        // Hard-coded dependency
        FindObjectOfType<UIManager>().ShowGameOverScreen();
    }
}

This approach forces PlayerHealth to "know" about UIManager. If you want to use the player system in a different scene without a UI, or swap the UI system for a new one, you have to rewrite the code.

The Solution: Manual Dependency Injection

Dependency Injection (DI) is the practice of "injecting" a required service into a class rather than letting that class go out and fetch it itself. By passing dependencies through a constructor or a setup method, we decouple our logic from specific concrete implementations.

Instead of hunting for objects, we define an interface and require the system to be provided:

public interface IGameOverHandler 
{
    void ShowGameOverScreen();
}

public class PlayerHealth : MonoBehaviour 
{
    private IGameOverHandler _uiHandler;

    // Manual Injection via a Setup method
    public void Initialize(IGameOverHandler uiHandler) 
    {
        _uiHandler = uiHandler;
    }

    void Die() 
    {
        _uiHandler?.ShowGameOverScreen();
    }
}

Implementing the Pattern

To wire this up, you need a "Composer" or a central Controller script that lives in your scene. This script acts as the authority that knows where the UIManager is and tells the PlayerHealth about it during Awake() or Start():

public class GameComposer : MonoBehaviour 
{
    public PlayerHealth player;
    public UIManager uiManager;

    void Awake() 
    {
        // The composer bridges the two disconnected systems
        player.Initialize(uiManager);
    }
}

By shifting the responsibility of "connecting" systems to a central composer, your individual classes become "agnostic." They don't care which UI system they are talking to, as long as it fulfills the IGameOverHandler contract. This makes your architecture significantly easier to maintain and extend as your project grows in complexity.

Key Takeaway: By injecting dependencies through explicit setup methods, you transform hard-coded systems into plug-and-play components that function independently of their environment.