From a6b2b0293010289ae985b5d21605f6827a33d141 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Mon, 11 May 2026 08:43:38 +0200 Subject: [PATCH] feat(hazard): afegeix estat SPAWN a DynamicHazard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DynamicHazard guanya un mecanisme de SPAWN amb dues fases: SPAWNING (visible, sense moviment ni dany, comptador en pausa) i ACTIVE (lògica normal). `enterSpawn(duration_s)` deixa l'entitat congelada esperant indefinidament; `triggerSpawnExit()` arma el comptador i, en arribar a zero, transita a ACTIVE. Per defecte les noves DynamicHazard arrenquen en ACTIVE — backward-compat amb subclasses existents (RollingRock) que encara no usen el mecanisme. EntityManager::checkPlayerCollisions salta DynamicHazards en SPAWN: no disparen onPlayerContact, així no fan dany ni triggers durant la fase. Aquest commit només afegeix la infraestructura — cap subclasse l'usa encara, comportament del joc inalterat. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../entities/entity_manager_loadlevel.cpp | 5 ++ source/game/entities/hazard.hpp | 46 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/source/game/entities/entity_manager_loadlevel.cpp b/source/game/entities/entity_manager_loadlevel.cpp index 091a5c8..be45798 100644 --- a/source/game/entities/entity_manager_loadlevel.cpp +++ b/source/game/entities/entity_manager_loadlevel.cpp @@ -18,6 +18,7 @@ #include "game/entities/entity_factory.hpp" #include "game/entities/entity_loader.hpp" #include "game/entities/entity_manager.hpp" +#include "game/entities/hazard.hpp" #include "game/entities/player.hpp" #include "game/map/map.hpp" #include "utils/defaults.hpp" @@ -68,6 +69,10 @@ void EntityManager::checkPlayerCollisions(const std::vector& players) { // mai no es disparaven) i obrim la porta a hazards no-físics // com `Hole`. if (!entity->collides() && !entity->triggersOnOverlap()) { continue; } + // DynamicHazard en SPAWN: visible però "no juga" — no dispara + // onPlayerContact, així no fa dany ni triggers durant la fase + // inicial de ROOM_INTRO (o futurs spawns en runtime). + if (const auto* dh = dynamic_cast(entity.get()); dh != nullptr && dh->isSpawning()) { continue; } const auto BOX = entity->getCollisionRect(); const int E_HEIGHT = entity->getHeight(); for (auto* p : players) { diff --git a/source/game/entities/hazard.hpp b/source/game/entities/hazard.hpp index 79c5cce..641dd4c 100644 --- a/source/game/entities/hazard.hpp +++ b/source/game/entities/hazard.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include "game/entities/entity.hpp" // Base d'entitats perilloses: al contacte amb el jugador, apliquen dany. @@ -56,8 +58,20 @@ class StaticHazard : public Hazard { // Perill dinàmic: es mou. Velocitat expressada en píxels/segon i una // direcció (vec2). Les subclasses decideixen la política de moviment // (IA corridor_wanderer, línia recta, etc.) sobreescribint `step(dt)`. +// +// Estat SPAWN: tota DynamicHazard pot estar en SPAWNING (visible, sense +// moviment ni dany) fins que algú extern arme el countdown amb +// `triggerSpawnExit()`. Quan el comptador arriba a zero passa a ACTIVE i +// recupera la lògica normal (`step()` per frame). Per defecte arrenca en +// ACTIVE — backward-compat amb subclasses que encara no usen el mecanisme. +// El filtre central de col·lisió a `EntityManager::checkPlayerCollisions` +// salta les entitats en SPAWN: per això el contacte no fa dany ni dispara +// triggers durant aquesta fase. class DynamicHazard : public Hazard { public: + enum class SpawnState : std::uint8_t { SPAWNING, + ACTIVE }; + DynamicHazard() = default; void setSpeed(float px_per_s) { speed_ = px_per_s; } @@ -70,6 +84,21 @@ class DynamicHazard : public Hazard { [[nodiscard]] auto getDirX() const -> float { return dir_x_; } [[nodiscard]] auto getDirY() const -> float { return dir_y_; } + // Entra en SPAWNING amb una duració determinada. El comptador NO + // descompta fins que algú crida `triggerSpawnExit()` — l'entitat queda + // visible i quieta esperant indefinidament. + void enterSpawn(float duration_s) { + spawn_state_ = SpawnState::SPAWNING; + spawn_duration_s_ = duration_s; + spawn_remaining_s_ = duration_s; + spawn_exit_armed_ = false; + } + // Arma el comptador del SPAWN. A partir d'aquí, cada `update(dt)` + // descompta `dt` fins arribar a 0 i transitar a ACTIVE. + void triggerSpawnExit() { spawn_exit_armed_ = true; } + [[nodiscard]] auto isSpawning() const -> bool { return spawn_state_ == SpawnState::SPAWNING; } + [[nodiscard]] auto getSpawnDurationS() const -> float { return spawn_duration_s_; } + // Política de moviment per frame. La base desplaça el pixel_rect_ // proporcionalment a velocitat i direcció; les subclasses amb IA la // sobreescriuen per triar nova direcció a les interseccions o rebotar. @@ -79,6 +108,19 @@ class DynamicHazard : public Hazard { } void update(float delta_time) override { + if (spawn_state_ == SpawnState::SPAWNING) { + // Sense `step()`: l'entitat es queda quieta al pixel_rect_ + // inicial. `Entity::update` tic d'animació igual: si en algun + // moment posem un GIF de spawn aquí ja anirà. + Entity::update(delta_time); + if (spawn_exit_armed_) { + spawn_remaining_s_ -= delta_time; + if (spawn_remaining_s_ <= 0.0F) { + spawn_state_ = SpawnState::ACTIVE; + } + } + return; + } Entity::update(delta_time); step(delta_time); } @@ -87,4 +129,8 @@ class DynamicHazard : public Hazard { float speed_{0.0F}; float dir_x_{0.0F}; float dir_y_{0.0F}; + SpawnState spawn_state_{SpawnState::ACTIVE}; + float spawn_duration_s_{0.0F}; + float spawn_remaining_s_{0.0F}; + bool spawn_exit_armed_{false}; };