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

Discussion Dependency Injection in Unity - Vampire Survivors uses Zenject!

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

  1. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    222
    Maybe that's why I struggled the first time, I was trying to make everything a burstable, parallel unmanaged ISystem. I should learn to do it the simple way.

    Sorry I'm confused what you mean by this?
     
  2. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    Parameter injected for the .WithParameter(new GameObject("Enemies").transform);

    I'm assuming you're using it to parent newly created instances to the "Enemies".
    Unity can compute transform hierarchy in parallel if transforms are not attached to the same root object (in your case "Enemies");

    So if you parent transform to the same transform, Unity cannot ensure there is no race conditions while computing resulting hierarchy. Instead of scheduling multiple jobs to process those transforms in parallel, it schedules one bulky job which is slower on average (depending on hierarchy complexity). You can observe that in Profiler.

    This becomes even bigger problem when app domain multithreading is introduced to the project, since that would require all other jobs to wait for one bulky job.

    Tl;DR: More complex hierarchy -> slower it gets.
    Flat hierarchy (or no parent) -> faster.

    +This also removes the need to inject parent transform or parent / unparent pooled object (saves some ops).
     
    Last edited: Apr 20, 2023
  3. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    222
    Okay, the parent transform is optional (as well as the name). You're saying it would faster to just spawn them into the scene with no parent? Do you know if GameObject.Instantiate(prefab, parent) helps this issue? For now the number of spawns is around 10 so I'm happy to lose some performance in order to keep things tidy in the scene view, but something to look out for in the future.

    If my enemy count or bullet count exceeded 100 I'd most likely try to redo it in ECS.
     
  4. CodeKiwi

    CodeKiwi

    Joined:
    Oct 27, 2016
    Posts:
    119
    I just realized I wasn’t very clear about DI and testing in my original post. Normally when creating software tests you focus on the Unit tests, then the integration tests. In my original post I was only talking about Unit tests. Unit tests normally require DI and a mocking framework while integration tests don’t really need it.

    testing_pyramid2.png

    If I take a car as an example then a Unit test might be does the spark plug work. While an integration test might be does the engine works. A unit test shouldn’t require the dependencies e.g. I shouldn’t need to put a spark plug in an engine and turn on the engine to know if the spark plug works.

    Let’s say I want a Unit test for the following:.
    Code (CSharp):
    1.  public async Task PurchasePowerup()
    2. {
    3.     if (await purchaserService.Purchase("powerUp"))
    4.     {
    5.         game.PowerUpPurchased();
    6.     }
    7. }
    Some dependencies need to avoid triggering logic. For example: purchaserService.Purchase() shouldn’t try to charge me money when testing on device (would also softlock the test waiting for user input). Depending on the test I just want it to return true, false or maybe throw an exception when Purchase() is called.

    Most dependencies include other dependencies. To call game.PowerUpPurchased() I shouldn’t need to create an entire game. So I don't want to created the entire game in code or load a prefab of the game. Plus I still need some way for the test to know that game.PowerUpPurchased() was called.

    DI allows me to replace the game and purchaserService methods with my own logic. Normally this would be a mocking framework. Inheritance also works but it’s a bit messy (would work with MonoBehaviours).

    As a few others have already mentioned I was wrong about being unable to Unit test. I thought play mode tests might be a lot slower but they seem ok. Not really keen on inheriting from the MonoBehaviours for testing but it seems to work. DI frameworks also aren’t required for Unit testing.

    I normally hate making Unit tests so I don't use them that much. I’m tempted to try them again after seeing that GPT example.

    That's an excellent point, ECS definitely seems to be the best choice for gameplay.
     
  5. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    Yes, its also faster to process transforms [each frame] without parents.
    Only for the native pass / spawning part, not the internal processing.

    If you want visual arrangement in Scene View - use editor only tooling to do so.
    Better solution though, is to just use the search while in runtime.
    It works by name or by script type.
     
    Last edited: Apr 21, 2023
    SisusCo and TheNullReference like this.
  6. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    222
    It's a good tip which I didn't know about. Has anyone done any benchmarking to see the performance impact?
     
  7. Noisecrime

    Noisecrime

    Joined:
    Apr 7, 2010
    Posts:
    2,000
    Back in the distance past of Unity 5.4, they changed how transforms and hierarchy was organised internally to provide greater efficiency, though much of that comes at the expense of deep hierarchies being non-optimal, or anything other than keep dynamic objects ( i.e. transforms that change frequently ) at the root.

    Basically breadth not depth is always more efficient, but nothing beats fewer transforms. When you do need transforms try to split them into roots of dynamic vs static gameobjects etc.

    More guidance can be found here - spotlight-team-optimizing-the-hierarchy

    This video explains it really well and points out some other optimisations and has some benchmarking.
     
    Last edited: Apr 22, 2023
  8. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    222
    A small update.

    I've switch from VContainer to Extenject (for the 4th time).

    The reason I chose VContainer is because it's being actively maintained, is up to 5x faster at resolving dependencies, support source generators and has ECS integration.

    Despite that, Extenject just has too many useful features, and I find it's more 'Unity' Friendly with it's idea of SceneContext and GameObjectContext.

    I don't regret spending the last 2 weeks deep diving into VContainer, and I think I'm using IoC more 'correctly' after having to learn how to do everything with such a limited amount of features. There is just some fundamental differences in the framework that make VContainer hard to use and found myself extending the framework too much.

    As far the speed, it's a difference between 200ms and 800ms when the scene is first loaded. Faster isn't always better. Reflex IoC is faster than both VContainer and Extenject, but has even more limited features.

    It's just a shame that the guy maintaining Extenject has stopped working on the project.

    I should have a small prototype to show off in a week or so.
     
    SisusCo likes this.
  9. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,459
    Honestly, fear I have to say it would probably have been more beneficial for your overall mid and long term usage of Unity if you had spent those two weeks deep diving into DOTS.
     
    Antypodish and xVergilx like this.
  10. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,321
    As long as he's having fun and has no deadlines, why the heck not.
     
  11. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,509
    And if you're talking "mid and long term" then why do either when you can instead do both? Try them all, when and as time permits.

    That said, wishy-washy super high level banter aside, DI and DO approaches are solutions to different problems. If Simon's current focus is on the type of problems which DI solves, and if experience with that is also of benefit elsewhere, then spending time trying out different DI frameworks makes perfect sense.
     
    SisusCo and TheNullReference like this.
  12. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    222
    Wanted to post a small update of my progress. Here's what my character installer currently looks like:




    I am happy with the separation of concerns, I didn't set out to create an MVC pattern but things just kind of went that way.

    My next big question is how event driven should I make the application? You can see in this case I've used Extenject Signals to communicate events. What I like about this, is that the installer can subscribe methods to events. So my subscribers have zero reference to anything event related.

    I've been using Data oriented approach for checking position and input, but this only makes sense for per frame updates. In order to detect collisions I need to either check for collisions every frame or use events.

    While signals are convenient, I'm not sure I like the idea of having some of my application state contained in data, and the rest contained in Signals. I'd prefer everything be stored in data (helps with saving, undo, replay systems etc) and then allow systems to Observe data.

    It seems the a recommended package for this is UniRx, which Zenject supports. It's a little outdated (2019) but the only breaking change is WWW is now obsolete in Unity.



    Here's a preview of how things are going. I'm trying to teach myself UIElements at the same time.

    There have been some difficulties using the frameworks, however these stem from the frameworks no longer being supported, and they would be easier if they where getting some kind of support. I will probably have to switch to ECS at some point in the next year or so, but only because there's no alternative, which goes back to my original post.

    It feels like UI Toolkit was created to appeal to web developers. Essentially being an HTML/CSS/JavaScript Framework (UXML, UCSS, C#), so I don't think it's unfair to say that frameworks directed at enterprise application developers would be off the mark either.

    It seems like Unity is adamant on shoving everyone through the ECS shaped hole though. I just don't think reducing the engines diversity is going to workout for them in the long run.
     
  13. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,793
    I woulda been able to do that with a single component with all data, nav code and health stats, all fully adjustable from the Inspector, no inspector obfuscation, a coherent to follow framework and drag and drop functionality. I hope yer having fun.
     
    Ryiah and Lurking-Ninja like this.
  14. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    222
    That's how assume most unity developers code, writing it in a single script is a lot faster. This is called a god object, it fast but highly coupled (i.e. what we refer to as spaghetti code)

    I know game developers who have written their entire game in a single 10,000 line script.

    Im purposely trying to build code that isn't highly coupled. The architecture doesn't pay back it's dividends in the early stage development onto the game complexity reaches a certain level.
     
    CodeRonnie likes this.
  15. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,321
    It wouldn't be a spaghetti. It would be very compact, but structured and readable.

    DI does not save you from spaghetti. It does give you tools to give your spaghetti one extra dimensions or several, so the spaghetti becomes hyperdimensional.

    Speaking of spaghetti, if you started with OOP, I'd recommend at some point to write old school procedural code. It can be incredibly compact and concise, so it is a good learning experience. In C# you could also try experimenting with extension methods and "using static" classes. Because if you're interested in DI and OOP, this is the stuff you're likely to miss.

    Regarding procedural code, for example, I really love glut programming code. See examples here:
    https://cs.lmu.edu/~ray/notes/openglexamples/
     
    Lurking-Ninja likes this.
  16. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,124
    One of the developers of Terraria (@Yoraiz0r) is frequently on Jason Weimann's podcast and he occasionally talks about a gigantic method in that game that is thousands of lines long and has been that way for years but it's never prevented them from releasing updates to the game.

    Just in case I pinged a different person here's the twitter for them: https://twitter.com/yoraiz0r
     
    Last edited: Apr 30, 2023
    Lurking-Ninja likes this.
  17. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,509
    I wouldn't put much stock in that, even if it turns out to be numerically true.
     
    neginfinity and Lurking-Ninja like this.
  18. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,321
    Regarding that. I would argue that you're actually getting distracted.

    At the moment you're trying to follow ideology which says that "tight coupling" --> "bad". This is not necessarily true. It is nice to explore DI and play with it, but I you probably should not subscribe to some ideology, as you risk joining a cargo cult.

    For example: "game complexity reaches a certain level". Will it actually reach that level? Games can be incredibly simply programmed.

    Writing in a single script is a lot slower, because navigation is going to slow you down.

    Even if you end up with a huge class, you can still structure it through partial classes, and move frequently used functions into external static methods. In this scenario you'll end up breaking it down rapidly.

    A single file can be 30 lines long, for example.

    Also, due to unity's nature t he universal controller behavior can be tiny.
     
  19. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    From compiler perspective - putting large amounts of LOC into single method is a bad idea because of JIT compilation.

    Even if you're not targeting Mono (e.g. for non-mobile platforms).
    Editor still uses Mono as a core which results in lag spikes upon compiling some large method.
    More you put - harder the spike is.

    Composition is the way to go. Don't go deep, go wide.
    What you really want is atomic [medium size, not too small, not too large] components.
    A lego constructor for the game designers to mess around with.
    Easy to swap, easy to remove. Involving zero code and maximum compatibility between each component to promote combinations of game features (see feature matrix).

    Also, while writing bindings is cute and all, that's not game related code.
    Injects do not solve spaghetti code, it just makes it even harder to track what's going on.
    A nice crutch to get dependencies.
    An illusory wall [or yet another abstraction layer] you build between yourself and the editor.
    In the end it just falls flat and you'd be forced to remove all that code.
    And you'd have to ask yourself, was it worth the struggle?

    As for the reactive, UniRx still works fine in recent versions with minor changes.
    But, if at some point you're going to transfer project into into ECS / DOD, you'd start to realize that events are no go.
    They're clunky, tedious, break often, have undefined order of execution and are random access.

    While they are a mentally cheap way of communication - they couple and bind things in a semi-random human way of thinking. Sometimes they're nice. Sometimes you want they never existed in the first place.
    The more you move logic into DOD realm, the more events become an issue.
    Because as it turns out - you don't actually need them.

    Events on the system level is semi-okayish.
    Events for entities are actually requests, state changes, or notifications depending on the type of logic required to be executed.

    This is not an issue with OOP style of programming because everything is suboptimal [and expected to be so]. But the more you dive into DOD, the more you realize - human style of coding is forever to be flawed. The programs we write are not for humans to interpret. The paradigms and patterns humans are bound to are forever to be flawed from CPU perspective.

    That's a philosophical question of ease-of-write vs performance though. And quite an offtopic.
     
    Last edited: Apr 30, 2023
    ippdev likes this.
  20. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    222
    It may very well come to that. It does feel nice when you have self contained components. I always just seem to get caught up in game state management with too many references between classes.

    It is quite possible to remove IoC entirely from my project at the moment, although I will begin implementing integration tests next week so I might wait to see if DI makes that process easier before I consider dropping it or not.
     
  21. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,509
    "Too many references" isn't the problem, though. They're the cause you've identified. What's the problem? By which I mean, what pain are you trying to avoid by reducing references between classes (objects?)? How are they making your life worse?

    I'm not suggesting that there aren't too many, by the way. Just interested in why.
     
  22. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,321
    The important question is whether "it feels nice" has practical application. I.e. whether it improves your program in any way or brings any benefit.

    It does feel nice to construct elegant class hierarchies while overusing OOP. You feel like an artist. A programmer version of michelangelo, with beauty, elegance and art dripping off your fingertips into your code editor.

    However, overuse of OOP leads you towards model collapse we discussed earlier and moving classes around, splitting and merging them does not provide tangible changes in program's behavior. It fixes no bugs and produces no features. So it becomes a programmer's trap - you are engrossed in solving an "amazingly interesting problem", but your work has no effect on your product.
     
    Ryiah likes this.
  23. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,793
    It wouldn't coupled to anything except the Animator component and would reside on the same GameObject. It would be so highly uncoupled that it could be dropped into any scene in any 3D game and just work. Yours won't. You say you are trying to build code that is uncoupled. Well uncouple your framework from the DI and it will fall to pieces with no functionality. Talk about coupled.
     
  24. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    222
    It's a good question that I haven't articulated well. Here's some graphs I created months ago before I knew about IoC when I was trying to figure out the difference between existing patterns.

    All of them relate to the game I'm trying to make.

    Screenshot 2023-05-01 091720.png

    **Monoscript:**

    This is what some people have advocated I stick to. I identified here that it's extremely fast and easy to write code in this manner. I do write all my prototypes in this manner, which is how I wrote the original prototype for my game.

    Screenshot 2023-05-01 092236.png

    **Gameobject - Component:**

    This is what I see "Classic" Unity as being. The application is composed of Gameobjects with modular components. Components talk to each other via GameObject.GetComponent<T> and [SerializedField]. If every component is a node in the dependency graph that can talk to any other component then the potential for connections becomes n(n-1)/2. Practically it wouldn't hit this maximum but it becomes difficult to track all the inter connections.

    In order to reduce the complexity people will introduce a GameManager. Now individual UI components don't need to directly reference the components they require data from. However this often gets abused and the Game Manager will become a god object where it's managing the UI, the game state and acting as a service locator.

    Screenshot 2023-05-01 095347.png

    ** Singleton / Service Locator:**

    A common solution I see is using Service Locator Singletons in order to hold references to other components. Now the previous many to many relationships become a many to one. Possible interconnections between components grows linearly.

    Practically speaking this means instead of using FindGameObject("Player").GetComponent<Health>() once can use GameCatalog.Instance.Player.GetComponent<Health>().

    In all scenarios you end up with essentially a monolith. Concrete class references leading to a brittle code base where changes to one monobehaviour can have unexpected consequences to the rest of the code base. i.e I change the behaviour of the player, all other objects referencing the player are now potentially effected. The only way to know for sure is to test. However it's difficult to run automated tests on these other components in an isolated fashion. This was the issue we run into at my last job, where every new component added or modified would ripple out undetected bugs to the rest of the code base.

    The way around these concrete coupling is to use interfaces, making it quite easy to replace the actual GameManager with TestManager. While you can't [SerializeField] interfaces it's possible to just declare them as the interface in the Game Manager, and for default unity components that you would like to interface you can create wrapper classes.


    Screenshot 2023-05-01 101747.png

    By adding these abstractions I can remove a lot of my concrete dependencies. However I will still need something like a Game Manager, or Game Catalog in order to set up the references (unless I want to resort to components directly referencing each other again).

    IoC Container also achieves the same goal as the Game Controller/Manager/Catalog. So the question might be, why use IoC container then? It does essentially the same things.

    Personally I've just been finding that Zenject features are nice to have. [Inject] attribute saves on writing new Class(x,y,z,e,r,y) etc. "Sub-managers" are already managed by the framework. I like that plain C# classes can be called on Update, Start so not everything has to be a monobehaviour. (saves a couple clicks in the editor). I also like Signals, being able to subscribe a class method to an event without the class ever having a reference to the event. Usually you would have to += and -= in the OnEnable() and OnDisable()

    Again, the consensus continues to be "You don't need it". You don't need to use abstractions to make a game, you don't need to decouple events to make a game, you can make everything a mono-behaviour, you can do everything in the editor, you can use a manager, you can use a singleton, you can write everything in a single script. True, all true. I don't have a response to this other than, I want to explore other ways of doing things, to really know the benefits and drawbacks.

    I'll leave it with this clip from Lorne Lanning, the creator of Oddworld:Soulstorm



    "The talent pool for people who know Unity is very different form the talent pool of people that know Unreal ... I think that's something Unity needs to fix, but I don't think it's going to happen until they make a commitment to AAA ... Your talent pool is greatly reduced ... good people are just that much harder to find"

    It's not an argument for my point as he's not referring to IoC or architecture in general, but I think it does highlight that to remain complacent is a mistake. IoC might turn out to be a huge waste of time.
     
  25. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,459
    Oh boy, don't open that box of pandora.. It's a disucssion held here multiple times :D
    Thing is, having full on AAA and good Mobile support is very, very hard and they got recently again voted as the best engine for mobile games: https://www.pocketgamer.biz/mga2023/winners/best-game-engine-platform/
    Comitting even more to AAA than what they do with HDRP would be risky.

    Btw. definitely very interesting graphs you have designed!
     
    TheNullReference likes this.
  26. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,793
    The one error/fallacy I saw in a quick skim was assuming the connection of every component to every other component. Never have I had that happen. The only time a connection should be made is when one component has to inform another of a value change so that it can respond to new condition/inputs.
     
  27. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,321
    The words of youtube dude are cute, because normally unity has higher number of people with high technical expertise. This may have something to do with unreal's fascination with blueprints.

    Your example with container spawner represents questionable design, because you have three class chains doing the same job. Basically when you see a scenario where there's "Object1Factory" and "Object2Factory", there's a good chance that object type should become a parameter.

    Normally system like "Spawner" does not know what it spawns. It doesn't need to. The data is defined by prefab and nothing else. A "Spawner" also in many cases does not need public fields. And can be self-contained.

    Also components do not talk to each other SerializeField. its only purpose is to show data in inspector.

    So, in your example you could normally delete all factories and make a single spawner.

    Next. Having a class called "Gun" is a case of overspecialization. Often it is a good idea to have a "Weapon" class which could describe ALL weapons in the game, and if you try it hard enough, it'll also include all melee ones. This will parametrize everything, and your job will be done once.

    Next. Normally you don't want to have a "player health" class. Because those sort of widgets are placed by your designer, and scrolling through screens of "PlayerHealthClass", "PlayerManaClass", "PlayerStaminaClass", "EnemyHealthClass", will be tiresome.

    In this case the widget is screaming at you that you need to generalize and you should PROBABLY implement a single configurable widget instead. Either through an enum or through C# format string, with C# format string being configurable, because it will allow to quickly alter displayed numbers in UI view.

    Next, the camera. Does your camera need to know what the player is? In many cases, it does not. Camera can easily be defined as a point of space attached to a transform, and said transform can be a brick on sidewalk.

    So, with all corrections, your design can end up looking like this:
    upload_2023-5-1_6-3-21.png
    Now, in this case, what do you even need DI for? Why not just kill Container Installer too? It is a dependency.

    There are other things. For example, a Player and Enemy can be the same object. Combatant. The difference would be that Player would have PlayerControl, and Enemy could have AIControl.

    "Bullet Factory" can become a job of a Weapon, which will either spawn prefabs, or fire rays. Because not every bullet is an object.

    Overall I feel like in time spent discussing DI you could've finished your game. If you're having fun, sure, but uh, I wouldn't recommend picking questionable habits.

    Also, regarding AAA. AAA has vastly different needs, and if unity starts t argeting AAAs chances are it will be unusable for you.
     
    tmonestudio, ippdev and Ryiah like this.
  28. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,793
    I had always questioned the use of Interfaces because they have to be tap danced to produce Inspector fields or Debug.Log instead of just watching the field in the Inspector. I felt Unity had slowed down since implementing interfaces for many new packages. Turns out there is a consistent 16+-% penalty for implementing them. The stats for the actual tests are in this thread. https://www.reddit.com/r/Unity3D/comments/4f1w1s/c_interfaces_and_performance/

    So not only is your code tightly coupled to the DI framework, you are adding another layer of indirection by channeling everything through interfaces, giving you a default 15% performance decrease. Not sure if that is cumulative when using multiple interfaces as the tests showed single interfaces vs no interface for a single iterated operation. Not sure how this impacts the marshaling to C++ at the engine level. Perhaps more technically knowledgeable folks can pipe up and amplify or correct my assumptions.
     
    Last edited: May 1, 2023
  29. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    Those are kinda invalid tests.

    Assuming you've got 1bln iterations - you're screwed no matter what you do if you call over managed objects without multithreading support. More suitable test is 1k or 10k. That's the ranges where potentially update loops could run. But even with large project they rarely reach 1k. And in these ranges cost is pretty much negligible.

    Different case is IL2CPP.
    Conversion of interface vtable is really subpar to the actual C# code. + there's no sealed microoptimizations done. Ideally C++ code should have a hard codegen'ed methods instead of virtual ones. So targeting IL2CPP there will be different penalty as well. Capturing data from the actual device is tricky though as it varies majorly by the target platform and each separate device. Mobile is mostly affected.

    So in terms of speed - interfaces aren't that bad in managed environment.
    For example, I'd pick interface + custom update system over coroutines.
    Because coroutines are unavoidable heap memalloc and a ticking bomb for the refactoring due to state capture. Zero alloc strategy wins over interface cost any case.

    Interfaces are also faster than native <-> managed communication / marshalling.
    (see as running .Update methods over 10k MonoBehaviours)

    But then again, ideal option exists as actual system with a query filtering.
    No virtual methods involved.

    In both cases composition wins, but in some cases avoiding interfaces is impossible.
    DI is one of those cases. And that's why DOD wins over OOP in terms of performance.
    For the same reason Unity softly funnels into DOD.

    Technically DI can be done without interface decoupling but then it turns into complete crutch with 0 benefits of usage.
     
    Last edited: May 1, 2023
  30. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,321
    I could definitely outline why interfaces are a thing, but, uh... does anyone here actually need an infodump about that?
     
  31. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    222


    I thought this video would also be relevant for anyone stumbling on the thread. Would be interesting to know if they're still using it.
     
  32. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    222
    I agree all these things can be abstracted, DRY etc. I tend to not abstract until I need to, just to avoid over abstracting and getting into YAGNI / over engineering situations. Concrete class until there's more than a single instance / use case.

    The factories are individual because they have different behaviours, I've decided Bullet Factory should be responsible for the initialization of the bullet, rather than the bullet itself. Bullet's require different initialization parameters compared to an enemy, hence different factories.

    I'm a little confused by this. The graph makes it look like you've removed all functionality from the game. You have a camera with no dependencies and some UI. Basically an empty scene?

    Really I'm not so focused on the semantics of how classes are implemented or what they're called. More interested in how classes acquire their required dependencies.

    As for AAA, Unreal is so far ahead in that regard I don't think it's worth Unity to even try to catch up. Stick to mobile, VR and push more into web.
     
  33. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,321
    Did you just rename yourself? That's not very helpful when done mid-conversation, but oh well.

    I've removed all unnecessary abstractions.

    In the first place the graph never represented the game. It represent class model used by the scripts on programming side.

    You used object oriented approach, and created a model of classes you think is necessary to make the game happen. The assumption was that there's a need for factories, that there's a need of three separate factory classes, that there are four interfaces, and that camera requires a global connection to the player and has to be initialized by external controller.

    All of those assumptions are false. And I explained you why. Who do you think I was writing those paragraphs of text for?

    Also, none of those classes had anything to do with the game in the first place. You can remove all of them, and create a game. It is also possible to create large experiences while writing no code. That's the point of game engine.

    For example, your factories. The factory only needs a separate class if there's going to be a more than one instance of it. Otherwise you're polluting global namespace, and can create a universal generic factory which will take a lambda to initialize a freshly constructed object. "Entities must not be multiplied beyond necessity" is a familiar principle, yes?. Then you can question "do you really need factories"? You can just spawn prefab, you know. It takes next to no code, it is not like you need a whole class for it.

    the problem in your case that you're trying to approach the whole thing as a programming task where you start from nothing. Which is wrong.

    You're not writing a classic program. You're not starting from nothing. You're designing a robot controller for a physically controlled sandbox full of rigidbodies. Bulk of your work will be spent creating media assets, placing them in editor, and not messing with code. You are also provided a kit of components, some of which are very smart. For example, Animator component provides you pretty much a robot controller, which can be used to drive locomotion (if you have root motion enabled). You could probably even write game logic using Animator state machines, although there's not much point for that.

    So, most of the time, you'll be designing VERY simple TINY systems which know nothing about the rest of the code, do not ever hold references to anything and are completely self-contained. For example, stuff like "a door which opens when a player approaches it" will be in ballpark of five, maybe ten lines of code. Because a bulk of it will be setting animator's bool in response to collision system. You can then use that five line controller for all machinery in your game with it and build huge levels with it.

    You likely would be able to make a basic car controller in under 50 lines, and add two more 10 line classes, and you'll have a basic racing game.

    That's the mindset you need to get into. In many cases you'll be scripting very simple behaviors of very simple mechanisms, and not designing business systems. And that's the stuff ippdev, me and other people tried to explain. So in many cases you would not NEED DI, you do not need testing, you do not need interfaces, and with minimal code and tiny number of classes you can make a product you'd be able to ship. And using DI, or too many classes will be overengineering, which will waste your time.

    The issue here is that if you tried to write game engines before, or programmed professionally before, it'll take time to get into this mindset, as it is the opposite of everything you're taught. Because instead of designing class hierarchies, you'll be smashing bricks together. I think it took several months for me before this whole thing clicked, and smashing bricks together turned out to be far more efficient.

    That's the rough idea of it.

    P.S. Trying to play games like Scrap Mechanic, Space Engineers, Factorio or Opus Magnum might help you to understand the difference. Do keep in mind that some of those titles can be a huge time-wasters.
     
    ippdev likes this.
  34. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    222
    Please be aware that the graph is in the context of Zenject, which does use Factories for spawning poolable monobehaviours as well as using interfaces in general.

    Game engines facilitate game development, but they do not eliminate the need for custom code entirely. Many games require unique features, mechanics, or optimizations that go beyond the scope of built-in engine capabilities. An example might be Unity's own Boss Room concept which uses custom classes not found in the default editor.

    Namespaces and assembly definitions ensure you can create as many classes as you want without polluting the global namespace. It's true that lambda expressions can be used instead of simple factories but it just comes down to style. Factories can improve encapsulation and readability of the code base, while the lambda expressions are more concise, simple and don't unnecessarily increase the code size. I'm leaning more towards scalability of the project, so I prefer factories to ensure they're easily extendable into the future.

    I think using the Animator component to drive locomotion would be okay for a small scope project however I'd worry about tightly coupling animations to movement behaviour. If an animator decides to change the animation at some point that's creating a side effect of changing your character movement behaviour which may break many other systems. It would also be suboptimal for network Synchronization. In my game I want the play input to feel snappy, so player movement updates are instantaneous. The animator is setup to respond to the change in velocity in a more natural looking way.

    I understand the idea of using small, self-contained systems for simple games or prototypes, but consider that as a game grows in scope and complexity, managing numerous small scripts can become challenging. Maintaining a clean and organized codebase might get difficult, leading to code duplication, inconsistencies, and decreased maintainability over time.

    In more complex games, you'll often find that various systems need to interact with one another. Designing completely isolated systems could make these interactions hard to manage, potentially resulting in unintended consequences or conflicts between different parts of the game. Plus, there might be performance-critical situations where having many independent tiny systems isn't the most optimal solution.

    Just for clarity, I've been coding professionally in Unity for 5 years now, Unity was my first experience programming so I'm not bringing any burden from other coding applications. I learn the typical way through Ben Tristam on Unity and all the standard tutorials which very much emphasis the patterns you're describing. That style of coding has served me well and is an extremely fast way to produce small scope games (or parts of games).

    However now I'm looking to tackle more complex projects (like the cross platform VR/WebGL/Mobile/Tablet backend integrated SaaS multiplayer business application I have been referring to in my previous posts). In the 7 Unity applications I've helped release, every single one has been a hot mess by the end of that and all of them attempted to follow standard Gameobject-Component architecture until they were forced to implement custom logic on top.

    We will fundamentally disagree on a few things as I will always assume testing as necessary to releasing products. Not just for production but through the whole lifecycle of the product, which probably stems from my background in design. Most companies rely on unstructured user testing, bug reports and feedback. This becomes a huge waste of time and money when it comes to bugs that could have easily been avoided with automated testing.

    I agree with you when it comes to small single player games and prototypes. Make the animator your character controller, why not, it's fast and gets the job done. I wouldn't recommend it for large and long term projects though.
     
  35. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,321
    There will never be any code duplication, if you generalize in time, apply KISS/YAGNI and, sparingly DRY. Those are the principles why you shouldn't have 3 factories, when one will do, and why you shouldn't have specialized widgets like "Player Health".

    If you duplicate anything, you're being sloppy. You should not be sloppy. That's what principles are for.

    Your responses demonstrate specific level of skill. While it may be true that you had unity related jobs for several years, the impression I get from your responses is that some ideas and concepts failed to stick. Those are t he reasons you get responses you receive.

    You can claim to have five thousand years of experience, but based on your text I can only see myself from the time before I even worked with unity, when I was in love with OOP hierarchies.

    For concrete, specific problems related to integrationg of your frontend, you could try scripting forum, after you provided SSCCE for them.

    I do not share t his belief, and to me it looks like cargo cult. In my opinion need for tests arises as a a result of a flaw in language design. Typically, need for automated tests arise when there is insufficient compile time checking provided by language facilities. Typically this happens in languages like Javascript and Python which lack proper compile-time checks. But we are not programming in those languages, we use C#. It is still not C++ or functional languages, but amount of compile time checks is sufficiently large to cover majority of cases. Also, in my opinion, code should be written in such fashion which will not ever need testing.

    Anyway, I'm going to excuse myself. I spent a lot of time writing responses here, and I think by now this has been enough. I'm not getting much in return from participating anymore.

    Have fun.
     
    Lurking-Ninja likes this.
  36. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    222
    I've appreciated the responses from xVergilx. They're providing fact based evidence backed up with examples, which provides constructive responses to the discussion. If you can provide a code base example of large project that uses your methodology that would add credibility to your responses, it mostly seems like personal opinion. There seems to be a super-user, crab bucket mentality on the forum where you all keep each other in line. A lot of noise drowning out useful discussion.

    The fact is Unity Developers are viewed pretty low down on the totem pole, and are some of the lowest paid developers on average. Artists who have never touched a script in their life assume an Unreal Developer is much more capable than a Unity Developer. Which is why it's strange to me people hold on so tightly to the type of patterns that have given Unity Developers this reputation in the first place. By that standard ECS is a revolution, at least a small portion of people can finally admit Gameobject-Component pattern has flaws.

    The consensus I'm gathering is that the Gameobject-Component is perfectly valid as is, and we'll just sweep Unity Developer's reputation, and all the bloated monoliths, under the rug.
     
  37. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    9,904
    Oh, the classic "you are all stupid, non-talented unity developers, except me, I'm a helicopter". It was missing from the collection in this thread.
     
    Luxxuor likes this.
  38. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,321
    So somewhere in a galaxy far far away there's supposedly a group of people that are looking down at me and unity developers? Or something?

    Why should I care? Are they even real? Do they matter? You think it is a fact, but is it a fact truly? Probably not.

    Or you think that by giving you advice I'm undermining unity, upset the world order and bring forth the end of the world or something?

    Again, why should I care? That's your belief, I find it ridiculous, which is the reason for me to just dismiss it.

    Keeping people in in line? Crab buckets? Do you actually think it is important to me what practice you use? Don't you think I have better things to do?

    I only share information that I find useful and usable, and recommend to avoid things that I found to be non-working and unusable. In my own practice. And the reason I do it, so the information accumulates for people who came across this thread in the future, and learn something from it.

    You worry too much about nonsensical stuff. Totem poles, who looks up, who looks down on whom, and so on. And also take advice personally.

    Instead of wasting time on all this useless fluff with no practical application, you should be programming. Because only being able to get things done matters.
     
    Last edited: May 2, 2023
  39. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    222
    Please share some examples.
     
  40. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,575
    This is weird.

    5 years on Unity exp. Supposidly, but is it any in depth?
    7 projects involvement. And all are mess...?
    Spending 2 weeks on prototyping some DI for anything than learning?
    Seems like avoiding ECS, or even DOD/DOP.
    Not understanding and dismissing GO-Components design.
    Various of devs here had solid advice, based on their long experience. Many of them been releasing solo and team Unity projects. Making living. Please at least hold some respect to them.
    Over-engineering design on many levels.
    Spending so much effort writing wall of text, to defend own point of view for past 100 posts, bringing nothing conclusive. Seems going in circles.
    Doesnt feel like OP is even willing discuss any other options.
    Possibly that time could be used better on game design improvements.
    Initial OP question was an interesting and completely valid. But then it showed lacking over duration of the thread.
     
    Luxxuor and neginfinity like this.
  41. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,124
    It's only weird if you try to look too deeply into it. OP was on a project that wasn't able to be properly maintained and they drew the conclusion that the solution was to adopt enterprise practices but the real problem here is not the approach but just a lack of experience.

    Worse yet while he did gain some experience working on that project it wasn't enterprise experience which means his new approach has no experience backing up how to properly use it. I'm not surprised he's overkilling everything because that's what I did back in the day when I first learned OOP.
     
    Last edited: May 2, 2023
  42. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,321
    Yup. Exactly my conclusion.

    Beautiful class hierarchies, everywhere.
    upload_2023-5-2_12-1-33.png
    I went through this phase too. Nearly killed a project I was part of with it. Due to model collapse.

    Then again, I'm not sure that back then component based design was even a thing and anyone could imagine defining Cat and Dog as an instance of Animal where "makeNoise()" is a non-virtual function calls a lambda you passed into a constructor. I think only common lisp could pull this off at this point. Templates were black magic, template metaprogramming was not popular, though apparently Gang of Four already wrote their book. Which I wasn't aware existed. *sighs*
     
    Ryiah and Lurking-Ninja like this.
  43. BIGTIMEMASTER

    BIGTIMEMASTER

    Joined:
    Jun 1, 2017
    Posts:
    5,181
    @TheNullReference

    have you given ECS a go at all?

    If not, why not make some very tiny projects and practice implementing it? Take it as far as feels useful, then you might get a better idea where it helps and where sticking to other approaches remains beneficial.

    Much easier to extrapolate from that compared to just thinking / talking.

    In my experience, for the solo-developer, I found component based architecture to be a big hassle. Just too much fussing over communications for my taste. And the general mindset just seems counterproductive to my way of thinking. I don't like to think of thinks as a summation of tiny parts. Then the entire project becomes like a hyper complicated math equation. Bug in my vehicle, is it because engine is misfiring, or wheels bounciness isn't tuned, or gasoline parameters had a typo??? Too many things! I'd rather just go to a Vehicle System which I know will encompass anything and everything about vehicles. And when it comes to data entry, I don't like to have the designer have to do any calculations. I try to have my data be focused on the only thing we really care about - players time and attention. So rather than say how many gallons of gas a vehicle can hold, I say "At max fuel, how many real time minutes before empty at max speed." The code can figure the calculation from there. You get the idea.

    I think there has to be a difference between way a team codes compared to an individual. Much complexity has to be added if its a team. A soloist can do away with a lot of that. And they need to because only finite energy you can put into each corner of the project, and programming is only a portion of the total effort.

    You didn't mention that you are working solo but it seems like that is the case, at least for this query?

    Key principles I've been developing lately:
    • Standardize class to class communications
    • limit total number of classes as far as possible by using data-oriented design such that we have a few classes which can represent many different things based on data input
    • Separate data from logic so that we can easily swap logic out anytime
    • design around data first. Ask question, "as designer, what parameters do I change to balance the game?"
    • handle communications via events.
    • prioritize development speed above anything else. Have to verify the game first - no reason to build a nice assembly line before you are certain about the product its meant to create (certainty is determined through actual testing, not gut feelings or paper planning)
     
    Last edited: May 2, 2023
  44. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,793

    This is a load of prevaricating hogwash. Salaries average 120K USD a year in the US. Folks who can pay 25 cents for a burger drop that significantly worldwide via Upwork and similar and I wouldn't call most of them devs but glorified script kiddies. Folks wanting mid to senior dev level on Upwork pay 50-125 an hour. Just a look at some of the repos on github by various independent devs is proof that statement is just blither and posturing. You have not gotten crab-potted here. You have gotten sage advice from folks who are or would qualify as Senior Unity Engineers and have put the time into a vast array of shipped products. There have been large scale projects shipped using the Unity component based, frame dependent architecture. You keep on and on about the large project that needs DI, yet you are one developer. To have any scale you are going to need at least one other dev. probably half a dozen. Yet you devise a framework that you fancy but would be opaque to new entrants to your project. This will cost money to on-board them and get them up to speed.

    Do what you fancy but don't pretend you are some code genius. You aren't. Yer an enterprise dev who can't even wrap your head around doing things the way the creators of the engine set it up for workflow, prototype iteration and shipping. If yer gonna toss insults around expect any niceties towards you to cease and truth take the prize.
     
  45. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    222
    I have used ECS in the past, around version ~0.50. It was quite verbose back then, and the rapid changes made me think it wasn't ready to start a project yet.

    I have been considering going back. ECS is clearly more optimal for the gameplay elements. I'm planning to use firebase as a backend and that's something I worry about. How will ECS go with integrating firebase? I'd imagine I will probably need to use an alternative to ECS at a high level in order to interface with existing 3rd party plugins for these external services.

    In my head I have a vague idea that a nice balance would your core gameplay being built with ECS with a high level OOP abstraction layer for those other services (analytics, firebase, authentication etc). Perhaps that's where UniRx / Zenject might be helpful to know about.

    I got this idea from these blog posts:

    https://www.sebaslab.com/oop-abstraction-layer-in-a-ecs-centric-application/
    https://www.sebaslab.com/ecs-abstraction-layers-and-modules-encapsulation/

    My thinking with DI is to feel it out, to know what parts of it don't work, and where it is useful. I've always wanted to explore using SOLID in Unity and the IoC Framework gives one the opportunity to do so, even if it is fighting against the engine in many ways. I'm also learning about Reactive Extensions and MVC at the same time, which is nice.

    I also don't think there's much harm in waiting in regards to ECS. The framework will get better, more plugins, more tutorials and GPT4 will catch up with the current version, making learning it easier.

    I've appreciated all the constructive responses, things have gone off on a tangent now so best to stop.
     
  46. BIGTIMEMASTER

    BIGTIMEMASTER

    Joined:
    Jun 1, 2017
    Posts:
    5,181
    It's your thread! FWIW I've found it pretty interesting.

    What I don't understand is what does these third party services do that confounds integration with ECS?

    I only make make single player games and have small understanding of unreal c++ but do most programming with visual scripting. But the bottom line is, say maybe you use an external database like SQLite for example, once it gets to the engine side of things, we are just talking about some data that would be interpreted somehow, right?

    I mean, maybe its players inventory, maybe login credentials, whatever. It ultimately boils down to a string or int, etc, and the game code just decides what to do with it, right? Like what difference where it comes from?

    Is there like, limited ways you can connect the service to engine, and this necessitates certain types of classes be present?

    I'm not specifically harping on ECS here, by the way, just as a general question, like one architecture versus another, considering third party tools, what difference does it make?
     
  47. TheNullReference

    TheNullReference

    Joined:
    Nov 30, 2018
    Posts:
    222
    For example the Firebase Unity SDK: https://firebase.google.com/docs/reference/unity is a singleton instance object. To interact with firebase you're calling methods on an object (I think it might actually be a MonoBehaviour as well).

    Common advice is to write Wrapper classes that can then pass in data into the ECS world, perhaps that's more trivial than I think. I think Hybrid ECS/OOP applications will be the a common use for ECS until 3rd party tools are able to fill in the gaps, or it will just become standard to have an OOP interaction layer in your ECS application.
     
  48. Noisecrime

    Noisecrime

    Joined:
    Apr 7, 2010
    Posts:
    2,000
    I've been reading this thread since it started, not really got any input as I've never used DI, but I have found the discussion interesting, mainly from the side of better/cleaner code architecture.

    However by chance I've been digging into Managed code stripping ( I can't seem to avoid Unity built in modules like ClothModule or AIModule always being included in a windows build il2cpp - but that's for a different thread if I don't find a solution, it might be as simple as standalone doesn't even support it ) and came across this Unity blog about code stripping that highlighted its potential to really mess up DI ( mentioned Zenject ). So figured it might be worth mentioning.

    Granted the blog is from 2020 and Unity has improved both stripping and provided means to avoid it, though I'm unsure if that can be implemented in DI frameworks or whether as the developer you still have to account for it. Either way it would seem prudent once you've got a working project to run some tests with different stripping levels to check that it still works or if you'll have to invest time in adding link.xml or Preserve attributes.
     
    neginfinity and TheNullReference like this.
  49. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,321
    I don't want to.

    Prior to unity, I had several years of experience with traditional programming in C++ also Pascal (and prior to that with assembly and programmable calculators) and wrote a game engine (in Delphi) along with couple of other people. So, if you've started with unity around 5 years ago, you'd be roughly at the spot BEFORE the moment where I discovered while unity components makes sense. You're using exact same reasoning and are making the same mistakes.

    Now, here's the thing. I'm not paid for explaining, I'm not your consultant, and info dumps I provide take time to write (I think up to 40 minutes per response) , time I could and probably should spend doing something else. Were I charging for time, you'd be probably owing me a couple of hundred bucks by now.

    Now, some of your previous responses were rude, there was complete drivel about totem poles and people looking down (why the hell would you ever think of that or care?), plus you have prejudice against specific approaches and at the moment put your faith into some other approaches. I.e. "monolithic bad, corporate good". That is something you'd need to untangle. There are also 148 responses in the thread by now and you don't quite seem to be improving.

    What does that means. It means that making proper research for you will take hours of my time, for which I'm gonna get a whole lot of nothing. A whole lot of nothing because even if I convince you that I'm right, that won't make any difference for me, and because the much more likely outcome is that you'll entrench in your belief.

    So, I don't want to do that. You're gonna get the reasoning you received in this thread and only that. I believe the reasoning is sound and useful. And for specific problems, try SSCCE in scripting forums.
     
    Ryiah likes this.
  50. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,292
    I'm still using 0.51 just because 2022.3 is not out yet. And its been I think a year between major release.
    3 years since 0.17.

    Nobody forces you to update.

    That's the packages charm. Unity LTS versions tend to break more than Entities updates tbh.
    The only big jump would be 0.51 -> 1.0. But from my tests it still works just fine, a few methods renamed, basis is the same.

    As for the verbosity, I think DI does it worse. At least with ECS you write code that actually makes your game.
    It might be rough in some places, but its a game code. Even more - multithreaded code with lots of scalability potential.

    As long as services got a MonoBehaviour, you can always author / AddComponentObject to the entity and work with it just like any other entity via SystemBase / managed system. Or even better, work with them from SystemBase directly if they aren't MonoBehaviours.

    But here's the catch.
    With ECS you've got data already exposed you need to feed to those services.
    So no extra logic is required to be written.

    In reality interop between ECS and MonoBehaviour is minimal and overexaggerated.
    Folks tend to forget that not everything has to be entity, bursted, or multithreaded or even DOD.

    Hybrid setup covers best of both worlds.
     
    Last edited: May 3, 2023
    Luxxuor and spiney199 like this.