1
\$\begingroup\$

I've tried my hand at writing component systems and entity systems.

What I haven't managed to solve is the problem of event collision.

Say, we have an components A,B and C. A triggers 'kill B' event. When B recieves it, it erases the message (doesn't propagate it further). When C recieves it, it erases B. The order of which component will get the event first is not defined.

When the order is 'A,B,C', B stays alive but with 'A,C,B' B is erased. In other words, side effects of the event are undefined. A,B and C need to know about each other (which I don't want) to handle the event properly.

I intend to use components for games so for a more substantial example: we have an arrow (Projectile component) which fires of a collision event upon striking Link (Collidable component). Link has shield (also Collidable) which stops damage from affecting Link's Health component.

Arrows and health were written without the shield in mind and my component architecture is supposed to be encapsulated, so Health processes the collision event without knowing about the arrow or shield. Whether or not the damage is stopped depends on the order of components, which is random.

The best I've came up with is keeping a 'cache' of actions the event will do at the end of processing but still this solution has the same problems, only delayed. I could ditch events and try entity component system with storing 'mutations' in systems but that's not much different from just using global data for everything.

EDIT:

Okay, my communication skills are terrible. Here's what I've done to make components working without knowing about each other:

  1. Use 'pure' entity system (components are pure data, all logic in systems)
  2. Components do not talk to each other. The systems talk to each other by adding system-exclusive components to entities
  3. Systems for above situation are: Movement, HandleCollision, ApplyArrowDamage. Movemen t appends a Collision component to the 'reciever' of the collision (Link) when calculating movements. HandleCollision reacts to Collision component, appends ArrowDamage component to Link entity and removes Collision component from Link. ApplyArrowDamage does just that and removes ArrowDamage component and maybe appends Dead component to be handled elswere.
  4. Now, to create the situation when the shield blocks arrows, I create an CheckShield system and place it between HandleCollision and ApplyArrowDamage. CheckShield works with ApplyArrowDamage, checks whenether the reciever (Link in this case) has a shield equiped and removes ArrowDamage component if so.
  5. This way, my old code doesn't need to account for new code to work properly and vice versa.

For the 'ABC' situation I do the same: If I want C to erase B, I write a new system that works with C and fires off before the system that works with B. The only sane way to solve this problems with event raising is to keep rethrowing the event whenether it's state changed, and keep rethrowing it until it doesn't changed in the last iteration.

\$\endgroup\$

3 Answers 3

0
\$\begingroup\$

You're possibly trying to over-use game object components.

The shield doesn't need to be a component. It's an equipped item. That might be handled entirely by the InventoryComponent (it doesn't have to be, but it's an option).

So far as damage, yet another option is to make sure that the HitPointComponent is aware of the states and things that alter damage taken and does the adjustment internally. This might be by querying the InventoryComponent.

Another option is for the shield to create a separate collision box in front of the character. If that collision box is hit then it takes the DamageMessage in a ShieldDamageReceiverComponent, halves the amount, and forwards it on to the main character object. This also gives you direction-based blocking. Larger, more complex characters in 3D games will often use their physics models for this purpose without necessarily needing separate game objects.

One of the nice things about components is that you can just use different components in different circumstances. e.g., you don't need a single HitPointComponent; you can have a HeroHitPointComponent that takes into account shields and such, an EnemyHitPointComponent that does not, and so on. Just be careful of walking into a design where you need to mix-and-match those behaviors, but don't avoid the setup out of hand.

Remember, component-based design is a collection of patterns, not a single solution. You can have a game object like your character made up of "components." Your ArmorComponent can then be made up of a dynamic set of ArmorModifier. That's a component that has components, basically. It might not be implemented with an actual set of sub-objects, but the general idea is the same. Your Engine can be a composite of Module components. Your resource objects can be composites of resource providers. etc.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks for the comment about subdividing components. It guided me to solution. \$\endgroup\$
    – kuniqs
    Commented Oct 18, 2014 at 10:19
1
\$\begingroup\$

Consider using Sub/Pub pattern, or Observer pattern as a messaging system. This will allow you to register as many event listener as you want, while keeping all the listener decoupled. The basic idea is you keep a list of objects that want to get the message when an event happens. When the event does happen, you iterate through the list, sending the event message to every object registered to receive it. Each object will process the message however they need to. If a listener no longer needs to receive the message, you simply unregister it.

\$\endgroup\$
1
  • \$\begingroup\$ I know this is old but to add to this, what I've done is create a CommHub script on an entity where I like all the events to functions for all components used for that entity. This script is entity specific and it's a centralized place to really get a high level view of how that entity works. I find this works great! All components stay decoupled because they use events to communicate out and all event hooks in 1 file (vs components knowing about other components and registering events inside themselves). \$\endgroup\$
    – user441521
    Commented Mar 2, 2017 at 2:20
0
\$\begingroup\$

Event handling can be done using Observer pattern. What it does is that it forwards a certain event to the all registered objects. Let's say we have a projectile entity which triggers two events: OnThrown and OnHit. During or after the creation, components which want to listen that event registers it by passing a callback with a syntax like this:

projectile.registerOnHitEvent(handleProjectileHit);

Projectile component keeps all registered callbacks in an array so you can call them with a for loop.

private void fireOnHit()
{
    for(int i=0;i<hitCallbacks.length;i++)
    {
        hitCallbacks[i](this);
    }
}

In addition, you can decide whether the next callback will receive the event by checking the return value;

bool notifyOthers = hitCallbacks[i](this);
if(!notifyOthers)
    break;

As the further customization, you can assign priorities so that shield component will always receive the projectile hit event first and doesn't allow propagation.

Note: I would let damage handling to the character controller, so it can find out whether there is a shield or not.

\$\endgroup\$
1
  • \$\begingroup\$ I knew about observer pattern before.. But it won't solve my problems here. Neither assigning arbitrary priorities since I (or the person who would add late content) would potentially need to reprioritize my previous events, which I don't want to. \$\endgroup\$
    – kuniqs
    Commented Oct 18, 2014 at 10:17

You must log in to answer this question.

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