Search Unity

Backwards Standards of Architecture

Discussion in 'General Discussion' started by jdgeci, Aug 2, 2018.

?

Have you encountered these similar frustration and flaws in Unity's misused parent/child relations

  1. Yes

    18.2%
  2. No

    81.8%
  1. jdgeci

    jdgeci

    Joined:
    Jul 27, 2018
    Posts:
    12
    https://pastebin.com/GuGMQSrL is where you can see the system i will be using as my example in this post. Feel free to use or pull form this architecture in creating or improving your own design and relational dynamics.

    Hi, my name is Azeranth, and I have come today to open a discussion on how remarkably backwards and flawed the core architecture of Unity is. Namely, gameobjects, components, and their relationships.

    Current Design:
    In the current unity design, the children of a GameObject or Component (note that Components share an inheritance with GameObject which allows it to contain other Components) are obtained through the GetComponent() or GetComponents() methods. The GameObject with whom a Component or GameObject is associated is stored as a field. This is entirely backwards from the universal standards of inheritance, parent/child relationships, and managing.

    The Parent/Child analogy extends beyond inheritance into many other domains, such as injection, scope, and managed sets. Each of those functions should inform the relationship between the Parent/Child in a very important way. First of which, being that a child is assigned to the parent, NOT VICE VERSA as is explicitly stated in the description of the Instantiate() method which has a parameter for "The parent to which the Component is assigned".

    Scope Management, and Obfuscation:
    The importance of the concept of scope, is that an object is entirely ignorant. Any given object needs to be told the absolute minimum amount of information to accomplish the maximum amount of functionality. Any time an object needs to know more information, it becomes another wire which can become entangled when extricating it from its current setting. Large amounts of dependency on surrounding architecture makes code stagnant, dynamic- restrictive not modular.

    Imagine the following system:
    In an RPG game, their are Spell objects. They contain a MonoBehaviour script, named SpellController which expects a SpellData asset to be passed into before activation. This SpellData object is a ScriptableObject and contains all the details about the Spell's Collision triggers, Particle Systems, and Audio. It also contains another ScriptableObject, called SpellEffect.

    SpellEffect is an abstract type, from which numerous subclasses inherit. These subclasses are known as Delegate Objects. Inside the subclasses such as HealSpellEffect or PosionCloudSpellEffect are the functionality for what that particular spell actually does. The Spell Effects depend solely on generic information passed in by the SpellController into which it is unloaded. The SpellController is totally oblivious as to what the SpellEffect is doing, it is simply a means by which information from the Spell objects components, like its collision, can pass information to the SpellEffect without any awareness of what or where it is. The SpellController is the only piece which ever bridges any kind of gap between Components.

    What information is actually passed into the SpellEffect from the SpellController in order to make a consistent signature to Invoke the constructor of the Parent class, SpellEffect but also make it possible for every child of SpellEffect to do anything it likes. Presumably, the caster, and the Target's Hit by the spell. This is a fairly easy answer, because everything the spell collides with will inform what it hits. this creates the restriction however, that Spells can only effect things which they collide with. This is an acceptable restriction.

    Now consider the class StatusEffect. StatusEffect is an object which represents an ongoing modification to the stats of a character, or an effect which procs repeatedly and targets a particular character. The Interfeace IAfflictable applies to any object which can have a StatusEffect applied to it, and requires the method Afflict.

    This means, that when a SpellEffect hits a player, and does something such as apply a StatusEffect it simply calls the targets Afflict() method, and passes in the StatusEffect as the sole parameter. The Afflict method within the particular controller can do whatever it needs to. Such as handle immunities, modify values, or subscribe to its Events such as OnExpire. This means the SpellEffect is totally ignorant of what its applying it to, every target is totally ignorant of what the StatusEffect does, and each can maintain total seperation of concerns. This system generates no additional constraints, while still allowing for total dynamic usage through connection to its many events and methods which represent when things occur.

    The Question then is "What parameters are passed into the StatusEffect to allow it to function" the status effect may need to slow a character, and will thus need to know how much to slow by, it may need to apply damage, and then it will need to know how much and of what type. It also needs to know who to target. It may need to reproduce a spell effect centered on the targeted player. It may need to heal its caster for an amount of health relative to the post-mitigation damage applied by the StatusEffect, which means it can't be accomplished with a StatusEffect applied to the Caster. Suddenly the possibilities for parameters begin overflowing. Hundreds of potential fields or contrstructor overloads begin manifesting. Extensive filtering is needed to interpret the input and the system is in need of updating everytime new functionality is desired. Perhaps you could limit yourself to the only parameters being the caster, the target, the duration, a float value, and an enum that says what to do with that float. But even then, you're so restricted. It becomes obvious now why understanding Parent/Child is important.

    In this case, the Child is the StatusEffect and the Parent is the SpellEffect which placed it onto another object. In a well designed system, any information the Child needs to obtain must be provided directly by the SpellEffect, but the Child should not know the SpellEffect which created it. What if something other than a SpellEffect wants to apply a StatusEffect? This is why C# contains such things as delegates. in my StatusEffect I can have an OnProcDelegate and an OnProc property, which stores the delegate. The delegate take no parameters and returns void. when the SpellEffect is assigning the StatusEffect is can add to the OnProc or OnStart or OnExpire delegates things such as anonymous functions. I can say

    Code (CSharp):
    1. foreach (GameObject member in Targets)
    2.         {
    3.             statusEffect.OnProc += delegate { member.GetComponent<IDamagable>().ApplyDamage(Damage, DamageType); };
    4.             member.GetComponent<IAfflictable>().Afflict(statusEffect);
    5.         }
    To add an OnProc effect which will deal damage to the target Controller (which implements IDamagable). The Status effect ever takes a parameter or field which represents who it is targeting. Meanwhile, an example such as the PlayerController can manage the existence of the Status Effect by subscribing to its OnExpire effect with the following two methods

    Code (CSharp):
    1. public void Afflict(StatusEffect statusEffect)
    2.     {
    3.         statusEffect.OnExpire += RemoveStatusEffect;
    4.         StartCoroutine(statusEffect.Proc());
    5.         StatusEffects.Add(statusEffect);
    6.     }
    7.     public void RemoveStatusEffect(StatusEffect Target)
    8.     {
    9.         StatusEffects.Remove(Target);
    10.     }
    Note how here, the PlayerController is the ONLY one who is aware of itself. It assigns Delegates to the StatusEffect, and thus the StatusEffect is managed by the PlayerController without ever having the StatusEffect need to contain a reference to the player. No matter what, when that remove is called, the StatusEffect is dropped from memory, instantly clearing up space, with 0 back and forth entangling relations

    In Unity
    In unity, such as with its gameobjects, the components are not oblivious objects who simply offer Events to connect to it. Instead, methods and complex layer all envelop eachother in a way that makes keeping a set method impossible. There is no collection of Components on the GameObject with a simple Apply() method to add them. There is no way to implements events in different scopes, to keep objects separate, or to inject them from some managing object, instead the current architecture of Unity makes it a requirement to entagle and complicate every object beyond belief. Nothing can be collected by the garbage collector, and useless things sit in memory forever. Long stack filling methods are required to Instantiate objects, rather than having its own internal delegation which can capitalize on event subscriptions and threading. No amount of flexibility is offered. It works, sure, but it could be FAR better.
     
    Last edited: Aug 2, 2018
  2. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,536
    What is this thread about? Can you simplify it at all because it seems like it's basically a huge wall of text about how your spell system doesn't scale.
     
  3. Deleted User

    Deleted User

    Guest

    Man, have you got beef.
     
  4. jdgeci

    jdgeci

    Joined:
    Jul 27, 2018
    Posts:
    12
    No, its a huge wall of text about how my sell system does scale, because it conforms to the conventions regarding separation of concerns, and scope/dependency limitations where as Unity does NOT conform to these standards, and even though the system works, there is abundant room for improvement, as well as making it far easier to interact with the GameObject's and their components, opening up their internal events, making them function according to delegation, giving them appropriate separation from their dependent architectures, and generally making the system far more standarized and clarifying some of the "black box" nature of the Unity system, especially in notorious cases such as the AnimtionController debacle that has existed since day 1
     
  5. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Sounds like you haven't got the hang of using Unity properly yet. You can do all of the above within the current Unity architecture. And you have the ability to write your own separate architecture if you need it.

    You may also be interested in investigating the new ECS system. It does allow you to directly do many of the things you are asking for.
     
  6. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    I think a lot of what you're saying has merit for consideration. That said...
    No, it doesn't. It makes it possible to entangle and complicate stuff. It does not force you to do so. There is no requirement for you to use the parent field. That said, for many common tasks in game programming in a Component-oriented design it's really handy to be able to do that stuff (eg: "Get references to all Collider components in child GameObjects"), so I think it's really handy to have it there even if it's not always a good idea to use it.

    If you don't like setting things up by wiring them all within a Scene as GameObjects and Components then that's perfectly cool. And you're right that in many cases that would be a poor, difficult and error prone approach. The thing is... Unity totally does not "require" you to do things that way. Scenes, GameObjects and Components are a grab bag of tools intended to solve certain problems. They're not perfect - I agree there - but for what they're meant to do they're far more than serviceable. It sounds to me like you're trying to use that approach to everything just because you're using Unity and are under the impression that that's how Unity wants you to do things.

    But the thing is there's a full .NET runtime environment at your disposal. You can use that however you want, including any kind of software architecture you care to use. Unity's only requirement is that when you need stuff to interact with a Scene or its contents you do so via MonoBehaviours. In plenty of cases it makes sense to write your code directly as MonoBehaviours and attach it straight to the relevant parts of the scene. In other cases that makes no sense at all... so don't do it!

    Personally, one way I think that non-MonoBehaviour-centric approaches coudl be made a bit "cleaner" would be to have a Scene- or Application-level script that lets us do things completely agnostically of the GameObjects within Scenes. In practicality I'm not sure how much difference this would make, I haven't given it particularly a lot of thought, but it feels like it'd be nicer than needing to spawn one or more GameObjects with Components that exist primarily or even purely to kickstart some .NET objects.
     
    Last edited: Aug 3, 2018
    hippocoder, dadude123, Ryiah and 2 others like this.
  7. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    4,044
    I just spent about 20 minutes looking over your code trying to figure out how you intend everything to work here.

    A core misunderstanding here is what exactly a 'parent child' relationship is in Unity.

    The Instantiate method takes a parent transform as parameter. It's important to understand that the transform hierarchy is not there for parent/child relationships in code. It's there for parent/child relationships in world position, rotation and scale.

    This is a common misunderstanding that many developers have. The transform hierarchy isn't like a folder structure for components (although you can choose to try to make it so), it is really about spatial representation. Sometimes (even often), we developers abuse the transform hierarchy to imply component relationships, but this this is fundamentally a hack.


    ScriptableObject Usage

    Code (csharp):
    1.  
    2. [CreateAssetMenu(fileName = "New Spell Effect", menuName = "Spell Effect")]
    3. public abstract class SpellEffect : ScriptableObject
    4. {
    5.     public GameObject Spell;
    6.     public float Duration = 1f;
    7.     public List<GameObject> Targets;
    8.     public List<StatusEffect> StatusEffects;
    9.     public CharacterController Caster;
    10.     public abstract void OnStart();
    11.     public abstract IEnumerator OnTick();
    12. }
    13. public class HealSpellEffect : SpellEffect
    14. {
    15.     public override void OnStart()
    16.     {
    17.         Destroy(Spell,Duration);
    18.     }
    19.  
    The SpellEffect ScriptableObject is clearly intended to be 1-1 with each spell cast. Each SpellEffect has a caster, targets and spell. In order to make this work, you would need to instantiate a new scriptableobject per cast and destroy it once the effect is complete.

    Although this is technically supported by unity, I have no idea why you would want to implement in this form.

    Code (csharp):
    1. private StatusEffect statusEffect = new StatusEffect { Name = "Poisoned", Description = "Deals damage repeatedly"};
    2. ...
    3. public class StatusEffect : ScriptableObject
    The above clip has two separate errors.

    1 - you cannot call a ScriptableObject constructor directly.
    2 - you really can't call the constructor directly within object construction.

    This is literally invalid Unity code:
    • "ScriptableObject.ctor is not allowed to be called from a ScriptableObject constructor (or instance field initializer), call it in OnEnable instead."
    • "[x] must be instantiated using the ScriptableObject.CreateInstance method instead of new"
    Conclusion
    Both the code provided and the description of the problem are deeply flawed. I think you need to hold back on the judgement calls until you learn a bit more about how the systems you're talking about actually work.
     
    angrypenguin and Kiwasi like this.
  8. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    Sometimes the child has every right to talk back to the parent. Treat it as an entity that can have a voice under the right conditions and not an hierarchical object to be talked down to and only obey. Let the parent manage the children when they have something to say but otherwise just let them go play. A game engine is a simulation of action processes involved with lifeforms and their environments and interactions with such. Hence it involves continuous encapsulations of interactive processes frame by frame. These encapsulations of complex processes by the nature of mimicry to a degree are best represented by composites in which each elemental sub-component may need to communicate with some other lower level, child or governing process to determine the next frame of reference. Performance and rapid development can both be tethered as well as systems that would be very complex in an OOP bog standard setup can be quite simple and powerful to implement and extend or scale using a component based architecture such as Unity's which does not necessarily adhere to OOP principles..The Inspector is a wonderful thing as well.
     
    Kiwasi likes this.
  9. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    I think that many people miss that composition over inheritance is a very good pattern to apply to Unity.
     
  10. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,536
    I think a lot of people miss that you can use whatever pattern you want in Unity.
     
  11. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    Sure, but all are not that good :)
     
  12. jdgeci

    jdgeci

    Joined:
    Jul 27, 2018
    Posts:
    12
    This is what Linq exists for. If the object contains a collection, Linq can be used to filter in ways beyond what the current out of the box Unity offers. In fact, I use Linq to filter for MonoBehaviours that implement IDamagable to figure out which one is the controller. Unity should just have a collection, and give you direct read write access to it, and then Linq can do the rest. All Unity does is get in the way by obscuring object. If I have a reference to a ParticlSystem, why can't I just set it to a different instance. I can set any object as equal to another object of the same type, so why are components different?

    Is the advantage gained from the backward parent/child relationship really SO IMMENSE that it supercedes all cases where I would like to directly set entire components, to overwrite or freely duplicate them with full usage of .NET framework without and concerns or limitation as gained from Unity. Essentially, Unity hamstrings the ability to interact with their GameObjects like a standard .NET object in favor of offering a few tools which

    1.) Are as often the worst way as they are the best way to accomplish something

    2.) Can be reproduced to greater degrees of customization and modularity using preexisting .NET systems such as Linq, Reflection, and the like
     
  13. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,203
    If you can live with the performance hit. Both Unity and Microsoft point out that there is garbage generated (due to the boxing) and that you shouldn't be using it if performance is critical (actually, Microsoft outright says not to use it at all).

    https://unity3d.com/de/learn/tutori...ion/optimizing-garbage-collection-unity-games
    https://docs.microsoft.com/en-us/windows/mixed-reality/performance-recommendations-for-unity
     
    passerbycmc and Kiwasi like this.
  14. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    Depends on how you use it, if you use it for actions you know will not happen often it's ok.
     
  15. jdgeci

    jdgeci

    Joined:
    Jul 27, 2018
    Posts:
    12
    Then this information should be handed down by the containing object, into the object being created. In my PlayerController the Afflict method assigns the delegate which removes itself from the list of StatusEffects. I've successfully enforced .NET standards, I've created code that makes sense, I've maintained the modularity of my code, and I have not sullied the constructor of my code. I am still totally free to create them in any way I choose and just leave things to float in the aether of memory until I need them. GameObject should have an assign method, where I provide a gameobject or component to it, and the GameObject takes care of all the things like wiring up its removal, transform, and relative change events.

    .
    Yes, that is exactly how I intend to use it. Some reasons being, it makes things disposable. As soon as its done, its gone. The Spell Object which has the Tick of the spell as its coroutine is also the one containing the spell effect in memory. The Destroy() method will flag the effect for memory cleanup and its gone instantly. However, any StatusEffects or other such things it creates in the process persist, without keeping a reference to it alive. Also, I can treat the spell effects as Assets, and simply plug and chug with them. I can create them and drop them in a folder and they're completely self contained. Any spell object can use them, they're totally interchangeable. Lastly, I don;t need a prefab for Fireball, for PoisonCloud, for Heal, for any of them. I can have one generic spell object which is sitting on the spawner, and no other premade definitions. No wasted assets, no granularity issues with things wrapped up inside eachother. Each piece can be worked on and used entirely separate of eachother.

    .
    No actually, it doesn't. It run great, better than even I expected. SriptableObject can not be invoked, but children of it can, and all objects have two constructors. Their constructor, and their base constructor. I am using the constructor of StatusEffect, not the Base constructor of ScriptableObject. This available to me, because I correctly employ fields and properties.

    But does it not scream out to you "something is wrong here" when the new() is unavailable. Isn't the point to be able to invoke these classes at will.
     
  16. jdgeci

    jdgeci

    Joined:
    Jul 27, 2018
    Posts:
    12
    Yes, because its boxing things which are already boxed. GetComponents is returning boxed up collections, what you didn't realize was my point, is that Unity should expose the collection directly, manage it on the parent, and allow for a single layer of boxing to occur, the one we make, not the one which comes default with every GameObject
     
  17. jdgeci

    jdgeci

    Joined:
    Jul 27, 2018
    Posts:
    12
    But its not, cause 9/10 the parent has more information about the surroundings and siblings which should be fed to the children as needed, not queried from the parent or siblings. Did you miss the part about how bad it is to have such bloated and recursive things? Here's a fun one, do you know why in a compositional system, I must use GetComponent? because those things aren't really their the collection is being generated and boxed up on the fly rather than just returning an existing one. This is because i can say stuff like

    Code (CSharp):
    1. gameObject.GetComponent<Transform>().gameObject.GetComponent<MyMonoBehaviour>();
    and I return a reference to self, which I can manipulate, and I will begin causing issues, because I've created an inheritance loop when I save the reference in MyMonoBehaviour. It such a broken and backwards system, and it leads to a big recursive entangled mess. It may run, but it could stand to run much better, and make much more sense
     
  18. jdgeci

    jdgeci

    Joined:
    Jul 27, 2018
    Posts:
    12
    Ill be sure to look into it further. I understand it to be similar to MVC for web programming
     
  19. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    You can never write a system that is 100 safe for idiots to use. You should write your own API ontop or Unity to handle these things for you. For example we have a event bus we have written, it's context aware so a root or child can publish events only to listeners parented to the root. This eliminates the need for get component code in the domain plus it also decouples the code since anyone can fire events without knowing who fired it
     
    dadude123 likes this.
  20. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,203
  21. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    dadude123 likes this.
  22. Errorsatz

    Errorsatz

    Joined:
    Aug 8, 2012
    Posts:
    555
    Either there's a miscommunication here, or what you're saying doesn't quite make sense. In this case, you've just created a copy of this, which is not any kind of "inheritance loop". It seems like you're confusing class inheritance with other parent/child and container/element relationships:
    * Transform Hierarchy != Inheritance
    * GameObject/Component relationship != Inheritance

    Double-linked trees (the child nodes have a reference to the parent node and vice-versa) are not uncommon, nor are they an anti-pattern.
     
    angrypenguin likes this.
  23. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    4,044
    Don't bother, this guy is seriously clueless. It's bizarre writing, like he knows some of the right words, but he really has no idea what those words mean.
     
    dadude123 likes this.
  24. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    The fact that you are using this line of code as an example shows that you really don't understand what's going on in Unity. This line is exactly equivalent to
    Code (csharp):
    1. GetComponent<MyMonoBehaviour>();
    . If you write all of your code this way, its no wonder that you think Unity architecture is backwards.
     
  25. jdgeci

    jdgeci

    Joined:
    Jul 27, 2018
    Posts:
    12
    I don't actually write my code that way, I am using that as an example of something that shouldn't be possible. I should not have
     
  26. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    You explicitly described this as creating an "inheritance loop", which is incorrect. It's just a round-about way of getting a self reference.

    And, by the way, it has to be possible, because it's entirely possible that some queries might validly end up returning a reference to the calling object.

    Right... Linq is a great tool. If it fits your use case you can go ahead and use it. You have access to the data you're talking about filtering. What's the problem?

    That would have nasty side effects that nobody wants. Or it would necessitate a complete redesign of the engine which would add other complexities elsewhere.

    Again, it very much sounds like you're claiming that Unity and everyone in this conversation are all "wrong" because we don't do things The One Right Way that you're aware of.

    For what it's worth... while Unity provides us with a .NET runtime and uses C# as a language, Unity programming is not .NET programming. Claiming that Unity should do something a particular way because it's the .NET best practice approach (or whatever) isn't practical, because Unity applications are not .NET applications. The context and priorities are quite different, and that means different approaches are chosen to suit.

    In Unity, .NET is used as a scripting runtime within a game engine. That changes a lot about the context of how and why we do things. A lot of .NET practices are useful in this context, but others aren't. One thing to note in particular is that much of the data we access with our scripts is not .NET data and does not actually exist in the .NET runtime - it gets marshalled to or from native data structures upon request. Another is that .NET in general is designed around developing software where developer productivity is almost always of higher priority than performance, which is less often case in game development.

    For example, Linq's approach of making copies of data for every step of an operation is efficient for the developer, not the computer. In the context of the above, how is using Linq to "get all colliders in child objects" more correct than using Unity's built-in function that directly gives us what we want, optionally with zero allocation?

    Honestly, I suggest reading Jason Gregory's Game Engine Architecture. It'll help explain a lot of what's going on under the hood and give you far more context about the stuff you think is "backwards" than can be outlined in a forum thread.
     
    Last edited: Aug 4, 2018
    Ryiah, ippdev and Kiwasi like this.
  27. zombiegorilla

    zombiegorilla

    Moderator

    Joined:
    May 8, 2012
    Posts:
    9,052
    Similar in that they are both design patterns, but they are different. You can mix and match them. I worked on ECS designed game that had sub mvc framework for the ui that was essentially driven by ECS and communicated back.

    Rarely, if ever, does a decent sized, shipped game commit to a ‘pure’ pattern. It may be heavily weighted in a direction, but depending on the needs of the project you color outside the lines for pipeline, performance or other considerations.
     
    hippocoder, Ryiah and Kiwasi like this.
  28. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Why not? There is nothing inherently wrong with having circular references. Especially in a composition pattern. Each container needs to know what scripts it contains. Each script needs to know what container it is in.
     
  29. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    Agree but circular references can also indicate high coupling which is bad
     
    angrypenguin likes this.
  30. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    No 'can' about it. GameObject and Component are as tightly coupled as two classes can be. There is no getting around that. Is that what the OP is going on about with his endless paragraphs of words?

    The coupling is a little bit annoying. But its not really a big deal.
     
  31. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    5,358
    Not what I meant, I mean if you have a circular reference its often a sign of high coupled code. Class A knows about B and B knows about A. In an ideal world, they dont know about each other they communicate with a bus or similar. Next best is one knows about the other and most high coupled is that both knows about eachother.

    And I dont talk about the Unity API I talk about the game core domain
     
    nhold likes this.