Gamasutra is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Gamasutra: The Art & Business of Making Gamesspacer
View All     RSS
November 29, 2021
arrowPress Releases
If you enjoy reading this site, you might also want to check out these UBM Tech sites:


 

The "KISS" Dependency Injection Way in Unity

by Ruben Torres Bonet on 11/18/21 11:27:00 am   Expert Blogs

The following blog post, unless otherwise noted, was written by a member of Gamasutra’s community.
The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company.

 

Check the original blog post at The Gamedev Guru: The "KISS" Dependency Injection in Unity

Two questions for you:

  1. Are you using a dependency injection system in Unity?
  2. If so, can you understand, read, debug and maintain the code of your dependency injection system in less than 10 minutes?

If the answer to any is NO, this post might help :-)

Table of Content

datadriveninvestor.com

Why Use Dependency Injection in Unity?

Here’s one simple but (sadly) often ignored principle in software development: SRP.

That reads: Single Responsibility Principle.

And that means: every class, every function should have one and only one responsibility.

Some examples:

  • Class PlayerProfile. This class should only contain data related to the player (level, name) and functions directly related to reading/writing them.
    The PlayerProfile class should NOT show level-up popups nor grant xp-related achievements.
  • Function int CalculateGainedExperience(int level). This function should just calculate how much experience you gain when you beat a level.
    It should NOT grant xp to the player, nor write any data in the player profile. In fact, it should have no side effects. Ninguno.

This is a common mistake that violates SRP:

class Player
{
  void LevelUp()
  {
    Backend.Instance.SendLevelUp();
  }
}

You see that Backend.Instance access?

This access violates the Single-Responsibility Principle.

The LevelUp function should just do a level up. It is not part of its job description to find a reference to a backend system (in this case, the singleton).

So this function should just invoke the SendLevelUp function through a backend reference that someone passed it.

This is why breaking SRP in this manner is a problem:

  • The next time you change your specific backend implementation, gl hf changing all references.
  • Creating unit tests that mock the backend is hard, as you can’t easily change the references
  • Getting references makes your code messier and harder to read for everyone

More typical examples of SRP violation:

  • FindObjectOfType<Backend>()
  • GetComponent<Backend>()

I can sum it up this way: finding a reference to an object is not your class/function’s problem.

So, whose problem is it then?

Finding object references is the Dependency Injector’s problem.

What is Dependency Injection (DI)?

To keep it short, a DI system does two things:

  1. Keep track of your main project objects, e.g. Backend, VO, Localization, Logger
  2. Assign/Inject references into your objects as they require

Think of a DI system as the table of contents of a book. A ToC with a list of chapters ( Backend) and their page/location (0x123123).

This is how you 1. Keep track of your references:

Injector.Bind<IBackend>(new Backend());
Injector.Bind<ILogger>(new LoggerFile());
// ...

And this is how you 2. Inject your references into your objects:

class Player
{
  [Inject] private IBackend _backend;

  void LevelUp()
  {
    _backend.SendLevelUp();
  }
}
// ...
// ...
Injector.Inject(myPlayerObject);

That’s it.

When it comes to choosing a DI system, there are many third-party libraries out there like Zenject and StrangeIoC.

However, I have a big problem with most...

Woah! A 'Lightweight' framework, sure

My Main Problem With (Most) Existing Unity Dependency Injection Systems

The main problem I have is that they are not simple, but rather quite complex.

In other words, most DI implementations don’t follow the KISS approach (KISS = Keep it Simple Stupid).

It’s not uncommon to see DI systems with 20k lines of code or more (I’m looking at you, Zenject).

And this is a huge problem because of the effort and time it takes to:

  • Understand its system
  • Read its code
  • Maintain it
  • Debug it

I used Zenject a few times.

Can you guess what happened after running into problems?

I spent DAYS understanding and debugging its code base. Several times.

How much time did I save by using Zenject?

None. In fact, it was negative value. All in all, I spent more time working on a DI system than the DI system working for me.

Not great...

So, what to do about it?

My KISS Approach to Dependency Injection in Unity

Here’s a piece of advice that you can feel free to ignore (and eventually regret):

Every time you evaluate using a third-party library in your PRODUCTION-level project, investigate it well.

Ask yourself these questions about any library you want to use:

  1. Can I understand what it does without reading its documentation within 10 minutes?
  2. Can I effectively read most of its code within these 10 minutes?
  3. Could I easily debug it whenever I run into problems within 10 minutes?

It might be 10 minutes, it might be 1 hour. It depends on the system we’re talking about.

At its essence, the key part is this: you want to Keep It Simple Stupid.

Ever since I spent days working on a DI system rather than the DI system working for me, I adhere pretty much to the KISS rule whenever I can.

Sure, that’s not always the case with libraries like Photon or engines like Unity, but those are big for a reason.

A Dependency Injection system can be and should be simple.

Again, simple means:

  • You can easily understand it
  • You can quickly debug it when problems arise
  • You can simply extend it if you really have to

Following the KISS principle is a professional matter.

So no more bells and whistles, thanks.

What’s Next?

If you want to watch the full lesson on my approach to dependency injection, you can enroll in the Unity Performance Taskforce.

This (live) lesson includes everything you need to quickly get started:

  • A proper KISS DI system (1 file, < 150 lines)
  • Examples
  • Live QA discussion

Simply head to Week 025.

Ruben


Related Jobs

Square Enix Co., Ltd.
Square Enix Co., Ltd. — Tokyo, Japan
[11.28.21]

Experienced Game Developer
Insomniac Games
Insomniac Games — Remote - US, California, United States
[11.26.21]

Gameplay Programmer
Deep Silver Volition
Deep Silver Volition — Champaign, IL or Remote, Illinois, United States
[11.25.21]

Senior Technical Artist
4A Games
4A Games — Sliema, Malta
[11.25.21]

Lead AI Designer (Malta)





Loading Comments

loader image