Back To Home
Unity
25-03-2024
"Ever feel like your Unity project is tangled in a web of code dependencies, where changing one class throws the whole thing into chaos? Imagine a world where components collaborate seamlessly, each requesting the services it needs without getting bogged down in implementation details. That's the magic of Dependency Injection (DI) – a powerful design pattern that can take your Unity game development to the next level!"
In the world of game development, building captivating and well-organized games is the ultimate goal. However, as projects grow in complexity, managing dependencies and ensuring code flexibility can become a daunting task. This is where Dependency Injection (DI) steps in.
In this article, we dive into the world of Dependency Injection in Unity, exploring its concepts, benefits, and practical implementation. By the end, you’ll have a clear understanding of how Dependency Injection can level up your game development process.
At its core, Dependency Injection is all about shifting the responsibility of creating and providing dependencies from within a class to an external source. Rather than having a class create its own dependencies, it receives them from the outside. This not only reduces tight coupling between components but also allows for easier testing, code reusability, and maintainability.
In C#, Dependency Injection can be achieved using a technique called “constructor injection.” Let’s break down the key concepts:
Dependency :
A “dependency” is an object that a class depends on to perform its functionality. For example, if you’re building a game, a character might depend on a weapon to attack enemies. In software, dependencies can be anything from data sources to other classes or services.
Injection :
“Injection” refers to the process of providing these dependencies to a class from outside. Instead of the class creating its own dependencies, they are provided externally.
You cannot directly instantiate MonoBehaviour objects using the new keyword because MonoBehaviour is a special type of class that Unity manages internally. So you can not use constructors here.
public class Player : MonoBehaviour {
public int name;
}
public class User : MonoBehaviour {
Player p1 = new Player();
}
This will give a warning : “You are trying to create a MonoBehaviour using the new keyword” and it wont work.
We need to use Instantiate() function instead of the “new” keyword to make the object. However Instantiate() only takes some parameters like position, rotation and parent. We can not pass other reference here, thus we can not use default dependency Injection in unity. We will talk about its solution later.
In the context of Unity game development, Unity itself can help manage and inject dependencies. Unity provides a feature called “Inversion of Control (IoC) Container” which can handle the injection of dependencies.
IoC refers to the idea that control over the flow of a program is inverted or handed over to a framework or container, rather than being controlled by the application code itself.
example : A senior developer assigning his work to a junior developer and doing nothing. It is indeed a form of Inversion of Control. It’s a real-world example of how the concept of control inversion can apply outside of software development as well.
In the context of Unity game development, Unity itself can help manage and inject dependencies. Unity provides a feature called “Inversion of Control (IoC) Container” which can handle the injection of dependencies.
A constructor is a special method that’s used to create and set up objects when they’re first made. It is used to initialises the instance variables of the class.
Constructors are called automatically when you create a new instance of a class, and they help make sure that the object is properly initialized and ready to be used.
Let’s dive into a clear example to understand Dependency Injection (DI) in both classical C# programming and Unity. By walking through this example, you’ll grasp how DI works in each context and its benefits.
Let’s say you’re building a simple console application where you have a Character class that depends on a Weapon class. Here’s how you could implement DI without Unity:
Defining Interfaces :
Create interfaces that define weapon behavior, like IWeapon.
Implementing DI :
Design the Character class to accept different weapon implementations through constructor injection.
Putting It Together :
In your main program, create instances of weapons and a character with a selected weapon. The character can attack using the injected weapon.
public interface IWeapon
{
void Attack();
}
public class Sword : IWeapon
{
public void Attack()
{
Console.WriteLine("Swinging sword!");
}
}
public class Bow : IWeapon
{
public void Attack()
{
Console.WriteLine("Firing bow!");
}
}
Implement Dependency Injection :
public class Character
{
private IWeapon weapon;
public Character(IWeapon weapon)
{
this.weapon = weapon;
}
public void AttackEnemy()
{
weapon.Attack();
}
}
class Program
{
static void Main(string[] args)
{
IWeapon sword = new Sword();
Character character = new Character(sword);
character.AttackEnemy();
}
}
In Unity, MonoBehaviour classes don’t use traditional constructors in the same way as standard C# classes. This does change how Dependency Injection (DI) is implemented in Unity compared to classical C# programming. Instead of constructor injection, Unity uses other techniques to achieve DI, often involving the use of public fields and ScriptableObjects.
Imagine you have a simple game where the player collects coins to score points. You want to implement a scoring system using DI. Here’s how you could do it :
1. Start by defining an interface that represents the scoring behavior :
public interface IScoreManager
{
void AddScore(int points);
int GetScore();
}
Implement the Score Manager :
public class ScoreManager : IScoreManager
{
private int score;
public void AddScore(int points)
{
score += points;
}
public int GetScore()
{
return score;
}
}
Use Dependency Injection :
Now, you want to inject the IScoreManager into a MonoBehaviour class that needs it. This can be done using public fields in Unity’s Inspector :
using UnityEngine;
public class Player : MonoBehaviour
{
public IScoreManager scoreManager;
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Coin"))
{
scoreManager.AddScore(10);
Destroy(other.gameObject);
}
}
}
In the Unity Editor, attach the Player script to your player GameObject. Then, you need to create a GameObject with the ScoreManager script attached. This GameObject will serve as your DI container. Assign the ScoreManager instance to the scoreManager field in the Player script using the Inspector.
With this setup, every time the player collects a coin, the Player script adds 10 points to the score using the injected scoreManager instance.
If you want to inject dependencies through code instead of using the Inspector in Unity, you can follow a manual injection approach. This involves creating the necessary instances in your code and passing them to the appropriate components when you instantiate them.
Player Script with Property Injection :
In your Player script, use property injection to inject the IScoreManager instance:
using UnityEngine;
public class Player : MonoBehaviour
{
// Property for injecting the score manager
public IScoreManager ScoreManager { get; set; }
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Coin") && ScoreManager != null)
{
ScoreManager.AddScore(10);
Destroy(other.gameObject);
}
}
}
GameManager Script :
In your GameManager script, you can create instances and inject the dependencies like this :
using UnityEngine;
public class GameManager : MonoBehaviour
{
private void Start()
{
IScoreManager scoreManager = new ScoreManager();
// Create a GameObject with the Player component
GameObject playerGameObject = new GameObject("Player");
Player player = playerGameObject.AddComponent();
// Inject the ScoreManager dependency
player.ScoreManager = scoreManager;
// Other initialization code...
}
}
In this example, we’re creating a new GameObject named “Player” and adding the Player MonoBehaviour component to it using the AddComponent method. Then, we’re manually injecting the ScoreManager dependency into the Player instance using the ScoreManager property.
Extenject, also known as Zenject, is a powerful Dependency Injection (DI) and Inversion of Control (IoC) framework for Unity game development. This framework streamlines the process of managing and injecting dependencies into your game objects. With Extenject, you can easily create flexible and modular code by defining how different components work together through injection.
With these frameworks, you can collect all the special Unity components in one spot, and then let the framework give these components the things they need to work properly. In conclusion, Dependency Injection (DI) is a powerful technique that enhances code organization, maintainability, and flexibility in software development, including within the Unity game development environment.
By injecting dependencies from the outside rather than having classes create them internally, DI promotes loose coupling between components, simplifies testing, and allows for easy modifications and extensions.
Dependency Injection might seem like a complex concept at first, but its benefits for your Unity projects are undeniable. By embracing DI, you can create cleaner, more modular, and more testable code, ultimately leading to a smoother and more enjoyable development experience. So, break free from the chains of hardcoded dependencies and inject some flexibility into your Unity projects with DI!
I hope you found this blog post enjoyable!