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:
@@ -21,6 +21,7 @@
|
|||||||
#include "core/system/scene_context.hpp"
|
#include "core/system/scene_context.hpp"
|
||||||
#include "core/system/global_events.hpp"
|
#include "core/system/global_events.hpp"
|
||||||
#include "game/stage_system/stage_loader.hpp"
|
#include "game/stage_system/stage_loader.hpp"
|
||||||
|
#include "game/systems/collision_system.hpp"
|
||||||
|
|
||||||
// Using declarations per simplificar el codi
|
// Using declarations per simplificar el codi
|
||||||
using SceneManager::SceneContext;
|
using SceneManager::SceneContext;
|
||||||
@@ -504,9 +505,21 @@ void GameScene::update(float delta_time) {
|
|||||||
bullet.update(delta_time);
|
bullet.update(delta_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
detectar_col·lisions_bales_enemics();
|
{
|
||||||
detectar_col·lisio_naus_enemics();
|
Systems::Collision::Context col_ctx{
|
||||||
detectar_col·lisions_bales_jugadors();
|
.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);
|
debris_manager_.update(delta_time);
|
||||||
floating_score_manager_.update(delta_time);
|
floating_score_manager_.update(delta_time);
|
||||||
break;
|
break;
|
||||||
@@ -972,174 +985,6 @@ std::string GameScene::buildScoreboard() const {
|
|||||||
return score_p1 + " " + vides_p1 + " LEVEL " + stage_str + " " + score_p2 + " " + vides_p2;
|
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
|
// [NEW] Stage system helper methods
|
||||||
|
|
||||||
void GameScene::dibuixar_missatge_stage(const std::string& message) {
|
void GameScene::dibuixar_missatge_stage(const std::string& message) {
|
||||||
|
|||||||
@@ -82,9 +82,6 @@ class GameScene {
|
|||||||
|
|
||||||
// Funciones privades
|
// Funciones privades
|
||||||
void tocado(uint8_t player_id);
|
void tocado(uint8_t player_id);
|
||||||
void detectar_col·lisions_bales_enemics(); // Colisiones bullet-enemy
|
|
||||||
void detectar_col·lisio_naus_enemics(); // Ship-enemy collision detection (plural)
|
|
||||||
void detectar_col·lisions_bales_jugadors(); // Bullet-player collision detection (friendly fire)
|
|
||||||
void dibuixar_marges() const; // Dibuixar vores de la zona de juego
|
void dibuixar_marges() const; // Dibuixar vores de la zona de juego
|
||||||
void dibuixar_marcador(); // Dibuixar marcador de puntuación
|
void dibuixar_marcador(); // Dibuixar marcador de puntuación
|
||||||
void disparar_bala(uint8_t player_id); // Shoot bullet from player
|
void disparar_bala(uint8_t player_id); // Shoot bullet from player
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
// collision_system.cpp - Implementación del sistema de colisiones
|
||||||
|
|
||||||
|
#include "game/systems/collision_system.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "core/audio/audio.hpp"
|
||||||
|
#include "core/physics/collision.hpp"
|
||||||
|
#include "core/types.hpp"
|
||||||
|
|
||||||
|
namespace Systems::Collision {
|
||||||
|
|
||||||
|
void detectBulletEnemy(Context& ctx) {
|
||||||
|
constexpr float AMPLIFIER = Defaults::Game::COLLISION_BULLET_ENEMY_AMPLIFIER;
|
||||||
|
constexpr float VELOCITAT_EXPLOSIO = 80.0F; // px/s (explosión suau)
|
||||||
|
|
||||||
|
for (auto& bullet : ctx.bullets) {
|
||||||
|
for (auto& enemy : ctx.enemies) {
|
||||||
|
if (!Physics::check_collision(bullet, enemy, AMPLIFIER)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// *** COLISIÓN bullet → enemy ***
|
||||||
|
const Vec2& POS_ENEMIC = enemy.getCenter();
|
||||||
|
|
||||||
|
// 1. Puntos según tipo
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t owner_id = bullet.getOwnerId();
|
||||||
|
ctx.score_per_player[owner_id] += points;
|
||||||
|
ctx.floating_score_manager.crear(points, POS_ENEMIC);
|
||||||
|
|
||||||
|
// 2. Destruir enemy + crear explosión
|
||||||
|
enemy.destruir();
|
||||||
|
Vec2 vel_enemic = enemy.getVelocityVector();
|
||||||
|
ctx.debris_manager.explode(
|
||||||
|
enemy.getShape(),
|
||||||
|
POS_ENEMIC,
|
||||||
|
0.0F, // angle (la rotación es interna del enemy)
|
||||||
|
1.0F, // escala
|
||||||
|
VELOCITAT_EXPLOSIO,
|
||||||
|
enemy.getBrightness(),
|
||||||
|
vel_enemic,
|
||||||
|
enemy.getRotationDelta(),
|
||||||
|
0.0F // sin herencia visual
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Desactivar bullet (solo destruye 1 enemy)
|
||||||
|
bullet.desactivar();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void detectShipEnemy(Context& ctx) {
|
||||||
|
constexpr float AMPLIFIER = Defaults::Game::COLLISION_SHIP_ENEMY_AMPLIFIER;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < 2; i++) {
|
||||||
|
// Skip si ya tocado / muerto / invulnerable
|
||||||
|
if (ctx.hit_timer_per_player[i] > 0.0F ||
|
||||||
|
!ctx.ships[i].isAlive() ||
|
||||||
|
ctx.ships[i].isInvulnerable()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& enemy : ctx.enemies) {
|
||||||
|
if (enemy.isInvulnerable()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (Physics::check_collision(ctx.ships[i], enemy, AMPLIFIER)) {
|
||||||
|
ctx.on_player_hit(i);
|
||||||
|
break; // Solo una colisión por player por frame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void detectBulletPlayer(Context& ctx) {
|
||||||
|
if (!Defaults::Game::FRIENDLY_FIRE_ENABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr float AMPLIFIER = Defaults::Game::COLLISION_BULLET_PLAYER_AMPLIFIER;
|
||||||
|
|
||||||
|
for (auto& bullet : ctx.bullets) {
|
||||||
|
if (!bullet.esta_activa() || bullet.getGraceTimer() > 0.0F) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const uint8_t BULLET_OWNER = bullet.getOwnerId();
|
||||||
|
|
||||||
|
for (uint8_t player_id = 0; player_id < 2; player_id++) {
|
||||||
|
if (ctx.hit_timer_per_player[player_id] > 0.0F ||
|
||||||
|
!ctx.ships[player_id].isAlive() ||
|
||||||
|
ctx.ships[player_id].isInvulnerable()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const bool JUGADOR_ACTIU = (player_id == 0)
|
||||||
|
? ctx.match_config.jugador1_actiu
|
||||||
|
: ctx.match_config.jugador2_actiu;
|
||||||
|
if (!JUGADOR_ACTIU) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Physics::check_collision(bullet, ctx.ships[player_id], AMPLIFIER)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// *** FRIENDLY FIRE HIT ***
|
||||||
|
if (BULLET_OWNER == player_id) {
|
||||||
|
// Self-hit: víctima pierde 1 vida.
|
||||||
|
ctx.on_player_hit(player_id);
|
||||||
|
} else {
|
||||||
|
// Teammate hit: víctima pierde 1, atacante gana 1.
|
||||||
|
ctx.on_player_hit(player_id);
|
||||||
|
ctx.lives_per_player[BULLET_OWNER]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Audio::get()->playSound(Defaults::Sound::FRIENDLY_FIRE_HIT, Audio::Group::GAME);
|
||||||
|
bullet.desactivar();
|
||||||
|
break; // Una bullet solo impacta una vez por frame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void detectAll(Context& ctx) {
|
||||||
|
detectBulletEnemy(ctx);
|
||||||
|
detectShipEnemy(ctx);
|
||||||
|
detectBulletPlayer(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Systems::Collision
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// collision_system.hpp - Detección de colisiones de gameplay
|
||||||
|
// © 2025 Orni Attack
|
||||||
|
//
|
||||||
|
// Detecta colisiones bullet↔enemy, ship↔enemy y bullet↔player y aplica los
|
||||||
|
// efectos directos sobre las entidades (destruir enemy, desactivar bullet,
|
||||||
|
// crear debris/floating-score, ajustar score y lives). Las consecuencias de
|
||||||
|
// gameplay que requieren transición de estado (muerte del jugador, game over)
|
||||||
|
// se delegan a un callback `on_player_hit`.
|
||||||
|
//
|
||||||
|
// Esto es física de gameplay, NO física rígida — el PhysicsWorld ya resolvió
|
||||||
|
// los impulsos físicos antes. Aquí solo decidimos quién muere/destruye a quién.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include "core/defaults.hpp"
|
||||||
|
#include "core/system/game_config.hpp"
|
||||||
|
#include "game/effects/debris_manager.hpp"
|
||||||
|
#include "game/effects/floating_score_manager.hpp"
|
||||||
|
#include "game/entities/bullet.hpp"
|
||||||
|
#include "game/entities/enemy.hpp"
|
||||||
|
#include "game/entities/ship.hpp"
|
||||||
|
|
||||||
|
namespace Systems::Collision {
|
||||||
|
|
||||||
|
// Todo lo que las detecciones necesitan leer/modificar. Vive en GameScene;
|
||||||
|
// se le pasa por referencia (no copia, no ownership).
|
||||||
|
struct Context {
|
||||||
|
std::array<Ship, 2>& ships;
|
||||||
|
std::array<Enemy, Defaults::Entities::MAX_ORNIS>& enemies;
|
||||||
|
std::array<Bullet, Defaults::Entities::MAX_BALES * 2>& bullets;
|
||||||
|
std::array<float, 2>& hit_timer_per_player;
|
||||||
|
std::array<int, 2>& score_per_player;
|
||||||
|
std::array<int, 2>& lives_per_player;
|
||||||
|
Effects::DebrisManager& debris_manager;
|
||||||
|
Effects::FloatingScoreManager& floating_score_manager;
|
||||||
|
const GameConfig::MatchConfig& match_config;
|
||||||
|
// Trigger de muerte del jugador (GameScene::tocado).
|
||||||
|
std::function<void(uint8_t /*player_id*/)> on_player_hit;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Detecta colisiones bullet → enemy. Si hit:
|
||||||
|
// - destruye el enemy (radius=0 en physics body)
|
||||||
|
// - crea debris + floating score
|
||||||
|
// - desactiva la bullet
|
||||||
|
// - suma puntos al shooter
|
||||||
|
void detectBulletEnemy(Context& ctx);
|
||||||
|
|
||||||
|
// Detecta colisiones ship → enemy. Si hit, llama on_player_hit(player_id).
|
||||||
|
void detectShipEnemy(Context& ctx);
|
||||||
|
|
||||||
|
// Detecta colisiones bullet → player (friendly fire / self-hit).
|
||||||
|
// Self-hit: el shooter pierde 1 vida. Teammate-hit: la víctima pierde 1, el
|
||||||
|
// atacante gana 1. En ambos casos, llama on_player_hit y desactiva bullet.
|
||||||
|
void detectBulletPlayer(Context& ctx);
|
||||||
|
|
||||||
|
// Las tres en orden lógico del frame.
|
||||||
|
void detectAll(Context& ctx);
|
||||||
|
|
||||||
|
} // namespace Systems::Collision
|
||||||
Reference in New Issue
Block a user