4
\$\begingroup\$

I have CollisionSystem which basically iterates through the array of PhysicsComponent and checks if there was some collision between two entities. But where should interaction code go? And how should I deal with different types of entities? (pseudocode below)

// in collision system
for(e1 in all entities) {
    for(e2 in all entities) {
        if(e1 == e2) continue;
        if(e1 collides with e2) {
            handleCollision(e1, e2);
        }
    }
}

...
// probably in collision system too?
void handleCollision(Entity* e1, Entity* e2) {
    if(e1 has DamageComponent) {
        if(e2 has HealthComponent) {
            e2->HealthComponent->
            reduceHP(e1->DamageComponent->damageAmount);
    //but what if some entities show "Hurt" animation or
    // do some other stuff specific for that type of entity?
        }
    }
    ... // other types of entities
}

What is bad is that this handleCollision code will have lots of if-else conditions as more and more types of entities emerge and the code will be hard to maintain. How can I deal with this?

\$\endgroup\$
  • \$\begingroup\$ Have you noticed in Halo 3 stuff bumps when a play approaches them? (like the big square crates, or those radio towers...) or anything moving (say grenade). That's because the physics system isn't bothering with them at all. When something goes near them they fall (they are slightly above the surface) and for computational simplicity rather than ray casting, the next frame is when the force up is applied to keep them out of the floor (or impulse actually) but anyway, that's why they bump. \$\endgroup\$ – Alec Teal Apr 24 '14 at 10:08
7
\$\begingroup\$

There are many, many ways to approach this, depending on the needs of your game and what you want the API surfaces to look like. This is also not a problem that really has anything to do with entity systems. A physics API should maintain a list of physics object (often "rigid bodies"), detect, and resolve the collisions between them. The API can be a "system" in the common component-based parlance, or not. A physics object can be a physics component or not. It doesn't matter: the solution is the same.

You don't want the physics code itself to know anything about the interactions between specific objects, beyond the fact that they collided (and perhaps some metadata about the collision). Instead, design your physics API to support the notification of a collision to interested parties, who can subscribe to events on the physics API if needed. You can implement this behavior with the observer pattern. Thus you delegate the processing of the interaction elsewhere.

In whatever higher-level code deals with the interaction now (typically something more at the "game" level), you can employ techniques such as double-dispatch to resolve interactions between two runtime component types (normal dynamic dispatch in C++ and most languages is done only on a single parameter, the this pointer; double-dispatch is a way to perform dynamic dispatch on two parameter types, which is all you have varying in the case of two objects interacting).

Back in the physics system itself, you can avoid the n-squared problem of checking every rigid body against every other rigid body using spatial partitioning techniques. By dividing up your simulation space into distinct regions (for example using quad-trees or oct-trees, or even simple separation-of-axis approaches) reduces the problem space for detecting collisions by immediately discarding large numbers of objects that cannot possibly collide.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thanks for your answer. Do I need to have some sort of DamagedBehaviour classes to deal with collisions when CollisionSystem notifies them that collision happened? \$\endgroup\$ – Elias Daler Apr 23 '14 at 7:36
  • 1
    \$\begingroup\$ If you want. You could do it with or without such a system though. \$\endgroup\$ – user1430 Apr 23 '14 at 15:13
0
\$\begingroup\$

Personally, I'd do something like this:

// in collision system
for(e1 in all entities) {
    for(e2 in all entities) {
        if(e1 == e2) continue;
        if(e1 collides with e2) {
            e1.sendMessage(COLLISION, e2);
            e2.sendMessage(COLLISION, e1);
        }
    }
}

//on the component that needs to handle a collision
void DamageComponent::handleMessage(MessageType msg, Entity* e) {
    if (msg == COLLISION) {
        if(e has HealthComponent) {
            e2.sendMessage(reduceHp, damageAmount);
        }
    }
}

void AnimationComponent::handleMessage(MessageType msg, Entity* e) {
    if (msg == COLLISION) {
        //...
    }
}

This requires a sendMessage method on the Entity class that sends on the message and parameter to each component that makes up the Entity. The Entity parameter on the handleMessage method should really be a void* so you can pass anything in.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ But I want to have different collision responses. Does that mean I'll have different components for each? \$\endgroup\$ – Elias Daler Apr 23 '14 at 10:16
  • \$\begingroup\$ Yes, and that's the point of component based entity systems. In the above example the DamageComponent's response to a collision is to damage the Entity it collided into. The AnimationComponent could play an animation. The logic for what response you get is in the choice of components, not hard coded in an if statement. \$\endgroup\$ – elFarto Apr 23 '14 at 11:24
  • \$\begingroup\$ No, I mean do I need to have something like FurnitureDamageComponent and EnemyDamageComponent, etc. because different entities will react on collisions differently \$\endgroup\$ – Elias Daler Apr 23 '14 at 14:49
  • \$\begingroup\$ It depends on exactly how 'different' you want entities to react. There's nothing to stop you making the DamageComponent configurable, but you need to take care you don't bundle every feature into a single component. How different to you see collision responses being? \$\endgroup\$ – elFarto Apr 23 '14 at 15:57
0
\$\begingroup\$

I actually found a pretty good website recently that helped me.

http://afloatingpoint.blogspot.com/

This lets you test for derivations and whatnot. Personally I would detatch my physics code so that the component is simply a pointer to some data and that the index of that data can easily be indexed.

You could go GameComponent->PhysicsComponent (abstract)->BoxCollider ... etc. Then you just need to go with

         Entity->GetComponent<PhysicsComponent>() 

to get the collider, or if you want a specific one you can check to see if you have

         Entity->GetComponent<BoxCollider>()

Also on your physics code you don't want to recheck specific pairings so your for loop could be different for a nice speed up. Also it is unlikely you want your callbacks to happen RIGHT when the collision happens. Maybe but I'd let my physics code finish up first so you don't destroy the cache and that way if you raycast or whatever all the colliders are resituated where they need to be.

    /*
        Want a continuous block of colliders for processing.
        Makes processing much faster.
    */
    vector<AABB> m_aabbColliders;
    vector<AABBCollider*> m_aabbComponents;

    Queue<Collision> m_collisionQueue;

    Collision collision;
    //No comparison necessary because when i is zero it evaluates as false
    for(auto i = m_aabbColliders.size();i;--i) 
    {
       //No need to check i or greater then i because all collisions
       //combinations in that part of the list have been checked.
       //Since j will never equal i we can ignore having the if(j == i) clause here.  
       for(auto j = i - 1;j < 0;--j) 
       {
         //Physics Code
          if(Physics::AABBvAABBCollision(m_aabbColliders[i],m_aabbColliders[j],&collision))
          {
              collision.first = m_aabbComponents[i];
              collision.second  m_aabbComponents[j];
               m_CollisionQueue.push_back(collision);
          }
       }
    }

    //Let the physics engine deal with collisions before you take call backs.
    //To resituate collided objects. 

    //Collisions are DONE now deal with callbacks to pass that info to
    //Entities that need to know.
    Message collisionMessage;
    for(auto i = m_collisionQueue.size();i;--i)
    {
       collisionMessage = Message(m_collisionQueue.pop());
       collisionMessage.asCollision().first->SendMessage(MessageType::Collision,collisionMessage);
       //Maybe swap first and second here depending on
       //if you want your engine to have the collision message
       //have first be itself always or something.
       collisionMessage.asCollision().second->SendMessage(MessageType::Collision,collisionMessage);
    }


 //DamageComponent Code

 void DamageComponent::RecieveMessage(MessageType type,const Message& msg)
 {
  switch(type)
  {
     case MessageType::Collision:
         Message dmg = Message(dmg_amount);
         msg.asCollision().second->SendMessage(MessageType::DealtDamage,dmg);
         break;
  }
}

void HealthComponent::RecieveMessage(MessageType type,const Message& msg)
{
    switch(type)
    {
      case MessageType::DealtDamage:
        this->ChangeHealth(-msg.asInt());
        break;
    }
}

void HealthComponent::ChangeHealth(int amount)
{
   if(m_health != m_maxHealth && m_health + amount >= m_maxHealth)
   {
      m_health = m_maxHealth;
      SendMessage(MessageType::HealthFilled);
   }
   else
   {
      m_health += amount;
      if(m_health > m_maxHealth) m_health = m_maxHealth;
   }

   if(m_health <= 0)
   {
      Message msg = Message(playerComponent);
      SendMessage(MessageType::PlayerDeath,msg);
   }
}

//On the Player script you can have it recieve the event then remove itself from   queues and setup respawn and all that other stuff.

As you can see this can get really interconnected but still be completely encapsulated in individual components.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.