millorat el spawn d'enemics: perimetre de seguretat i animació amb invulnerabilitat

This commit is contained in:
2025-12-09 10:21:42 +01:00
parent ec6565bf71
commit 217ca58b1a
9 changed files with 157 additions and 21 deletions

View File

@@ -95,10 +95,10 @@ $(TARGET_FILE):
@echo "Build successful: $(TARGET_FILE)" @echo "Build successful: $(TARGET_FILE)"
# Debug build # Debug build
debug: debug: resources.pack
@cmake -B build -DCMAKE_BUILD_TYPE=Debug @cmake -B build -DCMAKE_BUILD_TYPE=Debug
@cmake --build build @cmake --build build
@echo "Debug build successful: $(TARGET_FILE)_debug" @echo "Debug build successful: $(TARGET_FILE)"
# ============================================================================== # ==============================================================================
# RELEASE PACKAGING TARGETS # RELEASE PACKAGING TARGETS

View File

@@ -228,5 +228,21 @@ 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_MIN = 0.5f; // Min speed multiplier
constexpr float ROTACIO_ACCEL_MULTIPLIER_MAX = 2.5f; // Max speed multiplier constexpr float ROTACIO_ACCEL_MULTIPLIER_MAX = 2.5f; // Max speed multiplier
} // namespace Animation } // namespace Animation
// Spawn safety and invulnerability system
namespace Spawn {
// Safe spawn distance from player
constexpr float SAFETY_DISTANCE_MULTIPLIER = 3.0f; // 3x ship radius
constexpr float SAFETY_DISTANCE = Defaults::Entities::SHIP_RADIUS * SAFETY_DISTANCE_MULTIPLIER; // 36.0f px
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 Enemies } // namespace Enemies
} // namespace Defaults } // namespace Defaults

View File

@@ -27,7 +27,7 @@ public:
// Constructor inicial amb escena LOGO i sense opcions // Constructor inicial amb escena LOGO i sense opcions
ContextEscenes() ContextEscenes()
: escena_desti_(Escena::LOGO), : escena_desti_(Escena::LOGO),
opcio_(Opcio::NONE) {} opcio_(Opcio::NONE) {}
// Canviar escena amb opció específica // Canviar escena amb opció específica
void canviar_escena(Escena nova_escena, Opcio opcio = Opcio::NONE) { void canviar_escena(Escena nova_escena, Opcio opcio = Opcio::NONE) {

View File

@@ -218,7 +218,11 @@ auto Director::run() -> int {
// Crear context d'escenes // Crear context d'escenes
ContextEscenes context; ContextEscenes context;
#ifdef _DEBUG
context.canviar_escena(Escena::JOC);
#else
context.canviar_escena(Escena::LOGO); context.canviar_escena(Escena::LOGO);
#endif
// Bucle principal de gestió d'escenes // Bucle principal de gestió d'escenes
while (context.escena_desti() != Escena::EIXIR) { while (context.escena_desti() != Escena::EIXIR) {

View File

@@ -25,12 +25,13 @@ Enemic::Enemic(SDL_Renderer* renderer)
tipus_(TipusEnemic::PENTAGON), tipus_(TipusEnemic::PENTAGON),
tracking_timer_(0.0f), tracking_timer_(0.0f),
ship_position_(nullptr), ship_position_(nullptr),
tracking_strength_(0.5f) { // Default tracking strength tracking_strength_(0.5f), // Default tracking strength
timer_invulnerabilitat_(0.0f) { // Start vulnerable
// [NUEVO] Forma es carrega a inicialitzar() segons el tipus // [NUEVO] Forma es carrega a inicialitzar() segons el tipus
// Constructor no carrega forma per permetre tipus diferents // Constructor no carrega forma per permetre tipus diferents
} }
void Enemic::inicialitzar(TipusEnemic tipus) { void Enemic::inicialitzar(TipusEnemic tipus, const Punt* ship_pos) {
// Guardar tipus // Guardar tipus
tipus_ = tipus; tipus_ = tipus;
@@ -68,7 +69,7 @@ void Enemic::inicialitzar(TipusEnemic tipus) {
std::cerr << "[Enemic] Error: no s'ha pogut carregar " << shape_file << std::endl; std::cerr << "[Enemic] Error: no s'ha pogut carregar " << shape_file << std::endl;
} }
// Posició aleatòria dins de l'àrea de joc // [MODIFIED] Posició aleatòria amb comprovació de seguretat
float min_x, max_x, min_y, max_y; float min_x, max_x, min_y, max_y;
Constants::obtenir_limits_zona_segurs(Defaults::Entities::ENEMY_RADIUS, Constants::obtenir_limits_zona_segurs(Defaults::Entities::ENEMY_RADIUS,
min_x, min_x,
@@ -76,10 +77,38 @@ void Enemic::inicialitzar(TipusEnemic tipus) {
min_y, min_y,
max_y); max_y);
int range_x = static_cast<int>(max_x - min_x); if (ship_pos != nullptr) {
int range_y = static_cast<int>(max_y - min_y); // [NEW] Safe spawn: attempt to find position away from ship
centre_.x = static_cast<float>((std::rand() % range_x) + static_cast<int>(min_x)); bool found_safe_position = false;
centre_.y = static_cast<float>((std::rand() % range_y) + static_cast<int>(min_y));
for (int attempt = 0; attempt < Defaults::Enemies::Spawn::MAX_SPAWN_ATTEMPTS; attempt++) {
float candidate_x, candidate_y;
if (intent_spawn_safe(*ship_pos, candidate_x, candidate_y)) {
centre_.x = candidate_x;
centre_.y = candidate_y;
found_safe_position = true;
break;
}
}
if (!found_safe_position) {
// Fallback: spawn anywhere (user's preference)
int range_x = static_cast<int>(max_x - min_x);
int range_y = static_cast<int>(max_y - min_y);
centre_.x = static_cast<float>((std::rand() % range_x) + static_cast<int>(min_x));
centre_.y = static_cast<float>((std::rand() % range_y) + static_cast<int>(min_y));
std::cout << "[Enemic] Advertència: spawn sense zona segura després de "
<< Defaults::Enemies::Spawn::MAX_SPAWN_ATTEMPTS << " intents" << std::endl;
}
} else {
// [EXISTING] No ship position: spawn anywhere (backward compatibility)
int range_x = static_cast<int>(max_x - min_x);
int range_y = static_cast<int>(max_y - min_y);
centre_.x = static_cast<float>((std::rand() % range_x) + static_cast<int>(min_x));
centre_.y = static_cast<float>((std::rand() % range_y) + static_cast<int>(min_y));
}
// Angle aleatori de moviment // Angle aleatori de moviment
angle_ = (std::rand() % 360) * Constants::PI / 180.0f; angle_ = (std::rand() % 360) * Constants::PI / 180.0f;
@@ -95,12 +124,34 @@ void Enemic::inicialitzar(TipusEnemic tipus) {
animacio_.drotacio_objetivo = drotacio_; animacio_.drotacio_objetivo = drotacio_;
animacio_.drotacio_t = 1.0f; // Start without interpolating animacio_.drotacio_t = 1.0f; // Start without interpolating
// [NEW] Inicialitzar invulnerabilitat
timer_invulnerabilitat_ = Defaults::Enemies::Spawn::INVULNERABILITY_DURATION; // 3.0s
brightness_ = Defaults::Enemies::Spawn::INVULNERABILITY_BRIGHTNESS_START; // 0.3f
// Activar // Activar
esta_ = true; esta_ = true;
} }
void Enemic::actualitzar(float delta_time) { void Enemic::actualitzar(float delta_time) {
if (esta_) { if (esta_) {
// [NEW] Update invulnerability timer and brightness
if (timer_invulnerabilitat_ > 0.0f) {
timer_invulnerabilitat_ -= delta_time;
if (timer_invulnerabilitat_ < 0.0f) {
timer_invulnerabilitat_ = 0.0f;
}
// [NEW] Update brightness with LERP during invulnerability
float t_inv = timer_invulnerabilitat_ / Defaults::Enemies::Spawn::INVULNERABILITY_DURATION;
float t = 1.0f - t_inv; // 0.0 → 1.0
float smooth_t = t * t * (3.0f - 2.0f * t); // smoothstep
constexpr float START = Defaults::Enemies::Spawn::INVULNERABILITY_BRIGHTNESS_START;
constexpr float END = Defaults::Enemies::Spawn::INVULNERABILITY_BRIGHTNESS_END;
brightness_ = START + (END - START) * smooth_t;
}
// Moviment autònom // Moviment autònom
mou(delta_time); mou(delta_time);
@@ -114,8 +165,10 @@ void Enemic::actualitzar(float delta_time) {
void Enemic::dibuixar() const { void Enemic::dibuixar() const {
if (esta_ && forma_) { if (esta_ && forma_) {
// [NUEVO] Usar render_shape amb escala animada // Calculate animated scale (includes invulnerability LERP)
float escala = calcular_escala_actual(); float escala = calcular_escala_actual();
// brightness_ is already updated in actualitzar()
Rendering::render_shape(renderer_, forma_, centre_, rotacio_, escala, true, 1.0f, brightness_); Rendering::render_shape(renderer_, forma_, centre_, rotacio_, escala, true, 1.0f, brightness_);
} }
} }
@@ -388,8 +441,21 @@ void Enemic::actualitzar_rotacio_accelerada(float delta_time) {
float Enemic::calcular_escala_actual() const { float Enemic::calcular_escala_actual() const {
float escala = 1.0f; float escala = 1.0f;
if (animacio_.palpitacio_activa) { // [NEW] Invulnerability LERP prioritza sobre palpitació
// Add pulsating scale variation if (timer_invulnerabilitat_ > 0.0f) {
// Calculate t: 0.0 at spawn → 1.0 at end
float t_inv = timer_invulnerabilitat_ / Defaults::Enemies::Spawn::INVULNERABILITY_DURATION;
float t = 1.0f - t_inv; // 0.0 → 1.0
// Apply smoothstep: t² * (3 - 2t)
float smooth_t = t * t * (3.0f - 2.0f * t);
// LERP scale from 0.0 to 1.0
constexpr float START = Defaults::Enemies::Spawn::INVULNERABILITY_SCALE_START;
constexpr float END = Defaults::Enemies::Spawn::INVULNERABILITY_SCALE_END;
escala = START + (END - START) * smooth_t;
} else if (animacio_.palpitacio_activa) {
// [EXISTING] Palpitació només quan no invulnerable
escala += animacio_.palpitacio_amplitud * std::sin(animacio_.palpitacio_fase); escala += animacio_.palpitacio_amplitud * std::sin(animacio_.palpitacio_fase);
} }
@@ -421,3 +487,28 @@ void Enemic::set_tracking_strength(float strength) {
tracking_strength_ = strength; tracking_strength_ = strength;
} }
} }
// [NEW] Safe spawn helper - checks if position is away from ship
bool Enemic::intent_spawn_safe(const Punt& ship_pos, float& out_x, float& out_y) {
// Generate random position within safe bounds
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);
int range_x = static_cast<int>(max_x - min_x);
int range_y = static_cast<int>(max_y - min_y);
out_x = static_cast<float>((std::rand() % range_x) + static_cast<int>(min_x));
out_y = static_cast<float>((std::rand() % range_y) + static_cast<int>(min_y));
// Check Euclidean distance to ship
float dx = out_x - ship_pos.x;
float dy = out_y - ship_pos.y;
float distancia = std::sqrt(dx * dx + dy * dy);
// Return true if position is safe (>= 36px from ship)
return distancia >= Defaults::Enemies::Spawn::SAFETY_DISTANCE;
}

View File

@@ -40,7 +40,7 @@ class Enemic {
: renderer_(nullptr) {} : renderer_(nullptr) {}
Enemic(SDL_Renderer* renderer); Enemic(SDL_Renderer* renderer);
void inicialitzar(TipusEnemic tipus = TipusEnemic::PENTAGON); void inicialitzar(TipusEnemic tipus = TipusEnemic::PENTAGON, const Punt* ship_pos = nullptr);
void actualitzar(float delta_time); void actualitzar(float delta_time);
void dibuixar() const; void dibuixar() const;
@@ -70,6 +70,10 @@ class Enemic {
void set_rotation(float rot) { drotacio_ = rot; animacio_.drotacio_base = rot; } void set_rotation(float rot) { drotacio_ = rot; animacio_.drotacio_base = rot; }
void set_tracking_strength(float strength); void set_tracking_strength(float strength);
// [NEW] Invulnerability queries
bool es_invulnerable() const { return timer_invulnerabilitat_ > 0.0f; }
float get_temps_invulnerabilitat() const { return timer_invulnerabilitat_; }
private: private:
SDL_Renderer* renderer_; SDL_Renderer* renderer_;
@@ -96,6 +100,9 @@ class Enemic {
const Punt* ship_position_; // Pointer to ship position (for tracking) const Punt* ship_position_; // Pointer to ship position (for tracking)
float tracking_strength_; // For Quadrat: tracking intensity (0.0-1.5), default 0.5 float tracking_strength_; // For Quadrat: tracking intensity (0.0-1.5), default 0.5
// [NEW] Invulnerability state
float timer_invulnerabilitat_; // Countdown timer (seconds), 0.0f = vulnerable
// [EXISTING] Private methods // [EXISTING] Private methods
void mou(float delta_time); void mou(float delta_time);
@@ -107,4 +114,5 @@ class Enemic {
void comportament_quadrat(float delta_time); void comportament_quadrat(float delta_time);
void comportament_molinillo(float delta_time); void comportament_molinillo(float delta_time);
float calcular_escala_actual() const; // Returns scale with palpitation applied float calcular_escala_actual() const; // Returns scale with palpitation applied
bool intent_spawn_safe(const Punt& ship_pos, float& out_x, float& out_y);
}; };

View File

@@ -129,6 +129,9 @@ void EscenaJoc::inicialitzar() {
stage_manager_ = std::make_unique<StageSystem::StageManager>(stage_config_.get()); stage_manager_ = std::make_unique<StageSystem::StageManager>(stage_config_.get());
stage_manager_->inicialitzar(); stage_manager_->inicialitzar();
// [NEW] Set ship position reference for safe spawn
stage_manager_->get_spawn_controller().set_ship_position(&nau_.get_centre());
// Inicialitzar estat de col·lisió // Inicialitzar estat de col·lisió
itocado_ = 0; itocado_ = 0;
@@ -428,7 +431,7 @@ void EscenaJoc::tocado() {
1.0f, // Normal scale 1.0f, // Normal scale
Defaults::Physics::Debris::VELOCITAT_BASE, // 80 px/s Defaults::Physics::Debris::VELOCITAT_BASE, // 80 px/s
nau_.get_brightness(), // Heredar brightness nau_.get_brightness(), // Heredar brightness
vel_nau_80 // Heredar 60% velocitat vel_nau_80 // Heredar 80% velocitat
); );
// Start death timer (non-zero to avoid re-triggering) // Start death timer (non-zero to avoid re-triggering)
@@ -507,6 +510,11 @@ void EscenaJoc::detectar_col·lisions_bales_enemics() {
continue; continue;
} }
// [NEW] Skip collision if enemy is invulnerable
if (enemic.es_invulnerable()) {
continue;
}
const Punt& pos_enemic = enemic.get_centre(); const Punt& pos_enemic = enemic.get_centre();
// Calcular distància quadrada (evita sqrt) // Calcular distància quadrada (evita sqrt)
@@ -564,6 +572,11 @@ void EscenaJoc::detectar_col·lisio_nau_enemics() {
continue; continue;
} }
// [NEW] Skip collision if enemy is invulnerable
if (enemic.es_invulnerable()) {
continue;
}
const Punt& pos_enemic = enemic.get_centre(); const Punt& pos_enemic = enemic.get_centre();
// Calculate squared distance (avoid sqrt) // Calculate squared distance (avoid sqrt)

View File

@@ -9,7 +9,7 @@
namespace StageSystem { namespace StageSystem {
SpawnController::SpawnController() SpawnController::SpawnController()
: config_(nullptr), temps_transcorregut_(0.0f), index_spawn_actual_(0) {} : config_(nullptr), temps_transcorregut_(0.0f), index_spawn_actual_(0), ship_position_(nullptr) {}
void SpawnController::configurar(const ConfigStage* config) { void SpawnController::configurar(const ConfigStage* config) {
config_ = config; config_ = config;
@@ -57,7 +57,7 @@ void SpawnController::actualitzar(float delta_time, std::array<Enemic, 15>& orni
// Find first inactive enemy // Find first inactive enemy
for (auto& enemic : orni_array) { for (auto& enemic : orni_array) {
if (!enemic.esta_actiu()) { if (!enemic.esta_actiu()) {
spawn_enemic(enemic, event.tipus); spawn_enemic(enemic, event.tipus, ship_position_);
event.spawnejat = true; event.spawnejat = true;
index_spawn_actual_++; index_spawn_actual_++;
break; break;
@@ -139,9 +139,9 @@ TipusEnemic SpawnController::seleccionar_tipus_aleatori() const {
} }
} }
void SpawnController::spawn_enemic(Enemic& enemic, TipusEnemic tipus) { void SpawnController::spawn_enemic(Enemic& enemic, TipusEnemic tipus, const Punt* ship_pos) {
// Initialize enemy // Initialize enemy (with safe spawn if ship_pos provided)
enemic.inicialitzar(tipus); enemic.inicialitzar(tipus, ship_pos);
// Apply difficulty multipliers // Apply difficulty multipliers
aplicar_multiplicadors(enemic); aplicar_multiplicadors(enemic);

View File

@@ -38,6 +38,9 @@ class SpawnController {
uint8_t get_enemics_vius(const std::array<Enemic, 15>& orni_array) const; uint8_t get_enemics_vius(const std::array<Enemic, 15>& orni_array) const;
uint8_t get_enemics_spawnejats() const; uint8_t get_enemics_spawnejats() const;
// [NEW] Set ship position reference for safe spawn
void set_ship_position(const Punt* ship_pos) { ship_position_ = ship_pos; }
private: private:
const ConfigStage* config_; // Non-owning pointer to current stage config const ConfigStage* config_; // Non-owning pointer to current stage config
std::vector<SpawnEvent> spawn_queue_; std::vector<SpawnEvent> spawn_queue_;
@@ -47,8 +50,9 @@ class SpawnController {
// Spawn generation // Spawn generation
void generar_spawn_events(); void generar_spawn_events();
TipusEnemic seleccionar_tipus_aleatori() const; TipusEnemic seleccionar_tipus_aleatori() const;
void spawn_enemic(Enemic& enemic, TipusEnemic tipus); void spawn_enemic(Enemic& enemic, TipusEnemic tipus, const Punt* ship_pos = nullptr);
void aplicar_multiplicadors(Enemic& enemic) const; void aplicar_multiplicadors(Enemic& enemic) const;
const Punt* ship_position_; // [NEW] Non-owning pointer to ship position
}; };
} // namespace StageSystem } // namespace StageSystem