Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

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

Discussion in 'Scripting' started by Reshima, Aug 13, 2020.

?

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

  1. Singleton

    13 vote(s)
    22.8%
  2. Dependency Injection (Zenject/Extenject/etc)

    12 vote(s)
    21.1%
  3. Service Locators

    7 vote(s)
    12.3%
  4. Scriptable Objects (Unity Atoms, or your own)

    34 vote(s)
    59.6%
  5. Other (post in the thread)

    2 vote(s)
    3.5%
Multiple votes are allowed.
  1. Reshima

    Reshima

    Joined:
    Dec 10, 2012
    Posts:
    51
    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:

    Code (CSharp):
    1.  
    2. // PlayerData.cs
    3. public class PlayerData
    4. {
    5.     public PlayerData(string playerName)
    6.     {
    7.         PlayerName = playerName;
    8.     }
    9.  
    10.     public string PlayerName { get; set; }
    11. }
    A GameManager.cs or any other class that initializes PlayerData:

    Code (CSharp):
    1.  
    2. // GameManager.cs
    3. class GameManager: MonoBehavior
    4. {
    5.     private void OnEnable()
    6.     {
    7.         var playerData = new PlayerData() { PlayerName = "Jane Doe" };
    8.         // make playerData available for MonoBehaviors that need it
    9.     }
    10. }
    Now let's say we have a `MonoBehavior` class that needs to have access to the player name.

    Code (CSharp):
    1.  
    2. // NameSayer.cs
    3. class NameSayer : MonoBehavior
    4. {
    5.     private void Start()
    6.     {
    7.         // Get player name from PlayerData and print it
    8.         // Singleton, Dependency Injection, Service Locator, Scriptable Object, something else?
    9.     }
    10. }
    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?
     
  2. Hellothere_1

    Hellothere_1

    Joined:
    Sep 18, 2018
    Posts:
    32
    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.
     
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,379
    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.
     
  4. koirat

    koirat

    Joined:
    Jul 7, 2012
    Posts:
    2,008
    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<T>()
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,710
    Yes. :)

    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.
     
    havokentity and lordofduct like this.
  6. Neto_Kokku

    Neto_Kokku

    Joined:
    Feb 15, 2018
    Posts:
    1,751
    +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.
     
    havokentity and Kurt-Dekker like this.
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,710
    havokentity likes this.
  8. koirat

    koirat

    Joined:
    Jul 7, 2012
    Posts:
    2,008
    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.
     
    havokentity likes this.
  9. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,710
    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):

    https://pastebin.com/SuvBWCpJ

    Unity3D Singleton with Prefab used for predefined data:

    https://pastebin.com/cv1vtS6G

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

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,710
  11. Reshima

    Reshima

    Joined:
    Dec 10, 2012
    Posts:
    51
    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).

    Code (CSharp):
    1. // Singleton
    2. class NameSayer : MonoBehavior
    3. {
    4.     private void Start()
    5.     {
    6.         Debug.Log(PlayerData.Instance.PlayerName);
    7.     }
    8. }
    9.  
    10.  
    11. // Service Locator
    12. class NameSayer : MonoBehavior
    13. {
    14.     private void Start()
    15.     {
    16.         Debug.Log(ServiceLocator.Get<IPlayerData>().PlayerName);
    17.     }
    18. }
    19.  
    20.  
    21. // Factory (kind of, since this could already exist in the scene with no instantiation)
    22. class NameSayer : MonoBehavior
    23. {
    24.     private PlayerData _playerData;
    25.     public void Init(PlayerData playerData)
    26.     {
    27.         _playerData = playerData;
    28.     }
    29.     private void Start()
    30.     {
    31.         Debug.Log(_playerData.PlayerName);
    32.     }
    33. }
    34.  
    35.  
    36. // Dependency Injection (requires reflection, could have significant runtime perf costs)
    37. class NameSayer : MonoBehavior
    38. {
    39.     [Inject] protected PlayerData _playerData;
    40.     private void Start()
    41.     {
    42.         Debug.Log(_playerData.PlayerName);
    43.     }
    44. }
    45.  
    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 (https://unity.com/how-to/architect-game-code-scriptable-objects), 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.