Aller au contenu

Système de Collisions

Détection AABB (Axis-Aligned Bounding Box).

AABB

struct AABB {
    float x, y;       // Position (top-left)
    float w, h;       // Dimensions

    bool intersects(const AABB& other) const {
        return x < other.x + other.w &&
               x + w > other.x &&
               y < other.y + other.h &&
               y + h > other.y;
    }

    bool contains(float px, float py) const {
        return px >= x && px < x + w &&
               py >= y && py < y + h;
    }

    AABB expanded(float margin) const {
        return {x - margin, y - margin, w + margin*2, h + margin*2};
    }
};

Visualisation

    x,y ─────────────────┐
     │                   │
     │      AABB         │ h
     │                   │
     └───────────────────┘
              w

Intersection :

  ┌─────────┐
  │    A    │
  │    ┌────┼────┐
  └────┼────┘    │
       │    B    │
       └─────────┘
     Intersection!


Détection dans GameWorld

void GameWorld::checkCollisions() {
    // Missiles joueurs vs Ennemis
    for (auto& missile : playerMissiles_) {
        for (auto& enemy : enemies_) {
            if (missile.getAABB().intersects(enemy.getAABB())) {
                enemy.takeDamage(missile.damage_);
                missile.destroy();

                if (!enemy.isAlive()) {
                    addScore(enemy.getScoreValue());
                    broadcastDestroy(enemy.id_);
                }
                break;
            }
        }
    }

    // Missiles ennemis vs Joueurs
    for (auto& missile : enemyMissiles_) {
        for (auto& player : players_) {
            if (!player.isAlive()) continue;

            if (missile.getAABB().intersects(player.getAABB())) {
                player.takeDamage(missile.damage_);
                missile.destroy();
                broadcastPlayerDamaged(player.id_, missile.damage_);

                if (!player.isAlive()) {
                    broadcastPlayerDied(player.id_);
                }
                break;
            }
        }
    }

    // Ennemis vs Joueurs (contact)
    for (auto& enemy : enemies_) {
        for (auto& player : players_) {
            if (!player.isAlive()) continue;

            if (enemy.getAABB().intersects(player.getAABB())) {
                player.takeDamage(CONTACT_DAMAGE);
                broadcastPlayerDamaged(player.id_, CONTACT_DAMAGE);

                if (!player.isAlive()) {
                    broadcastPlayerDied(player.id_);
                }
            }
        }
    }
}

Optimisation : Spatial Partitioning

Pour beaucoup d'entités, utiliser une grille :

class SpatialGrid {
    static constexpr int CELL_SIZE = 128;
    static constexpr int GRID_WIDTH = WORLD_WIDTH / CELL_SIZE;
    static constexpr int GRID_HEIGHT = WORLD_HEIGHT / CELL_SIZE;

    std::vector<Entity*> cells_[GRID_WIDTH][GRID_HEIGHT];

public:
    void clear() {
        for (int x = 0; x < GRID_WIDTH; x++)
            for (int y = 0; y < GRID_HEIGHT; y++)
                cells_[x][y].clear();
    }

    void insert(Entity* entity) {
        auto aabb = entity->getAABB();
        int x1 = std::max(0, (int)(aabb.x / CELL_SIZE));
        int y1 = std::max(0, (int)(aabb.y / CELL_SIZE));
        int x2 = std::min(GRID_WIDTH-1, (int)((aabb.x + aabb.w) / CELL_SIZE));
        int y2 = std::min(GRID_HEIGHT-1, (int)((aabb.y + aabb.h) / CELL_SIZE));

        for (int x = x1; x <= x2; x++)
            for (int y = y1; y <= y2; y++)
                cells_[x][y].push_back(entity);
    }

    std::vector<Entity*> query(const AABB& area) {
        std::set<Entity*> result;
        int x1 = std::max(0, (int)(area.x / CELL_SIZE));
        int y1 = std::max(0, (int)(area.y / CELL_SIZE));
        int x2 = std::min(GRID_WIDTH-1, (int)((area.x + area.w) / CELL_SIZE));
        int y2 = std::min(GRID_HEIGHT-1, (int)((area.y + area.h) / CELL_SIZE));

        for (int x = x1; x <= x2; x++)
            for (int y = y1; y <= y2; y++)
                for (auto* e : cells_[x][y])
                    result.insert(e);

        return {result.begin(), result.end()};
    }
};

Complexité

Approche Complexité Entités
Brute force O(n²) < 100
Spatial grid O(n) 100-1000
Quadtree O(n log n) 1000+

Pour R-Type (~50 entités max), brute force suffit.


Hitboxes

Définies dans src/common/collision/AABB.hpp :

Entité Largeur Hauteur Constante
Player (Ship) 64 30 SHIP_WIDTH, SHIP_HEIGHT
Enemy (default) 40 40 ENEMY_WIDTH, ENEMY_HEIGHT
Player Missile 16 8 MISSILE_WIDTH, MISSILE_HEIGHT
namespace collision::Hitboxes {
    static constexpr float SHIP_WIDTH = 64.0f;
    static constexpr float SHIP_HEIGHT = 30.0f;
    static constexpr float MISSILE_WIDTH = 16.0f;
    static constexpr float MISSILE_HEIGHT = 8.0f;
    static constexpr float ENEMY_WIDTH = 40.0f;
    static constexpr float ENEMY_HEIGHT = 40.0f;
}