revert: vuelve al modelo de efd18ff + ENEMY_LIFETIME 3.0 -> 4.5

This commit is contained in:
2026-05-21 13:46:25 +02:00
parent 0c8a9b744e
commit ae1d1397b1
5 changed files with 133 additions and 170 deletions
+12 -16
View File
@@ -23,25 +23,22 @@ namespace Defaults::Physics {
constexpr float VELOCITAT_BASE = 80.0F; // Velocidad inicial (px/s)
constexpr float VARIACIO_VELOCITAT = 40.0F; // ±variació aleatòria (px/s)
constexpr float ACCELERACIO = -60.0F; // Fricció/desacceleració (px/s²)
constexpr float ROTACIO_MIN = 0.2F; // Rotación mínima (rad/s ~11.5°/s)
constexpr float ROTACIO_MAX = 0.5F; // Rotación màxima (rad/s ~28.6°/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; // 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 en dos fases:
// 1. INTACTE: size_factor = 1.0 durant INTACT_TIME segons. Si la velocity
// cau per sota de SHRINK_SPEED_THRESHOLD abans, dispara la transició
// (el que arribi primer).
// 2. MENGUANT: size_factor decreix linealment a SHRINK_RATE per segon
// fins arribar a 0 → el fragment mor (mai "popa" en moviment).
constexpr float INTACT_TIME = 2.0F; // Default temps intacte (s)
constexpr float SHRINK_SPEED_THRESHOLD = 30.0F; // Velocity sota la qual dispara la mengua (px/s)
constexpr float SHRINK_SPEED_THRESHOLD_SQ = SHRINK_SPEED_THRESHOLD * SHRINK_SPEED_THRESHOLD;
constexpr float SHRINK_RATE = 1.0F; // 1/s = 1 segon per encongir de 100% a 0%
// 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
constexpr float FACTOR_HERENCIA_MAX = 1.0F; // Màxim 100% del drotacio heredat
constexpr float FRICCIO_ANGULAR = 0.5F; // Desacceleració de la rotació de TRAJECTÒRIA (rad/s²)
constexpr float FRICCIO_VISUAL = 0.05F; // Desacceleració de la rotació VISUAL (suau, persisteix més)
constexpr float FRICCIO_ANGULAR = 0.5F; // Desacceleració angular (rad/s²)
// Velocity heredada de la nau a l'explosió (80% del feel original).
constexpr float SHIP_VELOCITY_INHERITANCE = 0.8F;
@@ -52,8 +49,7 @@ namespace Defaults::Physics {
// Tuneig específic de l'explosió d'enemic (overrides als defaults
// que es passen com a paràmetres opcionals a explode()).
constexpr float ENEMY_INTACT_TIME = 0.5F; // Temps intacte abans de menguar (s) — comencen aviat
constexpr float ENEMY_SHRINK_RATE = 0.7F; // 0.7 per segon → ~1.4s de fade-out
constexpr float ENEMY_LIFETIME = 4.5F; // 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)
+15 -27
View File
@@ -9,43 +9,31 @@
namespace Effects {
// Debris: un segment de línia que vola perpendicular a sí mismo
// Representa un fragment de una shape destruïda (ship, enemy, bullet).
//
// Representació autoritaritzada (font de veritat):
// centro + original_angle (orientació spawn) + angle_rotacio (acumulat)
// + original_half_length × size_factor (mida actual)
// p1/p2 es reconstrueixen cada frame des d'aquestes dades — no es modifiquen
// iterativament (evita bugs de rotació quadràtica i shrink exponencial).
// Representa un fragment de una shape destruïda (ship, enemy, bullet)
struct Debris {
// Geometria del segment (2 points en coordenades mundials, derivats)
Vec2 p1;
Vec2 p2;
// Geometria original (font de veritat, no canvia després del spawn)
Vec2 centro; // Centre actual del segment (es mou amb velocity)
float original_angle; // Orientació del segment al spawn (rad)
float original_half_length; // Mitja-longitud al spawn (px)
// Geometria del segment (2 points en coordenades mundials)
Vec2 p1; // Vec2 inicial del segment
Vec2 p2; // Vec2 final del segment
// Física
Vec2 velocity; // Velocidad en px/s (components x, y)
float acceleration; // Aceleración negativa (fricció) en px/s²
// Rotación
float angle_rotacio; // Acumulat de rotació visual des del spawn (radians)
float angle_rotacio; // Angle de rotación acumulat (radians)
float velocitat_rot; // Velocidad de rotación de TRAYECTORIA (rad/s)
float velocitat_rot_visual; // Velocidad de rotación VISUAL del segment (rad/s)
// Estat de vida en dos fases:
// 1. INTACTE (shrinking=false): size_factor=1.0 durant intact_time segons,
// o fins que la velocity cau per sota de SHRINK_SPEED_THRESHOLD.
// 2. MENGUANT (shrinking=true): size_factor decreix a shrink_rate/s fins
// arribar a 0 → mor. Mai pop visual: sempre fade-out.
float temps_vida; // Temps transcorregut (segons)
float intact_time; // Temps en fase INTACTE (segons)
bool shrinking; // false=intacte, true=menguant
float size_factor; // Mida actual (1.0=ple, 0.0=mort)
float shrink_rate; // Velocity de menguada (per segon)
bool active; // Està actiu?
// 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 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)
// Rendering
float brightness; // Factor de brightness (0.0-1.0, heretat de l'objecte original)
+100 -118
View File
@@ -57,8 +57,7 @@ namespace Effects {
SDL_Color color,
float lifetime,
float friction,
int segment_multiplier,
float shrink_rate) {
int segment_multiplier) {
if (!shape || !shape->isValid()) {
return;
}
@@ -80,7 +79,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, shrink_rate)) {
if (!spawnDebris(world_p1, world_p2, centro, velocitat_base, brightness, velocitat_objecte, velocitat_angular, factor_herencia_visual, color, lifetime, friction)) {
return;
}
}
@@ -106,21 +105,14 @@ 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, float shrink_rate) -> 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";
return false;
}
// Geometria autoritaritzada: centro + original_angle + original_half_length.
// p1/p2 es reconstrueixen cada frame en update() des d'aquestes dades.
const float DX = world_p2.x - world_p1.x;
const float DY = world_p2.y - world_p1.y;
debris->centro = {.x = (world_p1.x + world_p2.x) / 2.0F,
.y = (world_p1.y + world_p2.y) / 2.0F};
debris->original_angle = std::atan2(DY, DX);
debris->original_half_length = std::sqrt((DX * DX) + (DY * DY)) / 2.0F;
// Geometria
debris->p1 = world_p1;
debris->p2 = world_p2;
@@ -144,14 +136,11 @@ namespace Effects {
debris->angle_rotacio = 0.0F;
// Estat inicial: INTACTE durant `lifetime` segons (o fins que la
// velocity baixi de SHRINK_SPEED_THRESHOLD). Després MENGUANT a
// shrink_rate per segon fins arribar a size_factor=0 → mor.
// 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->intact_time = lifetime;
debris->shrinking = false;
debris->size_factor = 1.0F;
debris->shrink_rate = shrink_rate;
debris->min_lifetime = lifetime;
debris->factor_shrink = Defaults::Physics::Debris::SHRINK_RATE;
// Visuals heretades
debris->brightness = brightness;
@@ -223,116 +212,109 @@ namespace Effects {
}
}
// INTACTE → MENGUANT → mort. Retorna true si el debris segueix viu.
static auto updateLifecycle(Debris& debris, float delta_time) -> bool {
debris.temps_vida += delta_time;
const float SPEED_SQ = (debris.velocity.x * debris.velocity.x) +
(debris.velocity.y * debris.velocity.y);
if (!debris.shrinking) {
const bool TIME_TRIGGER = debris.temps_vida >= debris.intact_time;
const bool SPEED_TRIGGER = SPEED_SQ < Defaults::Physics::Debris::SHRINK_SPEED_THRESHOLD_SQ;
if (TIME_TRIGGER || SPEED_TRIGGER) {
debris.shrinking = true;
}
}
if (debris.shrinking) {
debris.size_factor -= debris.shrink_rate * delta_time;
if (debris.size_factor <= 0.0F) {
debris.active = false;
return false;
}
}
return true;
}
// Fricció lineal: redueix |velocity| en acceleration per segon.
static void applyLinearFriction(Debris& debris, float delta_time) {
const float SPEED = std::sqrt((debris.velocity.x * debris.velocity.x) +
(debris.velocity.y * debris.velocity.y));
if (SPEED > 1.0F) {
const float DIR_X = debris.velocity.x / SPEED;
const float DIR_Y = debris.velocity.y / SPEED;
const float NEW_SPEED = std::max(SPEED + (debris.acceleration * delta_time), 0.0F);
debris.velocity.x = DIR_X * NEW_SPEED;
debris.velocity.y = DIR_Y * NEW_SPEED;
} else {
debris.velocity.x = 0.0F;
debris.velocity.y = 0.0F;
}
}
// Aplica fricció a una velocity angular (genèric per a trajectòria i visual).
static void decayAngular(float& vel, float friction, float delta_time) {
if (std::abs(vel) <= 0.01F) {
vel = 0.0F;
return;
}
const float SIGN = (vel > 0) ? 1.0F : -1.0F;
vel -= SIGN * friction * delta_time;
if ((vel > 0) != (SIGN > 0)) {
vel = 0.0F;
}
}
// Rota el vector velocity (trajectòria corba) i aplica fricció a ambdues
// velocitats angulars (trajectòria i visual).
static void applyAngularDynamics(Debris& debris, float delta_time) {
// Rotar vector de velocity amb matriu 2D (només si encara gira la trajectòria)
if (std::abs(debris.velocitat_rot) > 0.01F) {
const float DANGLE = debris.velocitat_rot * delta_time;
const float VX = debris.velocity.x;
const float VY = debris.velocity.y;
const float COS_A = std::cos(DANGLE);
const float SIN_A = std::sin(DANGLE);
debris.velocity.x = (VX * COS_A) - (VY * SIN_A);
debris.velocity.y = (VX * SIN_A) + (VY * COS_A);
}
// Decay independent de les dues velocitats angulars.
decayAngular(debris.velocitat_rot,
Defaults::Physics::Debris::FRICCIO_ANGULAR,
delta_time);
decayAngular(debris.velocitat_rot_visual,
Defaults::Physics::Debris::FRICCIO_VISUAL,
delta_time);
}
void DebrisManager::update(float delta_time) {
for (auto& debris : debris_pool_) {
if (!debris.active) {
continue;
}
if (!updateLifecycle(debris, delta_time)) {
continue;
// 1. Actualitzar time de vida
debris.temps_vida += delta_time;
// 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;
}
}
applyLinearFriction(debris, delta_time);
applyAngularDynamics(debris, delta_time);
// 2. Actualitzar velocity (desacceleració)
// Aplicar fricció en la direcció del movement
float speed = std::sqrt((debris.velocity.x * debris.velocity.x) +
(debris.velocity.y * debris.velocity.y));
// Posició del centre: integra velocity (separat de p1/p2).
debris.centro.x += debris.velocity.x * delta_time;
debris.centro.y += debris.velocity.y * delta_time;
if (speed > 1.0F) {
// Calcular direcció normalitzada
float dir_x = debris.velocity.x / speed;
float dir_y = debris.velocity.y / speed;
// Acumular rotació visual. Modulada per size_factor: durant
// INTACTE (size=1) no afecta, però quan el segment mengua la
// rotació també s'apaga.
debris.angle_rotacio += debris.velocitat_rot_visual * debris.size_factor * delta_time;
// Aplicar aceleración negativa (fricció)
float nova_speed = speed + (debris.acceleration * delta_time);
nova_speed = std::max(nova_speed, 0.0F);
// Reconstruir p1/p2 des de la geometria autoritaritzada:
// centro + (cos/sin(original_angle + angle_rotacio)) × original_half_length × size_factor
// No iteratiu — evita la rotació quadràtica i el shrink exponencial.
const float CURRENT_ANGLE = debris.original_angle + debris.angle_rotacio;
const float HALF_LEN = debris.original_half_length * std::max(0.0F, debris.size_factor);
const float COS_A = std::cos(CURRENT_ANGLE);
const float SIN_A = std::sin(CURRENT_ANGLE);
debris.p1.x = debris.centro.x - (HALF_LEN * COS_A);
debris.p1.y = debris.centro.y - (HALF_LEN * SIN_A);
debris.p2.x = debris.centro.x + (HALF_LEN * COS_A);
debris.p2.y = debris.centro.y + (HALF_LEN * SIN_A);
debris.velocity.x = dir_x * nova_speed;
debris.velocity.y = dir_y * nova_speed;
} else {
// Velocidad mucho baixa, aturar
debris.velocity.x = 0.0F;
debris.velocity.y = 0.0F;
}
// 2b. Rotar vector de velocity (trayectoria curva)
if (std::abs(debris.velocitat_rot) > 0.01F) {
// Calcular angle de rotación este frame
float dangle = debris.velocitat_rot * delta_time;
// Rotar vector de velocity usant matriu de rotación 2D
float vel_x_old = debris.velocity.x;
float vel_y_old = debris.velocity.y;
float cos_a = std::cos(dangle);
float sin_a = std::sin(dangle);
debris.velocity.x = (vel_x_old * cos_a) - (vel_y_old * sin_a);
debris.velocity.y = (vel_x_old * sin_a) + (vel_y_old * cos_a);
}
// 2c. Aplicar fricció angular (desacceleració gradual)
if (std::abs(debris.velocitat_rot) > 0.01F) {
float sign = (debris.velocitat_rot > 0) ? 1.0F : -1.0F;
float reduccion =
Defaults::Physics::Debris::FRICCIO_ANGULAR * delta_time;
debris.velocitat_rot -= sign * reduccion;
// Evitar canvi de signe (no pot passar de CW a CCW)
if ((debris.velocitat_rot > 0) != (sign > 0)) {
debris.velocitat_rot = 0.0F;
}
}
// 3. Calcular centro del segment
Vec2 centro = {.x = (debris.p1.x + debris.p2.x) / 2.0F,
.y = (debris.p1.y + debris.p2.y) / 2.0F};
// 4. Actualitzar posición del centro
centro.x += debris.velocity.x * delta_time;
centro.y += debris.velocity.y * delta_time;
// 5. Actualitzar rotación VISUAL
debris.angle_rotacio += debris.velocitat_rot_visual * delta_time;
// 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
float dx = debris.p2.x - debris.p1.x;
float dy = debris.p2.y - debris.p1.y;
// 7. Reconstruir segment con nueva mida i rotación
float half_length = std::sqrt((dx * dx) + (dy * dy)) * shrink_factor / 2.0F;
float original_angle = std::atan2(dy, dx);
float new_angle = original_angle + debris.angle_rotacio;
debris.p1.x = centro.x - (half_length * std::cos(new_angle));
debris.p1.y = centro.y - (half_length * std::sin(new_angle));
debris.p2.x = centro.x + (half_length * std::cos(new_angle));
debris.p2.y = centro.y + (half_length * std::sin(new_angle));
}
}
+4 -6
View File
@@ -34,10 +34,9 @@ namespace Effects {
// - 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 INTACTE del debris (s, per defecte INTACT_TIME)
// - 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)
// - shrink_rate: velocity de mengua durant MENGUANT (1/s, per defecte SHRINK_RATE)
void explode(const std::shared_ptr<Graphics::Shape>& shape,
const Vec2& centro,
float angle,
@@ -49,10 +48,9 @@ namespace Effects {
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
float lifetime = Defaults::Physics::Debris::INTACT_TIME,
float lifetime = Defaults::Physics::Debris::TEMPS_VIDA,
float friction = Defaults::Physics::Debris::ACCELERACIO,
int segment_multiplier = 1,
float shrink_rate = Defaults::Physics::Debris::SHRINK_RATE);
int segment_multiplier = 1);
// Actualitzar todos los fragments active
void update(float delta_time);
@@ -88,7 +86,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, float shrink_rate) -> 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) -> bool;
static void applyAngularVelocity(Debris& debris, const Vec2& direccio, float velocitat_angular);
static void applyVisualRotation(Debris& debris, float velocitat_angular, float factor_herencia_visual);
};
+2 -3
View File
@@ -74,10 +74,9 @@ namespace Systems::Collision {
0.0F, // sin herencia visual
Defaults::Sound::EXPLOSION,
COLOR,
Defaults::Physics::Debris::ENEMY_INTACT_TIME,
Defaults::Physics::Debris::ENEMY_LIFETIME,
Defaults::Physics::Debris::ENEMY_FRICTION,
Defaults::Physics::Debris::ENEMY_SEGMENT_MULTIPLIER,
Defaults::Physics::Debris::ENEMY_SHRINK_RATE);
Defaults::Physics::Debris::ENEMY_SEGMENT_MULTIPLIER);
}
} // anonymous namespace