feat(debris): vida híbrida (mínima + umbral velocidad) + multiplier para enemigos

This commit is contained in:
2026-05-21 12:07:50 +02:00
parent 44aa4e76e2
commit efd18ff852
5 changed files with 388 additions and 357 deletions
+15 -3
View File
@@ -25,9 +25,15 @@ namespace Defaults::Physics {
constexpr float ACCELERACIO = -60.0F; // Fricció/desacceleració (px/s²)
constexpr float ROTACIO_MIN = 0.1F; // Rotación mínima (rad/s ~5.7°/s)
constexpr float ROTACIO_MAX = 0.3F; // Rotación màxima (rad/s ~17.2°/s)
constexpr float TEMPS_VIDA = 2.0F; // Duració màxima (segons) - enemy/bullet debris
constexpr float TEMPS_VIDA_NAU = 3.0F; // Ship debris lifetime (matches DEATH_DURATION)
constexpr float SHRINK_RATE = 0.5F; // Reducció de mida (factor/s)
constexpr float TEMPS_VIDA = 2.0F; // Vida mínima garantida (s) — després pot morir per velocitat baixa
constexpr float TEMPS_VIDA_NAU = 3.0F; // Ship debris min lifetime (matches DEATH_DURATION)
constexpr float SHRINK_RATE = 0.5F; // Reducció de mida (factor sobre min_lifetime)
// Política de mort: passat el min_lifetime, el fragment mor quan la
// seva velocity cau per sota d'aquest llindar. Així els fragments
// ràpids no "popen" en moviment.
constexpr float MIN_SPEED_TO_DIE = 5.0F; // px/s — al cuadrat per evitar sqrt en update
constexpr float MIN_SPEED_TO_DIE_SQ = MIN_SPEED_TO_DIE * MIN_SPEED_TO_DIE;
// Herència de velocity angular (trayectorias curvas)
constexpr float FACTOR_HERENCIA_MIN = 0.7F; // Mínimo 70% del drotacio heredat
@@ -41,6 +47,12 @@ namespace Defaults::Physics {
// 1.0 = inèrcia completa; >1.0 amplifica la deriva; <1.0 la atenua.
constexpr float ENEMY_VELOCITY_INHERITANCE = 1.0F;
// Tuneig específic de l'explosió d'enemic (overrides als defaults
// que es passen com a paràmetres opcionals a explode()).
constexpr float ENEMY_LIFETIME = 3.0F; // Vida mínima del debris (s) — els que segueixen movent-se viuen més
constexpr float ENEMY_FRICTION = -30.0F; // Fricció més suau perquè s'estenguin més
constexpr int ENEMY_SEGMENT_MULTIPLIER = 3; // Còpies de cada segment (5 cares × 3 = 15 trossos)
// Angular velocity sin for trajectory inheritance
// Excess above this threshold is converted to tangential linear velocity
// Prevents "vortex trap" problem with high-rotation enemies
+5 -2
View File
@@ -25,9 +25,12 @@ struct Debris {
float velocitat_rot_visual; // Velocidad de rotación VISUAL del segment (rad/s)
// Estat de vida
// Política: viu sempre durant min_lifetime, després mor quan
// |velocity| < MIN_SPEED_TO_DIE (definit en Defaults). Així els
// fragments ràpids no "popen" en moviment.
float temps_vida; // Temps transcorregut (segons)
float temps_max; // Temps de vida màxim (segons)
bool active; // Está active?
float min_lifetime; // Temps mínim garantit (segons)
bool active; // Està actiu?
// Shrinking (reducció de distancia entre points)
float factor_shrink; // Factor de reducció per segon (0.0-1.0)
+34 -21
View File
@@ -54,7 +54,10 @@ void DebrisManager::explode(const std::shared_ptr<Graphics::Shape>& shape,
float velocitat_angular,
float factor_herencia_visual,
const std::string& sound,
SDL_Color color) {
SDL_Color color,
float lifetime,
float friction,
int segment_multiplier) {
if (!shape || !shape->isValid()) {
return;
}
@@ -64,6 +67,11 @@ void DebrisManager::explode(const std::shared_ptr<Graphics::Shape>& shape,
const Vec2& shape_centre = shape->getCenter();
// Multiplier: cada segment s'emet N vegades amb direccions aleatòries
// distintes (la variació ±15° de computeExplosionDirection ho garanteix).
const int COPIES = std::max(1, segment_multiplier);
for (int copy = 0; copy < COPIES; copy++) {
for (const auto& primitive : shape->getPrimitives()) {
for (const auto& [local_p1, local_p2] : extractSegments(primitive)) {
// Transformar points locals → coordenades mundials
@@ -71,14 +79,13 @@ void DebrisManager::explode(const std::shared_ptr<Graphics::Shape>& shape,
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)) {
if (!spawnDebris(world_p1, world_p2, centro, velocitat_base, brightness, velocitat_objecte, velocitat_angular, factor_herencia_visual, color, lifetime, friction)) {
return;
}
}
}
}
}
auto DebrisManager::extractSegments(const Graphics::ShapePrimitive& primitive)
-> std::vector<std::pair<Vec2, Vec2>> {
@@ -98,10 +105,7 @@ auto DebrisManager::extractSegments(const Graphics::ShapePrimitive& primitive)
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) -> 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) -> bool {
Debris* debris = findFreeSlot();
if (debris == nullptr) {
std::cerr << "[DebrisManager] Warning: no debris slots disponibles\n";
@@ -122,7 +126,7 @@ auto DebrisManager::spawnDebris(const Vec2& world_p1, const Vec2& world_p2,
Defaults::Physics::Debris::VARIACIO_VELOCITAT);
debris->velocity.x = (direccio.x * speed) + velocitat_objecte.x;
debris->velocity.y = (direccio.y * speed) + velocitat_objecte.y;
debris->acceleration = Defaults::Physics::Debris::ACCELERACIO;
debris->acceleration = friction;
// Rotación de trayectoria (con conversió a tangencial si excedeix cap)
applyAngularVelocity(*debris, direccio, velocitat_angular);
@@ -132,9 +136,10 @@ auto DebrisManager::spawnDebris(const Vec2& world_p1, const Vec2& world_p2,
debris->angle_rotacio = 0.0F;
// Vida i shrinking
// Vida i shrinking — min_lifetime és el temps mínim garantit; després
// el fragment mor quan |velocity| < MIN_SPEED_TO_DIE.
debris->temps_vida = 0.0F;
debris->temps_max = Defaults::Physics::Debris::TEMPS_VIDA;
debris->min_lifetime = lifetime;
debris->factor_shrink = Defaults::Physics::Debris::SHRINK_RATE;
// Visuals heretades
@@ -145,8 +150,7 @@ auto DebrisManager::spawnDebris(const Vec2& world_p1, const Vec2& world_p2,
return true;
}
void DebrisManager::applyAngularVelocity(Debris& debris, const Vec2& direccio,
float velocitat_angular) {
void DebrisManager::applyAngularVelocity(Debris& debris, const Vec2& direccio, float velocitat_angular) {
if (std::abs(velocitat_angular) <= 0.01F) {
debris.velocitat_rot = 0.0F; // Nave: sin curvas
return;
@@ -185,8 +189,7 @@ void DebrisManager::applyAngularVelocity(Debris& debris, const Vec2& direccio,
debris.velocitat_rot = sign_ang * CAP;
}
void DebrisManager::applyVisualRotation(Debris& debris, float velocitat_angular,
float factor_herencia_visual) {
void DebrisManager::applyVisualRotation(Debris& debris, float velocitat_angular, float factor_herencia_visual) {
if (factor_herencia_visual > 0.01F && std::abs(velocitat_angular) > 0.01F) {
// Heredar rotación visual con factor proporcional + ±5% de variació
debris.velocitat_rot_visual = debris.velocitat_rot * factor_herencia_visual;
@@ -218,11 +221,17 @@ void DebrisManager::update(float delta_time) {
// 1. Actualitzar time de vida
debris.temps_vida += delta_time;
// Desactivar si ha superat time màxim
if (debris.temps_vida >= debris.temps_max) {
// Política de mort: viu sí o sí durant min_lifetime; després mor
// quan la velocity cau per sota d'un llindar. Així els fragments
// ràpids no desapareixen en moviment.
if (debris.temps_vida >= debris.min_lifetime) {
const float SPEED_SQ = (debris.velocity.x * debris.velocity.x) +
(debris.velocity.y * debris.velocity.y);
if (SPEED_SQ < Defaults::Physics::Debris::MIN_SPEED_TO_DIE_SQ) {
debris.active = false;
continue;
}
}
// 2. Actualitzar velocity (desacceleració)
// Aplicar fricció en la direcció del movement
@@ -286,9 +295,11 @@ void DebrisManager::update(float delta_time) {
// 5. Actualitzar rotación VISUAL
debris.angle_rotacio += debris.velocitat_rot_visual * delta_time;
// 6. Aplicar shrinking (reducció de distancia entre points)
float shrink_factor =
1.0F - (debris.factor_shrink * debris.temps_vida / debris.temps_max);
// 6. Aplicar shrinking (reducció de distancia entre points).
// El shrink es normalitza al min_lifetime (capat a 1.0) perquè els
// fragments que viuen més no es continuïn fent més petits per sempre.
const float SHRINK_T = std::min(debris.temps_vida / debris.min_lifetime, 1.0F);
float shrink_factor = 1.0F - (debris.factor_shrink * SHRINK_T);
shrink_factor = std::max(0.0F, shrink_factor); // No negatiu
// Calcular distancia original entre points
@@ -319,7 +330,9 @@ void DebrisManager::draw() const {
static_cast<int>(debris.p1.y),
static_cast<int>(debris.p2.x),
static_cast<int>(debris.p2.y),
debris.brightness, 0.0F, debris.color);
debris.brightness,
0.0F,
debris.color);
}
}
+14 -14
View File
@@ -3,7 +3,6 @@
#pragma once
#include "core/rendering/render_context.hpp"
#include <SDL3/SDL.h>
#include <array>
@@ -13,6 +12,7 @@
#include "core/defaults.hpp"
#include "core/graphics/shape.hpp"
#include "core/rendering/render_context.hpp"
#include "core/types.hpp"
#include "debris.hpp"
@@ -34,6 +34,9 @@ class DebrisManager {
// - velocitat_objecte: velocity de l'objecte que explota (px/s, per defecte 0)
// - velocitat_angular: velocity angular heretada (rad/s, per defecte 0)
// - factor_herencia_visual: factor de herència rotación visual (0.0-1.0, per defecte 0.0)
// - 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)
void explode(const std::shared_ptr<Graphics::Shape>& shape,
const Vec2& centro,
float angle,
@@ -44,7 +47,10 @@ class DebrisManager {
float velocitat_angular = 0.0F,
float factor_herencia_visual = 0.0F,
const std::string& sound = Defaults::Sound::EXPLOSION,
SDL_Color color = {0, 0, 0, 0}); // alpha==0 → fragmentos usan oscilador global
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);
// Actualitzar todos los fragments active
void update(float delta_time);
@@ -62,10 +68,9 @@ class DebrisManager {
Rendering::Renderer* renderer_;
// Pool de fragments (màxim concurrent)
// Un pentágono té 5 línies, 15 enemigos = 75 línies
// + ship (3 línies) + balas (5 línies * 3) = 93 línies màxim
// Arrodonit a 100 per seguretat
static constexpr int MAX_DEBRIS = 150;
// Pentàgon 5 línies × 15 enemics × multiplier 3 = 225 trossos només d'enemics.
// + ship (3 línies) + balas (5 línies × 3) = ~243. Arrodonit a 300.
static constexpr int MAX_DEBRIS = 300;
std::array<Debris, MAX_DEBRIS> debris_pool_;
// Trobar primer slot inactiu
@@ -81,14 +86,9 @@ class DebrisManager {
-> 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) -> bool;
static void applyAngularVelocity(Debris& debris, const Vec2& direccio,
float velocitat_angular);
static void applyVisualRotation(Debris& debris, float velocitat_angular,
float factor_herencia_visual);
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;
static void applyAngularVelocity(Debris& debris, const Vec2& direccio, float velocitat_angular);
static void applyVisualRotation(Debris& debris, float velocitat_angular, float factor_herencia_visual);
};
} // namespace Effects
+4 -1
View File
@@ -73,7 +73,10 @@ namespace Systems::Collision {
DROTACIO,
0.0F, // sin herencia visual
Defaults::Sound::EXPLOSION,
COLOR);
COLOR,
Defaults::Physics::Debris::ENEMY_LIFETIME,
Defaults::Physics::Debris::ENEMY_FRICTION,
Defaults::Physics::Debris::ENEMY_SEGMENT_MULTIPLIER);
}
} // anonymous namespace