Skip to main content
GameDev.net gamedev.net
🔒 Locked

ECS architecture

Started by Tanzan May 7, 2021 at 7:21 AM 30 replies 45.7k views
Original Post
Tanzan
Tanzan

Hello fellow developers i am currently developing a game on the ecs architecture and it works out fine.

The plumbing was terrible i must say but now it starts to get fruitful…

But i have a dilemma that i want your opinion on (i assume you know about ecs)

Imaging we have a hero which can move has health etc

so the movesystem handles all entities which has a move component with a move vector etc

now when the entity can't move anymore because of freeze spell or unconscious, death etc we can handle it two ways 1) we temporally ‘block’ the movement with the reason so we keep the move component with a boolean or so…i really don't like this option because the component manager of move has an ‘illegal’ component and in the end then all entities will have all components and it destroys the ecs architecture (i think) 2) we remove the movement component temporally until it needs to get back…healed, spell wears off etc…this is my favourite option because it respects the ecs and will work just fine. problem with this is how to get the move component back? who will be responsible for it ? and which will we add ? walk or was he flying ?

i wrote this right away while i was thinking about it, hope it makes sense ! ;-)

so what is your opinion about it ? i am curious.

SyncViews
SyncViews

Seems to me that in most cases velocity should still be used as normal, it can just become zero, and prevent further input from adding to it (and the stuff for handling character movement, could reasonably also access some spell status etc. components)? Then all the physics maths should just work out by itself.

And potentially in some cases depending on design you still want gravity, momentum to apply so a frozen/dead object might still move a bit.

Tanzan
Tanzan

Hi SyncViews,

thx for your reply :-)

Don't you think that that is the same solution as using a boolean in the move struct ? or at an extra component ‘blocked’. i mean the playercontroller adds velocity and a direction vector (all then not calculated). But then another ‘environment’ system (through component(s)) will set it to zero or add the blocked component. but in that way the move component stays connected to the entity.

Don't get me wrong that solution will work perfectly but for me it feels strange that entities keep components which are deactivated

again thx for thoughts!

Juliean
Juliean

I do it the same way - my Movement-component has a “paused” bool which allows me to halt en entity right where it is, and resume later. I don't think there is anything strange about it - no matter what the context, entity/game-object systems will probably always end up needing different mechanisms for handling subsystems like movement. Detaching the component for example is not valid for that use-case, since you need to retain the values (speed, etc…). Simply disabling it (if you have a general “disabled” state for your components) might be another solution, but might also not be applicable for that use-case (depening on how you treat “disabled" components; in my system a disabled component is virtually non-existant until re-enabled or explicitely requested). So yeah, I think having some sort of boolean to toggle this is perfectly normal.

Tanzan
Tanzan

@Juliean Hi Juliean, thx as well to think with me :-)

i agree that we need some control of course, but worst case will be that every entity ends up with almost every component which will be toggled on pause.

It feels a bit then like the OO method where we would create millions of isPickupable, isMoveable, isEquipable etc etc

i think that the power of ecs is that we just have a placeholder (entityid) and by adding components to them (dynamically) we add or remove members and so functionality (through the systems) to them.

again guys thx for thinking with me

Juliean
Juliean

Tanzan said:
i think that the power of ecs is that we just have a placeholder (entityid) and by adding components to them (dynamically) we add or remove members and so functionality (through the systems) to them. again guys thx for thinking with me,

In truth, I'm not using the ability to dynamically attach/remove components all the much anymore. For me the power of ECS lies in being able to develop features in isolation without running into the problems that rigid inheritance could lead to. For me, having a “move” component on an entity mean “this entity has the ability to move”. Whether the entity moves at that point in time is up to the component. If an entity becomes unmoveable for a large period of time or permanently, I detach the component or deactivate it, but if say my cutscene pauses the movement of my player-avatar, I simply “pause” the movement and animation. There are certainly up or downsides to eigther approaches, but you shouldn't be afraid to keep a simple problem like “I want to pause movement” simple and not overcomplicate it out of fear or just to comply with a purist-version of a pattern ?

Tanzan
Tanzan

@Juliean out of fear or just to comply with a purist-version of a pattern ?

So true ! ?

I am not at all a purist in that context its just that i wonder how to pro's do it….

The way how you describe your solution looks a lot like component based developing (which again is nothing wrong with) and not ecs

regards,

SyncViews
SyncViews

Tanzan said:
i agree that we need some control of course, but worst case will be that every entity ends up with almost every component which will be toggled on pause.

Oh pause I approached differently, since it pauses all game entities I just stopped most of the update stuff executing (basically skip the entire fixed step update, except input which feeds into the ui)

Juliean
Juliean

Tanzan said:
The way how you describe your solution looks a lot like component based developing (which again is nothing wrong with) and not ecs

Well, I'm pretty much using a hybrid-approach at this point, but I'm still using an ECS in the sense that components are mostly data (and very easy to copy/pooled), and logic is handled by systems operating on a bunch of components. I dropped the messaging-part though and now mostly call methods on systems to implement specific logic, and do some other things that go against the pure “ECS” idea. But I'm still much closer to ECS then unitys “component based” approach.

Tanzan
Tanzan

@SyncViews :-0 with pause i didn't mean literally pause but more that a component is not active for a while….

so for example if the hero would be glowing because of a powerup its really simple because after the powerup is done we can just destroy it…but in the context of flying or walking you need it to add it later again or disable it for a period as i discussed with Juliean

All8Up
All8Up

I would suggest different approach with a few notes in general. First, I wouldn't let the movement system know about any of why something is fast, slow or frozen, it should simply act on a set of results created by other systems. So, I would just add a new component which goes with velocity, the “velocity_multiplier” which is a f32. When the movement system executes, it simply multiplies velocity by that value and uses the results in the standard p' = p + v*t equation. The key reason for this separation is that the movement system doesn't get butchered up with a bunch of logic trying to figure out what “v” should actually be, that is left to an external separate system. Given this, I would not use add/remove of a component to trigger the special case here, the modifier always exists in this scenario and is simply set to 1.0f under most circumstances.

Given this new piece of data, add a new system which decides what to set the new modifier too once a frame. All the possible reasons it could be non 1.0f are now separate from the movement code and you can make that as simple or as complex as you desire. The big benefit here is that you can set the value to a range without modifying the movement code. So, for instance someone casts “slow”, modifier is set to 0.5f, the “freeze” spell you mention “0.0f” or how about “haste” 1.25f. This system could even inspect inventory of an entity and change the modifier for encumbrance effects or whatever you want to eventually add.

This still leaves the question of tracking how the state of the modifier is tracked which I leave to a separate solution. For one, I would not use an add/remove component for most things given that the data in question is likely nothing to be concerned with and also if the ECS is an archetype based solution it will be a notable performance impact. Rather, I just create a component that tracks the state information required and attach it to any entity which can be impacted. It could be as simple as three bool's given my description: “is slowed”, “is frozen” and “is hasted”. But typically I want these things on a timer so I again use f32 as a countdown timer for the effects. To slow something you add say 1 second to “is slowed”, the new system will subtract delta time from that and clamp to 0.0f, if the remaining value is > 1.0f, the set the velocity multiplier to 0.5f. Via the separation you can also have combinations which don't overwrite each other: slowed + hasted means that the modifier (depending on order of application you desire) would be 1 x 1.25 x 0.5.

Your needs may be very simple and this is overkill but it doesn't cost notably more to keep the door open to extended functionality through composition of components and systems which is the primary code organizing principle that ECS lends itself to.

Tanzan
Tanzan

@All8Up hello and thx for your reply,

your first 2 paragraphs i agree completely with and its not what for me is ‘strange’ as in pure ecs design.

But you last paragraph is exactly what i try to ‘solve’ pure ecs way (as if it exists everything works fine i just try to see it differently)

i think a system should be strong coupled with its component(s) and not that you have to check multiple states of different components…so the move system should just work with move component (and indeed let other systems adjust speed or whatever) and not check 'if not unconscious && not death && not frozen && not etc i think

thx a lot guys its a very interesting topic

ROGRat
ROGRat

@Tanzan

Firstly, to satisfy your curiosity about how the professionals do it, may I suggest taking a look at this fairly comprehensive break-down of entity component systems by developer Mick West. He worked on the Tony Hawk series of games and when he took it on, he had to refactor/rewrite the engine because of the state that it was in at the time. His article is here:

http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/

Secondly, it’s worth considering whether an ECS is worth the time invested. For smaller projects ‘ simple games, it’s overkill. If you’re developing an engine, as opposed to a one-off project, then the benefits of ECS are clearer and the time invested better spent.

Tanzan
Tanzan

@ROGRat Thank you rograt,

For me it is also more a study to research what the positive side of different architectures is ,

especially when you go ‘deeper’ into it …at top level most work really good but in the detail i always have the feeling that we need to ‘cheat’ a bit ;-)

to go back on topic, the status component of all8up looks like a nice solution. but again it feels like cheating.

if the monster can see give him eyes and the system rules it, if not take his eyes (temporally) away and the system ignores all functionality related to it…

but what if the player wears shades, is affected by a blind spell or has sand in his eyes etc ;-)…i don't want the see-system be checking all those conditions because it will go out of control and also can give priority problems

your link is really interesting thank you for that.

just again guys ;-) i am not at all a purist and i don't mind workarounds i just like to get the model clear

good weekend!

frob
frob

Tanzan said:
now when the entity can't move anymore because of freeze spell or unconscious, death etc we can handle it two ways

This is more of an overall design question than an architecture implementation concept.

In some games, the design calls for the thing to be frozen solid, immovable. Nobody can bump it or nudge it. Gravity doesn't apply. All motion is stopped. This can be very comical or cartoonish, where even the gravity is suspended while frozen or dead.

In some games, the design calls for the player to stop controlling it, but otherwise the world still interacts. People can bump or nudge it. Gravity applies. They may be paralyzed but still work with physics, they may be a ragdoll, but no matter the implementation the player can't manipulate them while other forces can.

If you go the first route, I'd approach it as something in your physics system. Maybe you could disable the physics component if that's what you are using, clear the enabled flag perhaps. Maybe you could have a self-destructing freeze component that forcefully sets all physics actions to zero. Maybe you could accumulate the action in a local variable and subtract it from the physics settings each update, several games have a frozen state where you can hit many times to build up energy before it becomes unlocked and all applies at once. Or maybe you have some other ideas that better fit your project.

If you go the second route, I'd approach it as something in your player controller system. Maybe a flag that says to consume player controls. Maybe disable the component temporarily. Maybe a check built into the player controller component directly. Maybe a component attached to the player controller component separately, or that applies the change after the controller is updated. Maybe an attribute on the character that the controller system recognizes. Or maybe you have some other ideas that better fit your project.

Both options have been used successfully in a range of games.

There are also options where the two can be mixed. If you've seen it, think of comic situations like Sharknado: A shark can be flying around rapidly, thrown around by the air, but the moment it gets killed it freezes and stops midair, then falls from the sky as though it were in calm air. Basically it enters the first style for a moment, freezing in place against all physics reality, then it enters the second style, tumbling down as though a ragdoll uncontrolled physics object.

Tanzan
Tanzan

@frob Hi Frob,

Thx for your reaction, and indeed it is a functional question which should be designed but implemented in the ECS architecture.

So the question was not specific what but more how and then in relation to ecs.

again thx for you thoughts!

Shaarigan
Shaarigan

An option I'm a bit disappointed nobody mentioned so far: What about adding a Buff-Component? In nearly every RPG game, there is the point when an item increases movement, I don't assume your game to be an RPG but the same may apply here. So instead of getting a workaround solution for a emi special issue, you should generalize your game for using different kinds of (De)Buffs, e.g. for movement. This way you can apply a Death-Debuff to your hero or any other enemy/character in your game and let that have an impact on movement as well (or controlling the character). This also opens up an option for necroing the character by simply remove the death component from it

Tanzan
Tanzan

@Shaarigan Hi Shaarigan,

thx for your reply and buffs and debuffs are already included in this conversation its all in the name ! ;-)

a buff component could increase the speed which the movement system uses to multiply the basic movement with.

it is a good example how strong ecs can be if you just would use (timed) components to add and to remove from an entity…the entity is buffed or not, simple.

But the problem starts if you want to temporally don't want to be able to move anymore, nice ecs logics would be to just remove it like the buff/debuff…but then you have to keep score which (assembly and/or archetype) component the entity had….flying , walking crawling…

and if you use an extra component to ‘block’ the movement e.g. frozen, hold spell ,unconscious component…then your movesystem has to check all those states which will be fast exploding…

right now i just gave all components an active boolean which all the system can set….doesn't feel ‘ecs’ but hey ;-)

Andor Patho
Andor Patho

Tanzan said:
and if you use an extra component to ‘block’ the movement e.g. frozen, hold spell ,unconscious component…then your movesystem has to check all those states which will be fast exploding…

This sounds like you are missing a level of abstraction at this point. There is many ways to add the abstraction, off the top of my head these components can be all of the same type, or have the same ‘tag’ that tells the move system that they affect movement speed, and a multiplier it can read (the same way for every such component). That way, it doesn't have to know about all the specific components, it's enough if it knows about the ‘concept’ of a component that can affect movement.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.