Parent class with various child-class properties: issue with coupling

Started by
9 comments, last by SummonThanatos 4 years, 7 months ago

Hi all,

So I've been thinking about some ways to extend the functionality of game objects by adding some properties using the composition approach with writing classes.

This is my concrete idea and problem:

Let's say we have several properties of a game object: Renderable, Moveable, etc..

And we have some concrete objects: Background (which has the Renderable property), Character (which has both the Renderable and the Moveable property) and Zone (which has only the Moveable property).

So we have the following classes (written in C++) :


class Renderable
{
  void render(Screen*)
  {
    // render at X,Y
  }
};

class Moveable
{
  void move_to(int x, int y)
  {
    // change X and Y
  }
};

class Character
{
  Renderable render;
  Moveable move;
};

class Background
{
  Renderable render;
};

The problem is where to store the X and Y variables and how to access them?

As you can see X and Y are both used by the Renderable and the Moveable properties for calculation.

 

If we instantiate them in Renderable, they are not visible by the Moveable property and vice-versa.

If we instantiate them in both Renderable and Moveable we need to maintain two copies of each variable and this calls for trouble.

If we instantiate them in the parent, Character, for example, we need to have a reference or a pointer in the child objects pointing to the parent, which is also not ideal because we're restricting the child to certain parent class only and it leads to circular dependency.

 

Can you suggest another approach that could make use of these property classes but avoids the complications above?

P.S. I also thought of another possible solution: Instead of using composition, we could use inheritance, but in the example above the Character class would then lead to multiple inheritance, which has its own problems (like the diamond problem) and is not supported by most OOP languages (C++ is an exception).

So what would you suggest?

Thanks.

Advertisement

It's an unavoidable fact that things will depend on other things. In this case, both Renderable and Moveable objects need to depend on the concept of a position. So, that could be another component (Positionable?), or it could be an interface implemented by Character - those are your two main approaches.

If you take the example of the Unity engine, every GameObject contains a Transform component - so they can always assume there is a position available.

Practically speaking you either have to wire in the dependencies at creation time (e.g. bake them into constructors etc) or you look them up at run-time (by giving components unique identifiers).

Actually, that's a pretty good idea.

A class which holds the common data for both Components/Properties, say Position.

Then the two properties hold a reference or a pointer to this Position.

And the Parent class encapsulates everything.

This way the components would only be coupled to the Position, as they should be, and every object that has a Position can also have the Renderable and Moveable properties/components.

 

The only overhead in this situation is the fact that the components hold a pointer/reference to the Position object, so 10 components means 10 additional pointers to some common data.

 

Well, you cannot benefit from syntactic sugar without some overhead.

 

Thanks for the reply, Kylotan.

On 9/20/2019 at 4:22 PM, SummonThanatos said:

he only overhead in this situation is the fact that the components hold a pointer/reference to the Position object, so 10 components means 10 additional pointers to some common data

If you would do it right, where right means in an atomic data aspect, you won't have logic in the components that rely on data hold by a super class. Instead you might want to keep only specific data in your components that no other component has (atomic data) and handle anything else by logic on the outside.

This is the ECS approach where components on an entity only keep specific data and specific systems collect specific combinations of components. The overhead here exists during construction time where components that rely on other components have to be checked for validity. So the Unity Render-System takes the Transform-Component and the Renderer-Component to display the Mesh placed in the Mesh-Renderer-Component at Position of the Transform-Component.

Those components don't know of each other nor keep pointers to each other. Take this as a hint for optimization ?

5 hours ago, Shaarigan said:

If you would do it right, where right means in an atomic data aspect, you won't have logic in the components that rely on data hold by a super class. Instead you might want to keep only specific data in your components that no other component has (atomic data) and handle anything else by logic on the outside.

So you suggest that the component should only hold state and state-specific functionality and the global functionality which depends on external-to-the-component state should be implemented elsewhere (in the parent class or in a separate unit).

So in my example, instead of relying on Renderable to do the drawing, we should only use its state and do the rendering outside like this:


class Character
{
  Renderable renderable;

  void render(Screen*)
  {
    //get renderable state and draw using it
  }
};

I guess it depends on how tightly coupled you want your components to be.

This is the usual Entity-Component-System approach, it is used when you have interchangeable object behavior, a Camera Entity, a Character Entity and a World Entity that each share certain behavior like the capability to be moved arround in the scene, to be rendered etc. but share a huge common base. Instead of writing Camera, Character and World classes, you attach characteristic components to anonymous entities that become something specific in that process.

Then some logic comes in to handle different combinations of those components and perform well typed tasks. A character doesn't render itself rather than have a Render System perform that on all entities with a render component attached at once. It has some advantages especially in maintaining your game code but also has some clues you need to handle for complex scenarios.

It is just a suggestion because your use-case sounds like ECS could improve your code

Sounds like the solution I am looking for. I've known about this design concept. I just didn't know it was called "Entity-Component-System".

I wonder about one thing though. How do the different systems communicate with each other?

Let's say for example that the MovementSystem detects a jump action and updates the velocity of the object. But then, also the RenderingSystem needs to know about this in order to start the jumping animation.

How does the MovementSystem communicate this information to the RenderingSystem?

Through flags in the entity? This would violate the concept of objects not holding shared state, wouldn't it?

Or maybe an Observer pattern? The MovementSystem notifies all listeners that object X jumped and the RenderingSystem (as a listener) receives this information and updates the rendering state of the object.

The easy, quick answer: they don't.

The more complex answer is that systems work on components, nothing else. There exist approaches to have events between systems but we avoid that because our systems run in N - 1 threads where N is the number of CPU cores of current device.

Systems are allowed to modify the data of components so in your case a MovementSystem would also take a character state component that keeps current state of your character to check if a 'jump' is currently possible and sets the upward velocity. The state is also used for the animation system to trigger the right animation.

A PhysicsSystem now gets the entity and changes the TransfromComponent taking velocity and other entities into account such as walls, platforms, ground and other characters.

Finally the component is taken from the RenderSystem and pushes the transform, shader states and whatever into the graphics pipeline.

As I said above, Systems perform atomic tasks as same as compnents keep atomic properties. An atomic task means it is used to performs exactly one job, there don't exist multi-job system that do the movement, toggle animations and render the entity all at once. It is some philosophy of seperating different tasks frome ach other as same as you won't put anything together into a single method

19 minutes ago, SummonThanatos said:

Sounds like the solution I am looking for. I've known about this design concept. I just didn't know it was called "Entity-Component-System".

Be aware that there's a gulf between the 'pure' definition of 'ECS' that people talk about, and the real-world examples that people actually use in practice. Don't get bogged down in following dogma. For example, there's no reason why you can't have components that interact with each other - Unity and Unreal do this, and they power most of the world's games.

1 hour ago, Kylotan said:

Be aware that there's a gulf between the 'pure' definition of 'ECS' that people talk about, and the real-world examples that people actually use in practice. Don't get bogged down in following dogma. For example, there's no reason why you can't have components that interact with each other - Unity and Unreal do this, and they power most of the world's games.

Yes, yes, sure. That's why I'm investigating different strategies for handling the interactions between objects in a game. I like to write my own engines rather than sticking to someone else's implementation. I'm just collecting ideas.

Also, a dogma is good as long as it makes your life easier. If you find yourself fighting with the dogma or trying to bend your ideas to fit with it that's probably a signal to change the strategy.

 

1 hour ago, Shaarigan said:

Systems are allowed to modify the data of components so in your case a MovementSystem would also take a character state component that keeps current state of your character to check if a 'jump' is currently possible and sets the upward velocity. The state is also used for the animation system to trigger the right animation.

 

OK, so actually it's like having flags but the flags are stored in some of the components. What I missed is that systems can access multiple components at the same time, so for example the RenderSystem should access the RenderingComponent (to get the visual data), the PositionComponent (to see the position of the object), the MovementComponent (for the animation of the object) and possibly some other components to do the complete rendering of the object.

Thanks.

This topic is closed to new replies.

Advertisement