1
\$\begingroup\$

In an entity component system (implemented where components only contain data and systems operate on groups of data) one can guarantee that the entity in which a system is operating on has the desired data. For example, a MovementSystem only operates on an entity if it has both Transform and RigidBody components. The MovementSystem knows as a matter of fact that the entity has all the data required to perform its logic. The problem I find is if a system needs to operate on another entity's properties - we cannot guarantee that this other Entity has the data we want to operate on.

Lets use a contrived example because I like code

DamageComponent:

DamageComponent(target, damage) : Component {
    Entity target = target;
    int damage = damage;
}

HealthComponent:

HealthComponent(health) : Component {
    int currentHealth = health;
}

The System:

DamageSystem() : System {
    // Operates on all Entities which have a DamageComponent

    DamageComponent dc = this.entity.getComponent(DamageComponent);

    // damage the target every update tick
    void update(dtime) {
        HealthComponent health = dc.target.getComponent(HealthComponent);
        health.currentHealth -= dc.damage;
    }
}

You see, a problem arises if the target Entity does not have a HealthComponent. What if the Entity never had a HealthComponent or some other game logic removed the HealthComponent? Is it typical to check every time if the target Entity has the desired Component?

if (dc.target.hasComponent(HealthComponent) {
    //...
}

I'm looking for a more "elegant" approach, if one exists. Thank you

\$\endgroup\$
2
  • \$\begingroup\$ Why not try and think in terms of "getting damaged", not "damaging"? \$\endgroup\$
    – jmegaffin
    May 19 '15 at 3:29
  • \$\begingroup\$ don't forget that you might also want to have the option to handle this explicitly! E.g. Hitting a wall that has no health shows a message e.g. "Your hit has no effect" \$\endgroup\$
    – Babis
    May 19 '15 at 9:24
1
\$\begingroup\$

It's probably easier to enforce that an entity has certain components when its reference is assigned to the component property in the first place rather than every time it is used. Depending on the language, there are ways of doing this that are enforced at compile time or run time. Compile time would be ideal for a performance sensitive game. For example, in a language that supports generic programming like C# or templates like C++, you could wrap your entity reference in a type that is generically typed for the components it allows the entity it references to have.

\$\endgroup\$
1
  • \$\begingroup\$ Looks like this may be the way to go. I am actually using javascript so generics aren't necessarily supported but I bet there are ways to achieve a similar result. \$\endgroup\$
    – bzach
    May 20 '15 at 18:30
1
\$\begingroup\$

One way to solve this problem is to perform necessary checking while setting the "other" entity of the component. Like so:

DamageComponent(target, damage) : Component {
public:
    Entity GetTarget() { return target; }
    void SetTarget(Entity other)
    {
        if (other->contains(healthComponent))
            target = other;
    }
    int damage = damage;
private:
    Entity target = target;
}

Something like that. Actual code implementation may depend on the language.

\$\endgroup\$
0
\$\begingroup\$

When I implemented something similar I used a messaging system exclusively to handle the necessary communication between different entities in various systems.

That way the messenger can perform the relevant required component checks while marshalling the your messages between the entities. You can think of the Events as mini SubSystems if you like.

It also cleans up your systems quite a bit as well as providing a great deal of de-coupled logic, and hence less dependency chains overall etc.

(contrived and untested psuedo-code disclaimer) example :

public class Event_TakeDamage : GameEvent {
    public Entity target;
    public float damage;

    public ComponentMapper<HealthComponent> health;
    public ComponentMapper<EventManagerComponent> eventManager;

    public Event_TakeDamage(Entity target, float damage) {
        this.target = target;
        this.damage = damage;

        health.init(world);
        eventManager.init(world);

        if(health.Get(target) != null && eventManager.Get(target) != null) {
            eventManager.AddListener<Event_TakeDamage>(health.TakeDamage);
        }
    }
}

public class HealthComponent : EntityComponent {
    public float health;

    public void TakeDamage(float damage) {
        health -= damage;
        // TODO check for death etc
        // possibly do a RemoveListener for this Entity's EventManager etc.
    }
}

public class ColliderComponent : EntityComponent {
    public Bounds bounds;
}

public class CollisionSystem : EntitySystem {
    ComponentMapper<ColliderComponent> collider;

    void Start() {
        collider = AddComponentType<ColliderComponent>();
    }

    void Init() {
        collider.init(world);
    }

    void Process(Entity entity) {
        foreach(Entity collidee in SpacialManager.Instance.QueryCollisions(collider.Get(entity).bounds)) {
            EventManager.Instance.QueueEvent(new Event_TakeDamage(collidee, 5f));
        }
    }
}
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .