refactor(debris): la bala impacta al cos O als trossos, mai a tots dos

This commit is contained in:
2026-05-25 21:26:32 +02:00
parent b511740d93
commit 610eaf257e
8 changed files with 87 additions and 32 deletions
+10 -6
View File
@@ -57,7 +57,8 @@ namespace Effects {
SDL_Color color,
float lifetime,
float friction,
int segment_multiplier) {
int segment_multiplier,
const Vec2& bullet_impulse_velocity) {
if (!shape || !shape->isValid()) {
return;
}
@@ -84,7 +85,7 @@ namespace Effects {
Vec2 world_p2 = transformPoint(local_p2, shape_centre, centro, angle, scale);
// Si el pool es ple, no té sentit continuar amb la resta de segments
if (!spawnDebris(world_p1, world_p2, centro, velocitat_base, brightness, velocitat_objecte, velocitat_angular, factor_herencia_visual, color, lifetime, friction)) {
if (!spawnDebris(world_p1, world_p2, centro, velocitat_base, brightness, velocitat_objecte, velocitat_angular, factor_herencia_visual, color, lifetime, friction, bullet_impulse_velocity)) {
return;
}
}
@@ -110,7 +111,7 @@ namespace Effects {
return segments;
}
auto DebrisManager::spawnDebris(const Vec2& world_p1, const Vec2& world_p2, const Vec2& centro, float velocitat_base, float brightness, const Vec2& velocitat_objecte, float velocitat_angular, float factor_herencia_visual, SDL_Color color, float lifetime, float friction) -> bool {
auto DebrisManager::spawnDebris(const Vec2& world_p1, const Vec2& world_p2, const Vec2& centro, float velocitat_base, float brightness, const Vec2& velocitat_objecte, float velocitat_angular, float factor_herencia_visual, SDL_Color color, float lifetime, float friction, const Vec2& bullet_impulse_velocity) -> bool {
Debris* debris = findFreeSlot();
if (debris == nullptr) {
std::cerr << "[DebrisManager] Warning: no debris slots disponibles\n";
@@ -131,13 +132,16 @@ namespace Effects {
// Direcció radial (desde el centro hacia el segment)
Vec2 direccio = computeExplosionDirection(world_p1, world_p2, centro);
// Velocidad inicial (base ± variació aleatòria + velocity heretada de l'objecte)
// Velocidad inicial (base ± variació aleatòria + velocity heretada de l'objecte +
// velocitat de la bala escalada per BULLET_IMPULSE_FACTOR).
float speed =
velocitat_base +
(((std::rand() / static_cast<float>(RAND_MAX)) * 2.0F - 1.0F) *
Defaults::Physics::Debris::VARIACIO_SPEED);
debris->velocity.x = (direccio.x * speed) + velocitat_objecte.x;
debris->velocity.y = (direccio.y * speed) + velocitat_objecte.y;
debris->velocity.x = (direccio.x * speed) + velocitat_objecte.x +
(bullet_impulse_velocity.x * Defaults::Physics::Debris::BULLET_IMPULSE_FACTOR);
debris->velocity.y = (direccio.y * speed) + velocitat_objecte.y +
(bullet_impulse_velocity.y * Defaults::Physics::Debris::BULLET_IMPULSE_FACTOR);
debris->acceleration = friction;
// Rotación de trayectoria (con conversió a tangencial si excedeix cap)
+8 -2
View File
@@ -47,6 +47,11 @@ namespace Effects {
// - lifetime: temps de vida del debris (s, per defecte TEMPS_VIDA = 2s)
// - friction: desacceleració del debris (px/s², per defecte ACCELERACIO = -60)
// - segment_multiplier: nombre de còpies per segment (per defecte 1 = sense duplicar)
// - bullet_impulse_velocity: velocitat de la bala que ha causat l'impacte (px/s,
// per defecte 0). S'aplica a cada fragment escalada per
// Defaults::Physics::Debris::BULLET_IMPULSE_FACTOR, independent de
// velocitat_objecte. Permet que els trossos "salten amb la força de la bala"
// encara que el cos sigui pesat i amb prou feines es mogui.
void explode(const std::shared_ptr<Graphics::Shape>& shape,
const Vec2& centro,
float angle,
@@ -60,7 +65,8 @@ namespace Effects {
SDL_Color color = {0, 0, 0, 0}, // alpha==0 → fragmentos usan oscilador global
float lifetime = Defaults::Physics::Debris::TEMPS_VIDA,
float friction = Defaults::Physics::Debris::ACCELERACIO,
int segment_multiplier = 1);
int segment_multiplier = 1,
const Vec2& bullet_impulse_velocity = {.x = 0.0F, .y = 0.0F});
// Actualitzar todos los fragments active
void update(float delta_time);
@@ -97,7 +103,7 @@ namespace Effects {
-> std::vector<std::pair<Vec2, Vec2>>;
// Inicialitza un debris en un slot lliure i el deixa actiu. Retorna
// false si el pool está ple (la cridadora ha d'aturar el bucle).
auto spawnDebris(const Vec2& world_p1, const Vec2& world_p2, const Vec2& centro, float velocitat_base, float brightness, const Vec2& velocitat_objecte, float velocitat_angular, float factor_herencia_visual, SDL_Color color, float lifetime, float friction) -> bool;
auto spawnDebris(const Vec2& world_p1, const Vec2& world_p2, const Vec2& centro, float velocitat_base, float brightness, const Vec2& velocitat_objecte, float velocitat_angular, float factor_herencia_visual, SDL_Color color, float lifetime, float friction, const Vec2& bullet_impulse_velocity) -> bool;
static void applyAngularVelocity(Debris& debris, const Vec2& direccio, float velocitat_angular);
static void applyVisualRotation(Debris& debris, float velocitat_angular, float factor_herencia_visual);
};
+7 -3
View File
@@ -580,7 +580,7 @@ auto GameScene::buildCollisionContext() -> Systems::Collision::Context {
.firework_manager = firework_manager_,
.floating_score_manager = floating_score_manager_,
.match_config = match_config_,
.on_player_hit = [this](uint8_t pid) { tocado(pid); },
.on_player_hit = [this](uint8_t pid, const Vec2& bv) { tocado(pid, bv); },
};
}
@@ -764,7 +764,7 @@ void GameScene::drawLevelCompletedState() {
drawScoreboard();
}
void GameScene::tocado(uint8_t player_id) {
void GameScene::tocado(uint8_t player_id, const Vec2& bullet_velocity) {
// Death sequence: 3 phases
// Phase 1: First call (hit_timer_per_player_[player_id] == 0) - trigger explosion
// Phase 2: Animation (0 < itocado_ < 3.0s) - debris animation
@@ -788,6 +788,9 @@ void GameScene::tocado(uint8_t player_id) {
// Mateixa dispersió i efecte que els debris d'enemic (lifetime,
// friction, segment_multiplier alineats); només canvien sound i color.
// bullet_velocity arriba a explode() com a impuls extra independent
// de la inèrcia del cos del ship — els trossos volen amb la força
// de la bala encara que el ship estiga quiet.
debris_manager_.explode(
ships_[player_id].getShape(),
SHIP_POS,
@@ -802,7 +805,8 @@ void GameScene::tocado(uint8_t player_id) {
ships_[player_id].getConfig().colors.normal,
Defaults::Physics::Debris::ENEMY_LIFETIME,
Defaults::Physics::Debris::ENEMY_FRICTION,
Defaults::Physics::Debris::ENEMY_SEGMENT_MULTIPLIER);
Defaults::Physics::Debris::ENEMY_SEGMENT_MULTIPLIER,
bullet_velocity);
// Start death timer (non-zero to avoid re-triggering)
hit_timer_per_player_[player_id] = Defaults::Game::HIT_TIMER_TRIGGER_DEATH;
+4 -1
View File
@@ -101,7 +101,10 @@ class GameScene final : public Scene {
bool init_hud_rect_sound_played_{false}; // Flag para evitar repetir sonido del rectángulo
// Funciones privades
void tocado(uint8_t player_id);
// bullet_velocity: velocitat de la bala que ha causat la mort (Vec2{} si no
// ve d'una bala). Es passa al debris perquè els fragments volin en direcció
// de la bala (independent de la inèrcia del cos del ship).
void tocado(uint8_t player_id, const Vec2& bullet_velocity = {.x = 0.0F, .y = 0.0F});
void drawScoreboard(); // Dibuixar marcador de puntuación
void fireBullet(uint8_t player_id); // Shoot bullet from player
[[nodiscard]] auto getSpawnPoint(uint8_t player_id) const -> Vec2; // Get spawn position for player
+20 -14
View File
@@ -150,7 +150,9 @@ namespace Systems::Collision {
const float DEATH_FACTOR = ctx.ships[i].getConfig().physics.death_impact_factor;
const Vec2 IMPULSE = SHIP_VEL * (ctx.ships[i].getBody().mass * DEATH_FACTOR);
touched_enemy->applyImpulse(IMPULSE);
ctx.on_player_hit(i);
// Sense bala: cap impuls de bala per als debris (mort per
// col·lisió cos-cos). Els debris hereten la inèrcia del ship.
ctx.on_player_hit(i, Vec2{});
} else {
// Primer impacte → estat HURT (rebot físic ja resolt per PhysicsWorld;
// l'enemic no rep dany per decisió de disseny).
@@ -197,13 +199,12 @@ namespace Systems::Collision {
}
// *** TEAMMATE HIT (friendly fire) ***
// Víctima perd 1 vida, atacant en guanya 1. Apliquem l'impuls
// de la bala a la nau ABANS de on_player_hit perquè tocado()
// captura la velocitat per als debris (si no, queden quiets).
const Vec2 BULLET_IMPULSE = bullet.getBody().velocity *
(bullet.getBody().mass * bullet.getConfig().physics.impact_momentum_factor);
ctx.ships[player_id].getBody().applyImpulse(BULLET_IMPULSE);
ctx.on_player_hit(player_id);
// Víctima perd 1 vida, atacant en guanya 1. Friendly fire sempre
// mata: el bullet va als debris (via tocado) i NO al cos del ship
// — el cos està a punt de desactivar-se, qualsevol impuls seria
// double-count amb la velocitat que ja reben els trossos.
const Vec2 BULLET_VEL = bullet.getBody().velocity;
ctx.on_player_hit(player_id, BULLET_VEL);
ctx.lives_per_player[BULLET_OWNER]++;
Audio::get()->playSound(Defaults::Sound::FRIENDLY_FIRE_HIT, Audio::Group::GAME);
breakBullet(ctx.debris_manager, bullet);
@@ -237,15 +238,20 @@ namespace Systems::Collision {
}
// *** BALA D'ENEMIC → SHIP ***
// Apliquem l'impuls de la bala abans del hurt/death perquè la
// velocitat de la nau quedi capturada per als debris.
const Vec2 IMPULSE = bullet.getBody().velocity *
(bullet.getBody().mass * bullet.getConfig().physics.impact_momentum_factor);
ctx.ships[player_id].getBody().applyImpulse(IMPULSE);
// Regla "cos XOR trossos": l'impuls de la bala s'aplica al cos
// només si el ship sobreviu (fereix). Si el ship mor, el bullet
// va directament als trossos (via tocado) i el cos no rep impuls
// — els trossos ja porten la força de la bala, qualsevol impuls
// afegit al cos seria double-count.
const Vec2 BULLET_VEL = bullet.getBody().velocity;
if (ctx.ships[player_id].isHurt()) {
// Segon impacte durant HURT → mort.
ctx.on_player_hit(player_id);
ctx.on_player_hit(player_id, BULLET_VEL);
} else {
// Fereix: el cos sobreviu, rep l'impuls. No hi ha debris encara.
const Vec2 IMPULSE = BULLET_VEL *
(bullet.getBody().mass * bullet.getConfig().physics.impact_momentum_factor);
ctx.ships[player_id].getBody().applyImpulse(IMPULSE);
ctx.ships[player_id].hurt();
}
breakBullet(ctx.debris_manager, bullet);
+5 -2
View File
@@ -40,8 +40,11 @@ namespace Systems::Collision {
Effects::FireworkManager& firework_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;
// Trigger de muerte del jugador (GameScene::tocado). bullet_velocity es
// la velocitat de la bala que ha causat la mort (Vec2{} si la mort no
// ve d'una bala — col·lisió ship-enemy, etc.). Es passa al debris perquè
// els trossos volin en direcció de la bala.
std::function<void(uint8_t /*player_id*/, const Vec2& /*bullet_velocity*/)> on_player_hit;
};
// Detecta colisiones bullet → enemy. Si hit:
+26 -4
View File
@@ -24,10 +24,11 @@ namespace Systems::EnemyEvents {
ctx.floating_score_manager.crear(POINTS, enemy.getCenter());
}
void doCreateDebris(Systems::Collision::Context& ctx, const Enemy& enemy) {
void doCreateDebris(Systems::Collision::Context& ctx, const Enemy& enemy, const Bullet* bullet) {
constexpr float SPEED_EXPLOSIO = 80.0F;
const Vec2 INHERITED_VEL = enemy.getVelocityVector() *
Defaults::Physics::Debris::ENEMY_VELOCITY_INHERITANCE;
const Vec2 BULLET_VEL = (bullet != nullptr) ? bullet->getBody().velocity : Vec2{};
ctx.debris_manager.explode(
enemy.getShape(),
enemy.getCenter(),
@@ -42,7 +43,8 @@ namespace Systems::EnemyEvents {
enemy.getConfig().colors.normal,
Defaults::Physics::Debris::ENEMY_LIFETIME,
Defaults::Physics::Debris::ENEMY_FRICTION,
Defaults::Physics::Debris::ENEMY_SEGMENT_MULTIPLIER);
Defaults::Physics::Debris::ENEMY_SEGMENT_MULTIPLIER,
BULLET_VEL);
}
void doCreateFireworks(Systems::Collision::Context& ctx, const Enemy& enemy) {
@@ -67,6 +69,24 @@ namespace Systems::EnemyEvents {
void dispatchEvent(Systems::Collision::Context& ctx, Enemy& enemy, EnemyEventType event, uint8_t shooter_id, const Bullet* bullet) {
const auto& actions = enemy.getConfig().events.getActions(event);
// Pre-scan: aquest event matarà l'enemic? Si sí, l'impuls de la bala
// va directament als debris (via doCreateDebris) i NO s'aplica al cos
// — així evitem el "double-count" on els trossos hereten la velocitat
// del cos (boostat per la bala) I a més el seu propi impuls de bala.
// Regla: el bullet impacta al cos O als trossos, mai a tots dos.
bool will_die = false;
for (const auto& action : actions) {
if (action.type == EnemyActionType::DESTROY) {
will_die = true;
break;
}
if (action.type == EnemyActionType::SET_HURT && enemy.isWounded()) {
will_die = true;
break;
}
}
for (const auto& action : actions) {
switch (action.type) {
case EnemyActionType::SET_HURT:
@@ -86,13 +106,15 @@ namespace Systems::EnemyEvents {
doAddScore(ctx, enemy, shooter_id);
break;
case EnemyActionType::CREATE_DEBRIS:
doCreateDebris(ctx, enemy);
doCreateDebris(ctx, enemy, bullet);
break;
case EnemyActionType::CREATE_FIREWORKS:
doCreateFireworks(ctx, enemy);
break;
case EnemyActionType::APPLY_IMPULSE:
doApplyImpulse(enemy, bullet);
if (!will_die) {
doApplyImpulse(enemy, bullet);
}
break;
}
}