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.