diff --git a/source/core/defaults.hpp b/source/core/defaults.hpp index 4465086..bedba23 100644 --- a/source/core/defaults.hpp +++ b/source/core/defaults.hpp @@ -10,6 +10,7 @@ // IWYU pragma: begin_exports #include "core/defaults/audio.hpp" +#include "core/defaults/border.hpp" #include "core/defaults/brightness.hpp" #include "core/defaults/controls.hpp" #include "core/defaults/effects.hpp" diff --git a/source/core/defaults/border.hpp b/source/core/defaults/border.hpp new file mode 100644 index 0000000..2524281 --- /dev/null +++ b/source/core/defaults/border.hpp @@ -0,0 +1,29 @@ +// border.hpp - Configuració del border del playfield (estàtic + reaccions) +// © 2026 JailDesigner + +#pragma once + +namespace Defaults::Border { + + // Desplaçament del border per impactes + constexpr float MAX_DISPLACEMENT_PX = 6.0F; // tope màxim de separació respecte la posició natural + constexpr float DISPLACEMENT_RECOVERY_PER_S = 30.0F; // px/s tornant cap a 0 (ease lineal) + + // Flash al impacte. Intensitat proporcional al desplaçament: + // max displacement → color = FLASH_COLOR pur + // 0 displacement → color = oscil·lador (base verd) + // La línia es dibuixa amb el color resultant del lerp; no hi ha sobreposició. + constexpr bool FLASH_ENABLED = true; + constexpr unsigned char FLASH_COLOR_R = 180; + constexpr unsigned char FLASH_COLOR_G = 255; + constexpr unsigned char FLASH_COLOR_B = 180; + + // Conversió velocitat d'impacte → strength del bump + constexpr float BUMP_VELOCITY_REFERENCE = 120.0F; // px/s donen strength 1.0 + constexpr float BUMP_MIN_VELOCITY = 20.0F; // sota d'açò no genera bump (filtrar fregaments) + + // Bump generat per explosions properes a la paret. + constexpr float EXPLOSION_FALLOFF_PX = 80.0F; // més enllà d'aquesta distància, sense bump + constexpr float EXPLOSION_BASE_STRENGTH = 0.7F; // strength màxim (a 0 px de la paret) + +} // namespace Defaults::Border diff --git a/source/core/graphics/border.cpp b/source/core/graphics/border.cpp new file mode 100644 index 0000000..1c67aac --- /dev/null +++ b/source/core/graphics/border.cpp @@ -0,0 +1,107 @@ +// border.cpp - Implementació del border del playfield +// © 2026 JailDesigner + +#include "core/graphics/border.hpp" + +#include +#include + +#include "core/defaults.hpp" +#include "core/rendering/line_renderer.hpp" + +namespace Graphics { + + Border::Border(Rendering::Renderer* renderer) + : renderer_(renderer) {} + + void Border::update(float delta_time) { + for (auto& side : sides_) { + // Desplaçament decau cap a 0 amb ritme constant (lineal). + const float DEC = Defaults::Border::DISPLACEMENT_RECOVERY_PER_S * delta_time; + side.displacement_px = std::max(0.0F, side.displacement_px - DEC); + } + } + + void Border::bumpAt(Vec2 contact_point, float strength) { + const SDL_FRect& zona = Defaults::Zones::PLAYAREA; + const std::array DISTANCES = { + /* TOP */ std::abs(contact_point.y - zona.y), + /* RIGHT */ std::abs((zona.x + zona.w) - contact_point.x), + /* BOTTOM */ std::abs((zona.y + zona.h) - contact_point.y), + /* LEFT */ std::abs(contact_point.x - zona.x)}; + + int closest_idx = 0; + float closest_dist = DISTANCES[0]; + for (int i = 1; i < SIDE_COUNT; i++) { + if (DISTANCES[i] < closest_dist) { + closest_dist = DISTANCES[i]; + closest_idx = i; + } + } + applyBump(closest_idx, strength); + } + + void Border::applyBump(int side_idx, float strength) { + const float S = std::clamp(strength, 0.0F, 1.0F); + SideState& side = sides_[static_cast(side_idx)]; + side.displacement_px = std::min( + Defaults::Border::MAX_DISPLACEMENT_PX, + side.displacement_px + (S * Defaults::Border::MAX_DISPLACEMENT_PX)); + } + + namespace { + + // Lerp de l'oscil·lador (color base actual) cap a un color "flash" en + // funció de f ∈ [0, 1]. Retorna sempre amb alpha>0 perquè el line_renderer + // l'use directament (sense barrejar amb el global). + auto lerpColor(SDL_Color flash, float f) -> SDL_Color { + const float CLAMPED = std::clamp(f, 0.0F, 1.0F); + const SDL_Color BASE = Rendering::getLineColor(); + const auto LERP_U8 = [&](unsigned char a, unsigned char b) { + const float OUT = (static_cast(a) * (1.0F - CLAMPED)) + (static_cast(b) * CLAMPED); + return static_cast(OUT); + }; + return SDL_Color{ + .r = LERP_U8(BASE.r, flash.r), + .g = LERP_U8(BASE.g, flash.g), + .b = LERP_U8(BASE.b, flash.b), + .a = 255}; + } + + } // namespace + + void Border::draw() const { + const SDL_FRect& zona = Defaults::Zones::PLAYAREA; + const int X1 = static_cast(zona.x); + const int Y1 = static_cast(zona.y); + const int X2 = static_cast(zona.x + zona.w); + const int Y2 = static_cast(zona.y + zona.h); + + const int OFF_TOP = static_cast(sides_[SIDE_TOP].displacement_px); + const int OFF_RIGHT = static_cast(sides_[SIDE_RIGHT].displacement_px); + const int OFF_BOTTOM = static_cast(sides_[SIDE_BOTTOM].displacement_px); + const int OFF_LEFT = static_cast(sides_[SIDE_LEFT].displacement_px); + + // Color per costat: lerp(oscil·lador → flash) en funció del desplaçament. + const SDL_Color FLASH = { + .r = Defaults::Border::FLASH_COLOR_R, + .g = Defaults::Border::FLASH_COLOR_G, + .b = Defaults::Border::FLASH_COLOR_B, + .a = 255}; + const float MAX_D = Defaults::Border::MAX_DISPLACEMENT_PX; + const bool DO_FLASH = Defaults::Border::FLASH_ENABLED; + + const SDL_Color C_TOP = DO_FLASH ? lerpColor(FLASH, sides_[SIDE_TOP].displacement_px / MAX_D) : SDL_Color{}; + const SDL_Color C_RIGHT = DO_FLASH ? lerpColor(FLASH, sides_[SIDE_RIGHT].displacement_px / MAX_D) : SDL_Color{}; + const SDL_Color C_BOTTOM = DO_FLASH ? lerpColor(FLASH, sides_[SIDE_BOTTOM].displacement_px / MAX_D) : SDL_Color{}; + const SDL_Color C_LEFT = DO_FLASH ? lerpColor(FLASH, sides_[SIDE_LEFT].displacement_px / MAX_D) : SDL_Color{}; + + // Una sola línia per costat (brillo 1.0). Si DO_FLASH = false → alpha = 0 → usa + // el color global de l'oscil·lador. + Rendering::linea(renderer_, X1, Y1 - OFF_TOP, X2, Y1 - OFF_TOP, 1.0F, 0.0F, C_TOP); + Rendering::linea(renderer_, X2 + OFF_RIGHT, Y1, X2 + OFF_RIGHT, Y2, 1.0F, 0.0F, C_RIGHT); + Rendering::linea(renderer_, X1, Y2 + OFF_BOTTOM, X2, Y2 + OFF_BOTTOM, 1.0F, 0.0F, C_BOTTOM); + Rendering::linea(renderer_, X1 - OFF_LEFT, Y1, X1 - OFF_LEFT, Y2, 1.0F, 0.0F, C_LEFT); + } + +} // namespace Graphics diff --git a/source/core/graphics/border.hpp b/source/core/graphics/border.hpp new file mode 100644 index 0000000..935579c --- /dev/null +++ b/source/core/graphics/border.hpp @@ -0,0 +1,52 @@ +// border.hpp - Border del playfield amb estat (desplaçaments i flash per impactes) +// © 2026 JailDesigner +// +// Substitueix el `drawMargins()` inline de GameScene. Cada un dels 4 costats +// té estat propi (desplaçament perpendicular outward + intensitat de flash blanc) +// que decau cap a 0. Esdeveniments externs (col·lisions contra els bounds, etc.) +// criden `bumpAt()` per generar reaccions. + +#pragma once + +#include +#include + +#include "core/rendering/render_context.hpp" +#include "core/types.hpp" + +namespace Graphics { + + class Border { + public: + explicit Border(Rendering::Renderer* renderer); + + // Decae desplaçaments i flash cap a 0. + void update(float delta_time); + + // Dibuixa els 4 costats amb el seu estat actual. + void draw() const; + + // Aplica un bump al costat més proper al punt de contacte. + // strength ∈ [0, 1]; valors superiors es retallen. + void bumpAt(Vec2 contact_point, float strength); + + private: + enum : std::uint8_t { + SIDE_TOP = 0, + SIDE_RIGHT = 1, + SIDE_BOTTOM = 2, + SIDE_LEFT = 3, + SIDE_COUNT = 4 + }; + + struct SideState { + float displacement_px{0.0F}; // outward (sempre ≥ 0); el flash es deriva d'aquí + }; + + void applyBump(int side_idx, float strength); + + Rendering::Renderer* renderer_; + std::array sides_{}; + }; + +} // namespace Graphics diff --git a/source/core/physics/physics_world.cpp b/source/core/physics/physics_world.cpp index 7a8d733..7eb5654 100644 --- a/source/core/physics/physics_world.cpp +++ b/source/core/physics/physics_world.cpp @@ -66,6 +66,51 @@ namespace Physics { // Rebote contra los 4 bordes del rectángulo bounds_. // Refleja la componente normal de la velocidad por la restitución. + namespace { + + // Resol col·lisió contra un parell paret-axis (mín i màx). + // pos/vel són les referències al component de l'axis actiu (x o y); + // contact_perp és la coordenada del component perpendicular (la fixa de la + // paret a l'eix actiu — usada per al contact_point). + void resolveAxis(float& pos, + float& vel, + float radius, + float min_val, + float max_val, + float restitution, + bool axis_is_x, + float contact_perp, + const PhysicsWorld::BoundsHitCallback& callback) { + // Cara mínima (esquerra o superior) + if (pos - radius < min_val) { + pos = min_val + radius; + if (vel < 0.0F) { + if (callback) { + const Vec2 CONTACT = axis_is_x + ? Vec2{.x = min_val, .y = contact_perp} + : Vec2{.x = contact_perp, .y = min_val}; + callback(BoundsHit{.contact_point = CONTACT, .impact_speed = -vel}); + } + vel = -vel * restitution; + } + } + // Cara màxima (dreta o inferior) + if (pos + radius > max_val) { + pos = max_val - radius; + if (vel > 0.0F) { + if (callback) { + const Vec2 CONTACT = axis_is_x + ? Vec2{.x = max_val, .y = contact_perp} + : Vec2{.x = contact_perp, .y = max_val}; + callback(BoundsHit{.contact_point = CONTACT, .impact_speed = vel}); + } + vel = -vel * restitution; + } + } + } + + } // namespace + void PhysicsWorld::resolveBoundsCollisions() { const float MIN_X = bounds_.x; const float MAX_X = bounds_.x + bounds_.w; @@ -76,36 +121,10 @@ namespace Physics { if (body == nullptr || body->isStatic()) { continue; } - const float R = body->radius; - - // Pared izquierda - if (body->position.x - R < MIN_X) { - body->position.x = MIN_X + R; - if (body->velocity.x < 0.0F) { - body->velocity.x = -body->velocity.x * body->restitution; - } - } - // Pared derecha - if (body->position.x + R > MAX_X) { - body->position.x = MAX_X - R; - if (body->velocity.x > 0.0F) { - body->velocity.x = -body->velocity.x * body->restitution; - } - } - // Pared superior - if (body->position.y - R < MIN_Y) { - body->position.y = MIN_Y + R; - if (body->velocity.y < 0.0F) { - body->velocity.y = -body->velocity.y * body->restitution; - } - } - // Pared inferior - if (body->position.y + R > MAX_Y) { - body->position.y = MAX_Y - R; - if (body->velocity.y > 0.0F) { - body->velocity.y = -body->velocity.y * body->restitution; - } - } + // Eix X (esquerra/dreta): contact_perp = y actual del cos. + resolveAxis(body->position.x, body->velocity.x, body->radius, MIN_X, MAX_X, body->restitution, /*axis_is_x=*/true, body->position.y, bounds_hit_callback_); + // Eix Y (superior/inferior): contact_perp = x actual (ja clampejada en l'eix X). + resolveAxis(body->position.y, body->velocity.y, body->radius, MIN_Y, MAX_Y, body->restitution, /*axis_is_x=*/false, body->position.x, bounds_hit_callback_); } } diff --git a/source/core/physics/physics_world.hpp b/source/core/physics/physics_world.hpp index 30ac588..3a72dfe 100644 --- a/source/core/physics/physics_world.hpp +++ b/source/core/physics/physics_world.hpp @@ -13,14 +13,27 @@ #include +#include +#include #include +#include "core/types.hpp" + namespace Physics { -struct RigidBody; + struct RigidBody; + + // Notificació d'impacte contra un dels bounds del PLAYAREA. impact_speed és + // la magnitud de la component de velocity perpendicular a la paret (≥ 0). + struct BoundsHit { + Vec2 contact_point; + float impact_speed; + }; + + class PhysicsWorld { + public: + using BoundsHitCallback = std::function; -class PhysicsWorld { - public: PhysicsWorld() = default; // Añade un cuerpo al mundo (no toma ownership). @@ -41,6 +54,13 @@ class PhysicsWorld { } void clearBounds() { has_bounds_ = false; } + // Callback opcional invocat cada vegada que un cos impacta contra + // un dels bounds del PLAYAREA. S'invoca abans de la reflexió de + // velocity perquè impact_speed sigui la magnitud entrant. + void setBoundsHitCallback(BoundsHitCallback callback) { + bounds_hit_callback_ = std::move(callback); + } + // Avanza la simulación dt segundos: // 1. Integra cada cuerpo (semi-implicit Euler + damping) // 2. Resuelve colisiones contra los bounds (si configurados) @@ -51,10 +71,11 @@ class PhysicsWorld { [[nodiscard]] auto getBodyCount() const -> std::size_t { return bodies_.size(); } [[nodiscard]] auto getBodies() const -> const std::vector& { return bodies_; } - private: + private: std::vector bodies_; SDL_FRect bounds_{0.0F, 0.0F, 0.0F, 0.0F}; bool has_bounds_{false}; + BoundsHitCallback bounds_hit_callback_; void integrate(float dt); void resolveBoundsCollisions(); @@ -62,6 +83,6 @@ class PhysicsWorld { // Resol un únic parell (a, b): correcció posicional + impulso elàstic. // Estàtic: només toca els dos cossos rebuts, no consulta el world. static void resolveBodyPair(RigidBody& a, RigidBody& b); -}; + }; } // namespace Physics diff --git a/source/core/rendering/line_renderer.cpp b/source/core/rendering/line_renderer.cpp index 1f079b3..9707d11 100644 --- a/source/core/rendering/line_renderer.cpp +++ b/source/core/rendering/line_renderer.cpp @@ -47,6 +47,8 @@ namespace Rendering { void setLineColor(SDL_Color color) { g_current_line_color = color; } + auto getLineColor() -> SDL_Color { return g_current_line_color; } + void setLineThickness(float thickness) { if (thickness > 0.0F) { g_current_line_thickness = thickness; diff --git a/source/core/rendering/line_renderer.hpp b/source/core/rendering/line_renderer.hpp index 75f70e9..ce5fa1f 100644 --- a/source/core/rendering/line_renderer.hpp +++ b/source/core/rendering/line_renderer.hpp @@ -9,27 +9,32 @@ #pragma once -#include "core/rendering/render_context.hpp" #include +#include "core/rendering/render_context.hpp" + namespace Rendering { -// Dibuja una línea entre dos puntos en coordenadas lógicas (1280×720). -// brightness: factor de brillo (0.0..1.0, default 1.0 = brillo máximo). -// thickness: grosor en píxeles lógicos. Si <= 0 usa g_current_line_thickness. -// color: si alpha==0, se usa el color global del oscilador; si alpha>0 se -// usa este color directo (paleta semántica por entidad). -void linea(Renderer* renderer, - int x1, int y1, int x2, int y2, - float brightness = 1.0F, - float thickness = 0.0F, - SDL_Color color = {0, 0, 0, 0}); + // Dibuja una línea entre dos puntos en coordenadas lógicas (1280×720). + // brightness: factor de brillo (0.0..1.0, default 1.0 = brillo máximo). + // thickness: grosor en píxeles lógicos. Si <= 0 usa g_current_line_thickness. + // color: si alpha==0, se usa el color global del oscilador; si alpha>0 se + // usa este color directo (paleta semántica por entidad). + void linea(Renderer* renderer, + int x1, + int y1, + int x2, + int y2, + float brightness = 1.0F, + float thickness = 0.0F, + SDL_Color color = {0, 0, 0, 0}); -// Color global de las líneas (lo actualiza ColorOscillator vía SDLManager). -void setLineColor(SDL_Color color); + // Color global de las líneas (lo actualiza ColorOscillator vía SDLManager). + void setLineColor(SDL_Color color); + [[nodiscard]] auto getLineColor() -> SDL_Color; -// Grosor global por defecto (en píxeles lógicos). Default: 1.5. -void setLineThickness(float thickness); -[[nodiscard]] auto getLineThickness() -> float; + // Grosor global por defecto (en píxeles lógicos). Default: 1.5. + void setLineThickness(float thickness); + [[nodiscard]] auto getLineThickness() -> float; } // namespace Rendering diff --git a/source/game/effects/debris_manager.cpp b/source/game/effects/debris_manager.cpp index 9b8cc3f..12f4cbc 100644 --- a/source/game/effects/debris_manager.cpp +++ b/source/game/effects/debris_manager.cpp @@ -65,6 +65,11 @@ namespace Effects { // Reproducir sonido de explosión Audio::get()->playSound(sound, Audio::Group::GAME); + // Notificar als subscriptors (border, playfield, etc.). + if (explosion_callback_) { + explosion_callback_(centro); + } + const Vec2& shape_centre = shape->getCenter(); // Multiplier: cada segment s'emet N vegades amb direccions aleatòries diff --git a/source/game/effects/debris_manager.hpp b/source/game/effects/debris_manager.hpp index 6681deb..f5b9c97 100644 --- a/source/game/effects/debris_manager.hpp +++ b/source/game/effects/debris_manager.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -22,8 +23,17 @@ namespace Effects { // Manté un pool de objectes Debris i gestiona el seu cicle de vida class DebrisManager { public: + // Notificació opcional cada vegada que es genera una explosió. El + // consumidor pot usar-la per fer reaccionar elements del fons (border + // bumps, pulse del playfield, etc.). + using ExplosionCallback = std::function; + explicit DebrisManager(Rendering::Renderer* renderer); + void setExplosionCallback(ExplosionCallback callback) { + explosion_callback_ = std::move(callback); + } + // Crear explosión a partir de una shape // - shape: shape vectorial a explode // - centro: posición del centro de l'objecte @@ -66,6 +76,7 @@ namespace Effects { private: Rendering::Renderer* renderer_; + ExplosionCallback explosion_callback_; // Pool de fragments (màxim concurrent) // Pentàgon 5 línies × 15 enemics × multiplier 3 = 225 trossos només d'enemics. diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index b2a3be3..3a8e2f5 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -11,7 +11,6 @@ #include "core/audio/audio.hpp" #include "core/input/input.hpp" -#include "core/rendering/line_renderer.hpp" #include "core/system/scene_context.hpp" #include "game/stage_system/stage_loader.hpp" #include "game/systems/collision_system.hpp" @@ -31,7 +30,8 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context) floating_score_manager_(sdl.getRenderer()), trail_manager_(sdl.getRenderer()), text_(sdl.getRenderer()), - playfield_(sdl.getRenderer()) { + playfield_(sdl.getRenderer()), + border_(sdl.getRenderer()) { // Recuperar configuración de match des del context match_config_ = context_.getMatchConfig(); @@ -64,6 +64,32 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context) physics_world_.clear(); physics_world_.setBounds(Defaults::Zones::PLAYAREA); + // Connectar els impactes contra les parets al border (bump + flash). + physics_world_.setBoundsHitCallback([this](const Physics::BoundsHit& hit) { + if (hit.impact_speed < Defaults::Border::BUMP_MIN_VELOCITY) { + return; + } + const float STRENGTH = std::min( + 1.0F, + hit.impact_speed / Defaults::Border::BUMP_VELOCITY_REFERENCE); + border_.bumpAt(hit.contact_point, STRENGTH); + }); + + // Explosions properes a una paret també generen bump (falloff lineal amb la distància). + debris_manager_.setExplosionCallback([this](Vec2 center) { + const SDL_FRect& zona = Defaults::Zones::PLAYAREA; + const float DIST_LEFT = std::abs(center.x - zona.x); + const float DIST_RIGHT = std::abs((zona.x + zona.w) - center.x); + const float DIST_TOP = std::abs(center.y - zona.y); + const float DIST_BOTTOM = std::abs((zona.y + zona.h) - center.y); + const float MIN_DIST = std::min({DIST_LEFT, DIST_RIGHT, DIST_TOP, DIST_BOTTOM}); + if (MIN_DIST > Defaults::Border::EXPLOSION_FALLOFF_PX) { + return; + } + const float FALLOFF = 1.0F - (MIN_DIST / Defaults::Border::EXPLOSION_FALLOFF_PX); + border_.bumpAt(center, Defaults::Border::EXPLOSION_BASE_STRENGTH * FALLOFF); + }); + // Load stage configuration stage_config_ = StageSystem::StageLoader::load("data/stages/stages.yaml"); if (!stage_config_) { @@ -183,6 +209,7 @@ void GameScene::stepPhysics(float delta_time) { } trail_manager_.update(delta_time, ships_); playfield_.update(delta_time); + border_.update(delta_time); } void GameScene::stepShootingInput() { @@ -519,7 +546,7 @@ void GameScene::drawActiveShipsAlive() const { } void GameScene::drawContinueState() { - drawMargins(); + border_.draw(); drawEnemies(); drawBullets(); debris_manager_.draw(); @@ -530,7 +557,7 @@ void GameScene::drawContinueState() { } void GameScene::drawGameOverState() { - drawMargins(); + border_.draw(); drawEnemies(); drawBullets(); debris_manager_.draw(); @@ -601,7 +628,7 @@ void GameScene::drawInitHudState() { void GameScene::drawLevelStartState() { playfield_.draw(); - drawMargins(); + border_.draw(); trail_manager_.draw(); drawActiveShipsAlive(); drawBullets(); @@ -614,7 +641,7 @@ void GameScene::drawLevelStartState() { void GameScene::drawPlayingState() { playfield_.draw(); - drawMargins(); + border_.draw(); trail_manager_.draw(); drawActiveShipsAlive(); drawEnemies(); @@ -627,7 +654,7 @@ void GameScene::drawPlayingState() { void GameScene::drawLevelCompletedState() { playfield_.draw(); - drawMargins(); + border_.draw(); trail_manager_.draw(); drawActiveShipsAlive(); drawBullets(); @@ -679,23 +706,6 @@ void GameScene::tocado(uint8_t player_id) { // Phase 3 is handled in update() when hit_timer_per_player_ >= DEATH_DURATION } -void GameScene::drawMargins() const { - // Dibuixar rectangle de la zona de juego - const SDL_FRect& zona = Defaults::Zones::PLAYAREA; - - // Coordenades dels cantons - int x1 = static_cast(zona.x); - int y1 = static_cast(zona.y); - int x2 = static_cast(zona.x + zona.w); - int y2 = static_cast(zona.y + zona.h); - - // 4 línies per formar el rectangle - Rendering::linea(sdl_.getRenderer(), x1, y1, x2, y1); // Top - Rendering::linea(sdl_.getRenderer(), x1, y2, x2, y2); // Bottom - Rendering::linea(sdl_.getRenderer(), x1, y1, x1, y2); // Left - Rendering::linea(sdl_.getRenderer(), x2, y1, x2, y2); // Right -} - void GameScene::drawScoreboard() { // Construir text del marcador std::string text = buildScoreboard(); diff --git a/source/game/scenes/game_scene.hpp b/source/game/scenes/game_scene.hpp index e2f3164..9083f37 100644 --- a/source/game/scenes/game_scene.hpp +++ b/source/game/scenes/game_scene.hpp @@ -8,6 +8,7 @@ #include #include +#include "core/graphics/border.hpp" #include "core/graphics/playfield.hpp" #include "core/graphics/vector_text.hpp" #include "core/physics/physics_world.hpp" @@ -84,6 +85,9 @@ class GameScene final : public Scene { // Fons del playfield (graella + futures capes) Graphics::Playfield playfield_; + // Border del playfield (4 línies amb desplaçaments i flash per impactes) + Graphics::Border border_; + // [NEW] Stage system std::unique_ptr stage_config_; std::unique_ptr stage_manager_; @@ -93,7 +97,6 @@ class GameScene final : public Scene { // Funciones privades void tocado(uint8_t player_id); - void drawMargins() const; // Dibuixar vores de la zona de juego void drawScoreboard(); // Dibuixar marcador de puntuación void fireBullet(uint8_t player_id); // Shoot bullet from player [[nodiscard]] auto getSpawnPoint(uint8_t player_id) const -> Vec2; // Get spawn position for player