Which object dependency architecture do you mostly use for your published games?

I’ve seen many threads talking about different strategies to deal with object dependencies in Unity. Because of the nature of Unity where MonoBehaviors are not instantiated with new or even a factory methods, I was wondering how do you usually deal with object dependencies in Unity?

I’m primarily looking for responses of studios that have published many games using a specific architecture as well as having multiple people working on them. Which one do you think is usually the best? Is there any other architecture used in Unity I am not aware of?

Example:

Let’s say we have a class that holds the player data. For now let’s just say it holds the player name:

// PlayerData.cs
public class PlayerData
{
    public PlayerData(string playerName)
    {
        PlayerName = playerName;
    }

    public string PlayerName { get; set; }
}

A GameManager.cs or any other class that initializes PlayerData:

// GameManager.cs
class GameManager: MonoBehavior
{
    private void OnEnable()
    {
        var playerData = new PlayerData() { PlayerName = "Jane Doe" };
        // make playerData available for MonoBehaviors that need it
    }
}

Now let’s say we have a MonoBehavior class that needs to have access to the player name.

// NameSayer.cs
class NameSayer : MonoBehavior
{
    private void Start()
    {
        // Get player name from PlayerData and print it
        // Singleton, Dependency Injection, Service Locator, Scriptable Object, something else?
    }
}

As long as you provide what the MonoBehavior needs, they will do what they are supposed to do. What’s the best way to make what MonoBehaviors need explicit in code? Which architecture is the best to minimize problems where people use a MonoBehavior and they get a run time error later because they didn’t know it required a Singleton to be initialized with some values to work properly?

1 Like

Not really working in larger teams (sorry), but for me the most important factor is how the object usually comes about:

If it’s something that only ever gets created at runtime (bullets fired from a gun, enemies created by a spawner, etc.) then I usually do a dependency injection with an Init() method that basically acts as a constructor and gets called by the creating instance immediatly after creating the object.

For objects which are part of a scene from the start, dependency injections really suck though, because for large projects making sure that every game object has the data it needs before tries to do anything with it will quickly get increasingly difficult as your dependencies increase.

For that purpose, I usually use scriptable objects because they make it really easy to edit stuff from the scene view without getting into the code, but I’ve also sometimes used Singletons or in occasion just have the first object of a class to call GetXData() load the information from a file path and then put it in a static variable for all later objects of the same class to read.

Well I mean… I technically use all of those. Singleton I use the least, but some things are forced to.

But yeah… I use service locators, and often my services are scriptable objects.

And dependency injection is like… honestly I hate the topic of DI as DI is not a new concept or anything. I don’t use any DI frameworks, I just…you know… inject dependencies into objects when they need them. I honestly never understood why this concept blew up so big that it got entire frameworks just to do it when nearly all OO languages support it out of the box.

But yeah… it’s usually services for things I need in components, and DI for things that aren’t components themselves.

1 Like

I’m using combination of singelton called GameRoot and object provider inside of it called Blackboard where you can put any object (usually service of some kind).

When I need to retrieve something I do.

T GameRoot.Instance.Blackboard.Get()

Yes. :slight_smile:

All of the above. The only approach that I would NEVER consider again in a Unity context is the Zenject way. It confuses everything about initialization in a way that becomes almost inscrutable to any new team member once it reaches 3 or 4 different dependent objects. Never have I seen so much destruction as what this approach did to our codebase. Nobody could reason about it, nobody could actually document it, all the documentation we had turned out to be wrong (despite best intentions), and it had so many side effects and caveats that it was useless, and we were left supporting a nightmare network of stuff that cost us a fortune in lost engineering time.

Not only that but the ostensible reason we did it was for unit testing, when fully 99% of our bugs were UI alignment or weird edge case timing animation UI state bugs that would be an absolute nightmare to unit test and probably wouldn’t get caught in any case because it required a user to fatfinger in a very non-intuitive way, which users did all the time.

2 Likes

+1

I also had my go with the DI/IOC framework frenzy years ago. Recently got handed a project that relied entirely on it, with no docs whatsoever. Figuring out what did what was a nightmare. You simply cannot “follow” the code by just reading it.

2 Likes

It’s like DI/IOC was developed by the same folks who came up with Perl, a truly write-only language. :slight_smile:

https://en.wikipedia.org/wiki/Write-only_language

1 Like

It’s hard to follow but it is very nice when you got external documentation.
Still I don’t see it in unity3d. I’m so glad I decided not to use DI framework when I started my project in Unity few years ago.

1 Like

Also, this is what I use for most of my singleton needs, when I reach for a singleton. Don’t drop ANYTHING into any scene, access it only in code.

Simple Singleton (UnitySingleton):

Some super-simple Singleton examples to take and modify:

Simple Unity3D Singleton (no predefined data):

Unity3D Singleton with Prefab used for predefined data:

Again, these are pure-code solutions, do not put anything into any scene, just access it via .Instance!

And piling in one more post (am I over my limit?), I treat .AddComponent<>() as a private function in each class and use this pattern:

Factory Pattern in lieu of AddComponent (for timing and dependency correctness):

Thanks for the responses, very insightful. Yes, I understand all of the above are commonly used, but I guess my main question was more about MonoBehaviors/Components.

I guess it all comes down to either an object retrieves what it needs (Singleton/Service Locator) or it is handed what is needed (Factory/Constructor Injection/Dependency Injection).

// Singleton
class NameSayer : MonoBehavior
{
    private void Start()
    {
        Debug.Log(PlayerData.Instance.PlayerName);
    }
}


// Service Locator
class NameSayer : MonoBehavior
{
    private void Start()
    {
        Debug.Log(ServiceLocator.Get<IPlayerData>().PlayerName);
    }
}


// Factory (kind of, since this could already exist in the scene with no instantiation)
class NameSayer : MonoBehavior
{
    private PlayerData _playerData;
    public void Init(PlayerData playerData)
    {
        _playerData = playerData;
    }
    private void Start()
    {
        Debug.Log(_playerData.PlayerName);
    }
}


// Dependency Injection (requires reflection, could have significant runtime perf costs)
class NameSayer : MonoBehavior
{
    [Inject] protected PlayerData _playerData;
    private void Start()
    {
        Debug.Log(_playerData.PlayerName);
    }
}

Criticism:

Both criticisms talk about objects being instantiated while we do not know that they require external dependency initialization unless if we look at the code, which is a valid criticism.

What is usually advocated is constructor injection/factory pattern: when we instantiate an object we know what the object needs beforehand while also being easy to unit test.

Thing is, there’s no constructor for MonoBehaviors. Factory also assumes I’ll be instantiating all objects from code, while it is common in Unity (and gamedev in general) to have a scene where objects will be already instantiated when the scene starts. So unless I go full procedural/instantiate everything from an empty scene, there’s really no good way to do “Injection”, rather it’s easier if Service Locators/Singletons exist so that objects can just ask for what they need.

The only alternative I saw to this was using the Scriptable Object Architecture (Architect your code for efficient changes and debugging with ScriptableObjects | Unity), but I’m not sure if it really solves the problem since adding a scriptable object as a dependency doesn’t mean I’m initializing data, it just means I’m adding a placeholder where I’ll promise that I’ll fill with a value later.

In the end, I think I’ll just stick with:

  • Singleton/ServiceLocator for components that already exist in the scene.
  • Factory for components that can be instantiated.
  • Constructor Injection for other classes.

I just hope this is not too confusing for other developers.

I still don’t know how to solve the problem of components that depend on singleton/service locators initialization without having my team having to understand what the component depends on before using them. I guess that’s the value of scriptable object architecture since the dependency requirement is right there in the inspector (when we add a component in the inspector we can see the missing dependency), then we know we need to fill that data in a game manager or somewhere else.