CollisionSystem
The CollisionSystem handles the physical interactions between entities. It uses AABB (Axis-Aligned Bounding Box) logic to detect overlaps and resolves gameplay consequences such as damage application, projectile destruction, and entity death.
Required Components
Entities processed by this system must have the following components:
1. Transform: To verify the entity exists in the world.
2. Sprite: Used for the actual collision bounds (globalBounds).
3. HitBox: Acts as a tag to mark the entity as collidable.
4. Health (Optional): Required to take damage.
5. Projectile (Optional): Identifies the entity as a bullet/missile.
6. InputComponent (Optional): Used to distinguish Players from AI.
Logic & Algorithm
The system checks every collidable entity against every other.
-
Detection:
- Uses SFML's
FloatRect::intersects()on theSpritecomponents to detect AABB overlap.
- Uses SFML's
-
Projectile Rules (Friendly Fire):
- Player Projectiles: Can not damage entities with
InputComponent(other players or self). - Enemy Projectiles: Can only damage entities with
InputComponent(players). - Resolution: If a valid hit occurs, the projectile is destroyed immediately.
- Player Projectiles: Can not damage entities with
-
Damage Application:
- If collision is valid,
Health.currentHealthis reduced. - Death: If HP $\le$ 0, the entity is "killed" by removing its core components (
Transform,Sprite,Health,HitBox).
- If collision is valid,
-
Physical Collision (Non-Projectile):
- If two solid bodies collide (e.g., Player vs Enemy Ship), a default collision damage (10) is applied to both sides.
Code reference
src/game/src/systems/CollisionSystem.cpp
void CollisionSystem::onUpdate(float dt) {
auto& transforms = _engine.getComponents<Transform>();
auto& sprites = _engine.getComponents<Sprite>();
auto& healths = _engine.getComponents<Health>();
auto& hitboxes = _engine.getComponents<HitBox>();
auto& projectiles = _engine.getComponents<Projectile>();
auto& inputs = _engine.getComponents<InputComponent>();
std::vector<size_t> entities(_entities.begin(), _entities.end());
for (size_t i = 0; i < entities.size(); ++i) {
size_t e1 = entities[i];
if (!transforms[e1] || !sprites[e1] || !hitboxes[e1]) continue;
for (size_t j = i + 1; j < entities.size(); ++j) {
size_t e2 = entities[j];
if (!transforms[e2] || !sprites[e2] || !hitboxes[e2]) continue;
if (!checkAABBCollision(sprites[e1].value(), sprites[e2].value())) continue;
bool e1Projectile = e1 < projectiles.size() && projectiles[e1].has_value();
bool e2Projectile = e2 < projectiles.size() && projectiles[e2].has_value();
auto canHit = [&](const Projectile& proj, size_t targetId) {
bool targetIsPlayer = targetId < inputs.size() && inputs[targetId].has_value();
bool shooterIsPlayer = static_cast<size_t>(proj.shooterId) < inputs.size() && inputs[proj.shooterId].has_value();
if (shooterIsPlayer) return !targetIsPlayer; // no friendly fire
return targetIsPlayer; // enemy bullets hit players only
};
// projectile vs entity
// ... applyDamage/destroyProjectile helpers in code
}
}
}
Diagram: AABB Check
[ Entity A ]
+----------+
| |
| +---+------+
| | |///////////| <-- Overlap Detected
+------+---+ |
| Entity B |
+----------+
Usage
// Example: Creating a collidable entity
Entity e = gameEngine.createEntity("Asteroid");
gameEngine.addComponent(e, Transform(x, y, E_ROT, E_SCALE));
gameEngine.addComponent(e, Sprite(ASSET_ID, Z_INDEX, sf::IntRect(
0,
0,
RECT_WIDTH,
RECT_HEIGHT
)));
gameEngine.addComponent(e, HitBox()); // Essential tag
gameEngine.addComponent(e, Health(CURRENT, MAX));