sschmid / Entitas

Entitas is a super fast Entity Component System (ECS) Framework specifically made for C# and Unity
MIT License
7.09k stars 1.11k forks source link

Making it easier to work under multiple contexts #613

Open danielbe1 opened 6 years ago

danielbe1 commented 6 years ago

Hi,

One of the annoyances of having multiple contexts is having logical entities that are decomposed across two or more contexts, two examples that spring to mind are:

Handling these kinds of relationships require lots of boiler-plate code, and it would be nice if code generation could be utilized to avoid it. It could be nice to have something like:

class MetaEntity
{
    public EnemyEntity enemy;
    public PlayerCharacterEntity target;
    public ProjectileEntity hittingProjectile;
}

class MetaSytem<MetaEntity> 
{
    ....

    public void Execute(MetaEntity entity) 
    {
    }

    ...
}

Generating the code to create the MetaEntity and MetaSystem would be trivial if the relationship between all three entities would be formalized by indexes or some other component annotation. It would really make working with multiple contexts easier and more worthwhile.

WDYT?

FNGgames commented 6 years ago

I think you're making your contexts too granular. Players / NPCs / Projectiles (in my mind) are all Game entities - they likely share a lot of core state and functionality. My advice is to split your contexts in such a way that you never need this meta-system you're talking about.

In other words, if you find yourself with logically connected entities that exist in different contexts, you've gone wrong - you should design your contexts so that that cannot be a single "logical" entity that has to be represented by multiple entities in multiple contexts.

I would make NPC, PC, Projectile, proably even the tiles / terrain stuff just part of GameContext.

This is just my opinion obviously. Hope that makes some sense.

danielbe1 commented 6 years ago

That is obviously possible, which would mean I wouldn't be able to enjoy the benefits of having multiple contexts to begin with. The point is not to refactor my code to suit your own taste, but to make working with multiple (and single) contexts easier, which would also mean that you would probably find it easier to split your sparse single context into more granular sub contexts to lower memory overheads, lookup overheads using caching, and eliminating invalid game object states (projectiles with inventories, map tiles that spring to life and walk away for example). Maybe the multiple contexts thing is a red herring, what I am saying is that it would be nice to be able to express more complex relationships as entities of entities, hence MetaEntity. A meta entity could also be for example, two GameObjects in your scheme that fight with one another, something like:

class MetaEntity
{
    public GameEntity enemy;
    public GameEntity target;
}

class MetaSytem<MetaEntity> 
{
    ....

    public void Execute(MetaEntity entity) 
    {
    }

    ...
}

If you find these kinds of relationships to be useful in many places you would probably one an easy way to express it, this specific example can be used for collisions, combat AI, path finding etc.

So for example doing combat AI could go like this:

If this is easy, then decomposing your single to context into more granular contexts also becomes easy.

FNGgames commented 6 years ago

I guess I'm misunderstanding or missing something, maybe other can help. I've always handled this with components - e.g. CollisionComponent has a handle for the SELF and OTHER entities (plus a third field for collision data). Then i have reactive systems that consume these entities and do stuff. What does the proposed MetaEntity give you that a pure component wouldn't?

I didn't intend to sound dismissive, sorry if I'm missing your point here.

danielbe1 commented 6 years ago

Sure you can handle this with pure components, such as a collision event component, but I assume that the collision component is short lived (correct me if I am mistaken) and does not persist beyond the current frame so you don't have to handle cases where one or both of the references become invalid.

But what if you want to represent something long lived like seeking targets? What if you want references to 3, 10, 20 entities? You would either need to perform look ups every frame, or need to write code to make sure references do not become invalid, plus you would probably want to cache results, make sure no one is accessing (persisting) this data from the outside and have all of this tested and verified to work. This can be done by hand but its not so trivial and since it can be done mechanically its best to do it using code generation.

Essentially this can be thought of like JOINs and using views in a database. Depending on your requirements you might want to normalize your data and have certain views of your data optimized.

svanderweele commented 6 years ago

I agree with this, being able to use contexts to organise my code better would be incredible.

thygrrr commented 6 years ago

I only kind of agree.

The only problem is in having an entity that contains a bunch of references to other entities.

I solve this with extensions to ContextEntity, which minimizes boilerplate. So if I have an InputEntity e, that has a GameEntity associated with it, through an extension, I can do e.game.hasWhateverGameComponent.

It will obviously throw errors (as it should!) if e doesn't have a GameLinkComponent or whatever I call it.

Some boilerplate persists when cleaning these puppies up. At times I wish I could get the Component that was removed one last time in some kind of GraveyardSystem. (I wrote that once using delegates, but it wasn't really worth it, and it broke the timing of the systems). The inability to get the data of a removed component one last time in a System means that you end up with hacky secondary entities "destroyGameLink", that have their own system.

However, until that triggers, you can't reuse your original component. Meaning you might need to wait until the next tick (with everything that entails...)