Fase 9a: extraer CollisionSystem de GameScene a modulo aparte

GameScene::detectar_col*_* (3 funciones de deteccion de gameplay,
~170 LOC) salen a Systems::Collision en
source/game/systems/collision_system.{hpp,cpp}.

API:
- struct Systems::Collision::Context: agrupa todo lo que las
  detecciones leen/modifican (ships, enemies, bullets, hit_timer,
  score, lives, debris, floating_score, match_config) y un callback
  on_player_hit para delegar la muerte del jugador.
- Funciones libres: detectBulletEnemy, detectShipEnemy,
  detectBulletPlayer y detectAll.

GameScene::update construye el Context y llama detectAll. La
funcion GameScene::tocado se inyecta via lambda. El cuerpo de update
queda mas legible y separa fisica de gameplay (lo decide el solver)
de fisica rigida (lo decide PhysicsWorld).

GameScene.cpp: 1429 -> 1274 LOC. Smoke test xvfb OK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-20 07:47:42 +02:00
parent e98b87243b
commit 896a899b0f
4 changed files with 221 additions and 174 deletions
+16 -171
View File
@@ -21,6 +21,7 @@
#include "core/system/scene_context.hpp"
#include "core/system/global_events.hpp"
#include "game/stage_system/stage_loader.hpp"
#include "game/systems/collision_system.hpp"
// Using declarations per simplificar el codi
using SceneManager::SceneContext;
@@ -504,9 +505,21 @@ void GameScene::update(float delta_time) {
bullet.update(delta_time);
}
detectar_col·lisions_bales_enemics();
detectar_col·lisio_naus_enemics();
detectar_col·lisions_bales_jugadors();
{
Systems::Collision::Context col_ctx{
.ships = ships_,
.enemies = enemies_,
.bullets = bullets_,
.hit_timer_per_player = hit_timer_per_player_,
.score_per_player = score_per_player_,
.lives_per_player = lives_per_player_,
.debris_manager = debris_manager_,
.floating_score_manager = floating_score_manager_,
.match_config = match_config_,
.on_player_hit = [this](uint8_t pid) { tocado(pid); },
};
Systems::Collision::detectAll(col_ctx);
}
debris_manager_.update(delta_time);
floating_score_manager_.update(delta_time);
break;
@@ -972,174 +985,6 @@ std::string GameScene::buildScoreboard() const {
return score_p1 + " " + vides_p1 + " LEVEL " + stage_str + " " + score_p2 + " " + vides_p2;
}
void GameScene::detectar_col·lisions_bales_enemics() {
// Amplificador per hitbox més generós (115%)
constexpr float AMPLIFIER = Defaults::Game::COLLISION_BULLET_ENEMY_AMPLIFIER;
// Velocidad de explosión reduïda per efecte suau
constexpr float VELOCITAT_EXPLOSIO = 80.0F; // px/s (en lloc de 80.0f per defecte)
// Iterar per todas las balas i enemigos
for (auto& bullet : bullets_) {
for (auto& enemy : enemies_) {
// Comprovar colisión utilitzant la interfície genèrica
if (Physics::check_collision(bullet, enemy, AMPLIFIER)) {
// *** COL·LISIÓ DETECTADA ***
const Vec2& pos_enemic = enemy.getCenter();
// 1. Calculate score for enemy type
int points = 0;
switch (enemy.getType()) {
case EnemyType::PENTAGON:
points = Defaults::Enemies::Scoring::PENTAGON_SCORE;
break;
case EnemyType::QUADRAT:
points = Defaults::Enemies::Scoring::QUADRAT_SCORE;
break;
case EnemyType::MOLINILLO:
points = Defaults::Enemies::Scoring::MOLINILLO_SCORE;
break;
}
// 2. Add score to the player who shot it
uint8_t owner_id = bullet.getOwnerId();
score_per_player_[owner_id] += points;
// 3. Create floating score number
floating_score_manager_.crear(points, pos_enemic);
// 4. Destruir enemy (marca como inactiu)
enemy.destruir();
// 2. Crear explosión de fragments
Vec2 vel_enemic = enemy.getVelocityVector();
debris_manager_.explode(
enemy.getShape(), // Forma vectorial del pentágono
pos_enemic, // Posición central
0.0F, // Angle (enemy té rotación interna)
1.0F, // Escala normal
VELOCITAT_EXPLOSIO, // 50 px/s (explosión suau)
enemy.getBrightness(), // Heredar brightness
vel_enemic, // Heredar velocity
enemy.getRotationDelta(), // Heredar velocity angular (trayectorias curvas)
0.0F // Sin herencia visual (rotación aleatoria)
);
// 3. Desactivar bullet
bullet.desactivar();
// 4. Eixir del bucle intern (bullet solo destrueix 1 enemy)
break;
}
}
}
}
void GameScene::detectar_col·lisio_naus_enemics() {
// Amplificador per hitbox generós (80%)
constexpr float AMPLIFIER = Defaults::Game::COLLISION_SHIP_ENEMY_AMPLIFIER;
// Check collision for BOTH players
for (uint8_t i = 0; i < 2; i++) {
// Skip collisions if player is dead or invulnerable
if (hit_timer_per_player_[i] > 0.0F) {
continue;
}
if (!ships_[i].isAlive()) {
continue;
}
if (ships_[i].isInvulnerable()) {
continue;
}
// Check collision with all active enemies
for (const auto& enemy : enemies_) {
// Skip collision if enemy is invulnerable
if (enemy.isInvulnerable()) {
continue;
}
// Comprovar colisión utilitzant la interfície genèrica
if (Physics::check_collision(ships_[i], enemy, AMPLIFIER)) {
tocado(i); // Trigger death sequence for player i
break; // Only one collision per player per frame
}
}
}
}
void GameScene::detectar_col·lisions_bales_jugadors() {
// Skip if friendly fire disabled
if (!Defaults::Game::FRIENDLY_FIRE_ENABLED) {
return;
}
// Amplificador per hitbox exacte (100%)
constexpr float AMPLIFIER = Defaults::Game::COLLISION_BULLET_PLAYER_AMPLIFIER;
// Check all active bullets
for (auto& bullet : bullets_) {
if (!bullet.esta_activa()) {
continue;
}
// Skip bullets in grace period (prevents instant self-collision)
if (bullet.getGraceTimer() > 0.0F) {
continue;
}
uint8_t bullet_owner = bullet.getOwnerId();
// Check collision with BOTH players
for (uint8_t player_id = 0; player_id < 2; player_id++) {
// Skip if player is dead, invulnerable, or inactive
if (hit_timer_per_player_[player_id] > 0.0F) {
continue;
}
if (!ships_[player_id].isAlive()) {
continue;
}
if (ships_[player_id].isInvulnerable()) {
continue;
}
// Skip inactive players
bool jugador_actiu = (player_id == 0) ? match_config_.jugador1_actiu
: match_config_.jugador2_actiu;
if (!jugador_actiu) {
continue;
}
// Comprovar colisión utilitzant la interfície genèrica
if (Physics::check_collision(bullet, ships_[player_id], AMPLIFIER)) {
// *** FRIENDLY FIRE HIT ***
if (bullet_owner == player_id) {
// CASE 1: Self-hit (own bullet)
// Player loses 1 life, no gain
tocado(player_id);
} else {
// CASE 2: Teammate hit
// Victim loses 1 life
tocado(player_id);
// Attacker gains 1 life (no sin)
lives_per_player_[bullet_owner]++;
}
// Play distinct sound
Audio::get()->playSound(Defaults::Sound::FRIENDLY_FIRE_HIT, Audio::Group::GAME);
// Deactivate bullet
bullet.desactivar();
break; // Bullet only hits once per frame
}
}
}
}
// [NEW] Stage system helper methods
void GameScene::dibuixar_missatge_stage(const std::string& message) {