Dependency inversion with Unity - Better Code architecture in Unity3D

Let’s start with my first attempt:

public class Switch : MonoBehavior { Door door; void Activate() { door.Open(); } }

Looks simple, right? But here’s the problem → this Switch only works with a Door.

What if I want the same Switch to control a Light, a fan, a car or even an Elevator?
I’d have to rewrite it every time. That’s tight coupling and it violates Dependency Inversion.

The Fix → Dependency Inversion with Interfaces

Instead of tying the Switch to a Door, I made it work with any device that can be switched.
I defined a simple interface:

public interface ISwitchable

{

    void Activate();

    void Deactivate();

}


Now, every device just needs to implement this interface.

public class Door : MonoBehaviour, ISwitchable
{
    private bool isOpen;

    public void Activate()
    {
        if (!isOpen) { Debug.Log("Door opened!"); isOpen = true; }
    }

    public void Deactivate()
    {
        if (isOpen) { Debug.Log("Door closed!"); isOpen = false; }
    }
}
We have written door logic. currently its only logging you can write whatever logic you want. And now we have completely differnt logics or code Light toggle.
public class LightDevice : MonoBehaviour, ISwitchable
{
    private Light _light;
    private void Awake() => _light = GetComponent<Light>();

    public void Activate() => _light.enabled = true;
    public void Deactivate() => _light.enabled = false;
}

As you can see it is completely different from door logic. But it has same function called Activate which inherited from the ISwitchable. Now you will see the real magic or its power in below code.

using UnityEngine;

public class PlayerInteraction : MonoBehaviour
{
    [SerializeField] private float interactDistance = 3f;
    [SerializeField] private KeyCode interactKey = KeyCode.E;
    [SerializeField] private Camera playerCamera;

    private void Update()
    {
        if (Input.GetKeyDown(interactKey))
        {
            TryInteract();
        }
    }

    private void TryInteract()
    {
        Ray ray = new Ray(playerCamera.transform.position, playerCamera.transform.forward);
        if (Physics.Raycast(ray, out RaycastHit hit, interactDistance))
        {
            var switchable = hit.collider.GetComponent<ISwitchable>();
            if (switchable != null)
            {
                // Toggle logic inside the device itself
                ToggleDevice(switchable);
            }
        }
    }

    private void ToggleDevice(ISwitchable device)
    {
        // You can decide if you want a "toggle" contract in the interface
        // For now, just call Activate as a demo
        device.Activate();
    }
}

Here most important part is hit.collider.GetComponent<ISwitchable>(); You are picking the generic interface, and PlayerInteraction dont know about the implementation of Activate. You can add as many devices or togglers without touching the PlayerInteraction class. Remember, our first example where we were using the Switch class with a hardcoded Door dependency.

Now,

What you just saw is called applying the Dependency Inversion Principle (DIP) together with Programming to an Interface (aka Interface-based design).

In Unity/game dev terms, people often call this pattern:

  • Decoupling via Interfaces → your PlayerInteraction (or previously switch class)doesn’t know if it’s a Door, Light, Trap, etc., it only cares about ISwitchable.
  • Now you can plug any object that implements ISwitchable into the PlayerInteraction 
  • It will be easy to test!


Key Benefits of DIP:

  • Loose coupling (classes are easier to modify or replace).
  • Testability (can mock dependencies in unit tests).
  • Flexibility (swap implementations at runtime via dependency injection).
  • Maintainability (fewer code changes when details change).

Post a Comment

0 Comments