From 622ccd22bcbbfabeb09b1fb6da2d4c394d680f5a Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Wed, 3 Dec 2025 13:47:31 +0100 Subject: [PATCH] afegits diferents enemics --- data/shapes/enemy_pinwheel.shp | 30 +++ data/shapes/enemy_square.shp | 19 ++ source/core/defaults.hpp | 54 +++++ source/game/entities/enemic.cpp | 353 ++++++++++++++++++++++++----- source/game/entities/enemic.hpp | 48 +++- source/game/escenes/escena_joc.cpp | 17 +- 6 files changed, 463 insertions(+), 58 deletions(-) create mode 100644 data/shapes/enemy_pinwheel.shp create mode 100644 data/shapes/enemy_square.shp diff --git a/data/shapes/enemy_pinwheel.shp b/data/shapes/enemy_pinwheel.shp new file mode 100644 index 0000000..f625f8d --- /dev/null +++ b/data/shapes/enemy_pinwheel.shp @@ -0,0 +1,30 @@ +# enemy_pinwheel.shp - ORNI enemic (molinillo de 4 triangles) +# © 2025 Port a C++20 amb SDL3 + +name: enemy_pinwheel +scale: 1.0 +center: 0, 0 + +# Molinillo: 4 triangles, un en cada quadrant +# Cada triangle comparteix el centre (0,0) i té: +# - Un vèrtex en un eix (±20, 0) o (0, ±20) +# - Un vèrtex en la diagonal del quadrant (±14.14, ±14.14) +# - El tercer vèrtex al centre (0,0) +# +# Geometria: +# Triangle 1 (quadrant superior-dret): centre → eix dret → diagonal +# Triangle 2 (quadrant superior-esq): centre → eix superior → diagonal +# Triangle 3 (quadrant inferior-esq): centre → eix esquerre → diagonal +# Triangle 4 (quadrant inferior-dret): centre → eix inferior → diagonal + +# Triangle 1: quadrant superior-dret +polyline: 0,0 20,0 14.14,-14.14 0,0 + +# Triangle 2: quadrant superior-esquerre +polyline: 0,0 0,-20 -14.14,-14.14 0,0 + +# Triangle 3: quadrant inferior-esquerre +polyline: 0,0 -20,0 -14.14,14.14 0,0 + +# Triangle 4: quadrant inferior-dret +polyline: 0,0 0,20 14.14,14.14 0,0 diff --git a/data/shapes/enemy_square.shp b/data/shapes/enemy_square.shp new file mode 100644 index 0000000..2d6f16c --- /dev/null +++ b/data/shapes/enemy_square.shp @@ -0,0 +1,19 @@ +# enemy_square.shp - ORNI enemic (quadrat regular) +# © 2025 Port a C++20 amb SDL3 + +name: enemy_square +scale: 1.0 +center: 0, 0 + +# Quadrat regular radi=20 (circumscrit) +# 4 punts equidistants al voltant d'un cercle (90° entre ells) +# Començant a angle=-90° (amunt), rotant sentit horari +# +# Angles: -90°, 0°, 90°, 180° +# Conversió polar→cartesià (SDL: Y creix cap avall): +# angle=-90°: (0.00, -20.00) +# angle=0°: (20.00, 0.00) +# angle=90°: (0.00, 20.00) +# angle=180°: (-20.00, 0.00) + +polyline: 0,-20 20,0 0,20 -20,0 0,-20 diff --git a/source/core/defaults.hpp b/source/core/defaults.hpp index 83aa112..e19a5df 100644 --- a/source/core/defaults.hpp +++ b/source/core/defaults.hpp @@ -173,4 +173,58 @@ constexpr bool ENABLED = true; // Sonidos habilitados constexpr const char* EXPLOSION = "explosion.wav"; // Explosión constexpr const char* LASER = "laser_shoot.wav"; // Disparo } // namespace Sound + +// Enemy type configuration (tipus d'enemics) +namespace Enemies { +// Pentagon (esquivador - zigzag evasion) +namespace Pentagon { +constexpr float VELOCITAT = 35.0f; // px/s (slightly slower) +constexpr float CANVI_ANGLE_PROB = 0.20f; // 20% per wall hit (frequent zigzag) +constexpr float CANVI_ANGLE_MAX = 1.0f; // Max random angle change (rad) +constexpr float DROTACIO_MIN = 0.5f; // Min visual rotation (rad/s) +constexpr float DROTACIO_MAX = 2.5f; // Max visual rotation (rad/s) +constexpr const char* SHAPE_FILE = "enemy_pentagon.shp"; +} // namespace Pentagon + +// Quadrat (perseguidor - tracks player) +namespace Quadrat { +constexpr float VELOCITAT = 40.0f; // px/s (medium speed) +constexpr float TRACKING_STRENGTH = 0.5f; // Interpolation toward player (0.0-1.0) +constexpr float TRACKING_INTERVAL = 1.0f; // Seconds between angle updates +constexpr float DROTACIO_MIN = 0.2f; // Slow rotation +constexpr float DROTACIO_MAX = 1.0f; +constexpr const char* SHAPE_FILE = "enemy_square.shp"; +} // namespace Quadrat + +// Molinillo (agressiu - fast straight lines, proximity spin-up) +namespace Molinillo { +constexpr float VELOCITAT = 50.0f; // px/s (fastest) +constexpr float CANVI_ANGLE_PROB = 0.05f; // 5% per wall hit (rare direction change) +constexpr float CANVI_ANGLE_MAX = 0.3f; // Small angle adjustments +constexpr float DROTACIO_MIN = 2.0f; // Base rotation (rad/s) +constexpr float DROTACIO_MAX = 4.0f; +constexpr float DROTACIO_PROXIMITY_MULTIPLIER = 3.0f; // Spin-up multiplier when near ship +constexpr float PROXIMITY_DISTANCE = 100.0f; // Distance threshold (px) +constexpr const char* SHAPE_FILE = "enemy_pinwheel.shp"; +} // namespace Molinillo + +// Animation parameters (shared) +namespace Animation { +// Palpitation +constexpr float PALPITACIO_TRIGGER_PROB = 0.01f; // 1% chance per second +constexpr float PALPITACIO_DURACIO_MIN = 1.0f; // Min duration (seconds) +constexpr float PALPITACIO_DURACIO_MAX = 3.0f; // Max duration (seconds) +constexpr float PALPITACIO_AMPLITUD_MIN = 0.08f; // Min scale variation +constexpr float PALPITACIO_AMPLITUD_MAX = 0.20f; // Max scale variation +constexpr float PALPITACIO_FREQ_MIN = 1.5f; // Min frequency (Hz) +constexpr float PALPITACIO_FREQ_MAX = 3.0f; // Max frequency (Hz) + +// Rotation acceleration +constexpr float ROTACIO_ACCEL_TRIGGER_PROB = 0.005f; // 0.5% chance per second +constexpr float ROTACIO_ACCEL_DURACIO_MIN = 3.0f; // Min transition time +constexpr float ROTACIO_ACCEL_DURACIO_MAX = 8.0f; // Max transition time +constexpr float ROTACIO_ACCEL_MULTIPLIER_MIN = 0.5f; // Min speed multiplier +constexpr float ROTACIO_ACCEL_MULTIPLIER_MAX = 2.5f; // Max speed multiplier +} // namespace Animation +} // namespace Enemies } // namespace Defaults diff --git a/source/game/entities/enemic.cpp b/source/game/entities/enemic.cpp index 3c85640..6db99c1 100644 --- a/source/game/entities/enemic.cpp +++ b/source/game/entities/enemic.cpp @@ -21,25 +21,53 @@ Enemic::Enemic(SDL_Renderer* renderer) drotacio_(0.0f), rotacio_(0.0f), esta_(false), - brightness_(Defaults::Brightness::ENEMIC) { - // [NUEVO] Carregar forma compartida des de fitxer - forma_ = Graphics::ShapeLoader::load("enemy_pentagon.shp"); - - if (!forma_ || !forma_->es_valida()) { - std::cerr << "[Enemic] Error: no s'ha pogut carregar enemy_pentagon.shp" - << std::endl; - } + brightness_(Defaults::Brightness::ENEMIC), + tipus_(TipusEnemic::PENTAGON), + tracking_timer_(0.0f), + ship_position_(nullptr) { + // [NUEVO] Forma es carrega a inicialitzar() segons el tipus + // Constructor no carrega forma per permetre tipus diferents } -void Enemic::inicialitzar() { - // Inicialitzar enemic (pentàgon) - // Copiat de joc_asteroides.cpp línies 41-54 +void Enemic::inicialitzar(TipusEnemic tipus) { + // Guardar tipus + tipus_ = tipus; - // [NUEVO] Ja no cal crear_poligon_regular - la geometria es carrega del - // fitxer Només inicialitzem l'estat de la instància + // Carregar forma segons el tipus + const char* shape_file; + float drotacio_min, drotacio_max; + + switch (tipus_) { + case TipusEnemic::PENTAGON: + shape_file = Defaults::Enemies::Pentagon::SHAPE_FILE; + velocitat_ = Defaults::Enemies::Pentagon::VELOCITAT; + drotacio_min = Defaults::Enemies::Pentagon::DROTACIO_MIN; + drotacio_max = Defaults::Enemies::Pentagon::DROTACIO_MAX; + break; + + case TipusEnemic::QUADRAT: + shape_file = Defaults::Enemies::Quadrat::SHAPE_FILE; + velocitat_ = Defaults::Enemies::Quadrat::VELOCITAT; + drotacio_min = Defaults::Enemies::Quadrat::DROTACIO_MIN; + drotacio_max = Defaults::Enemies::Quadrat::DROTACIO_MAX; + tracking_timer_ = 0.0f; + break; + + case TipusEnemic::MOLINILLO: + shape_file = Defaults::Enemies::Molinillo::SHAPE_FILE; + velocitat_ = Defaults::Enemies::Molinillo::VELOCITAT; + drotacio_min = Defaults::Enemies::Molinillo::DROTACIO_MIN; + drotacio_max = Defaults::Enemies::Molinillo::DROTACIO_MAX; + break; + } + + // Carregar forma + forma_ = Graphics::ShapeLoader::load(shape_file); + if (!forma_ || !forma_->es_valida()) { + std::cerr << "[Enemic] Error: no s'ha pogut carregar " << shape_file << std::endl; + } // Posició aleatòria dins de l'àrea de joc - // Calcular rangs segurs amb radi de l'enemic float min_x, max_x, min_y, max_y; Constants::obtenir_limits_zona_segurs(Defaults::Entities::ENEMY_RADIUS, min_x, @@ -47,7 +75,6 @@ void Enemic::inicialitzar() { min_y, max_y); - // Spawn aleatori dins dels límits segurs int range_x = static_cast(max_x - min_x); int range_y = static_cast(max_y - min_y); centre_.x = static_cast((std::rand() % range_x) + static_cast(min_x)); @@ -56,14 +83,17 @@ void Enemic::inicialitzar() { // Angle aleatori de moviment angle_ = (std::rand() % 360) * Constants::PI / 180.0f; - // Velocitat (2 px/frame original * 20 FPS = 40 px/s) - velocitat_ = 40.0f; - - // Rotació visual aleatòria (rad/s) - // Original Pascal: random * 0.1 rad/frame * 20 FPS ≈ 2 rad/s - drotacio_ = (static_cast(std::rand()) / RAND_MAX) * 2.0f; + // Rotació visual aleatòria (rad/s) dins del rang del tipus + float drotacio_range = drotacio_max - drotacio_min; + drotacio_ = drotacio_min + (static_cast(std::rand()) / RAND_MAX) * drotacio_range; rotacio_ = 0.0f; + // Inicialitzar estat d'animació + animacio_ = AnimacioEnemic(); // Reset to defaults + animacio_.drotacio_base = drotacio_; + animacio_.drotacio_objetivo = drotacio_; + animacio_.drotacio_t = 1.0f; // Start without interpolating + // Activar esta_ = true; } @@ -73,6 +103,9 @@ void Enemic::actualitzar(float delta_time) { // Moviment autònom mou(delta_time); + // Actualitzar animacions (palpitació, rotació accelerada) + actualitzar_animacio(delta_time); + // Rotació visual (time-based: drotacio_ està en rad/s) rotacio_ += drotacio_ * delta_time; } @@ -80,21 +113,31 @@ void Enemic::actualitzar(float delta_time) { void Enemic::dibuixar() const { if (esta_ && forma_) { - // [NUEVO] Usar render_shape en lloc de rota_pol - Rendering::render_shape(renderer_, forma_, centre_, rotacio_, 1.0f, true, 1.0f, brightness_); + // [NUEVO] Usar render_shape amb escala animada + float escala = calcular_escala_actual(); + Rendering::render_shape(renderer_, forma_, centre_, rotacio_, escala, true, 1.0f, brightness_); } } void Enemic::mou(float delta_time) { - // Moviment autònom d'ORNI (enemic pentàgon) - // Basat EXACTAMENT en el codi Pascal original: ASTEROID.PAS lines 279-293 - // Copiat EXACTAMENT de joc_asteroides.cpp línies 348-394 - // - // IMPORTANT: El Pascal original NO té canvi aleatori continu! - // Només ajusta l'angle quan toca una paret. + // Dispatcher: crida el comportament específic segons el tipus + switch (tipus_) { + case TipusEnemic::PENTAGON: + comportament_pentagon(delta_time); + break; + case TipusEnemic::QUADRAT: + comportament_quadrat(delta_time); + break; + case TipusEnemic::MOLINILLO: + comportament_molinillo(delta_time); + break; + } +} + +void Enemic::comportament_pentagon(float delta_time) { + // Pentagon: zigzag esquivador (frequent direction changes) + // Similar a comportament original però amb probabilitat més alta - // Calcular nova posició PROPUESTA (time-based, però lògica Pascal) - // velocitat_ ja està en px/s (40 px/s), multiplicar per delta_time float velocitat_efectiva = velocitat_ * delta_time; // Calcular desplaçament (angle-PI/2 perquè angle=0 apunta amunt) @@ -104,41 +147,241 @@ void Enemic::mou(float delta_time) { float new_y = centre_.y + dy; float new_x = centre_.x + dx; - // Obtenir límits segurs compensant el radi de l'enemic + // Obtenir límits segurs float min_x, max_x, min_y, max_y; Constants::obtenir_limits_zona_segurs(Defaults::Entities::ENEMY_RADIUS, - min_x, - max_x, - min_y, - max_y); + min_x, max_x, min_y, max_y); - // Lògica Pascal: Actualitza Y si dins, sinó ajusta angle aleatòriament - // if (dy>marge_dalt) and (dy= i <=) per evitar fugides + // Zigzag: canvi d'angle més freqüent en tocar límits if (new_y >= min_y && new_y <= max_y) { centre_.y = new_y; } else { - // Pequeño ajuste aleatorio: (random(256)/512)*(random(3)-1) - // random(256) = 0..255, /512 = 0..0.498 - // random(3) = 0,1,2, -1 = -1,0,1 - // Resultado: ±0.5 rad aprox - float rand1 = (static_cast(std::rand() % 256) / 512.0f); - int rand2 = (std::rand() % 3) - 1; // -1, 0, o 1 - angle_ += rand1 * static_cast(rand2); + // Probabilitat més alta de canvi d'angle + if (static_cast(std::rand()) / RAND_MAX < Defaults::Enemies::Pentagon::CANVI_ANGLE_PROB) { + float rand_angle = (static_cast(std::rand()) / RAND_MAX) * + Defaults::Enemies::Pentagon::CANVI_ANGLE_MAX; + angle_ += (std::rand() % 2 == 0) ? rand_angle : -rand_angle; + } } - // Lògica Pascal: Actualitza X si dins, sinó ajusta angle aleatòriament - // if (dx>marge_esq) and (dx= i <=) per evitar fugides if (new_x >= min_x && new_x <= max_x) { centre_.x = new_x; } else { - float rand1 = (static_cast(std::rand() % 256) / 512.0f); - int rand2 = (std::rand() % 3) - 1; - angle_ += rand1 * static_cast(rand2); + if (static_cast(std::rand()) / RAND_MAX < Defaults::Enemies::Pentagon::CANVI_ANGLE_PROB) { + float rand_angle = (static_cast(std::rand()) / RAND_MAX) * + Defaults::Enemies::Pentagon::CANVI_ANGLE_MAX; + angle_ += (std::rand() % 2 == 0) ? rand_angle : -rand_angle; + } + } +} + +void Enemic::comportament_quadrat(float delta_time) { + // Quadrat: perseguidor (tracks player position) + + // Update tracking timer + tracking_timer_ += delta_time; + + // Periodically update angle toward ship + if (tracking_timer_ >= Defaults::Enemies::Quadrat::TRACKING_INTERVAL) { + tracking_timer_ = 0.0f; + + if (ship_position_) { + // Calculate angle to ship + float dx = ship_position_->x - centre_.x; + float dy = ship_position_->y - centre_.y; + float target_angle = std::atan2(dy, dx) + Constants::PI / 2.0f; + + // Interpolate toward target angle + float angle_diff = target_angle - angle_; + + // Normalize angle difference to [-π, π] + while (angle_diff > Constants::PI) angle_diff -= 2.0f * Constants::PI; + while (angle_diff < -Constants::PI) angle_diff += 2.0f * Constants::PI; + + // Apply tracking strength + angle_ += angle_diff * Defaults::Enemies::Quadrat::TRACKING_STRENGTH; + } } - // Nota: La rotació visual (rotacio_ += drotacio_) ja es fa a actualitzar() + // Move in current direction + float velocitat_efectiva = velocitat_ * delta_time; + float dy = velocitat_efectiva * std::sin(angle_ - Constants::PI / 2.0f); + float dx = velocitat_efectiva * std::cos(angle_ - Constants::PI / 2.0f); + + float new_y = centre_.y + dy; + float new_x = centre_.x + dx; + + // Obtenir límits segurs + float min_x, max_x, min_y, max_y; + Constants::obtenir_limits_zona_segurs(Defaults::Entities::ENEMY_RADIUS, + min_x, max_x, min_y, max_y); + + // Bounce on walls (simple reflection) + if (new_y >= min_y && new_y <= max_y) { + centre_.y = new_y; + } else { + angle_ = -angle_; // Vertical reflection + } + + if (new_x >= min_x && new_x <= max_x) { + centre_.x = new_x; + } else { + angle_ = Constants::PI - angle_; // Horizontal reflection + } +} + +void Enemic::comportament_molinillo(float delta_time) { + // Molinillo: agressiu (fast, straight lines, proximity spin-up) + + // Check proximity to ship for spin-up effect + if (ship_position_) { + float dx = ship_position_->x - centre_.x; + float dy = ship_position_->y - centre_.y; + float distance = std::sqrt(dx * dx + dy * dy); + + if (distance < Defaults::Enemies::Molinillo::PROXIMITY_DISTANCE) { + // Temporarily boost rotation speed when near ship + float boost = Defaults::Enemies::Molinillo::DROTACIO_PROXIMITY_MULTIPLIER; + drotacio_ = animacio_.drotacio_base * boost; + } else { + // Normal rotation speed + drotacio_ = animacio_.drotacio_base; + } + } + + // Fast straight-line movement + float velocitat_efectiva = velocitat_ * delta_time; + float dy = velocitat_efectiva * std::sin(angle_ - Constants::PI / 2.0f); + float dx = velocitat_efectiva * std::cos(angle_ - Constants::PI / 2.0f); + + float new_y = centre_.y + dy; + float new_x = centre_.x + dx; + + // Obtenir límits segurs + float min_x, max_x, min_y, max_y; + Constants::obtenir_limits_zona_segurs(Defaults::Entities::ENEMY_RADIUS, + min_x, max_x, min_y, max_y); + + // Rare angle changes on wall hits + if (new_y >= min_y && new_y <= max_y) { + centre_.y = new_y; + } else { + if (static_cast(std::rand()) / RAND_MAX < Defaults::Enemies::Molinillo::CANVI_ANGLE_PROB) { + float rand_angle = (static_cast(std::rand()) / RAND_MAX) * + Defaults::Enemies::Molinillo::CANVI_ANGLE_MAX; + angle_ += (std::rand() % 2 == 0) ? rand_angle : -rand_angle; + } + } + + if (new_x >= min_x && new_x <= max_x) { + centre_.x = new_x; + } else { + if (static_cast(std::rand()) / RAND_MAX < Defaults::Enemies::Molinillo::CANVI_ANGLE_PROB) { + float rand_angle = (static_cast(std::rand()) / RAND_MAX) * + Defaults::Enemies::Molinillo::CANVI_ANGLE_MAX; + angle_ += (std::rand() % 2 == 0) ? rand_angle : -rand_angle; + } + } +} + +void Enemic::actualitzar_animacio(float delta_time) { + actualitzar_palpitacio(delta_time); + actualitzar_rotacio_accelerada(delta_time); +} + +void Enemic::actualitzar_palpitacio(float delta_time) { + if (animacio_.palpitacio_activa) { + // Advance phase (2π * frequency * dt) + animacio_.palpitacio_fase += 2.0f * Constants::PI * animacio_.palpitacio_frequencia * delta_time; + + // Decrement timer + animacio_.palpitacio_temps_restant -= delta_time; + + // Deactivate when timer expires + if (animacio_.palpitacio_temps_restant <= 0.0f) { + animacio_.palpitacio_activa = false; + } + } else { + // Random trigger (probability per second) + float rand_val = static_cast(std::rand()) / RAND_MAX; + float trigger_prob = Defaults::Enemies::Animation::PALPITACIO_TRIGGER_PROB * delta_time; + + if (rand_val < trigger_prob) { + // Activate palpitation + animacio_.palpitacio_activa = true; + animacio_.palpitacio_fase = 0.0f; + + // Randomize parameters + float freq_range = Defaults::Enemies::Animation::PALPITACIO_FREQ_MAX - + Defaults::Enemies::Animation::PALPITACIO_FREQ_MIN; + animacio_.palpitacio_frequencia = Defaults::Enemies::Animation::PALPITACIO_FREQ_MIN + + (static_cast(std::rand()) / RAND_MAX) * freq_range; + + float amp_range = Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MAX - + Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MIN; + animacio_.palpitacio_amplitud = Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MIN + + (static_cast(std::rand()) / RAND_MAX) * amp_range; + + float dur_range = Defaults::Enemies::Animation::PALPITACIO_DURACIO_MAX - + Defaults::Enemies::Animation::PALPITACIO_DURACIO_MIN; + animacio_.palpitacio_temps_restant = Defaults::Enemies::Animation::PALPITACIO_DURACIO_MIN + + (static_cast(std::rand()) / RAND_MAX) * dur_range; + } + } +} + +void Enemic::actualitzar_rotacio_accelerada(float delta_time) { + if (animacio_.drotacio_t < 1.0f) { + // Transitioning to new target + animacio_.drotacio_t += delta_time / animacio_.drotacio_duracio; + + if (animacio_.drotacio_t >= 1.0f) { + animacio_.drotacio_t = 1.0f; + animacio_.drotacio_base = animacio_.drotacio_objetivo; // Reached target + drotacio_ = animacio_.drotacio_base; + } else { + // Smoothstep interpolation: t² * (3 - 2t) + float t = animacio_.drotacio_t; + float smooth_t = t * t * (3.0f - 2.0f * t); + + // Interpolate between base and target + float initial = animacio_.drotacio_base; + float target = animacio_.drotacio_objetivo; + drotacio_ = initial + (target - initial) * smooth_t; + } + } else { + // Random trigger for new acceleration + float rand_val = static_cast(std::rand()) / RAND_MAX; + float trigger_prob = Defaults::Enemies::Animation::ROTACIO_ACCEL_TRIGGER_PROB * delta_time; + + if (rand_val < trigger_prob) { + // Start new transition + animacio_.drotacio_t = 0.0f; + + // Randomize target speed (multiplier * base) + float mult_range = Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MAX - + Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MIN; + float multiplier = Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MIN + + (static_cast(std::rand()) / RAND_MAX) * mult_range; + + animacio_.drotacio_objetivo = animacio_.drotacio_base * multiplier; + + // Randomize duration + float dur_range = Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MAX - + Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MIN; + animacio_.drotacio_duracio = Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MIN + + (static_cast(std::rand()) / RAND_MAX) * dur_range; + } + } +} + +float Enemic::calcular_escala_actual() const { + float escala = 1.0f; + + if (animacio_.palpitacio_activa) { + // Add pulsating scale variation + escala += animacio_.palpitacio_amplitud * std::sin(animacio_.palpitacio_fase); + } + + return escala; } diff --git a/source/game/entities/enemic.hpp b/source/game/entities/enemic.hpp index 1431cf6..af64d59 100644 --- a/source/game/entities/enemic.hpp +++ b/source/game/entities/enemic.hpp @@ -10,13 +10,36 @@ #include "core/graphics/shape.hpp" #include "core/types.hpp" +// Tipus d'enemic +enum class TipusEnemic : uint8_t { + PENTAGON = 0, // Pentàgon esquivador (zigzag) + QUADRAT = 1, // Quadrat perseguidor (tracks ship) + MOLINILLO = 2 // Molinillo agressiu (fast, spinning) +}; + +// Estat d'animació (palpitació i rotació accelerada) +struct AnimacioEnemic { + // Palpitation (breathing effect) + bool palpitacio_activa = false; + float palpitacio_fase = 0.0f; // Phase in cycle (0.0-2π) + float palpitacio_frequencia = 2.0f; // Hz (cycles per second) + float palpitacio_amplitud = 0.15f; // Scale variation (±15%) + float palpitacio_temps_restant = 0.0f; // Time remaining (seconds) + + // Rotation acceleration (long-term spin modulation) + float drotacio_base = 0.0f; // Base rotation speed (rad/s) + float drotacio_objetivo = 0.0f; // Target rotation speed (rad/s) + float drotacio_t = 0.0f; // Interpolation progress (0.0-1.0) + float drotacio_duracio = 0.0f; // Duration of transition (seconds) +}; + class Enemic { public: Enemic() : renderer_(nullptr) {} Enemic(SDL_Renderer* renderer); - void inicialitzar(); + void inicialitzar(TipusEnemic tipus = TipusEnemic::PENTAGON); void actualitzar(float delta_time); void dibuixar() const; @@ -26,6 +49,9 @@ class Enemic { const std::shared_ptr& get_forma() const { return forma_; } void destruir() { esta_ = false; } + // Set ship position reference for tracking behavior + void set_ship_position(const Punt* ship_pos) { ship_position_ = ship_pos; } + private: SDL_Renderer* renderer_; @@ -41,5 +67,25 @@ class Enemic { bool esta_; float brightness_; // Factor de brillantor (0.0-1.0) + // [NEW] Enemy type and configuration + TipusEnemic tipus_; + + // [NEW] Animation state + AnimacioEnemic animacio_; + + // [NEW] Behavior state (type-specific) + float tracking_timer_; // For Quadrat: time since last angle update + const Punt* ship_position_; // Pointer to ship position (for tracking) + + // [EXISTING] Private methods void mou(float delta_time); + + // [NEW] Private methods + void actualitzar_animacio(float delta_time); + void actualitzar_palpitacio(float delta_time); + void actualitzar_rotacio_accelerada(float delta_time); + void comportament_pentagon(float delta_time); + void comportament_quadrat(float delta_time); + void comportament_molinillo(float delta_time); + float calcular_escala_actual() const; // Returns scale with palpitation applied }; diff --git a/source/game/escenes/escena_joc.cpp b/source/game/escenes/escena_joc.cpp index 2e92729..0fbcb85 100644 --- a/source/game/escenes/escena_joc.cpp +++ b/source/game/escenes/escena_joc.cpp @@ -119,9 +119,22 @@ void EscenaJoc::inicialitzar() { // Inicialitzar nau nau_.inicialitzar(); - // Inicialitzar enemics (ORNIs) + // Inicialitzar enemics (ORNIs) amb tipus aleatoris for (auto& enemy : orni_) { - enemy.inicialitzar(); + // Random type distribution: ~40% Pentagon, ~30% Quadrat, ~30% Molinillo + int rand_val = std::rand() % 10; + TipusEnemic tipus; + + if (rand_val < 4) { + tipus = TipusEnemic::PENTAGON; + } else if (rand_val < 7) { + tipus = TipusEnemic::QUADRAT; + } else { + tipus = TipusEnemic::MOLINILLO; + } + + enemy.inicialitzar(tipus); + enemy.set_ship_position(&nau_.get_centre()); // Set ship reference for tracking } // Inicialitzar bales