Entitas-csharp: Same type of data on the same entity within the same tick

Created on 22 Jul 2017  路  3Comments  路  Source: sschmid/Entitas-CSharp

Hi,
Firstly, thanks for the great project! I have been using Entitas on my own project and I am trying to deal with the problem of having multiple instances of the same type of data on an entity.

Imagine a hypothetical situation where I have a TakeHitComponent which contains information about the hit that a unit should take (e.g. a simple integer and the ID of the attacker). I will have a system that picks up this component when added and will subtract the health of the entity and a system that will pick up the ID of attacker and notify the target. TakeHitComponent is going to get removed by a clean up system at the end of the tick. All is good.

Unfortunately this system will eventually break once I have multiple units attacking the same target as they might try to apply the same component on the target within the same tick.

Well, I can try to aggregate all the information in an enumeration of some sort but that just doesn't seem like the right way to do. It will be difficult to keep the track of execution order of things for example (e.g. a hit might be added to the enumeration after the health resolution system already picked it up and since the health resolution system only picks it up once a new component has been added - not when any data is added to the list - it will just be ignored if I am mistaken).

It seems to me that I am missing something since I have already encountered this issue in several unrelated parts of my small prototype and it's only going to get worse as the game grows.

So can someone point me in the right direction how to deal with this the right way? How are you dealing with this?

Most helpful comment

Hi @FiveP,

correct, you can add only one component of a type to an entity.
I see two different solutions to this, but I will only describe one solution in detail as I think it's generally the better solution.

  1. Intermediate entity
    I regularly use this kind of concept where I create a separate entity that holds some kind of information, in your case:
// in a method where a hit gets emitted
context.CreateEntity().AddDamage(amount, attackerEntity.id.value, targetEntity.id.value);
[Game]
public sealed class DamageComponent : IComponent {
    public int amount;
    public int attackerId;
    public int targetId;
}

Create a new entity for the damage information and add DamageComponent. A ProcessDamageSystem with a trigger GameMatcher.Damage can now easily iterate over all Damage entities and execute some logic. This also allows you to do some additional sanity check if needed, e.g. a certain target got 100 damages from 10 attackers but the game design rules limit a maximum damage to 10 hits for bullets plus an additional 10 damage for fire or sth like that.

If needed you can also use an entity index to perform fast queries like

context.GetEntitiesWithDamage(e.id.value);
    [EntityIndex]
    public int targetId;

In this example it probably doesn't make much sense, but I remember having a few cases where an entity index made working with intermediate very convenient and simple.

To give you another example where this concept makes sense:
You shoot at a wall which should spawn some kind of damage texture, sth like this:

Applying this same concept where each shot results in a new entity with information about the position on the wall let's you easily do optimization in a ProcessShotSystem, e.g. there are 10 shots at almost the same position, so instead of loading and spawning 10 textures at the same position probably resulting in weired z order issues you can only instantiate 2 or 3 and place them in a nice pattern.

Entities and components are all object pooled for you by Entitas, so creating lots and lots of those intermediate entities per frame should not be a problem!

  1. List of Damage struct
    The DamageComponent could have a List where the Damage might be a class or struct with all required information. I recommend not doing this because you cannot react to changes as easily.

All 3 comments

Hi @FiveP,

correct, you can add only one component of a type to an entity.
I see two different solutions to this, but I will only describe one solution in detail as I think it's generally the better solution.

  1. Intermediate entity
    I regularly use this kind of concept where I create a separate entity that holds some kind of information, in your case:
// in a method where a hit gets emitted
context.CreateEntity().AddDamage(amount, attackerEntity.id.value, targetEntity.id.value);
[Game]
public sealed class DamageComponent : IComponent {
    public int amount;
    public int attackerId;
    public int targetId;
}

Create a new entity for the damage information and add DamageComponent. A ProcessDamageSystem with a trigger GameMatcher.Damage can now easily iterate over all Damage entities and execute some logic. This also allows you to do some additional sanity check if needed, e.g. a certain target got 100 damages from 10 attackers but the game design rules limit a maximum damage to 10 hits for bullets plus an additional 10 damage for fire or sth like that.

If needed you can also use an entity index to perform fast queries like

context.GetEntitiesWithDamage(e.id.value);
    [EntityIndex]
    public int targetId;

In this example it probably doesn't make much sense, but I remember having a few cases where an entity index made working with intermediate very convenient and simple.

To give you another example where this concept makes sense:
You shoot at a wall which should spawn some kind of damage texture, sth like this:

Applying this same concept where each shot results in a new entity with information about the position on the wall let's you easily do optimization in a ProcessShotSystem, e.g. there are 10 shots at almost the same position, so instead of loading and spawning 10 textures at the same position probably resulting in weired z order issues you can only instantiate 2 or 3 and place them in a nice pattern.

Entities and components are all object pooled for you by Entitas, so creating lots and lots of those intermediate entities per frame should not be a problem!

  1. List of Damage struct
    The DamageComponent could have a List where the Damage might be a class or struct with all required information. I recommend not doing this because you cannot react to changes as easily.

Ahhhh yes, of course, I didn't think of having entities as just pure intermediate data containers, so obvious and simple :]. I will give it a shot. I did elaborate a bit on the second solution but found exactly the same problem, it really isn't a good solution. Thank you a lot for the illustrative explanation and again, good job on Entitas!

Simon's answer probably solved your initial problem, but just for the record, I ran into this problem for physics forces in one of my games. Since many different things can end up adding force to a rigidbody in a frame, I wanted to accumulate force rather than replace or add it to a component.

I wrote this extension:

public static void AccumulateForce(this GameEntity e, float force) {
        if (!e.hasForce) {
                e.AddForce(force);
        } else {
                e.ReplaceForce(e.force.value + force);
        }
}

I do the same thing with collisions (except my CollisionComponent holds a list of collisions, and in the extension i make a new list or add a value to the old list depending whether the entity already has the collision component - the same as Simon describes for damage).

When the collisions or forces are processed they are removed from the entity.

In future I plan to write my own code-generators with custom attributes ([Accumulate] for example) that add these methods into the generated files.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

KumoKairo picture KumoKairo  路  4Comments

vaudevillian picture vaudevillian  路  3Comments

LywOPUS picture LywOPUS  路  3Comments

jakovd picture jakovd  路  3Comments

angelotadres picture angelotadres  路  5Comments