Component Dependency

So this questions spans a bit outside of Unity, as I am looking at composition as a whole. Often times I hear of the wonderful things that composition can bring: Re-usability, modularity, easier debugging, easier to collaborate on, fewer dependencies, the list goes on. This concept is honestly something I just learned of about a year ago, and yes I am a little embarrassed to say that, but this design is something I am still trying to fully understand.

I am trying to adopt this design more whole heatedly, but I am curious at how you handle component dependency. I have heard of the use of interfaces, using “SendMessage”, aborting opperations upon not finding a needed component, adding missing components at runtime, there a number of ways to keep components working independently. Yet, most of the examples and code I have seen simply rely on other components being there to function.

Let’s look at two examples:

Example A
Let’s say we have a component which handles the aiming of the AIs gun, we will call it GunAim. We also have a component that aims the AIs body, but it only allows it to aim on the Y axis, but can be fed the same target information as GunAim. We will call this component BodyAim. Lastly we have a component that can drive these two components, the AimBot component.

In this example we have one component which can drive two other components, with BodyAim being able to be applied to any object while GunAim would only work on objects holding a gun.

Example B
This time the GunAim and BodyAim components take in different data, and thus cannot be driven by the same component. To that end we now have the GunAimBot and BodyAimBot components that each drive their respective Aim components.

This example is similar to before, however now GunAimBot and BodyAimBot both heavily rely on another component.

Obviously these examples aren’t very complex, but the question of dependency remains. How do you solve it? Is there a method you prefer? Would your method differ outside of a Unity environment? How would you make components generic enough to be able to be slapped onto any object without throwing out errors? Am I fundamentally misunderstanding composition?

Apologies for my lack of experience in this topic.

What is the dependency problem in the examples you’ve given? I understand that you could reduce dependency in both cases, but you seem to be approaching it as an absolute that you must achieve, as opposed to an ideal to strive for in order to have generally improved code maintainability.

To solve something you must first understand what the problem is. You’ve given it a name, but I’m not sure what you think is bad about those examples.

Why do they take different data? Based on their names I assume that their job or role is to aim at a specified object or position. With that in mind, why do they take different input? All the input should do is make them aware of the desired aim point. Even if that desired aim point isn’t possible for them to reach, that is an implementation detail that they should take care of internally.

For example, lets say that BodyAim can only rotate on its Y axis (as you’ve described) and you give it an aim location that is at a different height. That’s… actually perfectly fine, isn’t it? It should just rotate so that it’s pointing at the desired point along the horizontal plane, and all it has to do to achieve that is ignore the vertical component of the input.

Furthermore, lets consider the role split here. You have “AimBot”, and its role is (I assume) to decide where to aim. You then have “GunAim” and “BodyAim”, their roles are to point their relevant Transforms at the nominated point of aim. With that in mind:

  • “AimBot” should not know or care how “GunAim” or “BodyAim” implement their aiming. It can both make and communicate its decision without caring about these details. It also probably shouldn’t be aware of any constraints they impose. (If it does need to be I’d design it differently.)
  • “GunAim” and “BodyAim” should be able to accept identical inputs, and should not care where those inputs come from. Any differences in how those inputs are used are internal implementation details.
  • As such, both “GunAim” and “BodyAim” can share an interface, eg. “IAimable”, or a base class, eg. “TransformAimer”.
  • As such, “AimBot” can now be dependent on that interface or base class rather than on specific component types.

You could go one step further and use events or message passing to further reduce dependency and/or coupling, but I’d ask what the point is. What practical benefit does it achieve? Does it make sense to have a component responsible for deciding what to aim at that is completely unaware of what is doing the aiming?

On that note…

… I normally wouldn’t, because it doesn’t often make sense.

Sometimes it does make sense, and in those cases it’s usually pretty easy to do. For example, the other day I wanted to be able to represent a score value for any GameObject for a game I was making. In that case it does make sense to be able to drop it on anything and have it “Just Work”. And in that case it was also trivially easy - it’s just a MonoBehaviour with a couple of exposed values. It doesn’t depend on anything. Anything that depends on it can just look it up. Job done.

Does it truly make sense to have an AimBot component, however, that doesn’t depend in some way on components that can actually do aiming? Sure, you can use complicated mechanisms to avoid it throwing errors, but even without errors it still can’t do anything. In fact, by avoiding error messages in that case you’re just obfuscating something that is wrong with your code or your design. You’re making life harder for yourself.

Contrary to intuition, thrown or logged errors are your friends. They tell you when you’re doing something that doesn’t make sense. If you’ve attached an AimBot to a GameObject that doesn’t have any IAimables… what exactly were you trying to achieve? Was it a mistake? Did you forget to add IAimables? Has something been deleted by accident? If I do something like that I want an error message to tell me about it, immediately, loudly and clearly. That way I know something needs fixing and I know where and what it is.

If I avoid the error messages, though, all I’m doing is hiding a problem. I’m not going to know about it until later, and it’s going to take me far longer to solve it because the details are buried.


All of that said, there are cases where completely decoupling things makes a lot of sense. For instance, my Health component really shouldn’t have to be aware of my scoring system, nor vice versa. For this kind of thing, I strongly recommend looking into event systems. Using an event system messages can be passed between objects that are completely unaware of one another. Conveniently, Unity has just such an event system built in.

(Note that I’m referring to UnityEvent. EventSystem is for input handling.)

4 Likes

Explain this to me as if I were a newborn infant.

Never mind, just program my game for me or I’ll tell people you are uncool.

…I just clicked the reply button to my own post. Why is that possible to do?

1 Like

First up its worth acknowledging that dependencies are not specifically bad. Some classes simply are closely related to each other. They need to know details of how other classes work to function. Games in particular tend to have a lot of systems that are closely coupled.

Breaking dependencies between unrelated systems is a good idea. Where you need dependencies making them rely on interfaces or abstract classes instead of concrete classes is a good idea. But writing a complicated messaging system just to avoid a little coupling is a bad idea.

In your example breaking dependencies using interfaces is pretty trivial.

  • GunAim and BodyAim both implement an interface called IAim
  • AimBot relies on IAim.

Now we have an AimBot that does not care what type of aiming system its attached to. As long as the interface stays the same, you can swap out any other piece of the system with no effect on the other classes.

Edit: Plus all the good stuff @angrypenguin said while I was typing.

2 Likes

Oh, you know, it’s one of those situations where one component isn’t aware of the other. :stuck_out_tongue:

5 Likes

So you can talk to yourself. It’s both comforting when you are alone - and entertaining to the rest of us. :smile:

When I first used Unity and was still a fanboy of interfaces, events and other such stuff Unity nearly drove me bonkers. It seemed like it was designed & implemented in such a way (component-based) to almost require creating a bunch of spaghetti code in the form of a mass of dependencies being wired up here, there and everywhere.

So like a “good programmer” I set out to solve this problem. I used interfaces to decouple a lot of things from requiring hardwiring. I wrote an event system to decouple other things.

It took several iterations across many (never finished) game projects to finally achieve my goal. From an engineering perspective it was a good thing. From a development perspective it was a slow thing and a more complex thing. In a sense I had traded one problem for another.

I realized the root cause of the problem was a combination of all of the things I had thought were good: component-based, interfaces, event systems and other “proper modern” practices. So I threw it all out and simplified. I returned to a procedurally-oriented devolopment approach like I used decades ago. However, for my day job I still use all of these things. My day job is business application software development with a SQL Server DB and TCP/IP communication. I find all of these modern practices fit business development much better than game dev.

So… I just say all of that to say… simplify. Figure out the simplest way to do things. You can spend a lot of time engineering means to get around a problem when you could just remove the root underlying problem itself.

My current Unity projects look like this: a single GameObject named GameManager is the the only thing that has a script handling Awake and Update attached to it. Another GameObject named Configuration holds all of the config info. All other code exists completely outside of the Unity Editor not attached to anything. I have several Object Manager singleton classes. Each of which manage a list of objects. GameManager calls Init on each of these managers in Awake and calls Update on each manager in its Update method. I find it highly streamlined and much better for game dev. Which isn’t really surprising since it is how I was doing it long ago.

Just throwing that out as something to consider. Don’t be afraid to go against convention. It’s not always right and us software engineers have a tendency to like complex systems and abstraction and other craziness. So you will see these things parroted around the web as the REAL way to do it right. And it may not be the best way. I don’t think it is. Not for me at least.

1 Like

[RequireComponent(typeof(…))].
Don’t use sendmessage.

In your scenarios there’s zero need for any kind of dependency. For all pracitcal purposes both “aim bots” (bad choice of a name, by the way) don’t need to know where each of them is. They only need to know location of target, their rotation axis and rotation limit. And that’s it.

Now, if you had an IK chain there (you don’t have an ik chain there), then you could have one master controller controlling entire chain and driving every element of the chain. once again, no need for dependencies. Although even in case of IK chain you don’t necessarily need to know of other components in the chain. You only need to know location of IK target, and location of IK target’s desired position.

In multi-component scenario I just directly reference them by drag-and-drop.

     [SerializeField] SomeComponent someComponent = null;//set this up in inspector

Or put them onto same object and grab them within Start() (and disable component with a warning message if component isn’t present).

1 Like

Looking at it now that example is abysmal, I sincerely apologize. It doesn’t really help convey my question at all, and if anything, has detracted from it. Never the less thank you all for your insight, you’ve managed to pull some sense out of my mess of a post. I was more so looking to understand dependencies in composition as a whole, and was wondering in what ways we avoid it, and when it is necessary, how we enforce it.

This diagram was one that came to mind

Totally this.

I wrote one of my projects creating interfaces and events to try and decouple the systems as much as possible. My last few projects, I didn’t create as many interfaces and abstraction (I still used plenty of events). The difference between my earlier project and my last few, is that I shipped them. Don’t over-engineer if you don’t have to. However, I can understand if you’re in a situation where you are knowledgeable of design topics, yet you don’t have a full grasp of how to implement it, or when it’s useful. That’s when you really just have to implement stuff, and then when you feel like it needs to be refactored (for instance, if it’s difficult to read, work with, or extend, or there’s a lot of duplicate code that you can wrap up and encapsulate), then refactor it as necessary.

2 Likes

Where did you find this abomination?

ONE class. “Npc”.Optionally with “canFly” toggle.
On Npc, you have “AnimatorController”.
In controller, you have parameters “forward/right, isFalling, isSwimming”.
In update cycle you detect necessary parameters and pass them to controller.

And that is it. It encompasses every single item in the deagrom.

There’s no reason to subclass the duck, make “quackable” interface, when you can reduce it into ONE class with optional components.

Mallard/Redhead/Rubber/Decoy is Mesh used by the duck.
Quack/Squeak is an audio source on the duck. No audio source - it can’t use voice.

Basically instead of “virtual method Duck::display()” you’ll have something akin to display(duck->mesh).

No reason to create new virtual method with a fancy name, when difference in behavior can be represented by Data stored in objects.

2 Likes

This is true. The whole thing could be dramatically simplified by simply embracing component based design. Break the Display method off into its own component. None of the rest of the system cares what the mesh is. Break the Quack off into its own audio component. Break Fly off into a movement system.

Do all of this and you have no need for the Duck class to exist. Duck simply becomes a GameObject with a Fly, Quack and MeshRenderer component. Take out the duck class from your diagram and the whole thing simplifies.

Here is the structure in Pond Wars

2670103--188406--Structure.png

This is not a particularly complex game, but it does illustrate some of my philosophy about structure.

Key ideas to note:

  • Dependency is only one way
  • The code is organised into systems or layers
  • Systems are allowed to be highly coupled internally
  • Dependency between systems is kept to some narrow defined points
  • A system may only be dependent on a lower level system
  • Most of the game entities don’t have specific classes, instead they are made up of several components

There are some good advantages from this structure. If I pull out the top two layers I have a template framework that can be used for any other game. Its also relatively straight forward to replace any single part of the system

2 Likes

@Kiwasi : What software did you use to make the flowcharts?

I don’t remember. Probably power point. It might have been IGrafX, I had access to that on my work PC at the time. Unfortunately I don’t have the original to check.

Its also worth pointing out that I didn’t start out with the chart. I built the chart when I was in need of a new feature. The basic idea was to chart all my dependencies so I could see what changing some of my code would effect. The first chart was heavy spaghetti. There were a ton of other dependencies, circular dependencies, and all sorts of other evils. Figuring out side effects of the new feature ended up being more work then refactoring it. And since I had the chart made I kept it for a future guide to structure.

Also in pure trivia the original pen and paper drawing looked much more like a traditional chemical engineering block flow diagram then a nicely layered chart. I tend to be very much a product of my education.

1 Like

I use a program called Diagram Designer for my flow charts. It’s actually pretty cool, though not as fancy as something Microsoft or other big names can shell out. There are a lot of options for nodes/connections and there’s even a way you can create your own custom node templates which is really neat.

2 Likes

Reducing Component dependency is a matter of effective planning. Component architecture was one of the big hurdles for me in learning Unity initially. It took some practice and experimentation before I realized how to use them more effectively. To truly embrace components you have to stop thinking of your game objects as over-arching “types” that encompass functionality. Proper components encapsulate small, self-contained data and functionality, and can be added to or lifted from different objects at will. Approaching your planning in this fashion will allow you to drastically cut down on dependencies.

And dependencies are something should be minimized, but not necessarily exorcised. While reducing them is well and good, you don’t need to get rid of them entirely. There will be some circumstances where adding a component dependency or two is actually the best approach.

4 Likes

Actually now I thing about it I made a video about this. It might help with conceptualising how to solve the duck problem in Unity. It illustrates the idea (if you can call my art illustration) that @RichardKain pointed out.

https://www.youtube.com/watch?v=1YGVP6wsxj0

1 Like

I"m fairly sure something like this can be done in openoffice quickly.

It is worth keeping in mind that this can be taken too far. The example of that is unreal engine. It has UPathFollowing component, for example. If you need, say, to hijack ai pathfinding routines then you’ll quickly discover that pathfinding code bounces through 3 or 4 different components, all of them replaceable and with virtual methods that can be overridden. At this point architecture stops being easy to understand and becomes confusing.

The example with a duck is simple, because you can reduce everything to ONE class and one type of data, most likely without inheritance anywhere in the picture. That’s very close to ideal.

In the end my personal favorites are KISS/Yagni principles.Basically, splitting something into component/class is probably not a good idea… unless it is absolutely necessary.

1 Like

This is true too. There is a balance. And often the balance point is often very project specific. Ideally you want to keep different systems split. And you want to make sure that systems that are closely related are not split.

Unity’s UI system is another good example that may include too much split. It drives me crazy in its insistence on jumping almost at random between the event system, the input modules and the UI components.