feat(entities): migrar paràmetres compartits dels enemics a cada YAML

This commit is contained in:
2026-05-25 11:54:40 +02:00
parent 3dadd5fc1a
commit 4b6dc8a47a
8 changed files with 281 additions and 157 deletions
+58 -80
View File
@@ -36,18 +36,25 @@ namespace {
return std::atan2(velocity.y, velocity.x) + (Constants::PI / 2.0F);
}
// Random float [0..1).
auto randFloat01() -> float {
return static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX);
}
// Random float [min..max).
auto randRange(float min, float max) -> float {
return min + (randFloat01() * (max - min));
}
} // namespace
Enemy::Enemy(Rendering::Renderer* renderer)
: Entity(renderer) {
brightness_ = Defaults::Brightness::ENEMIC;
// Cuerpo físico — defaults comuns; init() ajusta mass/radius segons el tipus.
body_.setMass(Defaults::Enemies::Body::DEFAULT_MASS);
body_.radius = 0.0F; // 0 hasta spawn (no colisiona inactivo)
body_.restitution = Defaults::Enemies::Body::RESTITUTION;
body_.linear_damping = Defaults::Enemies::Body::LINEAR_DAMPING;
body_.angular_damping = Defaults::Enemies::Body::ANGULAR_DAMPING;
// Body queda amb defaults inocus (radius=0 = no col·lisiona) fins
// que init() apliqui la configuració del tipus carregada via Registry.
body_.radius = 0.0F;
}
void Enemy::init(EnemyType type, const Vec2* ship_pos) {
@@ -55,7 +62,6 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) {
config_ = &EnemyRegistry::get(type);
const EnemyConfig& cfg = *config_;
// Cas Square: resetejar tracking timer al spawn.
if (type_ == EnemyType::SQUARE) {
tracking_timer_ = 0.0F;
tracking_strength_ = cfg.behavior.tracking_strength;
@@ -66,14 +72,17 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) {
std::cerr << "[Enemy] Error: no se ha podido cargar " << cfg.shape.path << '\n';
}
// Radi de col·lisió derivat del cercle circumscrit de la shape * scale * collision_factor.
// Radi de col·lisió derivat del cercle circumscrit de la shape.
const float BOUNDING = (shape_ != nullptr) ? shape_->getBoundingRadius() : 0.0F;
collision_radius_ = BOUNDING * cfg.shape.scale * cfg.shape.collision_factor;
body_.setMass(cfg.physics.mass);
body_.radius = collision_radius_;
body_.restitution = cfg.physics.restitution;
body_.linear_damping = cfg.physics.linear_damping;
body_.angular_damping = cfg.physics.angular_damping;
// Posición aleatoria con comprobación de seguridad
// Posició aleatòria amb comprovació de safety_distance.
float min_x;
float max_x;
float min_y;
@@ -85,7 +94,7 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) {
for (int attempt = 0; attempt < Defaults::Enemies::Spawn::MAX_SPAWN_ATTEMPTS; attempt++) {
float candidate_x;
float candidate_y;
if (attemptSafeSpawn(*ship_pos, collision_radius_, candidate_x, candidate_y)) {
if (attemptSafeSpawn(*ship_pos, collision_radius_, cfg.spawn.safety_distance, candidate_x, candidate_y)) {
center_.x = candidate_x;
center_.y = candidate_y;
found_safe_position = true;
@@ -107,8 +116,7 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) {
center_.y = static_cast<float>((std::rand() % RANGE_Y) + static_cast<int>(min_y));
}
// Dirección inicial aleatoria, velocidad escalar según tipo
const float ANGLE_INICIAL = (std::rand() % 360) * Constants::PI / 180.0F;
const float ANGLE_INICIAL = static_cast<float>(std::rand() % 360) * Constants::PI / 180.0F;
setVelocityFromAngle(ANGLE_INICIAL, cfg.physics.speed);
body_.position = center_;
@@ -116,10 +124,8 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) {
body_.angular_velocity = 0.0F;
body_.clearAccumulators();
// Rotación visual aleatoria dins del rang del tipus
const float ROTATION_RANGE = cfg.physics.rotation_delta_max - cfg.physics.rotation_delta_min;
rotation_delta_ = cfg.physics.rotation_delta_min +
((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * ROTATION_RANGE);
// Rotació visual aleatòria dins del rang del tipus
rotation_delta_ = randRange(cfg.physics.rotation_delta_min, cfg.physics.rotation_delta_max);
rotation_ = 0.0F;
animation_ = EnemyAnimation();
@@ -127,8 +133,8 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) {
animation_.rotation_delta_target = rotation_delta_;
animation_.rotation_delta_t = 1.0F;
invulnerability_timer_ = Defaults::Enemies::Spawn::INVULNERABILITY_DURATION;
brightness_ = Defaults::Enemies::Spawn::INVULNERABILITY_BRIGHTNESS_START;
invulnerability_timer_ = cfg.spawn.invulnerability_duration;
brightness_ = cfg.spawn.invulnerability_brightness_start;
direction_change_timer_ = 0.0F;
@@ -153,11 +159,11 @@ void Enemy::update(float delta_time) {
invulnerability_timer_ -= delta_time;
invulnerability_timer_ = std::max(invulnerability_timer_, 0.0F);
const float T_INV = invulnerability_timer_ / Defaults::Enemies::Spawn::INVULNERABILITY_DURATION;
const float T_INV = invulnerability_timer_ / config_->spawn.invulnerability_duration;
const float T = 1.0F - T_INV;
const float SMOOTH_T = T * T * (3.0F - (2.0F * T));
constexpr float START = Defaults::Enemies::Spawn::INVULNERABILITY_BRIGHTNESS_START;
constexpr float END = Defaults::Enemies::Spawn::INVULNERABILITY_BRIGHTNESS_END;
const float START = config_->spawn.invulnerability_brightness_start;
const float END = config_->spawn.invulnerability_brightness_end;
brightness_ = START + ((END - START) * SMOOTH_T);
}
@@ -190,13 +196,11 @@ void Enemy::draw() const {
if (!is_active_ || !shape_) {
return;
}
// El SCALE final = escala base del YAML * modulador dinàmic (spawn/pulse).
const float SCALE = config_->shape.scale * computeCurrentScale();
SDL_Color color = config_->colors.normal;
// Parpadeo dorado mientras está herido.
if (wounded_timer_ > 0.0F) {
const float CYCLE = 1.0F / Defaults::Enemies::Wounded::BLINK_HZ;
const float CYCLE = 1.0F / config_->wounded.blink_hz;
const float T = std::fmod(wounded_timer_, CYCLE);
if (T < (CYCLE / 2.0F)) {
color = config_->colors.wounded;
@@ -217,7 +221,7 @@ void Enemy::destroy() {
}
void Enemy::hurt(uint8_t shooter_id) {
wounded_timer_ = Defaults::Enemies::Wounded::DURATION;
wounded_timer_ = config_->wounded.duration;
last_hit_by_ = shooter_id;
}
@@ -230,7 +234,7 @@ void Enemy::setVelocity(float speed) {
if (CURRENT_SPEED > 0.0F) {
body_.velocity = body_.velocity * (speed / CURRENT_SPEED);
} else {
const float A = (std::rand() % 360) * Constants::PI / 180.0F;
const float A = static_cast<float>(std::rand() % 360) * Constants::PI / 180.0F;
setVelocityFromAngle(A, speed);
}
}
@@ -239,17 +243,14 @@ void Enemy::setVelocityFromAngle(float angle_movement, float speed) {
body_.velocity = angleToDirection(angle_movement) * speed;
}
// PENTAGON: zigzag esquivador. Cambios de dirección periódicos (probabilísticos)
// en lugar de detectar paredes; el rebote contra muros lo hace PhysicsWorld
// con restitution=1.0.
// PENTAGON: zigzag esquivador. Canvis de direcció periòdics (probabilístics)
// en lloc de detectar parets; el rebot contra murs el fa PhysicsWorld.
void Enemy::behaviorPentagon(float delta_time) {
direction_change_timer_ += delta_time;
const float RAND_VAL = static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX);
if (RAND_VAL < config_->behavior.zigzag_prob_per_second * delta_time) {
if (randFloat01() < config_->behavior.zigzag_prob_per_second * delta_time) {
const float CURRENT_ANGLE = velocityToAngle(body_.velocity);
const float DELTA = (static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) *
config_->behavior.angle_change_max;
const float DELTA = randFloat01() * config_->behavior.angle_change_max;
const float NEW_ANGLE = CURRENT_ANGLE + ((std::rand() % 2 == 0) ? DELTA : -DELTA);
const float SPEED = body_.velocity.length();
setVelocityFromAngle(NEW_ANGLE, SPEED);
@@ -257,8 +258,7 @@ void Enemy::behaviorPentagon(float delta_time) {
}
}
// SQUARE: tracking discreto cada tracking_interval. Ajusta dirección
// hacia el ship mezclando con tracking_strength_.
// SQUARE: tracking discret cap a la nau cada N segons.
void Enemy::behaviorSquare(float delta_time) {
tracking_timer_ += delta_time;
@@ -283,7 +283,7 @@ void Enemy::behaviorSquare(float delta_time) {
}
}
// PINWHEEL: movimiento recto + boost de rotación visual cerca del ship.
// PINWHEEL: movement rectilini + boost de rotació visual prop del ship.
void Enemy::behaviorPinwheel(float /*delta_time*/) {
if (ship_position_ != nullptr) {
const Vec2 TO_SHIP = *ship_position_ - center_;
@@ -302,38 +302,26 @@ void Enemy::updateAnimation(float delta_time) {
}
void Enemy::updatePulse(float delta_time) {
const auto& cfg = config_->animation.pulse;
if (animation_.pulse_active) {
animation_.pulse_phase += 2.0F * Constants::PI * animation_.pulse_frequency * delta_time;
animation_.pulse_time_remaining -= delta_time;
if (animation_.pulse_time_remaining <= 0.0F) {
animation_.pulse_active = false;
}
} else {
const float RAND_VAL = static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX);
const float TRIGGER_PROB = Defaults::Enemies::Animation::PULSE_TRIGGER_PROB * delta_time;
if (RAND_VAL < TRIGGER_PROB) {
animation_.pulse_active = true;
animation_.pulse_phase = 0.0F;
const float FREQ_RANGE = Defaults::Enemies::Animation::PULSE_FREQ_MAX -
Defaults::Enemies::Animation::PULSE_FREQ_MIN;
animation_.pulse_frequency = Defaults::Enemies::Animation::PULSE_FREQ_MIN +
((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * FREQ_RANGE);
const float AMP_RANGE = Defaults::Enemies::Animation::PULSE_AMPLITUD_MAX -
Defaults::Enemies::Animation::PULSE_AMPLITUD_MIN;
animation_.pulse_amplitude = Defaults::Enemies::Animation::PULSE_AMPLITUD_MIN +
((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * AMP_RANGE);
const float DUR_RANGE = Defaults::Enemies::Animation::PULSE_DURATION_MAX -
Defaults::Enemies::Animation::PULSE_DURATION_MIN;
animation_.pulse_time_remaining = Defaults::Enemies::Animation::PULSE_DURATION_MIN +
((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * DUR_RANGE);
}
return;
}
if (randFloat01() < cfg.trigger_prob_per_second * delta_time) {
animation_.pulse_active = true;
animation_.pulse_phase = 0.0F;
animation_.pulse_frequency = randRange(cfg.frequency_min, cfg.frequency_max);
animation_.pulse_amplitude = randRange(cfg.amplitude_min, cfg.amplitude_max);
animation_.pulse_time_remaining = randRange(cfg.duration_min, cfg.duration_max);
}
}
void Enemy::updateRotationAcceleration(float delta_time) {
const auto& cfg = config_->animation.rotation_accel;
if (animation_.rotation_delta_t < 1.0F) {
animation_.rotation_delta_t += delta_time / animation_.rotation_delta_duration;
if (animation_.rotation_delta_t >= 1.0F) {
@@ -347,34 +335,24 @@ void Enemy::updateRotationAcceleration(float delta_time) {
const float TARGET = animation_.rotation_delta_target;
rotation_delta_ = INITIAL + ((TARGET - INITIAL) * SMOOTH_T);
}
} else {
const float RAND_VAL = static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX);
const float TRIGGER_PROB = Defaults::Enemies::Animation::ROTATION_ACCEL_TRIGGER_PROB * delta_time;
if (RAND_VAL < TRIGGER_PROB) {
animation_.rotation_delta_t = 0.0F;
const float MULT_RANGE = Defaults::Enemies::Animation::ROTATION_ACCEL_MULTIPLIER_MAX -
Defaults::Enemies::Animation::ROTATION_ACCEL_MULTIPLIER_MIN;
const float MULTIPLIER = Defaults::Enemies::Animation::ROTATION_ACCEL_MULTIPLIER_MIN +
((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * MULT_RANGE);
animation_.rotation_delta_target = animation_.rotation_delta_base * MULTIPLIER;
const float DUR_RANGE = Defaults::Enemies::Animation::ROTATION_ACCEL_DURATION_MAX -
Defaults::Enemies::Animation::ROTATION_ACCEL_DURATION_MIN;
animation_.rotation_delta_duration = Defaults::Enemies::Animation::ROTATION_ACCEL_DURATION_MIN +
((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * DUR_RANGE);
}
return;
}
if (randFloat01() < cfg.trigger_prob_per_second * delta_time) {
animation_.rotation_delta_t = 0.0F;
const float MULTIPLIER = randRange(cfg.multiplier_min, cfg.multiplier_max);
animation_.rotation_delta_target = animation_.rotation_delta_base * MULTIPLIER;
animation_.rotation_delta_duration = randRange(cfg.duration_min, cfg.duration_max);
}
}
auto Enemy::computeCurrentScale() const -> float {
float scale = 1.0F;
if (invulnerability_timer_ > 0.0F) {
const float T_INV = invulnerability_timer_ / Defaults::Enemies::Spawn::INVULNERABILITY_DURATION;
const float T_INV = invulnerability_timer_ / config_->spawn.invulnerability_duration;
const float T = 1.0F - T_INV;
const float SMOOTH_T = T * T * (3.0F - (2.0F * T));
constexpr float START = Defaults::Enemies::Spawn::INVULNERABILITY_SCALE_START;
constexpr float END = Defaults::Enemies::Spawn::INVULNERABILITY_SCALE_END;
const float START = config_->spawn.invulnerability_scale_start;
const float END = config_->spawn.invulnerability_scale_end;
scale = START + ((END - START) * SMOOTH_T);
} else if (animation_.pulse_active) {
scale += animation_.pulse_amplitude * std::sin(animation_.pulse_phase);
@@ -396,7 +374,7 @@ void Enemy::setTrackingStrength(float strength) {
}
}
auto Enemy::attemptSafeSpawn(const Vec2& ship_pos, float collision_radius, float& out_x, float& out_y) -> bool {
auto Enemy::attemptSafeSpawn(const Vec2& ship_pos, float collision_radius, float safety_distance, float& out_x, float& out_y) -> bool {
float min_x;
float max_x;
float min_y;
@@ -411,5 +389,5 @@ auto Enemy::attemptSafeSpawn(const Vec2& ship_pos, float collision_radius, float
const float DX = out_x - ship_pos.x;
const float DY = out_y - ship_pos.y;
const float DISTANCE = std::sqrt((DX * DX) + (DY * DY));
return DISTANCE >= Defaults::Enemies::Spawn::SAFETY_DISTANCE;
return DISTANCE >= safety_distance;
}