Advertisement

Help understanding how ECS should be structured

Started by November 09, 2019 08:14 AM
8 comments, last by logicandchaos 5 years, 1 month ago

I've just started to try and learn how to use ECS (Entity component systems) in games, but I've found it a bit hard to understand. Right now I have two main questions:

  1. In an ECS, is it preferable to store a boolean value in a component to flag an entity, and keep that component permanently on that entity, or to add/remove a component to flag an entity? For example, if I want some way of flagging an entity when it dies so it can be removed, is it better to have an AliveComponent with a boolean dead to indicate if it's dead or a DeadComponent that gets added to indicate if an entity is dead.
  2. Should adding more types of components, or trying to make a component reusable through its fields be favored? For example, if I'm trying to give all entities a shape for collision detection, should I have a different component for every shape (RectangleComponent, CircleComponent, PolygonComponent), or should I have one ShapeComponent, and in there have a field which is of type Shape, with Shape having subclasses Rectangle, Circle, and Polygon.

Also, I couldn't find many good resources on ECS concepts, so if anyone knows any good ones, it would be really helpful.

Thanks!

 

I personally wouldn't add/remove components at run time to indicate state like alive/dead. It might be necessary to do this for some other kinds of functionality, like for example removing an animator and adding a ragdoll component on death, but for something as simple as a boolean I would include it as part of the component/interface that is responsible for returning health values or receiving damage.

The second question is similar to one I asked recently:

Advertisement

Youve got generalities versus specific implementations with specific needs.

In general it is a concept, there is no right or best in general, although some implementations are better suited to different uses. In general it is a system of composition, building up things (entities) out of other things (components).

Different games implement the concepts in many different ways. Most programmers actually implement it multiple ways in their code, often not even realiizing they are following the pattern.

For most games the implementation details don't matter for performance nor space, as most games have many million cycles and many gigabytes to spare. In some cases they matter, and in those cases you need to understand the computer science behind it to know why, understanding data locality, access patterns and cache friendliness, and processing algorithm choices in order to know what to do diffrently.

Do whatever works for your game and makes sense to you.

Maybe you should just use the Health attribute (in whatever component that is stored in) to determine if something is dead.

 

You may want to have a death animation that plays when it dies, and have it on the ground for a certain amount of time, and then you'd have some kind of timer associated with that component, which, once expires, triggers a removal event, which maybe fades the dead character away.

 

Lots and lots of different ways to do this, don't pigeon hole yourself into on way. Experiment and see.

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

An idea I recently had was adding a DeathDecay component, because in most games there is a despawn of dead bodies after some time ?

In general, you are free to use ECS as it just defines how the the systems gears work together, not if one of them is made from wood and the other one fro  metal. We have used ECS even different in our gamedesign software and I'm using another implementation in our build tool. As long as you don't violate the atomicity principle (each component and each system in an ECS has it's very special task); for example you have a death-animation system that requires a death animation component to exist and despawns the NPC after playing the animation rather than control anything with the general animation system and let the NPC system control despawn,

and didn't double data or implement components just to solve a task that also could be solved by a combination of existing components (component reuse and case composition); for example a colider component and a render component each have a position property, a very-special-usecase component that has properties also covered by more generic components but is used by a very-special-usecase-system exclusively,

then anything should be fine and nobody will blame you

There's dead and dead.

Entities getting hurt and telling whether the entity has suffered lethal damage is unquestionably a job for a specific "health" component and damage system, as Danzabarr and BeerNutts suggested, but "killing" entities is not special: there are countless other ways for entities to "die", for example distant scenery that can be culled now and recreated as needed or finished animations, and actually disposing of unneeded entities should be addressed uniformly.by the framework.

Entity lifetime bookkeeping can be addressed with a variety of technical means which might or might not involve a "to be recycled" flag inside entities (for example, a specialized data structure like a list of entities or a compact bitmap index might provide better performance).

 

Omae Wa Mou Shindeiru

Advertisement

If it's about the data structure then you could think of ESC like this.
You have a GameObjStruct like so


struct GameObj {
	float health, mana;
	vec3 pos, vel, acc;
	vec3 bb_min, bb_max;
	// etc
};

It contains everything a game object could ever want. And you have them all in a giant std::vector<GameObj>. They reference each other by index into the array, and a generation count. 

Now imagine not everything has health, maybe there's some rocks in the GameObj array. You might benefit from moving that out. You can do this in one of two ways. Either the GameObj can have a pointer to a HealthComponent, or the HealthComponent list can know which GameObj it belongs to. If you do this with all data in the structure then you wind up with no GameObj struct at all, just a bunch of systems that use IDs to say which game object it belongs to.

Each system can be responsible for maintaining the pool of a given component type. The physics system can manage all rigidbody components, that way you can just iterate over them when you do your physics step. The problem is when you need to find what other components a GameObj has. So you could have a vector of GameObj still, and do them like this


struct GameObj
{
	std::string name; // For debugging maybe?
	std::vector<Component*> components;
}

How you look up which entity a component belongs to there's many variations. It's up to you to choose whichever you think is best.
My tip would be to think about what you're trying to solve and use that as a guide. Easy to read code is easy to debug, but if you're just messing around go wild and experiment.

Think there's a chapter on this on Game Programming patterns, free to read online I think. It's a good book to read.
This has been a bit stream of thought but hopefully there'll be some useful info here.

Video Game Programmer.
5 years in industry.

I'm under the impression that I need to go out, experiment, and not be too restricted by ECS, so I'll start making my game now. Thanks to everyone for your advice!

If you are using unity, what I do is keep track of alive and dead entities is add the and remove them from lists in OnEnable and OnDisable, I do my pooling like that too.

This topic is closed to new replies.

Advertisement