Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Discussion Dependency Injection in Unity [Inversion of Control Container]

Discussion in 'General Discussion' started by TheNullReference, Apr 11, 2023.

  1. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    268
    Update Feb 6th, 2024:
    ____________________________________________________________________________

    "Dependency Injection" is a broad and confusing term, this thread is really discussing 2 different topics.

    1) We're discussing the merits of an IoC Container Framework like Zenject or VContainer in Unity.

    More on IoC Containers: https://www.sebaslab.com/ioc-container-unity-part-1/

    2) We're discussing Dependency Injection in general.

    "DI is just... data passed to an object at time of construction."

    Unity provides DI solutions based on this simple descriptor. Could it do a better job? or is it just fine as it is.

    One example given is Sisus's 'Init Args': https://assetstore.unity.com/packages/tools/utilities/init-args-200530

    Update May 2nd, 2023:
    ____________________________________________________________________________

    Unity shouldn't support Dependency Injection (beyond maybe introducing [SerializedInterface])
    VContainer or Extenject if you want to use it.

    Original:
    ____________________________________________________________________________

    There's very few options for using Dependency Injection in Unity. Building games using Dependency Injection is really fun, and is a good way to decouple code.

    It has the added benefit of your bugs showing up early (resolving the dependency graph) instead of happening some time down the line (after your game ships). It makes testing much easier as well, and replaces the need for:

    - Gameobject.Find()
    - GetComponent<T>()
    - Singleton Managers
    - Null Reference Exception for Gameobjects/Components.


    Scriptable Objects is not a long term solution to the issue, they're not ideal for dynamic object creation (you really want 1000 scriptable objects for every bullet?)

    Dependency Injection Container can handle that problem no issue.

    ECS is great, for the people who are dedicated enough to use it. However I predict the vast majority of Unity Developers will not use it.

    Does anyone else want a framework that makes S.O.L.I.D possible? I'm hoping one of Unity's 3500 engineers sees this.

    https://www.sebaslab.com/ioc-container-unity-part-1/
     
    Last edited: Feb 6, 2024
    Simon2048 and Conor_Envision like this.
  2. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,431
    Having spent entirely too much time on these forums I'm of the opinion that this will be the same for people who use or are considering to use dependency injection. There have been discussions in the Scripting subforum concerning DI and the people who I thought were most likely to use it are the people who aren't using it.

    https://forum.unity.com/threads/sin...ndency-injections-oh-my.1413726/#post-8884461
     
  3. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,621
    > There's very few options for using Dependency Injection in Unity.

    We had MDADigital who has a strong advocate of it, until he got banned.

    At the moment I'd recommend to avoid dependency injection and keep things simple. If you REALLY love it, you're free to develop and use your own framework.

    On top of that SOLID is not a principle I'd recommend to strongly adhere to, as there are plausible scenarios where it an make your codebase less readable by spanning too many entities. KISS is much more reliable in comparison. So I would not be interested in unity trying to strictly follow SOLID.

    The problem with dependency injection is that as they say "it is a 50 dollar name for 5 cent idea".
    For example, if you take a look here:
    https://en.wikipedia.org/wiki/Dependency_injection
    "Construct injection" is a thing I'd expect any program to do intuitively, same goes for "Setter Injection".

    Now, "Assembly" example is actually a bad thing, as you're passing arguments as string, and by doing that you lose compile time checking.

    For the record, C# gamepad example is the very definition of inheritance bloat, and I'd not recommend it.

    ----

    So rather than promoting dependence injection, which is also somewhat vaguely defined, it would be better if you specified which functions you'd want to be added. In this case it'll be possible to actually discuss something.
     
    drawcode, Rewaken, xVergilx and 2 others like this.
  4. More than enough. There is no need Unity spending valuable money and time on this.
    And that's the problem. I imagine it is fun for you, but it is not good for game development.
    Unity should propagate techniques which aren't necessarily fun, but they are actually good for game development.
     
    Mnemotic, sohamxbr, Kreshi and 2 others like this.
  5. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    268
    -
     
    Last edited: Apr 18, 2023
  6. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,633
    That may well be the case, but it's not due to a lack of dependency injection in particular, it's due to a lack of software design experience in general. If you're thinking about that stuff there are multiple decent approaches, of which DI may well be one.

    The implication that SOLID is impossible in Unity without a DI framework, or that using SOs to configure bullets requires thousands of them, make me wonder if there's a misunderstanding somewhere?

    If that's what works for your team then go for it. In most cases, though, teams prefer configuration to happen outside of code, and the flexibility to swap out components rather than relying on a programmer (not everyone making a game is one!) to come along and change that line of code.

    DI is useful, but it's no more The One Solution to Rule Them All than ScriptableObjects are.
     
  7. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,621
    There's a huge problem with your example. It looks like this

    Code (CSharp):
    1. public class Foo : MonoBehaviour
    2. {
    3.     [BlackMagicVoodoo]
    4.     private Dependency myDependency
    5.  
    6. }
    What does the attribute DO? Where does it happen? What is its purpose?

    The purpose of attributes is to attach metadata to fields, and when metadata vastly alters program behavior and overwrites variable initialization, I'd say you're breaking both KISS and Principle of Least Astonishment.

    In real-life scenario your second example would look like this:
    Code (csharp):
    1.  
    2. public class Bar: MonoBehaviour{
    3.     [SerializeField]Dependency dependency;
    4.     void Awake(){
    5.         if (!dependency){
    6.             throw new System.ArgumentNullException($"Dependency not set in {gameObject} on {this}");
    7.             enabled = false;//optional
    8.         }
    9.     }
    10. }
    11.  
    It is not your program's job to recover from invalid state. If the dependency is not set, but is required, then the program should throw.

    Additionally, it is not a good idea to provide setters unless they're REQUIRED. That's according to YAGNI.

    Likewise, in Unity engine, OnEnable and Update methods are usually private. Because they're are meant to be called only by the engine and not other components.

    ---

    Regarding this part:
    Normally you're supposed to change zero lines of code in this scenario. Unity provides facilities for abstracting away input devices, so you'd be using input actiosn while being unaware where the input is originating from.
     
    Saniell and drawcode like this.
  8. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    6,395
    I would argue that specifically for less experienced developers, DI will make programming in Unity actually more confusing due to the indirection and because of possible DI overuse.

    DI encourages throwing dependencies into a class because it's just so damn easy to do. Similarly, it discourages thinking about dependencies in the first place, where they come from, and when and how, and what that does to your software architecture. And what about lazy initialization? Not every dependency needs to be resolved right away, or only in some conditions (editor vs playmode, target platform). It is sometimes okay or indicative of a given state when some dependency is and remains null.

    In the "wrong hands", DI can quickly lead to a mess of highly coupled components that inject themselves into each other and plenty of issues that are hard to understand because of the indirection when resolving dependencies "magically".
     
  9. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    DI also has negative impact on loading speed. The more classes you have - the slower it gets over time.
    Which definitely not a good idea on a mobile. This can skyrocket to 30s of waiting easily on medium-large project which DI is usually self-promotes organizing towards.

    Not many people know, but Unity already uses service location.
    Use OnValidate instead of Awake for extra checks & component fetching. And you've got yourself free hierarchy / prefab based component location which is more robust and more transparent that what DI does. With 0 extra cost on top of what Unity already does during initialization. Plus fully automatic, so no dragging & dropping is required if prefabs are built correctly.

    Everything performed in OnValidate is performed only during editor time. Which also allows to figure out what components are missing on what objects fairly easily without running game on the target device.

    Expand editor - and you've got a pretty robust tool to handle complex checks & dependency management with an ability to show what is wrong to the game designer. Transparent, easy to use, and easy to understand.

    If you want "real" usable DI - use Entities instead. DOD allows to execute logic only when specific components are present, processing data pretty much across entire application without black magic of injects involved. Plus, performance.
     
    Saniell, drawcode and Ryiah like this.
  10. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,813
    Question to OP, are you working with a team of programmers on a project other than small?

    I feel like proposed solution for DI will introduce a mess to the project, where multiple devs start messing around with DIs all over the place.

    Assemblies purpose is for decoupling, not DIs.
     
    drawcode likes this.
  11. koirat

    koirat

    Joined:
    Jul 7, 2012
    Posts:
    2,083
    DI is great for services dependencies, but not so great for GameObjects instantiation.
     
    TheNullReference and Murgilod like this.
  12. BIGTIMEMASTER

    BIGTIMEMASTER

    Joined:
    Jun 1, 2017
    Posts:
    5,181
    Why is it that ECS requires dedication to use, but DI does not?

    So far in my journey as a solo-developer, I've found ECS to be the simplest paradigm to follow because it seems to standardize code such that entire project can follow same patterns over and over without many (if any) edge case caveats.
     
    UniqueCode and The_Island like this.
  13. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,813
    My understanding based on ECS experience is, that ECS constraints enforce design and data organization.
    While in case of DI, you have more freedom, which opens higher opportunity to the mess in the design.
     
  14. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    268
    The attribute marks the method, field or method to be injected by the IoC container during initialization using reflection based on a binding contract. It marks the field as injectable in simpler terms. If there's no IoC container to inject the field, then nothing will happen.

    The advantage of this is when all the dependencies are created at the composition root it's a lot easier to share them among users. But I understand there's limitations to this.

    what if your GameObject is a prefab, or loaded in an additive scene? [SeriaizedField] won't work in this case for mono-behaviour references.

    I agree, but the nature of the architecture means you're not going to know when it's going to throw. You might develop a system and 6 months later break it with some other change, it doesn't get picked up in testing, gets published and then 5% of users hit an edge case where the game crashes. This is probably the most common bug in large unity projects.

    [/QUOTE]

    My example didn't explain my point very well and for that I apologize. What I meant to say is I could change the input logic based on the same input, or change how the players health is handler, or how the camera should behave.

    I agree, I like it for its flexibility which is exactly why it can get out of hand.


    This can be alleviated with sub containers to ensure you're only loading the dependencies you need for the immediate task, it's still slow though I agree. Although some frameworks are using source generators to skip reflection.

    I think that's a great summary, and for the record I think ECS is the best framework for game design, but my Velocity is so damn slow using DOTS, plus I'm pretty sure you have to resort to OOP for some parts of the game anyway.
     
  15. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    268
    I've changed my opinion somewhat after reading this:

    https://www.sebaslab.com/ioc-container-unity-part-1/

    They built the first IoC Container for Unity in 2012, which was the basis for Zenject and many other IoC Frameworks. 10 years later in 2022 they say do not use IoC Containers and they've fully switched to their own version of ECS.

    If the guy who brought IoC to Unity is telling us not to use it, then I should probably listen. I will check out their ECS framework as I find DOTS too difficult to work with.
     
    Last edited: Apr 12, 2023
    xVergilx, drawcode and The_Island like this.
  16. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,813
    What do you mean by Velocity in this context?

    Depends on the design. You can limit the need of OOP design to handful of scriprlts, when operate with DOTS.

    There are multiple ECS frame works.
    But usage of them alone may have low value in comparison. Entitias for Unity is probably really good ECS framework, before DOTS was introduced. It just to be paid.
    But you loose an opportunity of using jobs and burst in meaningful way, withouth putting much effort in the design. As this last two are critical before ECS, to get best performance.
     
    DragonCoder likes this.
  17. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,431
    Are you referring to just the ECS aspect of DOTS? Or are you referring to the entirety of it? And which releases have you tried out? Because I remember back when DOTS was first showcased it was a much more verbose framework.
     
    Last edited: Apr 12, 2023
    Antypodish likes this.
  18. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,621
    I would argue that moving initialization into attribute breaks principle of least astonishment.

    It will work when the reference point to within the prefab or within the scene.

    Normally your "dependency" will be pointing at another object within the same prefab (i.e. it is a spawnable object), or in the scene (it is a scene object).
    If the dependency is not an object that can be stored on the prefab or within the scene, the dependency becomes a scriptable object asset.
    In addition we have lazy initialization, when it is necessary.

    It will throw when you make a mistake in configuration. If you need extra validation, there's OnValidate method brought up, I think, by xVergilx. The whole point of such system is to make sure it doesn't break 6 months later.

    A dependency injection with a string based config, on other hand, can absolutely break 6 months later in unchanged configuration if someone rewrite configs which do not go through compile check.

    Change of input should still be handled through action map.
    Change of camera begs the question - do you need extreme flexibility in the first place?
     
    xVergilx likes this.
  19. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    268
    It's funny as I just realized that [SerializedField] is itself an attirbute that allows your to inject dependencies into an object.

    The only difference between [Serializedfield] and [Inject] is how the dependencies are resolved.

    I find [Inject] more versatile as you can inject plain c# classes, not just monobehaviours and scriptableobjects.
     
    thomas-soba and SisusCo like this.
  20. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,621
    The difference is that SerializeField is well-defined in documentation and its behavior is known as part of unity API. Inject is not defined as an attribute and its behavior is not known.

    Additionally, SerializeField does not vastly alter behavior of a field. Your description of Inject implies that it alters default initialization behavior to the point where it guarantees that fields will not be null which goes against expected behavior of the language in C#. This is comparable to overriding operator+ to return multiplication through side effect. That's breach of "principle of least astonishment".

    At least that's the way I see it.

    Regarding plain C# classes, unless I'm missing something, System.Serializable makes them appear in inspector, and then there's SerializeReference, which, IIRC was recommended at some point either by DragonCoder or by spiney199
     
  21. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,621
    No. I would argue that the point is an attribute shouldn't do anything unusual behind the scene. I would also argue that the name of the attribute is non-descriptive. Inject what? Where? How? Why?

    An attribute is supposed to be "metadata", something attached to the type of the field. In case of unity, there's extra data for the serializer. Similar thing happens with data configuration for C# marshaller.

    With Inject, the intent and functionality is not obvious.

    [SerializeReference]
    says hi.
    https://docs.unity3d.com/ScriptReference/SerializeReference.html

     
  22. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,633
    This stuff is only "BlackMagicVoodoo" until you take the time to get familiar with it... just like the voodoo of Unity calling my private Update() methods, etc.

    If you've decided to use DI and imported a framework and are using it, then it's Inject attribute should definitely be defined and known at that point! ;)

    Listen? Sure. Follow them blindly..? I hope not.

    This whole thing makes me think of The Law of the Hammer, i.e. "If the only tool you have is a hammer, it is tempting to treat everything as if it were a nail" - Abraham Maslow. Learn a bunch of approaches, then pick the best one for each job on a case by case basis. Familiarity is a valid benefit, but it shouldn't be the sole decider.
     
    TimGS, SisusCo, NotaNaN and 2 others like this.
  23. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Or you could write that same logic in OnValidate, no dragging required. No black-box injects required.

    Clean, obvious, and easy to swap without the need to dig into bindings that someone accidentaly messed up later down the line. Unity already has what you're asking for without overburdening Awake's or using reflection.

    I mean, I get it. Business apps doesn't care about performance.
    Business devs like to throw all kinds of weird patterns into game dev to then figure out they:
    - Do not work;
    - Work but actually make things worse to maintain in the long run;
    - Work but ruin the performance;
    - Rage post on the forum;

    Instead of just learning the engine they attempt [and fail] to use.

    And regarding non-reflection binding - I'm yet to see fast enough injections that does not rely upon reflection.
     
    Last edited: Apr 12, 2023
    christh and cy_unity_dev like this.
  24. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,813
    It seems the problem here is, lack of the design ahead.

    Unity for prototyping is great indeed. But shuving exact prototype into production should be no go. That regardless of paradigm used.
    Created assets should be structured carefully, so no entering into spagetti and circural reference mess.

    Later refactoring may be much easier and safer, as it most likely will happen at some point into production.
     
  25. BIGTIMEMASTER

    BIGTIMEMASTER

    Joined:
    Jun 1, 2017
    Posts:
    5,181
    I found ECS to be slow and require a lot of boilerplate up front at first.

    But once the game is more than like 5% complete, I find that "make it up as you go" OOP just falls apart. Production speed decreases rapidly because in order for anything to be extensible it has to go through lots of abstraction processes and the exact process can be different for every little thing. Bug hunting becomes a nightmare because each system is made in a unique way, so the first thing you have to do is familiarize how the bugged system works.

    The major thing that pushed me to ECS though was that I was seeking standardization. Working on a team with other programmers, I think it's bad if each person just carves out little fiefdoms and does things their own way. You end up with a project that nobody understands and rather than team boosting each other, they instead become dependent on each other. As in, one person cannot easily debug a problem if another person designed it using a unique paradigm.

    That was the major attraction about ECS because it has simple rules for how data flows from one class to another. Then if I find a bug while playtesting and its in code that somebody else wrote, I dont face down time to have to reverse engineer their stuff - I know how it all works.

    What I've enjoyed about ECS so far is that it seems to build an assembly line such that the further you get into project, the more speed boost you get. Lots of reuse across systems and also since you are employing the same patterns over and over, it can become second nature how to link things together, so then you only have to think about how systems work - not how they communicate.

    Anyway, that's my spiel about ECS not because it's the exact subject here, but only to add a counter point that maybe it does indeed require dedication, but I think its less dedication (in the long run) than fighting what I call a make-it-up-as-you-go design.


    note that when first prototyping some new system I often do just put code whereever is easiest to work with immediately, and sometimes that means a more OOP way, but as soon as I verified how it works I categorize it to systems/components using ECS style paradigm. So its not like can only choose one tool and use it 100%.
     
  26. andyz

    andyz

    Joined:
    Jan 5, 2010
    Posts:
    2,285
  27. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    268
    How do you handle things like UI, Animation, Sound and possibly working with 3rd party services database, user logins etc? I assume there's some parts that need to be OOP
     
  28. BIGTIMEMASTER

    BIGTIMEMASTER

    Joined:
    Jun 1, 2017
    Posts:
    5,181
    Well, first thing I should mention is that 99% of my coding has been done in unreal, and most of that using visual scripting. So I am not doing "real" ECS but I take the principles of it as far as I can.

    Some things are easier to do in OOP way simply because the engine is setup that way. For instance, if I am working on a team and I need to have some system isolated from others to prevent merge conflicts (thats more of a visual scripting issue because they are binary assets), then I put that system into an component and it can attach to some actor/gameobject.

    So at a low level that isn't accomplishing anything that ECS is meant to, but at high level the same principles of how systems and components communicate can still be accomplished.

    For UI I have been able to run it all the same as most other code. Basically a system just listens for events and then feeds data into widgets. Same thing for input - for instance Unreal has several ways to handle input, but I only read input in a single place and then that directs it whereever it needs to go via events.

    Sound gets a big benefit from ECS architecture. Before I just had calls for SFX to happen strewn all throughout code. A complete nightmare to solve any issues with that. Now I just make systems like "Player SFX System", "Enemy SFX System", "Ambient SFX System". Of course you can make systems smaller or broader as you need, its totally arbitrary. Usually I try to keep them as large as possible and only split up if really necessary.

    These systems simply listen for events. So there may be a "projectiles system" and whenever a projectile is fired, it puts out an event. The SFX systems may be listening for that, and then they can react.

    So the major benefit here is that, lets say some months from now I notice some bugs related to SFX when a missile fires... I just open the system associated with that and I can be sure that they problem will be in there. I dont have to think about a nearly infinite number of ways a missile game object might be interacting with other things, and if any other class and have to jump throughout codebase like a detective, wasting all my precious brain juices.

    I keep all of the events in an abstract, globally accessible class. I guess you might call that a singleton? In unreal its either the game mode or play controller - I am certain unity has some equivalent. Basically any class can easily find this global class and then request to call one of its events. Then any system anywhere is going to receive that, but because all any class knows about is this one singleton, no systems know about each other. So systems can be added or dropped and its never going to break any code.

    One area where I mix in some OOP is with animations. There is a lot of animation focused features that you can only get if you work within specific animation focused classes, and those are associated with an actor (aka gameobject). So it is just simpler to use that, though as much as possible I try to have my systems pump data and events into the animation handling class. The reason I prefer it that way is so that if I have a bug or I need to refamiliarize how things work with the animations, I dont want to have to jump around 50 places. I want to view one place that was easy to find with text search and be done.

    I dont know a lot about 3rd party databases - i looked briefly at SQLite but ended up not needing it. But I dont expect that would be any different - just a system which has the responsibility to get/send data to and from the database, right?

    I probably use a lot of terminology wierdly- when I saw OOP, what I mean is that both logic and state (e.g. logic = code that calculates stuff, and state = variables) are housed within a class and that class it mostly responsible for itself. Then if classes need to interact in some way, thats where my problems occur. Becomes too complicated fast. So I like a more top down approach where I can just direct what I want to happen and I can be sure that if there is something happening with, say, Missiles, I will see everything about that in the Missiles System. If it happens that having a bit of code actually happen in some gameobject is easier, I ofcourse just do that, its not the end of the world to break protocol occassionally.

    An example is in an enemy AI system I'm working on. 90% of the code happens within two systems: Enemy Lifespan System and Enemy Behavior System.

    But for each enemy, I use a few timers that makes them change behavior at different rates. For instance, every 3 seconds I just flip a bool and enemies that rush player use that to alternate which side they flank around.

    In a System, I'd need an array or map (aka dictionary) to associate each enemy actor/gameobject with that bool, or more like, a struct of various data. That gets a bit unwiedly, so just for that timer I let it run in the enemy class, that way each instance can hold a bit of unique state and I dont have to do unwieldy data management so much. This is just convenience though and I think the wonkiness is mostly an issue of visual scripting - it's just not great for maps, arrays, and struct stuff.

    So, TLDR: Yes I still use a bit of OOP - basically whenever it is more convenient to do so, and that is usually when not using OOP means that I wouldn't be able to use some feature of the engine easily.

    I think this system makes most sense to me, because my young adult life was spent in the miitary. I think this has a lot in common with military communication methods. Basically, i dont want for privates in the field to make decisions. They should report what is happening, and then somebody in central command who is listening to all the goings-ons will tell the soldiers in the field what to do. That's pretty much the gist of it really. If soldier A bumps into soldier B, I never want for them to make a decision what to do. They should only report that a bump has occured, and then the Commander of Bumps is going to decide what happens next.

    For me as the programmer, this seems to make it simply a lot easier to read the code and know how it works with the least amount of effort.
     
    Last edited: Apr 13, 2023
  29. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    268
    I see what you mean. I guess the ECS you're talking about isn't Unity DOTS ECS. Seems like more of a observer pattern architecture.

    I think I've had a break through with IoC Containers. I was reading through the VContainer docs and was confused about this line "It's recommended to inject MonoBehaviour instead of into MonoBehaviour". Took me while to realise that you use monobehaviour components purely as Views.

    Here's an example of my character animation handler:

    Code (CSharp):
    1. public class CharacterAnimatorHandler : ILateTickable
    2.     {
    3.         private readonly Animator _animator;
    4.         private readonly CharacterMoveData _moveData;
    5.         private readonly CharacterAnimatorData _animData;
    6.        
    7.         private float _animatorSpeed = 0.0f;
    8.         private Vector3 lastPos = Vector3.zero;
    9.  
    10.         [Inject]
    11.         public CharacterAnimatorHandler(Animator animator, CharacterMoveData moveData, CharacterAnimatorData animData)
    12.         {
    13.             _animator = animator;
    14.             _moveData = moveData;
    15.             _animData = animData;
    16.         }
    17.  
    18.         public void LateTick()
    19.         {
    20.             var playerSpeed = _moveData.velocity.magnitude;
    21.             lastPos = _moveData.position;
    22.             _animatorSpeed = Mathf.Lerp(_animatorSpeed,  playerSpeed , Time.deltaTime * _animData.animatorAcceleration);
    23.            
    24.             _animator.SetFloat("MovementSpeed", _animatorSpeed);
    25.  
    26.             var trans = _animator.transform;
    27.            
    28.             trans.position = _moveData.position;
    29.            
    30.             // Rotate the y axis about the player direction
    31.             var angle = Mathf.Atan2( _moveData.velocity.x, _moveData.velocity.z) * Mathf.Rad2Deg;
    32.             if(_moveData.velocity.magnitude > 0.1f)
    33.                 trans.rotation = Quaternion.Slerp(trans.rotation, Quaternion.AngleAxis(angle, Vector3.up),
    34.                     Time.deltaTime * _animData.rotationSpeed);
    35.            
    36.             _animData.position = trans.position;
    37.             _animData.rotation = trans.rotation;
    38.             _animData.velocity = _moveData.velocity;
    39.         }
    40.     }
    What I really like about this, is unlike monobehaviour, It uses a constructor, I can abstract if I need to, all my fields are readonly, I'm only implementing the player loop methods I actually need and the lifetime of the handler is guaranteed. It's never going to disappear unexpectedly.

    Here is the registration builder / composition root:

    Code (CSharp):
    1. public class CharacterInstaller : MonoInstaller
    2.     {
    3.         [SerializeField] private Transform viewTransform;
    4.         [SerializeField] private Animator animator;
    5.  
    6.         [SerializeField]private CharacterStatsData characterStatsData;
    7.        
    8.         public override void Install(IContainerBuilder builder)
    9.         {
    10.             // Register Data.
    11.             builder.RegisterInstance(characterStatsData);
    12.             builder.Register<CharacterInputData>(Lifetime.Scoped);
    13.             builder.Register<CharacterMoveData>(Lifetime.Scoped);
    14.             builder.Register<CharacterAnimatorData>(Lifetime.Scoped);
    15.            
    16.             // Register view components.
    17.             builder.RegisterComponent<Transform>(viewTransform);
    18.             builder.RegisterComponent<Animator>(animator);
    19.            
    20.             // Register Entry Points.
    21.             builder.RegisterEntryPoint<CharacterInputHandler>(Lifetime.Scoped);
    22.             builder.RegisterEntryPoint<CharacterMoveHandler>(Lifetime.Scoped);
    23.             builder.RegisterEntryPoint<CharacterViewHandler>(Lifetime.Scoped)
    24.                 .WithParameter(typeof(Transform), viewTransform);
    25.             builder.RegisterEntryPoint<CharacterAnimatorHandler>(Lifetime.Scoped)
    26.                 .WithParameter(typeof(Animation), animator);
    27.         }
    28.     }
    This seems quite readable to me and breaks down the data, views and systems distinctly.

    An obvious issue is now we've lost Editor serialization, but we can afford to use monobehaviour proxy just to access data in our scope:

    Code (CSharp):
    1.     public class CharacterProxy : MonoBehaviour
    2.     {
    3.         [SerializeReference][Inject] private CharacterStatsData stats;
    4.         [SerializeReference][Inject] private CharacterInputData input;
    5.         [SerializeReference][Inject] private CharacterMoveData moveData;
    6.         [SerializeReference][Inject] private CharacterAnimatorData animData;
    7.     }
    Despite the clean structure, the system is still fully compatible with regular monobehaviours if I want to write prototype code first.
     
  30. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    Coming from over a decade of Unity I would not want to have to work in your codebase, Why are you doing all this complex stuff for no good reason? Unity components are not purely view. They are core to rapid application development, remove the need for DI and allows you to watch values you need to monitor in runtime. This is not a MVC or MVVM system. It is a frame dependent component based architecture. It is not a windows console nor a windows form app. Good luck bringing any artists on to work with you when you obfuscate everything they require to do their work.
     
  31. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    Code (CSharp):
    1. public Animator animator;
    2. public CapsuleCollider coll;
    3. public Rigidbody rb;
    4. //various stats types
    5. public int idx;
    6. public float statsF;
    7. public bool statsB;
    8. public Vector3 statsV3;
    9. public Quaternion statsRot;
    10. //move
    11. public float moveSpeed;
    12. public Vector3 moveDir;
    13. public bool isGrounded;
    14. public bool jump;
    15. //animator states
    16. public bool[] animStates;
    17. public string[] animStateNames;
    18. public string currAnimStateName;
    19.  
    20. void Update () {
    21.      //move code via input handling goes here
    22.      //jump handling isGrounded check and jump input handling here
    23. }
    24.  
    25. public void SetAnimatorState (string _stateName) {
    26.      for (int i = 0; i < animStateNames.Length; i++) {
    27.           if (animStateNames[i] == _stateName) {
    28.                animator.SetBool(_stateName, true);
    29.                animStates[i] = true;
    30.                currAnimStateName = _stateName;
    31.           } else {
    32.                animStates[i] = false;
    33.                animator.SetBool(animStateNames[i], false);
    34.           }
    35.      }
    36. }
    37.  
    This is all you need in one handy component. Know your animator state, control speed and direction, store stats. Drop on 1 or 1000. If you have many store a reference in a manager to them. If wanting to procedurally generate then Instantiate(AnimatorComponentObj, wantedParentTransform). Hire an artist to help you and they will instantly know what is going on. Don't make it harder than it should be. Don't throw away for free's like Update, FixedUpdate and LateUpdate. These are gonna roll regardless and they work with the Unity API. You gotta jackhammer your process in sideways.
     
    cy_unity_dev likes this.
  32. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,742
    How comes you have such a big fear of things disappearing unexpectedly?
    I find it is not too hard to categorize into permanent game objects, scene-bound game objects and system bound game objects (where e.g. the character is a system, every enemy is a system, the enemy spawners are a system, etc.).
    As long as you do not wildly add cross references between those categories and systems, there is not even a need to often have "if X != null", let alone needing to set up whole mechanics like DI to prevent issues that a solid, classic architecture would not have anyways.
     
    mahdi_jeddi likes this.
  33. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,431
    Enterprise software development teaches you to pay attention to object lifetime. DI (dependency injection) isn't just about making it easier to access dependencies. It's about managing the lifetime of those dependencies too.
     
    TheNullReference likes this.
  34. CodeKiwi

    CodeKiwi

    Joined:
    Oct 27, 2016
    Posts:
    119
    I’m quite a fan of DI, probably because I have more of a business programming background. I mainly use it for Unit testing. I also find it helps with the “Single Responsibility Principle”. If I’m Unit testing then I really need to use DI, and if I’m using DI then I need a DI container. Personally I find DI on its own kind of useless and it really requires a DI Container.

    I still use the non-unity version of Zenject, mainly for services and not gameplay. It might be discontinued but it’s probably fine as it is. It was used in a commercial game (Pokemon go), it’s stable and it’s not like it needs to continually have new features added. I find it a lot easier to maintain and modify but I do lose the ability to create quick prototypes. If I’m making a POC I normally use standard Unity.

    Can’t see Unity adding support for it. It only really makes sense in a few special cases or due to personal preference.
     
    Last edited: Apr 14, 2023
  35. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    268
    -
     
    Last edited: May 4, 2023
    tmonestudio likes this.
  36. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,621
    As mentioned, in unity C# is 2nd class citizen and this sort of thing is normal. What's more it is possible to reduce the application even further.

    This example kinda doesn't explain why you'd need Di and why does the issue with null checking arise.

    Typically when promoting some practice, if the practice has a merit, it is possible to reduce the situation where this practice is needed to something like a paragraph of text. If you cannot do that, that means the merit of your practice is unclear and might not exist.

    In your example with ILateTickable you're trying to apply normal C# coding practice, which is not the right thing to do. C# is second class citizen and unity is using its dialect. Thought it being a dialect is not as extreme as in unreal, for example, where C++ relies heavily on non-standard macros and engine provides garbage collection, while disabling exceptions.

    After some experience with unity, the first urge a programmer would feel when seeing your example would be to delete ILateTickable, MonoInstaller, remove Install, CharacterAnimatorHandler, make everything private and non-callable from outside and reduce entire codebase to 5 or 6 flat classes with no inheritance hierarchies. Also note, that LateTick is a bad idea, because that's Unreal terminology and there's already LateUpdate, meaning you're trying to sidestep methods unity offers without a good reason.

    Like I said, it would be best to focus on specific small use cases, without vague "on a big codebase".
    For example, in the past someone brought up idea of "initializer method" which is called on instantiated object before it is made active and added to the scene. Something like this makes sense.
     
    Luxxuor and DragonCoder like this.
  37. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    268
    The obvious null check is when a multiplayer client disconnects and their instance is destroyed.
    Anything referencing the client gameobject of the local instant will throw, so you would need to write recovery logic in your classes if this happened.

    There can also be issues with asynchronous scene loading via additive scenes. Unity's service locator methods don't work across scenes, so you will need to additively load the scene, place an object in the desired scene, then resolve it as a dependency via FindObjectOfType<>, or set the additively loaded scene to the main scene (this is required if you wanted use culling data from the additively loaded scene, for example). Sync that across multiple clients and you have to rely on coroutines or promises to ensure all objects are in the scene as they need to be.

    Although I don't need to give explicit examples, null is universally known to be an issue in software design: https://hackernoon.com/null-the-billion-dollar-mistake-8t5z32d6

    That's fair. This is from the VContainer documentation:

    "In Unity, MonoBehaviour is the entry point for our C# code. On the other hand, MonoBehaviour is also a "View component".

    In modern application design, "separation of domain logic and presentation layer (View component)" is important.

    It is against this that MonoBehaviour has many roles (event handling, control flow, domain logic calls, etc.) in addition to its behavior as a View.

    One of the purposes of DI is IoC (Inversion of Control). With DI containers, we can make pure C# classes the entry point (not MonoBehaviour). This means that the control flow and other domain logic can be separated from the function of MonoBehaviour as a view component.

    View components are dynamically created / destroyed at run time, while all "features" such as control flow and domain logic have a more stable lifespan.

    Generally speaking, it's a good idea to make the View layer stateless and separate it from control flow and data management.

    This is the main reason I like DI."
    I would add that it also make integration testing easier, which becomes more useful as your project scales.

    To be fair, ILateTickable just comes from the framework I'm using. It's very aware that it's sitting on top of Unity, I think this is the reason they used it. ILateTickable is a system method, LateUpdate is a view method. ILateTickable runs before LateUpdate as well, so there's some argument for them being different.

    That's true, it could be smooshed together into a Player class that handles everything (despite violating single responsibility) and in the example I gave I'd agree that would actually be the right way to do it.

    I'm going overboard with the DI just to get a feel for it. I'm not sure how I can describe a big codebase in a way that isn't summarised, it's literally thousands of classes and a couple hundred scenes at this point.
     
    Last edited: Apr 14, 2023
  38. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,621
    I'll be blunt. I consider goals listed by this framework a waste of time.

    At the beginning of programming career some people go through the phase, where they want to make things beautiful. Then they spend inordinate amount of time designing "elegant" class hierarchies and reworking them again and again, except that none of this affects the actual functionality of the program. That creates illusion of useful work. You keep rearranging the classes, and behavior of the program does not change.

    The description you gave is an example of this very behavior. They're trying to religiously follow SOLID without questioning if this is even necessary. As far as I know, It isn't.

    MonoBehaviors are not views. Unity framework is not MVC. So, "LateUpdate" is not a "view method". Additionally mechanisms like ILateTickable are likely to be wasting CPU cycles doing nothing useful. Unity already performs caching for methods like LateUpdate and does not call them if those are not present.

    Sure, playing with new approach to learn and see how you can use it is fine, just always keep in mind that you might need to throw it into garbage bin in production, plus it is necessary to spot a "honeymoon phase" if you enter it by accident.

    Regarding the situation, no offense intended, but there's a popular saying: "If you can't explain it simply, you don't understand it well enough.”. So, yes, normally I'd expect you to be able to describe the problem you're facing in concise way. If you cannot simplify the problem, it may be a good idea to analyze it, ponder it and think about it for a while. You may reach some important conclusions. Also, having thousands classes may be an indication of a problem. Meaning it may indicate that the software you're working on is being overengineering, which can drive up maintenance cost up in the long term.

    Anyway, it is simply my opinion. This sort of discussions tend to last many pages, at this moment I don't have much else to add to it.
     
    christh, SisusCo, Luxxuor and 2 others like this.
  39. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,633
    What is "the average unity architecture"?

    Keep in mind that professionals can't ganerally share their work source online, so what you see online doesn't necessarily represent how people do larger projects.
     
    stain2319 likes this.
  40. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    268
    Classic Unity architecture would be using Unity in the way it was designed since 2005, which inspired by Scott Bilas' 2002 GDC presentation on Data-Driven Game Object System.

    The usual way this manifests is that everything becomes a monobehaviour attached to a game object so things like your data, logic, events, event dispatcher, your 'thread' manager in the way of coroutines, your object lifetime and states (OnDestroy and OnDisable) as well as other meta systems like tags, layers and id via a name.

    I'm very thankful to this system as it made coding easy and approachable, and fun! I just wonder if there's a better way of doing things for certain applications.
     
    Last edited: Apr 14, 2023
  41. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,742
    Yeah well, then you have built in the need for null checks yourself. How about not doing that? :)
    Instead of simply destroying an instance which is something that does hardly convey any meaningful information at all, you have a proper "disconnection procedure"? Something that notifies everything that's dependent on the connection, that it has ended.
    Events (Unity's own or standard C#) are a possible tool for that. Have an event for "new player connected" and one for "player left". Then every class that needs to know this information can react accordingly.
    Only destroy objects once you are sure nobody needs them anymore.

    Yes it's true I have not worked on a really huge Unity project. Yet my thought wouldn't even have been "Unity is not designed for this". Instead I would support that everything you described can be done nicely modular with regular Unity functionality.
     
    Last edited: Apr 14, 2023
    Luxxuor and Antypodish like this.
  42. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,813
    Reading whole discussion, it feels like OP tries to over engineer many aspects of application development.

    Even tho explained by OP, why need Dl in every corner of the application, where Unity and developers provides cleaner solutions.

    The design feels like not thought about performance, but comes from none game related industry. Yet working on application for VR, where performance is critical for best user experience.

    We had few users of such approach in the past, defending their corporational approach of software development in game and performance critical applications. As the result the simple systems behaviours and design was taking ages. I don't remember their products to reach the day light in the end. (Maybe they did, I don't know). Long community members will surely know, what I am referring to.

    Why need for null checks at the runtime all over the place?
    Is the code base unstable and unpredictable? Is it lack of communication and the design between team members, source of even need for such approach? Is the application supporting modding at all, for needing of such?

    Like blindly keeping to one principle, discussing it, even others pointing out on alternative solutions, seems like every others points are countered.

    Is there even point for this further discussion? So far it starts showcasing more and more complexity of the software design. Or OP is awaiting for some white knight to arrive, to defend OP's points? I am really now lost, what is OP's goal here?
     
    ippdev, neginfinity and DragonCoder like this.
  43. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    268
    I wasn't suggesting not to use events, I was just putting out you need to add recovery procedures for null, if you had guarantee lifetime you wouldn't need extra code to handle such things. Null is a big issue in any application, not just Unity though.

    I hope it is possible. I've never worked on a team that made it a reality though, I haven't seen an example, nor have a seen a tutorial, blog or showcase of anyone achieving it. The best I've seen is splitting the project up into DLL's so that if one designer code wasn't good it wouldn't contaminate everyone else's work.

    I think a good example of this, is look at big companies that use unity. Microsoft built MRTK in Unity, it's completely different to what you'd see in a typical unity project.

    Acting as devil's advocate for DI is actually helping me think more deeply about it, I've gone away from this thread and refactored some things in my code just thinking about it more.

    I think I've just used this as a way to blurt out everything in my head in regards to DI. I don't have too many people in my life who want to talk about the pros and cons of IoC Containers for more than 0.3 seconds, apologies if I came across as annoying.
     
    SisusCo likes this.
  44. BIGTIMEMASTER

    BIGTIMEMASTER

    Joined:
    Jun 1, 2017
    Posts:
    5,181
    I think most people you'll talk to here are indie developers, many solo or tiny teams, and appropriately scoped games is their main focus. It's a necessarily different mindset if one or a few people are trying to make a game on a budget and schedule compared to large team making large, professional applications.

    Rethinking how the engine works is like anti-productivity. Better to just figure out the simplest way to use the engine to meet end goal and move forwards. Engine engineers can think about improving unity - that's entirely different profession.

    Just speaking for myself but that's the impression I have anyway.
     
    ippdev and TheNullReference like this.
  45. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    Here..Let me educate you on how Unity C# works.

    Unity, C#, Mono and .NET

    C# engineers are _initially_ going to be the wrong people for be programming games in general. They're trained in practices that work actively against how Unity uses C#.

    So they need to understand that game development is about making up new rules (breaking them) because the rules aren't designed for games.

    In the world of game development, it's all about optimizations because it has to run within a specific time limit in order to be interactive and enjoyable.

    This means breaking the rules.

    The earliest game developers (since the birth of the very first game) up to today, had to be about breaking rules because the hardware wasn't (and still isn't really) designed for games. So to make games you need to play outside the established set of rules. That means using things that aren't designed for something, and not using things that are designed for something (in a language and hardware context).

    This typically means that most optimizations are breaking the "rules".

    Of course to any game dev using Unity, we're not really aware we're breaking rules, we're just flexible enough to think well outside the box, because that's a normal every day mode of thinking for the humble game developer.

    At its core, Unity uses Mono. Mono is an implementation of the .NET framework. That by definition means it's not the same framework .NET developers may be used to. Even though it's supposed to behave the same as the framework they're used to, not all things are implemented the same way (if even at all).

    Unity took this a step further and changed a few rules about what to expect from C# via operator overloading -- using bool to check vs null, and overloading equals to check against a fake null being just two examples. This also broke newer C# features such as null propagating and coalescing operators. With constructors being forbidden, readonly fields become harder to set. And so on.

    Hell, Unity doesn't even use polymorphism to call Update() in MonoBehaviour derived classes. It feels a bit alien to not include "override" when creating an Update() function. But that's just how Unity works.

    So if anyone argues "But it works this way in .NET..." it's already irrelevant. There is a definitive distinction between "C# vs Unity C#”.Some random thoughts:

    1) If you want to stick with .NET code, then fine. Just keep it in DLLs. And create an API to let .NET code talk to Unity and vice versa. If they want to work within the Unity namespace, they must adhere to Unity's rules. No ifs, ands, or buts about that.

    2) You use an API in the way it's meant to be used. You don't try to bend the rules or work around it, then complain that the API doesn't work the way it should. Thinking you can write your own buttons for example and use physics raycasting instead of a Unity Button component hogties any system that uses the UI eventData looking for interactable bools, loses for free animation and colorBlock transitions from your toolkit. Using the OnClick event delegate is the exact same but faster as intercepting it in script. Real world testing has proven this even though it looks like they are the same. Unity does something under the hood with pooling and reassigning delegates on the fly.

    3) If upgrading Unity versions break your code or objects and force you to re- engineer it, then see point #1 about adhering to Unity's rules. Note, I am not talking about Unity bugs -- these are very valid reasons on skipping certain versions while upgrading. I am talking about how if you try to access internals via reflection, then your reflected code breaks because the internals changed, well guess what, that's on you for depending on internals to never change. (Note: This should be extremely unlikely now that Unity open sourced their C# code, but the point still stands). Setting up objects to be normalized with a uniform scale and Quaternion.Identity rotation and it’s coordinate system position at 0, 0, 0 will avoid serious headaches when Instantiating objects.

    4) Similar to #1 above, if they've ever worked with WPF, they should be familiar with MVVM. Or MVC. Keep the logic and UI code separate. Apply this line of thinking to Unity -- if you're going to get all fancy with your .NET code, keep it out of Unity code. Changing your code should not break Unity. Changing Unity UI should not break your code.

    5) As has already been said, game dev is very different from business dev. Unfortunately it's very hard to convey this point to folks, especially if they have little technical knowledge of the underlying issues. You have people with specialized skills and you don't try to put them on jobs they're not suited for. Even though architects and electricians work together to build a house, you don't expect an electrician to draw up blueprints for the house and expect the house to not collapse. Or architects to not die from electrocution. And so on.

    The irony in saying that games development "breaks all the rules" is that it can be tempting for a non-technical person to say, "Well isn't the opposite also true? Can't you break game dev rules then?"

    This isn't even a C# issue. There are similar arguments way back in the C++ days when avoiding the STL was a way to optimize your game. Avoiding the STL was a positively alien concept to business devs. Some things never change, I guess

    1. unity uses C# as a scripting language, not as C#, not as .NET
    2. as a scripting language, C# has some benefits, none of which come from .NET 3. unity is a constantly evolving game engine, getting better all the time
    4. unity is a game engine, not a corporate platform, like .NET
    5. using the latest releases of Unity has LOTS of advantages, few negatives
    6. moving up through Unity versions doesn't entail a lot of scripting changes
    7. moving up through Unity versions often bring benefits worth the effort
    8. Unity is better than it ever was, for most platforms, in 2018.1 onwards
    9. capable .NET programmers are perfectly equipped to move up versions

    I think the really important distinction to be made is that while you're writing code in C#, the majority of what the computer is doing isn't in the scripting runtime. I would point out that the scripting runtime sits on top of a game engine, and understanding what's going on in the game engine part of the system is really important. A dev might know C# really well, but that's only part of the picture.

    "to a man with a hammer everything looks like a nail".

    Whether you use IL2CPP or Mono or not, data must be marshalled to C++ since that is what the actual engine runs in. The engine will always run as C++

    Another thing of interest is how to allocate variables in a class. In most
    business .NET software, variables get declared inside functions and cleaned up automatically as they go out of scope. With a game, there is often some performance to be gained by declaring some variables at class level and re-using them throughout functions. It goes against modern scoping concerns, but it addresses memory and performance issues that are usually more important in games. If you need a reference then store it at Start. Do not ever get a reference in an Update loop.

    On Unity nulls
    if(gameObject!=null)
    A faster solution to a .NET dev is to use the following: if(!System.Object.ReferenceEquals(gameObject, null ))

    Keep in mind that those two lines of code do not actually do the same thing. If you destroy the GO, the top line will work as you expect, but the bottom line will not since it still references the container object. Unless you actively assign null to the variable, that won't work in a lot of cases. Unity uses a fake null object and overloads ==, which means that using reference equals does not return the same thing as a regular null check. A GameObject can pass a reference equals check, and still throw a null reference error on the very next line.

    Instantiate/AddComponent creates a C# object and the corresponding C++ object together. It registers the newly created GameObject/Component with the various systems Unity uses. (Incidentally calling new on a GameObject does the same thing too, don't be afraid to use new GameObject("SomeName") when the situation calls for it).

    New is used for creating regular vanilla C# objects. It goes ahead and calls the constructor.

    The thing is you can't use new to create a MonoBehaviour or to add Components to a GameObject. Unity will throw an error. Even if you do create your entire structure without GameObjects, you still need to use GameObjects and Components to interact with Unity's rendering system (and physics etc. too). Which means in most cases, you are better off just using components in the first place, rather then putting an extra layer in between you and the engine.

    Under the hood Unity reserves the MonoBehaviour constructor for its own purposes, and does stuff that isn't fully publicly documented hooking up internal engine stuff. There are other ways that probably could have been handled, but that's what Unity picked, so constructors are just out of bounds to us. Because of that they instead give us Awake() and Start(), but they really aren't the same thing for two notable reasons:

    1. You can't give them parameters.About the closest we can get is a static CreateBlah(...) method which calls Instantiate or AddComponent and then does whatever else we would want to do.

    2. Awake and Start are called at different times to a constructor. I believe that Awake is immediately after construction, so that one's pretty similar, but Start is called as a part of the next iteration of the engine loop. Understanding that engine loop and what it means for your objects and their lifecycles and when you can reliably access what things is pretty important, and fairly specific to a game engine (or similar real-time systems).
    This is of course only relevant for MonoBehaviour and friends. You can make plain C# classes and use their constructors and that's all groovy. You can use those classes from your MonoBehaviours, and you can have those classes reference MonoBehaviours to interact with the scene.

    That being said..the component I wrote for you is just dandy to start with and if you think it violates some enterprise coding rule then most of Unity's scripting examples are just as wrong. Could be that you are the one needs adjusting. I am doing fine over here and shipped over 40 projects to clients using these principles.
     
    yotiky, Meltdown, christh and 9 others like this.
  46. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,621
    You may want to switch to C++ in this case. It has no reference types, and has deterministic lifetimes. There is null, but only pointers can be null. And all types are value types.

    Actually, jokes aside non-deterministic lifetimes and nulls are flaws in the language design. People recognize that, and that's why in C# there was that recent push to make all types non-nullable by default and that's where string? types came from.

    Regarding your example:

    In this case the obvious solution is to reverse the way objects are controlled.

    Make your spawnable "multiplayer instance" control the player object. The player object is always there and does not get destroyed. When the "multiplayer instance" dies nothing happens. No null checks.

    So instead of "player grabs multiplayer instance and reads data from it" (null check), turn it into:
    "multiplayer instance grabs the player and commands it" (no null check).

    Or, if you REALLY want the player to spawn along the instance, and disappear without it, spawn player object within instance's "Awake()" and destroy it within instance's "OnDestroy()". Or spawn within "OnEnable()" and despawn within "OnDisable()".

    And here you go. Deterministic lifetimes. Sorta. Actually OnEnable/OnDisable pair is how you spawn/despawn dynamically created meshes and textures.

    Also check out this graph:
    https://docs.unity3d.com/Manual/ExecutionOrder.html

    It is extremely important. Probably the most important document in unity docs. It means that "OnEnable" cannot be called twice in a row. "OnDisable" cannot be called twice in a row. If you're within Update(), whatever's within OnEnable happened. If you're within OnDestroy, then OnDisable has been called. And so on.
     
  47. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,633
    That's like saying that the architecture of a house is that it's made of bricks. It's true, but it's only a part of the story.

    The way that Unity itself is architected, and the way that games made in Unity are architected, are related but different. Obviously the former influences the latter but there is still huge scope for people to do their own architecture.
     
    Ryiah likes this.
  48. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,742
    In my opinion null only shows that there is a bug. Am even applying that concept in large scale software in C++ (my day job). There we do not add intended nullptr checks either except for when we use shared_ptr as an alternative to std:: optional (since std:: optional<std::shared_ptr>> feels redundant).
     
  49. Murgilod

    Murgilod

    Joined:
    Nov 12, 2013
    Posts:
    10,249
    It is increasingly obvious that the problem here isn't a lack of native or acceptable third party DI frameworks, but DI being treated as a sort of panacea for overengineering pitfalls. Treating DI like that reveals reasonable project planning and architecture isn't being made use of more than anything else.
     
  50. PanthenEye

    PanthenEye

    Joined:
    Oct 14, 2013
    Posts:
    2,121
    Ultimately Unity implementing their own version of a DI framework wouldn't enable any new types of games to be made that are not currently possible, which is why I think this should not be considered officially. It's also not a universally applicable design pattern.
     
    TheNullReference and DragonCoder like this.