ECS How systems iterates only in components that they use?

Started by
15 comments, last by frob 2 years, 6 months ago

Juliean said:
The one downside of this is that it couples the entity with the position

Yes, of course! Hence, why you may or may not want to make that simplification.

The draw-back with having entities with different kinds of transforms (or maybe MULTIPLE transforms – I'm a “forest” and I have 100 tree positions!") is that everything that talks about entities now needs to be aware.

For example, what happens if I add a PhysicsComponent to an entity that only has a GuiTransform? Now, you end up with cross-component dependencies, which can totally be solved mechanically using runtime asserts and such, but THEN you end up with an ordering problem. Not to mention, I prefer it when my compiler tells me I'm wrong :-)

Yet another option is to bake the component into the entity class using multiple inheritance. Each inherited component would be called with the main entity as an argument, and the components would use compile-time type checking to make sure the entity has the right dependent components. The draw-back there is that the storage for components now lives in the entity object, not in a nice flat array managed by the component system, plus all the lifetime shenanigans that come with that.

The main trick is to understand what parts are most important to your engine (iteration speed, number of entities supported, flexibility, runtime performance, …) and optimize for those parts in the engine. And, no, most indie engines do not want to optimize for runtime performance as the top priority – there are much more important barriers to finishing and delivering a fun and working game for an indie developer!

enum Bool { True, False, FileNotFound };
Advertisement

hplus0603 said:
The draw-back with having entities with different kinds of transforms (or maybe MULTIPLE transforms – I'm a “forest” and I have 100 tree positions!

Sure, that can be highly problematic. For example in Unity where a GameObject might now all the sudden have a RectTransform which is kind of a Transform, but also not really. Oh yeah, thats also the example you brought on a second read.

In my own application, I don't really have that problem, as I decided to make it an eigther/or situation globally. Users will have to decide whether they want a Transform2D or Transform3D via a core-plugin, and thats all they get. After that, every entity has exactly that transform. The only main difference is that I can move this code into plugins (you could also do something like this of course):

using TransformT = std::conditional_t<IsUsing3D, Transform3D, Transform2D>;

class Entity
{
	TransformT& transform;
};

But I have my 2D/3D specific implementations entirely decoupled, thus why I mentioned it. So my actual implementation for using those transforms when using the plugins then is:

// never null, always valid
const auto& transform = Transform2D::Get(entity);

Which is perhaps a little more awkward to write, but does about the same as your idea (so I'm not totally opposed to it, just wanted to share a slightly different approach that solves the coupling).

hplus0603 said:
For example, what happens if I add a PhysicsComponent to an entity that only has a GuiTransform? Now, you end up with cross-component dependencies, which can totally be solved mechanically using runtime asserts and such, but THEN you end up with an ordering problem. Not to mention, I prefer it when my compiler tells me I'm wrong :-)

I don't treat GUI as entities at all. You can place a widget in a world with a WidgetComponent, but that only wraps that widget (kind of like unreal does it). For that exact reasons, and I also find it incredible awkward to work with such a system in say unity. Though I also haven't fully solved that problem in my own implementation; there are components that depend on other components, and I havn't found a nice way to enforce this constraint (since it might also change at runtime which is why I cannot enforce it entirely in the toolchain).

hplus0603 said:
The main trick is to understand what parts are most important to your engine (iteration speed, number of entities supported, flexibility, runtime performance, …) and optimize for those parts in the engine. And, no, most indie engines do not want to optimize for runtime performance as the top priority – there are much more important barriers to finishing and delivering a fun and working game for an indie developer!

Absolutely agreed! Though in practice unfortunately performance can be a major issue especially in engines like Unity (I had even more extrem issues in Rpg-Maker), so indies might have to spend some time optimizing, unless they roll their own tech entirely (which I quess is ok for small titles? But an indie probably shouldn't write their own fully functionaly engine, wouldn't you agree? :D)

Juliean said:
I don't treat GUI as entities at all.

Then you're probably OK with thinking that every entity has a position.

(You'd need something more light-weight for objects that aren't physical, like inventory items and such.)

This is the path Unreal went with AActor versus UObject (and UActorComponent.) All AActors have a position, and that works fine for them.

enum Bool { True, False, FileNotFound };

hplus0603 said:
(You'd need something more light-weight for objects that aren't physical, like inventory items and such.)

Inventory-items are not part of the ECS at all (well, kind of). For non-physical objects I have the concept of (non-ECS) “systems”, so the inventory is a “system” which lives at game-scope. The item-entries themselves are managed via prefab-asset pointers (or class-types in Unreal). When the item is used by the player, it is spawned (to execute its scripted logic), but just for displaying the item in a menu I can query the properties (texture, etc…) from the prefab.
That means that I'm even more fine with every entity having transform, since everything that doesn't simply lives in the system-space.

testing6 said:
What do you think, what is the way of doing this?

Following any design system rigorously without compromise will inevitably leads to major trouble near the edges of the domain. All design systems claim to fully cover everything you ever need to do, but nothing actually does. Don't try to take on the world with exactly one tool, use everything in your toolbox to get the best of all worlds.

Getting back to the pickable component of the topic rather than the weeds of what is or is not ECS, or components that have nothing to do with the question

Remember it is little more than a collection. Objects have a collection holding zero or more things.

It is okay to ask the container about its properties.

It is risky to start making assumptions about other items in the collection, such as that there is only one of a certain type, or that the collection also contains exactly one of another type.

With that in mind, you need to look at the problem being solved.

In this specific case, the goal is to have some objects pickable by the mouse. It's a good idea explore it a bit.

If it is a HUD or UI element, a UI approach of nested objects can be better. If you have a few sparse objects, a search of intersections with a ray or mouse volume may be the most efficient. If you have dense pickable objects a separate list might make more sense.

For the sparse objects, it would be (for my own interpretation) easiest to do a ray test for objects that might touch the mouse, and for each object, send the command / event to each component of that type it contains, which may be zero or more. If a thing has five components of that type all five get activated.

That can be quite easy if the mouse ray is likely to only intersect a few items. If it might hit five or ten things, send the event to all their components.

For dense objects it may be worth it to cull the list with spatial tools and also against if any of the components exist in the region before doing testing against the mouse cursor. Basically the opposite direction of above, depending on the density details of which is more expensive to do.

If the mouse is clicked on a tree and you are in a forest where the ray in the frustum might cross hundreds of trees, probably do something different for the dense instances.

What you do with the results is up to you. You might get several objects, multiple hit detection components all triggering. Maybe each component has a priority field and you look at the highest priority of the results. Maybe you want the nearest the camera, or the farthest. Maybe you select every object. Each has a place depending on the goal, and you can probably find even more scenarios where you take other actions.

The point is to think of components as items in a bag attached to an object. Try not to focus on the other elements in the container too much, building things so it works for zero, for one, and for more than one.

This topic is closed to new replies.

Advertisement