Merge branch 'feat/entities-yaml-enemy-shared': paràmetres compartits dels enemics a cada YAML
This commit is contained in:
@@ -8,17 +8,48 @@ shape:
|
|||||||
|
|
||||||
physics:
|
physics:
|
||||||
mass: 5.0
|
mass: 5.0
|
||||||
speed: 35.0 # px/s (esquivador lent)
|
speed: 35.0 # px/s (esquivador lent)
|
||||||
rotation_delta_min: 0.75 # rad/s — rotació visual mínima
|
rotation_delta_min: 0.75 # rad/s — rotació visual mínima
|
||||||
rotation_delta_max: 3.75 # rad/s — rotació visual màxima
|
rotation_delta_max: 3.75 # rad/s — rotació visual màxima
|
||||||
|
restitution: 1.0 # rebot elàstic perfecte contra parets
|
||||||
|
linear_damping: 0.0 # manté velocitat (sense fricció)
|
||||||
|
angular_damping: 0.0
|
||||||
|
|
||||||
behavior:
|
behavior:
|
||||||
# Pentagon: zigzag esquivador (canvi de direcció probabilístic per segon).
|
# Pentagon: zigzag esquivador (canvi de direcció probabilístic per segon).
|
||||||
angle_change_max: 1.0 # rad — magnitud del canvi de direcció
|
angle_change_max: 1.0 # rad — magnitud del canvi de direcció
|
||||||
zigzag_prob_per_second: 0.8
|
zigzag_prob_per_second: 0.8
|
||||||
|
|
||||||
|
animation:
|
||||||
|
pulse: # respiració d'escala aleatòria
|
||||||
|
trigger_prob_per_second: 0.01
|
||||||
|
duration_min: 1.0
|
||||||
|
duration_max: 3.0
|
||||||
|
amplitude_min: 0.08
|
||||||
|
amplitude_max: 0.20
|
||||||
|
frequency_min: 1.5
|
||||||
|
frequency_max: 3.0
|
||||||
|
rotation_accel: # acceleració/desacceleració de rotació visual
|
||||||
|
trigger_prob_per_second: 0.02
|
||||||
|
duration_min: 3.0
|
||||||
|
duration_max: 8.0
|
||||||
|
multiplier_min: 0.3
|
||||||
|
multiplier_max: 4.0
|
||||||
|
|
||||||
|
wounded:
|
||||||
|
duration: 1.0 # segons en estat ferit abans d'explotar
|
||||||
|
blink_hz: 10.0 # parpelleig color normal ↔ wounded
|
||||||
|
|
||||||
|
spawn:
|
||||||
|
invulnerability_duration: 3.0
|
||||||
|
invulnerability_brightness_start: 0.3
|
||||||
|
invulnerability_brightness_end: 0.7
|
||||||
|
invulnerability_scale_start: 0.0
|
||||||
|
invulnerability_scale_end: 1.0
|
||||||
|
safety_distance: 36.0 # px mínim respecte al player al spawn
|
||||||
|
|
||||||
colors:
|
colors:
|
||||||
normal: [0, 255, 255] # Cyan pur "esquivador"
|
normal: [0, 255, 255] # Cyan pur "esquivador"
|
||||||
wounded: [255, 220, 60] # Daurat (parpelleig al rebre impacte)
|
wounded: [255, 220, 60] # Daurat (parpelleig al rebre impacte)
|
||||||
|
|
||||||
score: 100
|
score: 100
|
||||||
|
|||||||
@@ -7,18 +7,49 @@ shape:
|
|||||||
collision_factor: 1.0 # ajust opcional del hitbox (default 1.0)
|
collision_factor: 1.0 # ajust opcional del hitbox (default 1.0)
|
||||||
|
|
||||||
physics:
|
physics:
|
||||||
mass: 4.0 # Més lleuger — àgil
|
mass: 4.0 # Més lleuger — àgil
|
||||||
speed: 50.0 # px/s (el més ràpid)
|
speed: 50.0 # px/s (el més ràpid)
|
||||||
rotation_delta_min: 3.0 # rad/s — rotació base elevada
|
rotation_delta_min: 3.0 # rad/s — rotació base elevada
|
||||||
rotation_delta_max: 6.0
|
rotation_delta_max: 6.0
|
||||||
|
restitution: 1.0
|
||||||
|
linear_damping: 0.0
|
||||||
|
angular_damping: 0.0
|
||||||
|
|
||||||
behavior:
|
behavior:
|
||||||
# Pinwheel: movement rectilíniauniforme + boost de rotació visual prop de la nau.
|
# Pinwheel: movement rectilíniauniforme + boost de rotació visual prop de la nau.
|
||||||
rotation_proximity_multiplier: 3.0 # Multiplicador de rotació quan és prop de la nau
|
rotation_proximity_multiplier: 3.0 # Multiplicador de rotació quan és prop de la nau
|
||||||
proximity_distance: 100.0 # Llindar de distància (px)
|
proximity_distance: 100.0 # Llindar de distància (px)
|
||||||
|
|
||||||
|
animation:
|
||||||
|
pulse:
|
||||||
|
trigger_prob_per_second: 0.01
|
||||||
|
duration_min: 1.0
|
||||||
|
duration_max: 3.0
|
||||||
|
amplitude_min: 0.08
|
||||||
|
amplitude_max: 0.20
|
||||||
|
frequency_min: 1.5
|
||||||
|
frequency_max: 3.0
|
||||||
|
rotation_accel:
|
||||||
|
trigger_prob_per_second: 0.02
|
||||||
|
duration_min: 3.0
|
||||||
|
duration_max: 8.0
|
||||||
|
multiplier_min: 0.3
|
||||||
|
multiplier_max: 4.0
|
||||||
|
|
||||||
|
wounded:
|
||||||
|
duration: 1.0
|
||||||
|
blink_hz: 10.0
|
||||||
|
|
||||||
|
spawn:
|
||||||
|
invulnerability_duration: 3.0
|
||||||
|
invulnerability_brightness_start: 0.3
|
||||||
|
invulnerability_brightness_end: 0.7
|
||||||
|
invulnerability_scale_start: 0.0
|
||||||
|
invulnerability_scale_end: 1.0
|
||||||
|
safety_distance: 36.0
|
||||||
|
|
||||||
colors:
|
colors:
|
||||||
normal: [255, 0, 255] # Magenta pur "agressiu"
|
normal: [255, 0, 255] # Magenta pur "agressiu"
|
||||||
wounded: [255, 220, 60]
|
wounded: [255, 220, 60]
|
||||||
|
|
||||||
score: 200
|
score: 200
|
||||||
|
|||||||
@@ -7,18 +7,49 @@ shape:
|
|||||||
collision_factor: 1.0 # ajust opcional del hitbox (default 1.0)
|
collision_factor: 1.0 # ajust opcional del hitbox (default 1.0)
|
||||||
|
|
||||||
physics:
|
physics:
|
||||||
mass: 8.0 # Més pesat — "tanc"
|
mass: 8.0 # Més pesat — "tanc"
|
||||||
speed: 40.0 # px/s (velocitat mitjana)
|
speed: 40.0 # px/s (velocitat mitjana)
|
||||||
rotation_delta_min: 0.3 # rad/s — rotació lenta
|
rotation_delta_min: 0.3 # rad/s — rotació lenta
|
||||||
rotation_delta_max: 1.5
|
rotation_delta_max: 1.5
|
||||||
|
restitution: 1.0
|
||||||
|
linear_damping: 0.0
|
||||||
|
angular_damping: 0.0
|
||||||
|
|
||||||
behavior:
|
behavior:
|
||||||
# Square: tracking discret cap a la nau cada N segons.
|
# Square: tracking discret cap a la nau cada N segons.
|
||||||
tracking_strength: 0.5 # Interpolació LERP cap a la direcció desitjada (0..1)
|
tracking_strength: 0.5 # Interpolació LERP cap a la direcció desitjada (0..1)
|
||||||
tracking_interval: 1.0 # segons entre updates d'angle
|
tracking_interval: 1.0 # segons entre updates d'angle
|
||||||
|
|
||||||
|
animation:
|
||||||
|
pulse:
|
||||||
|
trigger_prob_per_second: 0.01
|
||||||
|
duration_min: 1.0
|
||||||
|
duration_max: 3.0
|
||||||
|
amplitude_min: 0.08
|
||||||
|
amplitude_max: 0.20
|
||||||
|
frequency_min: 1.5
|
||||||
|
frequency_max: 3.0
|
||||||
|
rotation_accel:
|
||||||
|
trigger_prob_per_second: 0.02
|
||||||
|
duration_min: 3.0
|
||||||
|
duration_max: 8.0
|
||||||
|
multiplier_min: 0.3
|
||||||
|
multiplier_max: 4.0
|
||||||
|
|
||||||
|
wounded:
|
||||||
|
duration: 1.0
|
||||||
|
blink_hz: 10.0
|
||||||
|
|
||||||
|
spawn:
|
||||||
|
invulnerability_duration: 3.0
|
||||||
|
invulnerability_brightness_start: 0.3
|
||||||
|
invulnerability_brightness_end: 0.7
|
||||||
|
invulnerability_scale_start: 0.0
|
||||||
|
invulnerability_scale_end: 1.0
|
||||||
|
safety_distance: 36.0
|
||||||
|
|
||||||
colors:
|
colors:
|
||||||
normal: [255, 0, 0] # Roig pur "tanc"
|
normal: [255, 0, 0] # Roig pur "tanc"
|
||||||
wounded: [255, 220, 60]
|
wounded: [255, 220, 60]
|
||||||
|
|
||||||
score: 150
|
score: 150
|
||||||
|
|||||||
@@ -1,64 +1,18 @@
|
|||||||
// enemies.hpp - Configuració per tipus d'enemic (Pentagon/Square/Molinillo), spawn i scoring
|
// enemies.hpp - Constants tècniques compartides per al sistema d'enemics.
|
||||||
// © 2026 JailDesigner
|
// © 2026 JailDesigner
|
||||||
|
//
|
||||||
|
// Tots els paràmetres jugables (physics, animation, wounded, spawn,
|
||||||
|
// behavior, colors, scoring) viuen a data/entities/<type>/<type>.yaml i
|
||||||
|
// s'accedeixen via EnemyRegistry::get(EnemyType). Aquí només queda el
|
||||||
|
// que no és per personalitzar per tipus.
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
namespace Defaults::Enemies {
|
namespace Defaults::Enemies::Spawn {
|
||||||
|
|
||||||
// Cuerpo físico común (valores por defecto del constructor)
|
// Sostre de reintents al cercar una posició de spawn que respecti el
|
||||||
namespace Body {
|
// safety_distance del tipus. No és un paràmetre jugable: és el llindar
|
||||||
constexpr float DEFAULT_MASS = 5.0F; // Más liviano que la nave (10.0)
|
// tècnic abans de caure a un fallback aleatori amb advertència.
|
||||||
constexpr float RESTITUTION = 1.0F; // Rebote elástico perfecto contra paredes
|
constexpr int MAX_SPAWN_ATTEMPTS = 50;
|
||||||
constexpr float LINEAR_DAMPING = 0.0F; // Sin fricción: mantienen velocidad
|
|
||||||
constexpr float ANGULAR_DAMPING = 0.0F;
|
|
||||||
} // namespace Body
|
|
||||||
|
|
||||||
// NOTA: els paràmetres per tipus (Pentagon/Square/Pinwheel) i el Scoring
|
} // namespace Defaults::Enemies::Spawn
|
||||||
// viuen ara a data/entities/{pentagon,square,pinwheel}/*.yaml i s'accedeixen
|
|
||||||
// via EnemyRegistry::get(EnemyType). Aquí només queden els paràmetres
|
|
||||||
// compartits entre tots els tipus (animació, wounded, spawn).
|
|
||||||
|
|
||||||
// Animation parameters (shared)
|
|
||||||
namespace Animation {
|
|
||||||
// Palpitation
|
|
||||||
constexpr float PULSE_TRIGGER_PROB = 0.01F; // 1% chance per second
|
|
||||||
constexpr float PULSE_DURATION_MIN = 1.0F; // Min duration (seconds)
|
|
||||||
constexpr float PULSE_DURATION_MAX = 3.0F; // Max duration (seconds)
|
|
||||||
constexpr float PULSE_AMPLITUD_MIN = 0.08F; // Min scale variation
|
|
||||||
constexpr float PULSE_AMPLITUD_MAX = 0.20F; // Max scale variation
|
|
||||||
constexpr float PULSE_FREQ_MIN = 1.5F; // Min frequency (Hz)
|
|
||||||
constexpr float PULSE_FREQ_MAX = 3.0F; // Max frequency (Hz)
|
|
||||||
|
|
||||||
// Rotation acceleration
|
|
||||||
constexpr float ROTATION_ACCEL_TRIGGER_PROB = 0.02F; // 2% chance per second [4x more frequent]
|
|
||||||
constexpr float ROTATION_ACCEL_DURATION_MIN = 3.0F; // Min transition time
|
|
||||||
constexpr float ROTATION_ACCEL_DURATION_MAX = 8.0F; // Max transition time
|
|
||||||
constexpr float ROTATION_ACCEL_MULTIPLIER_MIN = 0.3F; // Min speed multiplier [more dramatic]
|
|
||||||
constexpr float ROTATION_ACCEL_MULTIPLIER_MAX = 4.0F; // Max speed multiplier [more dramatic]
|
|
||||||
} // namespace Animation
|
|
||||||
|
|
||||||
// Wounded state (entre primer impacto y explosión)
|
|
||||||
namespace Wounded {
|
|
||||||
constexpr float DURATION = 1.0F; // Segundos en estado herido antes de explotar
|
|
||||||
constexpr float BLINK_HZ = 10.0F; // Frecuencia de parpadeo color tipo ↔ dorado
|
|
||||||
} // namespace Wounded
|
|
||||||
|
|
||||||
// Spawn safety and invulnerability system
|
|
||||||
namespace Spawn {
|
|
||||||
// Safe spawn distance from player. Antic: SHIP_RADIUS(12) * 3 = 36 px.
|
|
||||||
// SHIP_RADIUS ha migrat al YAML del player; aquesta constant es
|
|
||||||
// mantindrà fixa fins al PR de migració dels enemics a YAML, on
|
|
||||||
// passarà a derivar-se en runtime del player_config.
|
|
||||||
constexpr float SAFETY_DISTANCE_MULTIPLIER = 3.0F;
|
|
||||||
constexpr float SAFETY_DISTANCE = 36.0F;
|
|
||||||
constexpr int MAX_SPAWN_ATTEMPTS = 50; // Max attempts to find safe position
|
|
||||||
|
|
||||||
// Invulnerability system
|
|
||||||
constexpr float INVULNERABILITY_DURATION = 3.0F; // Seconds
|
|
||||||
constexpr float INVULNERABILITY_BRIGHTNESS_START = 0.3F; // Dim
|
|
||||||
constexpr float INVULNERABILITY_BRIGHTNESS_END = 0.7F; // Normal (same as Defaults::Brightness::ENEMIC)
|
|
||||||
constexpr float INVULNERABILITY_SCALE_START = 0.0F; // Invisible
|
|
||||||
constexpr float INVULNERABILITY_SCALE_END = 1.0F; // Full size
|
|
||||||
} // namespace Spawn
|
|
||||||
|
|
||||||
} // namespace Defaults::Enemies
|
|
||||||
|
|||||||
@@ -36,18 +36,25 @@ namespace {
|
|||||||
return std::atan2(velocity.y, velocity.x) + (Constants::PI / 2.0F);
|
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
|
} // namespace
|
||||||
|
|
||||||
Enemy::Enemy(Rendering::Renderer* renderer)
|
Enemy::Enemy(Rendering::Renderer* renderer)
|
||||||
: Entity(renderer) {
|
: Entity(renderer) {
|
||||||
brightness_ = Defaults::Brightness::ENEMIC;
|
brightness_ = Defaults::Brightness::ENEMIC;
|
||||||
|
|
||||||
// Cuerpo físico — defaults comuns; init() ajusta mass/radius segons el tipus.
|
// Body queda amb defaults inocus (radius=0 = no col·lisiona) fins
|
||||||
body_.setMass(Defaults::Enemies::Body::DEFAULT_MASS);
|
// que init() apliqui la configuració del tipus carregada via Registry.
|
||||||
body_.radius = 0.0F; // 0 hasta spawn (no colisiona inactivo)
|
body_.radius = 0.0F;
|
||||||
body_.restitution = Defaults::Enemies::Body::RESTITUTION;
|
|
||||||
body_.linear_damping = Defaults::Enemies::Body::LINEAR_DAMPING;
|
|
||||||
body_.angular_damping = Defaults::Enemies::Body::ANGULAR_DAMPING;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Enemy::init(EnemyType type, const Vec2* ship_pos) {
|
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);
|
config_ = &EnemyRegistry::get(type);
|
||||||
const EnemyConfig& cfg = *config_;
|
const EnemyConfig& cfg = *config_;
|
||||||
|
|
||||||
// Cas Square: resetejar tracking timer al spawn.
|
|
||||||
if (type_ == EnemyType::SQUARE) {
|
if (type_ == EnemyType::SQUARE) {
|
||||||
tracking_timer_ = 0.0F;
|
tracking_timer_ = 0.0F;
|
||||||
tracking_strength_ = cfg.behavior.tracking_strength;
|
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';
|
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;
|
const float BOUNDING = (shape_ != nullptr) ? shape_->getBoundingRadius() : 0.0F;
|
||||||
collision_radius_ = BOUNDING * cfg.shape.scale * cfg.shape.collision_factor;
|
collision_radius_ = BOUNDING * cfg.shape.scale * cfg.shape.collision_factor;
|
||||||
|
|
||||||
body_.setMass(cfg.physics.mass);
|
body_.setMass(cfg.physics.mass);
|
||||||
body_.radius = collision_radius_;
|
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 min_x;
|
||||||
float max_x;
|
float max_x;
|
||||||
float min_y;
|
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++) {
|
for (int attempt = 0; attempt < Defaults::Enemies::Spawn::MAX_SPAWN_ATTEMPTS; attempt++) {
|
||||||
float candidate_x;
|
float candidate_x;
|
||||||
float candidate_y;
|
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_.x = candidate_x;
|
||||||
center_.y = candidate_y;
|
center_.y = candidate_y;
|
||||||
found_safe_position = true;
|
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));
|
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 = static_cast<float>(std::rand() % 360) * Constants::PI / 180.0F;
|
||||||
const float ANGLE_INICIAL = (std::rand() % 360) * Constants::PI / 180.0F;
|
|
||||||
setVelocityFromAngle(ANGLE_INICIAL, cfg.physics.speed);
|
setVelocityFromAngle(ANGLE_INICIAL, cfg.physics.speed);
|
||||||
|
|
||||||
body_.position = center_;
|
body_.position = center_;
|
||||||
@@ -116,10 +124,8 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) {
|
|||||||
body_.angular_velocity = 0.0F;
|
body_.angular_velocity = 0.0F;
|
||||||
body_.clearAccumulators();
|
body_.clearAccumulators();
|
||||||
|
|
||||||
// Rotación visual aleatoria dins del rang del tipus
|
// Rotació visual aleatòria dins del rang del tipus
|
||||||
const float ROTATION_RANGE = cfg.physics.rotation_delta_max - cfg.physics.rotation_delta_min;
|
rotation_delta_ = randRange(cfg.physics.rotation_delta_min, cfg.physics.rotation_delta_max);
|
||||||
rotation_delta_ = cfg.physics.rotation_delta_min +
|
|
||||||
((static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) * ROTATION_RANGE);
|
|
||||||
rotation_ = 0.0F;
|
rotation_ = 0.0F;
|
||||||
|
|
||||||
animation_ = EnemyAnimation();
|
animation_ = EnemyAnimation();
|
||||||
@@ -127,8 +133,8 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) {
|
|||||||
animation_.rotation_delta_target = rotation_delta_;
|
animation_.rotation_delta_target = rotation_delta_;
|
||||||
animation_.rotation_delta_t = 1.0F;
|
animation_.rotation_delta_t = 1.0F;
|
||||||
|
|
||||||
invulnerability_timer_ = Defaults::Enemies::Spawn::INVULNERABILITY_DURATION;
|
invulnerability_timer_ = cfg.spawn.invulnerability_duration;
|
||||||
brightness_ = Defaults::Enemies::Spawn::INVULNERABILITY_BRIGHTNESS_START;
|
brightness_ = cfg.spawn.invulnerability_brightness_start;
|
||||||
|
|
||||||
direction_change_timer_ = 0.0F;
|
direction_change_timer_ = 0.0F;
|
||||||
|
|
||||||
@@ -153,11 +159,11 @@ void Enemy::update(float delta_time) {
|
|||||||
invulnerability_timer_ -= delta_time;
|
invulnerability_timer_ -= delta_time;
|
||||||
invulnerability_timer_ = std::max(invulnerability_timer_, 0.0F);
|
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 T = 1.0F - T_INV;
|
||||||
const float SMOOTH_T = T * T * (3.0F - (2.0F * T));
|
const float SMOOTH_T = T * T * (3.0F - (2.0F * T));
|
||||||
constexpr float START = Defaults::Enemies::Spawn::INVULNERABILITY_BRIGHTNESS_START;
|
const float START = config_->spawn.invulnerability_brightness_start;
|
||||||
constexpr float END = Defaults::Enemies::Spawn::INVULNERABILITY_BRIGHTNESS_END;
|
const float END = config_->spawn.invulnerability_brightness_end;
|
||||||
brightness_ = START + ((END - START) * SMOOTH_T);
|
brightness_ = START + ((END - START) * SMOOTH_T);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,13 +196,11 @@ void Enemy::draw() const {
|
|||||||
if (!is_active_ || !shape_) {
|
if (!is_active_ || !shape_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// El SCALE final = escala base del YAML * modulador dinàmic (spawn/pulse).
|
|
||||||
const float SCALE = config_->shape.scale * computeCurrentScale();
|
const float SCALE = config_->shape.scale * computeCurrentScale();
|
||||||
SDL_Color color = config_->colors.normal;
|
SDL_Color color = config_->colors.normal;
|
||||||
|
|
||||||
// Parpadeo dorado mientras está herido.
|
|
||||||
if (wounded_timer_ > 0.0F) {
|
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);
|
const float T = std::fmod(wounded_timer_, CYCLE);
|
||||||
if (T < (CYCLE / 2.0F)) {
|
if (T < (CYCLE / 2.0F)) {
|
||||||
color = config_->colors.wounded;
|
color = config_->colors.wounded;
|
||||||
@@ -217,7 +221,7 @@ void Enemy::destroy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Enemy::hurt(uint8_t shooter_id) {
|
void Enemy::hurt(uint8_t shooter_id) {
|
||||||
wounded_timer_ = Defaults::Enemies::Wounded::DURATION;
|
wounded_timer_ = config_->wounded.duration;
|
||||||
last_hit_by_ = shooter_id;
|
last_hit_by_ = shooter_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,7 +234,7 @@ void Enemy::setVelocity(float speed) {
|
|||||||
if (CURRENT_SPEED > 0.0F) {
|
if (CURRENT_SPEED > 0.0F) {
|
||||||
body_.velocity = body_.velocity * (speed / CURRENT_SPEED);
|
body_.velocity = body_.velocity * (speed / CURRENT_SPEED);
|
||||||
} else {
|
} 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);
|
setVelocityFromAngle(A, speed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,17 +243,14 @@ void Enemy::setVelocityFromAngle(float angle_movement, float speed) {
|
|||||||
body_.velocity = angleToDirection(angle_movement) * speed;
|
body_.velocity = angleToDirection(angle_movement) * speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// PENTAGON: zigzag esquivador. Cambios de dirección periódicos (probabilísticos)
|
// PENTAGON: zigzag esquivador. Canvis de direcció periòdics (probabilístics)
|
||||||
// en lugar de detectar paredes; el rebote contra muros lo hace PhysicsWorld
|
// en lloc de detectar parets; el rebot contra murs el fa PhysicsWorld.
|
||||||
// con restitution=1.0.
|
|
||||||
void Enemy::behaviorPentagon(float delta_time) {
|
void Enemy::behaviorPentagon(float delta_time) {
|
||||||
direction_change_timer_ += delta_time;
|
direction_change_timer_ += delta_time;
|
||||||
|
|
||||||
const float RAND_VAL = static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX);
|
if (randFloat01() < config_->behavior.zigzag_prob_per_second * delta_time) {
|
||||||
if (RAND_VAL < config_->behavior.zigzag_prob_per_second * delta_time) {
|
|
||||||
const float CURRENT_ANGLE = velocityToAngle(body_.velocity);
|
const float CURRENT_ANGLE = velocityToAngle(body_.velocity);
|
||||||
const float DELTA = (static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX)) *
|
const float DELTA = randFloat01() * config_->behavior.angle_change_max;
|
||||||
config_->behavior.angle_change_max;
|
|
||||||
const float NEW_ANGLE = CURRENT_ANGLE + ((std::rand() % 2 == 0) ? DELTA : -DELTA);
|
const float NEW_ANGLE = CURRENT_ANGLE + ((std::rand() % 2 == 0) ? DELTA : -DELTA);
|
||||||
const float SPEED = body_.velocity.length();
|
const float SPEED = body_.velocity.length();
|
||||||
setVelocityFromAngle(NEW_ANGLE, SPEED);
|
setVelocityFromAngle(NEW_ANGLE, SPEED);
|
||||||
@@ -257,8 +258,7 @@ void Enemy::behaviorPentagon(float delta_time) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SQUARE: tracking discreto cada tracking_interval. Ajusta dirección
|
// SQUARE: tracking discret cap a la nau cada N segons.
|
||||||
// hacia el ship mezclando con tracking_strength_.
|
|
||||||
void Enemy::behaviorSquare(float delta_time) {
|
void Enemy::behaviorSquare(float delta_time) {
|
||||||
tracking_timer_ += 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*/) {
|
void Enemy::behaviorPinwheel(float /*delta_time*/) {
|
||||||
if (ship_position_ != nullptr) {
|
if (ship_position_ != nullptr) {
|
||||||
const Vec2 TO_SHIP = *ship_position_ - center_;
|
const Vec2 TO_SHIP = *ship_position_ - center_;
|
||||||
@@ -302,38 +302,26 @@ void Enemy::updateAnimation(float delta_time) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Enemy::updatePulse(float delta_time) {
|
void Enemy::updatePulse(float delta_time) {
|
||||||
|
const auto& cfg = config_->animation.pulse;
|
||||||
if (animation_.pulse_active) {
|
if (animation_.pulse_active) {
|
||||||
animation_.pulse_phase += 2.0F * Constants::PI * animation_.pulse_frequency * delta_time;
|
animation_.pulse_phase += 2.0F * Constants::PI * animation_.pulse_frequency * delta_time;
|
||||||
animation_.pulse_time_remaining -= delta_time;
|
animation_.pulse_time_remaining -= delta_time;
|
||||||
if (animation_.pulse_time_remaining <= 0.0F) {
|
if (animation_.pulse_time_remaining <= 0.0F) {
|
||||||
animation_.pulse_active = false;
|
animation_.pulse_active = false;
|
||||||
}
|
}
|
||||||
} else {
|
return;
|
||||||
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 (randFloat01() < cfg.trigger_prob_per_second * delta_time) {
|
||||||
if (RAND_VAL < TRIGGER_PROB) {
|
animation_.pulse_active = true;
|
||||||
animation_.pulse_active = true;
|
animation_.pulse_phase = 0.0F;
|
||||||
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);
|
||||||
const float FREQ_RANGE = Defaults::Enemies::Animation::PULSE_FREQ_MAX -
|
animation_.pulse_time_remaining = randRange(cfg.duration_min, cfg.duration_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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Enemy::updateRotationAcceleration(float delta_time) {
|
void Enemy::updateRotationAcceleration(float delta_time) {
|
||||||
|
const auto& cfg = config_->animation.rotation_accel;
|
||||||
if (animation_.rotation_delta_t < 1.0F) {
|
if (animation_.rotation_delta_t < 1.0F) {
|
||||||
animation_.rotation_delta_t += delta_time / animation_.rotation_delta_duration;
|
animation_.rotation_delta_t += delta_time / animation_.rotation_delta_duration;
|
||||||
if (animation_.rotation_delta_t >= 1.0F) {
|
if (animation_.rotation_delta_t >= 1.0F) {
|
||||||
@@ -347,34 +335,24 @@ void Enemy::updateRotationAcceleration(float delta_time) {
|
|||||||
const float TARGET = animation_.rotation_delta_target;
|
const float TARGET = animation_.rotation_delta_target;
|
||||||
rotation_delta_ = INITIAL + ((TARGET - INITIAL) * SMOOTH_T);
|
rotation_delta_ = INITIAL + ((TARGET - INITIAL) * SMOOTH_T);
|
||||||
}
|
}
|
||||||
} else {
|
return;
|
||||||
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 (randFloat01() < cfg.trigger_prob_per_second * delta_time) {
|
||||||
if (RAND_VAL < TRIGGER_PROB) {
|
animation_.rotation_delta_t = 0.0F;
|
||||||
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;
|
||||||
const float MULT_RANGE = Defaults::Enemies::Animation::ROTATION_ACCEL_MULTIPLIER_MAX -
|
animation_.rotation_delta_duration = randRange(cfg.duration_min, cfg.duration_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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Enemy::computeCurrentScale() const -> float {
|
auto Enemy::computeCurrentScale() const -> float {
|
||||||
float scale = 1.0F;
|
float scale = 1.0F;
|
||||||
if (invulnerability_timer_ > 0.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 T = 1.0F - T_INV;
|
||||||
const float SMOOTH_T = T * T * (3.0F - (2.0F * T));
|
const float SMOOTH_T = T * T * (3.0F - (2.0F * T));
|
||||||
constexpr float START = Defaults::Enemies::Spawn::INVULNERABILITY_SCALE_START;
|
const float START = config_->spawn.invulnerability_scale_start;
|
||||||
constexpr float END = Defaults::Enemies::Spawn::INVULNERABILITY_SCALE_END;
|
const float END = config_->spawn.invulnerability_scale_end;
|
||||||
scale = START + ((END - START) * SMOOTH_T);
|
scale = START + ((END - START) * SMOOTH_T);
|
||||||
} else if (animation_.pulse_active) {
|
} else if (animation_.pulse_active) {
|
||||||
scale += animation_.pulse_amplitude * std::sin(animation_.pulse_phase);
|
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 min_x;
|
||||||
float max_x;
|
float max_x;
|
||||||
float min_y;
|
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 DX = out_x - ship_pos.x;
|
||||||
const float DY = out_y - ship_pos.y;
|
const float DY = out_y - ship_pos.y;
|
||||||
const float DISTANCE = std::sqrt((DX * DX) + (DY * DY));
|
const float DISTANCE = std::sqrt((DX * DX) + (DY * DY));
|
||||||
return DISTANCE >= Defaults::Enemies::Spawn::SAFETY_DISTANCE;
|
return DISTANCE >= safety_distance;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
#include "core/defaults.hpp"
|
|
||||||
#include "core/entities/entity.hpp"
|
#include "core/entities/entity.hpp"
|
||||||
#include "core/types.hpp"
|
#include "core/types.hpp"
|
||||||
|
|
||||||
@@ -142,8 +141,8 @@ class Enemy : public Entities::Entity {
|
|||||||
void behaviorSquare(float delta_time);
|
void behaviorSquare(float delta_time);
|
||||||
void behaviorPinwheel(float delta_time);
|
void behaviorPinwheel(float delta_time);
|
||||||
[[nodiscard]] auto computeCurrentScale() const -> float;
|
[[nodiscard]] auto computeCurrentScale() const -> float;
|
||||||
// Static: passa collision_radius com a param per no acoblar a *this.
|
// Static: passa els paràmetres com a args per no acoblar a *this.
|
||||||
static auto attemptSafeSpawn(const Vec2& ship_pos, float collision_radius, float& out_x, float& out_y) -> bool;
|
static auto attemptSafeSpawn(const Vec2& ship_pos, float collision_radius, float safety_distance, float& out_x, float& out_y) -> bool;
|
||||||
|
|
||||||
// Helper: setear body_.velocity desde un ángulo y magnitud.
|
// Helper: setear body_.velocity desde un ángulo y magnitud.
|
||||||
// angle_movement=0 apunta hacia arriba (eje Y negativo SDL).
|
// angle_movement=0 apunta hacia arriba (eje Y negativo SDL).
|
||||||
|
|||||||
@@ -80,6 +80,60 @@ namespace {
|
|||||||
out.speed = p["speed"].get_value<float>();
|
out.speed = p["speed"].get_value<float>();
|
||||||
out.rotation_delta_min = p["rotation_delta_min"].get_value<float>();
|
out.rotation_delta_min = p["rotation_delta_min"].get_value<float>();
|
||||||
out.rotation_delta_max = p["rotation_delta_max"].get_value<float>();
|
out.rotation_delta_max = p["rotation_delta_max"].get_value<float>();
|
||||||
|
out.restitution = p["restitution"].get_value<float>();
|
||||||
|
out.linear_damping = p["linear_damping"].get_value<float>();
|
||||||
|
out.angular_damping = p["angular_damping"].get_value<float>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parseAnimation(const fkyaml::node& node, const std::string& name, EnemyConfig::AnimationCfg& out) -> bool {
|
||||||
|
if (!node.contains("animation") ||
|
||||||
|
!node["animation"].contains("pulse") ||
|
||||||
|
!node["animation"].contains("rotation_accel")) {
|
||||||
|
std::cerr << "[EnemyConfig] Error: falta 'animation.pulse' o 'animation.rotation_accel' a " << name << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto& p = node["animation"]["pulse"];
|
||||||
|
out.pulse.trigger_prob_per_second = p["trigger_prob_per_second"].get_value<float>();
|
||||||
|
out.pulse.duration_min = p["duration_min"].get_value<float>();
|
||||||
|
out.pulse.duration_max = p["duration_max"].get_value<float>();
|
||||||
|
out.pulse.amplitude_min = p["amplitude_min"].get_value<float>();
|
||||||
|
out.pulse.amplitude_max = p["amplitude_max"].get_value<float>();
|
||||||
|
out.pulse.frequency_min = p["frequency_min"].get_value<float>();
|
||||||
|
out.pulse.frequency_max = p["frequency_max"].get_value<float>();
|
||||||
|
|
||||||
|
const auto& r = node["animation"]["rotation_accel"];
|
||||||
|
out.rotation_accel.trigger_prob_per_second = r["trigger_prob_per_second"].get_value<float>();
|
||||||
|
out.rotation_accel.duration_min = r["duration_min"].get_value<float>();
|
||||||
|
out.rotation_accel.duration_max = r["duration_max"].get_value<float>();
|
||||||
|
out.rotation_accel.multiplier_min = r["multiplier_min"].get_value<float>();
|
||||||
|
out.rotation_accel.multiplier_max = r["multiplier_max"].get_value<float>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parseWounded(const fkyaml::node& node, const std::string& name, EnemyConfig::WoundedCfg& out) -> bool {
|
||||||
|
if (!node.contains("wounded")) {
|
||||||
|
std::cerr << "[EnemyConfig] Error: falta 'wounded' a " << name << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto& w = node["wounded"];
|
||||||
|
out.duration = w["duration"].get_value<float>();
|
||||||
|
out.blink_hz = w["blink_hz"].get_value<float>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parseSpawn(const fkyaml::node& node, const std::string& name, EnemyConfig::SpawnCfg& out) -> bool {
|
||||||
|
if (!node.contains("spawn")) {
|
||||||
|
std::cerr << "[EnemyConfig] Error: falta 'spawn' a " << name << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto& s = node["spawn"];
|
||||||
|
out.invulnerability_duration = s["invulnerability_duration"].get_value<float>();
|
||||||
|
out.invulnerability_brightness_start = s["invulnerability_brightness_start"].get_value<float>();
|
||||||
|
out.invulnerability_brightness_end = s["invulnerability_brightness_end"].get_value<float>();
|
||||||
|
out.invulnerability_scale_start = s["invulnerability_scale_start"].get_value<float>();
|
||||||
|
out.invulnerability_scale_end = s["invulnerability_scale_end"].get_value<float>();
|
||||||
|
out.safety_distance = s["safety_distance"].get_value<float>();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,6 +188,9 @@ auto EnemyConfig::fromYaml(const fkyaml::node& node, EnemyType expected_ai_type)
|
|||||||
if (!parseShape(node, cfg.name, cfg.shape)) { return std::nullopt; }
|
if (!parseShape(node, cfg.name, cfg.shape)) { return std::nullopt; }
|
||||||
if (!parsePhysics(node, cfg.name, cfg.physics)) { return std::nullopt; }
|
if (!parsePhysics(node, cfg.name, cfg.physics)) { return std::nullopt; }
|
||||||
parseBehavior(node, cfg.behavior);
|
parseBehavior(node, cfg.behavior);
|
||||||
|
if (!parseAnimation(node, cfg.name, cfg.animation)) { return std::nullopt; }
|
||||||
|
if (!parseWounded(node, cfg.name, cfg.wounded)) { return std::nullopt; }
|
||||||
|
if (!parseSpawn(node, cfg.name, cfg.spawn)) { return std::nullopt; }
|
||||||
if (!parseColors(node, cfg.name, cfg.colors)) { return std::nullopt; }
|
if (!parseColors(node, cfg.name, cfg.colors)) { return std::nullopt; }
|
||||||
if (!parseScore(node, cfg.name, cfg.score)) { return std::nullopt; }
|
if (!parseScore(node, cfg.name, cfg.score)) { return std::nullopt; }
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ struct EnemyConfig {
|
|||||||
float speed;
|
float speed;
|
||||||
float rotation_delta_min;
|
float rotation_delta_min;
|
||||||
float rotation_delta_max;
|
float rotation_delta_max;
|
||||||
|
float restitution; // rebot contra parets (1.0 = perfectament elàstic)
|
||||||
|
float linear_damping; // fricció lineal (s^-1)
|
||||||
|
float angular_damping;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Camps específics de cada AI. Els no aplicables a un tipus queden a 0.0F
|
// Camps específics de cada AI. Els no aplicables a un tipus queden a 0.0F
|
||||||
@@ -43,6 +46,43 @@ struct EnemyConfig {
|
|||||||
float proximity_distance{0.0F};
|
float proximity_distance{0.0F};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Animacions decoratives. Compartides estructuralment entre tots els tipus
|
||||||
|
// però amb valors propis per personalitzar la "personalitat" visual de cada un.
|
||||||
|
struct AnimationCfg {
|
||||||
|
struct PulseCfg {
|
||||||
|
float trigger_prob_per_second; // probabilitat per segon d'iniciar un pulse
|
||||||
|
float duration_min;
|
||||||
|
float duration_max;
|
||||||
|
float amplitude_min; // amplitud d'escala (±)
|
||||||
|
float amplitude_max;
|
||||||
|
float frequency_min; // Hz
|
||||||
|
float frequency_max;
|
||||||
|
};
|
||||||
|
struct RotationAccelCfg {
|
||||||
|
float trigger_prob_per_second;
|
||||||
|
float duration_min; // segons de transició al nou speed
|
||||||
|
float duration_max;
|
||||||
|
float multiplier_min; // multiplicador sobre rotation_delta_base
|
||||||
|
float multiplier_max;
|
||||||
|
};
|
||||||
|
PulseCfg pulse;
|
||||||
|
RotationAccelCfg rotation_accel;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WoundedCfg {
|
||||||
|
float duration; // segons en estat ferit abans d'explotar
|
||||||
|
float blink_hz; // freqüència del parpelleig color normal ↔ wounded
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SpawnCfg {
|
||||||
|
float invulnerability_duration; // segons d'invulnerabilitat post-spawn
|
||||||
|
float invulnerability_brightness_start; // brightness inicial (corba LERP)
|
||||||
|
float invulnerability_brightness_end; // brightness final
|
||||||
|
float invulnerability_scale_start; // escala inicial (corba LERP, 0 = invisible)
|
||||||
|
float invulnerability_scale_end; // escala final (1 = mida nativa)
|
||||||
|
float safety_distance; // px mínim respecte al player al spawn
|
||||||
|
};
|
||||||
|
|
||||||
struct ColorsCfg {
|
struct ColorsCfg {
|
||||||
SDL_Color normal;
|
SDL_Color normal;
|
||||||
SDL_Color wounded;
|
SDL_Color wounded;
|
||||||
@@ -53,6 +93,9 @@ struct EnemyConfig {
|
|||||||
ShapeCfg shape;
|
ShapeCfg shape;
|
||||||
PhysicsCfg physics;
|
PhysicsCfg physics;
|
||||||
BehaviorCfg behavior;
|
BehaviorCfg behavior;
|
||||||
|
AnimationCfg animation;
|
||||||
|
WoundedCfg wounded;
|
||||||
|
SpawnCfg spawn;
|
||||||
ColorsCfg colors;
|
ColorsCfg colors;
|
||||||
int score;
|
int score;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user