2

I have a simple setup where I've decided to reduce my overall OOP design and opt for lots of small individual components that I can add and remove from a particular object.

The whole point of this was so components could be added and removed with out depending on other components - essentially they are decoupled.

But this is hard to setup in my case, as I have an object that depending on some of its components attached will depend on whether it is functional.

Let me give an example:

Say the object is a SecurityCamera object for a stealth game.

It has the following components attatched:

Vision // defines what it can see
PowerInput // defines if it currently recieves power
Health // defines if it is damaged
Movement // Whether the camera can move back and forth depends on health and power being available

Now since each component is separate the problem I have is that the Movement script needs to know about Health and PowerInput. It cannot move the camera unless the camera has health and power.

Currently the way I have done it is a bit lazy: The Movement component checks the state of the Health and PowerInput components in every frame. To me that's not ideal, I'd prefer a less expensive check that I don't have to do in every frame, in that if Health reaches 0 or PowerInput no longer gets power, movement will then stop working.

What is the clean/performant way to link Movement to PowerInput and Health and for movement to be aware of the state of those components so it decides if it can move or not?

It's very easy at the moment for it to break if I forget one of the components so its going to get messy as i develop a project with lots of components for a variety of objects where they depend on each other a lot.

I use C# for my project though this isn't particular language specific.

5
  • Why not pass that data to Movement when you tell it to move inside of SecurityCamera ? Decoupling doesn’t have to be zero dependency by the way, it’s mostly about depending on contracts rather than concrete implementations. Commented Aug 13, 2021 at 6:40
  • What do you mean by pass the data? Wouldn't that mean PowerInput and Health need to know about Movement to pass the data along? I'm not sure they should know about Movement rather it should be Movement knowing about the two components instead at least thats what my mind thinks is more logical unless I misunderstand what data you mean ?
    – WDUK
    Commented Aug 13, 2021 at 6:48
  • I mean passing the objects to Movement. Commented Aug 13, 2021 at 7:37
  • Why movement has to has any other logic than changing cameras' position? Cameras' behaviour belongs to the camera. How to execute each thing is what you decouple
    – Laiv
    Commented Aug 13, 2021 at 9:17
  • "It has the following components attatched" - can you clarify what do you mean by that; are you using the word "component" in a general sense, or is this an engine organized around the ECS approach (so it's an ECS component)? Are the components just data structures, with separate scripts, or are components and scripts the same thing? When you say "script", are you using C# as a scripting language confined by the rules of some existing game engine, or is this your own engine and you have complete control over how scripts are organized and executed? Commented Aug 13, 2021 at 10:25

4 Answers 4

2

To me thats not ideal, I'd rather a less expensive check that i don't have to do every frame, in that if Health reaches 0 or PowerInput no longer gets power, movement will then stop working.

Be very, very careful here. It's not that you can't change your logic to be event-driven, but event-driven systems can get really complex and difficult to debug. In any sizeable codebase, you quickly lose the overview because events obfuscate the flow of your logic at runtime.

For example, while both PowerInput and Health are individually able to disable Movement, neither of them is individually able to enable it. Just because the power is restored doesn't mean that the health still isn't 0. This inherently requires some logic that spans across the three components in order to make that combined evaluation, which goes against the very outset you're starting from here (I agree with the posted comment that decoupling doesn't mean zero dependency and that you may be setting the bar too high).

It might not matter when your game only deals with the act of disabling cameras and never being able to re-enable them, but it is a very important consideration when you start applying this pattern at large.

Checking the value of an in-memory variable is not a significant performance hit in any way. There is likely no actual performance bottleneck to address here, no matter how many security camera objects are running in your level. Unless there is some underlying IO operation here, which I very much doubt, I would suggest you don't try to prematurely optimize this at all.

2
  • Yeah i did test an event based approach but i was finding it messy and hard to keep track of what was really linked to what. Which is kind've what led me to ask the question as i am unsure on what common solutions people employ for this situation.
    – WDUK
    Commented Aug 13, 2021 at 23:34
  • @WDUK: What you describe in the bit of your question that I quoted is an event based approach. What you are describing is pretty much exactly "on [event] do [handler]".
    – Flater
    Commented Aug 17, 2021 at 7:39
1

I pattern I have used is to wrap a value together with an event that is raised when the value is changed. Something like

public interface IMyValueWithEvent<T>{
    T Value {get; set;}
    event EventHandler<MyEventArgs> Changed;
}

This allow fine-grained dependencies between components to be expressed. You might also want to have a separate interface that is readonly. I find it most useful if the events is only raised if the object actually changes, but that requires that the T type have some sensible equality comparison.

With some additional helper classes it can be possible to combine properties, somewhat similar to Linq

var canMove = powerInput.Zip(health, (p, h) => p && h);
canMove.Changed += OnCanMoveChanged;

Note that this is a pattern I have found useful in some cases. There are some downsides, the major ones that I have experienced is difficulty debugging code, and difficulty navigating code. So moderation might be advisable.

1

I suggest the solution here is to use Rx and the Observable type available in C#. For example, your Movement module would subscribe to Health's and PowerInput's Observables. Instead of Movement checking Health and PowerInput every frame, it changes its state based on the output of the two observables.

The nice thing about this is that it's easy to see what Movement needs by simply examining it to see what it subscribes to. Here's a great talk on the subject - Reactive Programming: Why It Matters

0

Consider having your components post messages to each other rather than call into each other's code directly. This can save you a lot of trouble.

You can then either have a scheduler let each component do its thing in succession or let each component have it's own thread. Do not let components address the message queue of other components directly, use a broker that routes based on sender and recipient. This makes things easier to control and monitor.

I worked with a system that had everything done wrong in this department. So I have an idea about the kind of trouble you can expect if you do not properly decouple before starting to use multiple threads or throw actions around.

A message should contain a sender id and a request or an informative statement that the receiver can interpret. It is a little overhead that will likely be well worth it as your system grows more complex.

3
  • How does a message differ to an action exactly ?
    – WDUK
    Commented Aug 23, 2021 at 20:12
  • @WDUK An action (in a C# sense) is a piece of code the recipient can execute. It won't be able to tell what it does though or how. A message is a notification, a command or a request with meta data. It will be up to the recipient to decide whether to comply, to deny, to ignore or to acknowledge or postpone. That is more loosely coupled and potentially safer. Services needed to execute the action may be down already or not up yet. "here's some code, just run it" is hardly ever a good idea. Actions in a queue are very hard to debug. Who queued it? If you have a proper message you can inspect. Commented Aug 24, 2021 at 6:51
  • I've been trying to find a guide on using a message system setup in C# but im not finding much - i just keep finding observer pattern.
    – WDUK
    Commented Aug 24, 2021 at 20:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.