3
\$\begingroup\$

I'm using a component based entity design with Components that hold the data and Systems that act on entities based on the components they have. Entities are registered to all systems as they are created. If the entity has the list of components the system requires then it'll add it to the system's list to be processed on Update.


Lets say I have a BoundingBoxCollisionComponent a BoundingCircleCollisionComponent and an EdgeListCollisionComponent.

I have various entities that each have one of these components. For example a player entity could have the BoundingBoxCollisionComponent, a bullet entity could have a BoundingCircleCollisionComponent and a gameworld entity could have an EdgeListCollisionComponent.

Note that my player, bullet and gameworld names aren't types, they're just templates that define components that they all have along with default values for those components.


Any collision handling class(es) need to overcome the following issues, that I can't figure out how best to overcome.

  1. Obviously the collision detection code is different depending on the exact CollisionComponent that 2 entities have. How can I effectively create classes to handle checking 2 entities for a collision, given that I could add other collision components in the future?

  2. In the scenario above I wouldn't want to check each Bullet for a collision with every other Bullet. Once the entity is created from the bullet template all notion of it being a bullet disappears, so How can I manage what entities should be checked against what others? Do I need to have an EntityTypeComponent which would have Bullet, Player... so I can define in a class somewhere what collides with what, or maybe I could do it at entity creation time (when I know what template is used to create the entity)?

\$\endgroup\$

2 Answers 2

5
\$\begingroup\$

For your first question you will obviously have some base functions that handle collision detection for any combination of collision type pairs (i.e. circle/circle, circle/box, circle/ray, box/box, box/ray...). Routing a pair (using a common base class) will require some type introspection or at the very least a type.

For example if every collision component inherited from BaseCollisionComponent which had a type property (Circle, Box, Ray...) then you could define a Collision function in BaseCollisionComponent that takes any pair and based on their type property values routes to the correct collision detection function. This function would handle undefined pairs so when adding new CollisionComponents you would update this to support that type with every other type or it would return an error.

The second question is a case of collision filtering: before performing broad phase collision detection (bounding boxes) or narrow phase collision detection (testing for the exact collision) you want to filter out types (as in game-level types) that shouldn't collide.

A common way to do this to apply a mask to each object, again in a base class you define two bit fields one is the objects collision type and the other is the types you want that object to collide with. So each is an integer where each bit is considered a flag denoting a type. Then when you are comparing collisioncomponents you go to your broad phase only where object one's collision type & (as in the binary operator) object two's collides with field doesn't equal zero or where object two's collision type & (as in the binary operator) object one's collide's with field doesn't equal zero.

I always liked this solution, within your collision function it's just comparing these four integers, defining the fields can be kept at a game level and you can simply define key object's type and leave the configuration to other types (e.g. the player CollisionComponent has a type defining player but no collides with field, then the bullet has a type defining bullet and in it's collides with field you set the player type).

\$\endgroup\$
2
\$\begingroup\$

I struggled once with this problem. I'm not sure I solved it properly. But to answer your questions:

1. colliding heterogenous components:

The collision detection is done through Something like CollisionDetector(CollisionShape s1, PhyData ph1, CollisionShape s2, PhyData ph2) like function. this function then reorders the shapes by type:

if (s1.type < s2.type)
    return s2.Collide(ph2, s1, ph1).Invert();
else
    return s1.Collide(ph1, s2, ph2);

Each CollisionShape must be able to collide with "inferior" types, this way I ensure the system works when new types are added.

2 Colliding only with the right components:

My solution was to scrap all the different collision component types and create just a base CollisionComponentAbstract and add an array of CollisionShape to my CollisionComponentMultiImpl while my CollisionComponentTrivialImpl has only one CollisionShape (no need to have arrays for my bullets)

The collision component (Abstract) receives a list of groups it belongs to (one of my implementation uses a mask) and a list of groups if must ignore (very convenient for friendly fire switches or avoiding bullet collisions).

It is configured with a list of shapes for the initial collision detection (bounding spheres mostly) and a list of collision shapes for deeper collision checks. There are 3 levels in total but you can have more or less.

The process goes more or less like this:

  • On collision detection the ignoreGroup mask/array of each component close enough is checked with the belongsTo mask/array. If no groups are in common then OK.
  • Then the belongsTo masks are compared and if at least one group is OK then continue.
  • Then for each level:
    • For each shape in cmp1 collide with non collided shapes in cmp2. If a Collision is returned keep it
    • if at least one of the components has a higher level of collision shapes increment to the next level where possible and start colliding from scratch
    • else continue colliding at the same level and add each colision to the resulting array.
    • handle the collisions list.

It's a bit more complex than that but this method gives me a few advantages:

  • I can add multiple passes for each component and the shapes of one component are never tested against each other if it's a rigid body.
  • Components with a higher number of levels can collide with simple components like bullets (which have only one shape) without using any kind of special case.
  • I can add multiple collision components with different responders. For example a buff zone around the player will create a dedicated collision component with a responder set to buff any entity responding to the collision if it has the components which can be buffed.

I know this is not the most efficient way of doing it but it's flexible for me and until now my projectiles are pretty slow.

How it should be done:

For small fast projectiles, a friend who worked on AAA fps games told me they had collision arrays where one array was dedicated to bullets (position, velocity, type, game data). If I remember well this array is used to do collision detection on the other objects with a first ray cast pass for potential collisions on the bounding shapes. Then there is a second pass reordered by distance on the more detailed models with a dedicated collision resolution. (No shapes used for bullets I think).

Using this principle you can have a system with multiple lists of components (one dedicated to bullets) and when the template for a bullet type is used it will generate a CollisionComponentFSProjectileImpl which will be stored in a dedicated list in your system. Therefore The collision system will always pass another collision component to the CollisionComponentFSProjectileImpl during collision detection which will be able to do collision detection/resolution its own way.

I believe there is nothing wrong with component specialization as long as it doesn't interfere with other unspecialized components and it is properly handled by the systems.

\$\endgroup\$
1
  • \$\begingroup\$ I really like your method for heterogenous collisions. Simple yet effective! \$\endgroup\$
    – Alex
    Commented Aug 6, 2013 at 12:27

You must log in to answer this question.

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