0
\$\begingroup\$

I have implemented an entity component sytsem (ECS) in my game engine (SFML and C++ 17). An entity has an array of components. You can get a components by its type e.g.: myEntity.GetComponent<RenderComponent>(). So far so good.

The problem

Best to illustrate this point by an (admittedly oversimplified) example:

Some systems or possibly other components might want to be able to call myEntity.GetComponent<RenderComponent>() without knowing the type of that render component (e.g. whether it is a house render component with a sprite and a paritcle system for smoke or a character) but knowing that every render component has e.g. a GetPixelMask() method.

A more concrete case of this is the collision component that might be implemented as a collision box or a pixel mask that may come from any type of render component. However, every collision component would have a method bool Contains(Position). I would like to be able to loop through all entities with a collition component and check whether e.g. the mouse click lies within them.

I want a clean solution. It must not use polymorphism, it should solve the problem in a neat way.

Possible Solutions

  • This is bad code smell to me but still its a solution. Screw polymorphic behaivour and just add an if cause to the systems. Something like If(myEntity.HasComponent<HouseRenderComponent>()) { /*Call method a*/ } and If(myEntity.HasComponent<CharacterRenderComponent>()) { /*Also call method a*/ }

  • Another solution would be to make it so that the component are 'configured'. This is my current solution which kinda works but isn't all to clean (I find). This would mean making some RenderObject base class and letting the RenderComponent manage a dictionary of RenderObject pointers. RenderObjects would have to be added through a method like AddRenderObject() and accessed through an id which isn't all too efficient (Not my biggest concirn here). However, every RenderComponent would have the same methods and the specific type of the RenderComponent would be defined through the RenderObjects added.

Question

I'm pretty sure that adding some kind of real polymorphism to the components would be possible and I could figure out how to do it. The question is: is that good design? - as for me it kind of defeats the point of the ECS. I'd be grateful if you could share your experiences and how you have dealt with this. I can't be the first one to have come across this problem!

Thanks for your help!

\$\endgroup\$
2
  • \$\begingroup\$ Why do you feel that this kind of polymorphism defeats the point of an ECS? What specific downside are you concerned about? \$\endgroup\$
    – DMGregory
    Commented Mar 3, 2018 at 19:38
  • \$\begingroup\$ I actually think you are the first one to come across this "problem"; problems most often don't exist in and of themselves, but rather in how we think about tackling a given situation. As soon as that thinking is guided / corrected, the apparently objective "problem" goes away; good that you came and asked here. \$\endgroup\$
    – Engineer
    Commented Mar 4, 2018 at 14:33

1 Answer 1

3
\$\begingroup\$

Both your possible solutions are unnecessarily complicated or brittle: generally, ill-advised.

Polymorphism

Your question title is

Make the components of an ECS polymorphic

but then you don't want to use polymorphism since you state,

I basically just want a clean solution. It must not use polymorphism, it should solve the above problem in a neat way.

...

I'm pretty sure that adding some kind of real polymorphism to the components would be possible and I could figure out how to do it.

So you contradict yourself utterly. You want polymorphism without using polymorphism, is that it?

Since you have given no reason as to why you think polymorphism in an ECS is problematic, I can tell you straight off the cuff that it is, 99% of the time, normal and desirable in engines and ECS. Polymorphism is little more than a function pointer, which is a way to redirect logic without the costly use of conditionals (if, for, while, for each, ? etc.) whose use you suggest in ex. #2. I've implemented polymorphism in C (which lacks it) and it is a very simple and efficient concept.

So, polymorphism is the neat and elegant solution. That's why it's such a widespread approach.

Iteration

I would like to be able to loop through all entities with a collision component

OK, then every time you create a new collidable entity, you add that entity to a separate list/vector that contains all collidable entities, OR (better yet), add its component (which has a back-reference to its owning entity) to such a list/vector. Update same whenever an entity / component dies.

Golden Rule

Keep things simple in engineering - don't overcomplicate - or you will very quickly get yourself into ruts you cannot easily get out of, and that way lies a loss of motivation to get your project flying.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .