From f30b195778af044bc151d145322c30dc7aacf995 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 20:16:44 +0200 Subject: [PATCH 1/4] feat(grid): graella verda fosca de fons al playfield (16x8) --- source/core/defaults.hpp | 1 + source/core/defaults/grid.hpp | 12 ++++++++ source/core/graphics/playfield_grid.cpp | 37 +++++++++++++++++++++++++ source/core/graphics/playfield_grid.hpp | 23 +++++++++++++++ source/game/scenes/game_scene.cpp | 6 +++- source/game/scenes/game_scene.hpp | 4 +++ 6 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 source/core/defaults/grid.hpp create mode 100644 source/core/graphics/playfield_grid.cpp create mode 100644 source/core/graphics/playfield_grid.hpp diff --git a/source/core/defaults.hpp b/source/core/defaults.hpp index 3353f94..8f284fb 100644 --- a/source/core/defaults.hpp +++ b/source/core/defaults.hpp @@ -17,6 +17,7 @@ #include "core/defaults/entities.hpp" #include "core/defaults/floating_score.hpp" #include "core/defaults/game.hpp" +#include "core/defaults/grid.hpp" #include "core/defaults/hud.hpp" #include "core/defaults/math.hpp" #include "core/defaults/notifier.hpp" diff --git a/source/core/defaults/grid.hpp b/source/core/defaults/grid.hpp new file mode 100644 index 0000000..4c3097e --- /dev/null +++ b/source/core/defaults/grid.hpp @@ -0,0 +1,12 @@ +// grid.hpp - Configuració de la grilla de fons del playfield +// © 2026 JailDesigner + +#pragma once + +namespace Defaults::Grid { + + constexpr int COLUMNS = 16; // cel·les omplen tota la PLAYAREA: cell_w = w/16 + constexpr int ROWS = 8; // cel·les omplen tota la PLAYAREA: cell_h = h/8 (≈2% més altes que amples) + constexpr float BRIGHTNESS = 0.20F; // intensitat respecte al color global (border = 1.0) + +} // namespace Defaults::Grid diff --git a/source/core/graphics/playfield_grid.cpp b/source/core/graphics/playfield_grid.cpp new file mode 100644 index 0000000..cfe5c4f --- /dev/null +++ b/source/core/graphics/playfield_grid.cpp @@ -0,0 +1,37 @@ +// playfield_grid.cpp - Implementació de la grilla de fons +// © 2026 JailDesigner + +#include "core/graphics/playfield_grid.hpp" + +#include "core/defaults.hpp" +#include "core/rendering/line_renderer.hpp" + +namespace Graphics { + + PlayfieldGrid::PlayfieldGrid(Rendering::Renderer* renderer) + : renderer_(renderer) {} + + void PlayfieldGrid::draw() const { + const SDL_FRect& zona = Defaults::Zones::PLAYAREA; + const float CELL_W = zona.w / static_cast(Defaults::Grid::COLUMNS); + const float CELL_H = zona.h / static_cast(Defaults::Grid::ROWS); + + 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); + + // Línies verticals interiors (i=1..COLUMNS-1: no repetim el border) + for (int i = 1; i < Defaults::Grid::COLUMNS; i++) { + const int X = static_cast(zona.x + (static_cast(i) * CELL_W)); + Rendering::linea(renderer_, X, Y1, X, Y2, Defaults::Grid::BRIGHTNESS); + } + + // Línies horitzontals interiors (j=1..ROWS-1: no repetim el border) + for (int j = 1; j < Defaults::Grid::ROWS; j++) { + const int Y = static_cast(zona.y + (static_cast(j) * CELL_H)); + Rendering::linea(renderer_, X1, Y, X2, Y, Defaults::Grid::BRIGHTNESS); + } + } + +} // namespace Graphics diff --git a/source/core/graphics/playfield_grid.hpp b/source/core/graphics/playfield_grid.hpp new file mode 100644 index 0000000..fa2a04f --- /dev/null +++ b/source/core/graphics/playfield_grid.hpp @@ -0,0 +1,23 @@ +// playfield_grid.hpp - Grilla de fons del playfield (línies verdes fosques) +// © 2026 JailDesigner + +#pragma once + +#include "core/rendering/render_context.hpp" + +namespace Graphics { + + // Pinta una graella interior dins de Defaults::Zones::PLAYAREA. + // Cel·les quadrades; el nombre de columnes és fix (Defaults::Grid::COLUMNS), + // les files es deriven del que cap dins de l'altura. + // Color: oscil·lador global (mateix to que el border); brightness fixa al 25%. + class PlayfieldGrid { + public: + explicit PlayfieldGrid(Rendering::Renderer* renderer); + void draw() const; + + private: + Rendering::Renderer* renderer_; + }; + +} // namespace Graphics diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index e6f9417..26d0a6b 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -30,7 +30,8 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context) firework_manager_(sdl.getRenderer()), floating_score_manager_(sdl.getRenderer()), trail_manager_(sdl.getRenderer()), - text_(sdl.getRenderer()) { + text_(sdl.getRenderer()), + playfield_grid_(sdl.getRenderer()) { // Recuperar configuración de match des del context match_config_ = context_.getMatchConfig(); @@ -595,6 +596,7 @@ void GameScene::drawInitHudState() { } void GameScene::drawLevelStartState() { + playfield_grid_.draw(); drawMargins(); trail_manager_.draw(); drawActiveShipsAlive(); @@ -607,6 +609,7 @@ void GameScene::drawLevelStartState() { } void GameScene::drawPlayingState() { + playfield_grid_.draw(); drawMargins(); trail_manager_.draw(); drawActiveShipsAlive(); @@ -619,6 +622,7 @@ void GameScene::drawPlayingState() { } void GameScene::drawLevelCompletedState() { + playfield_grid_.draw(); drawMargins(); trail_manager_.draw(); drawActiveShipsAlive(); diff --git a/source/game/scenes/game_scene.hpp b/source/game/scenes/game_scene.hpp index 6b99b5b..4774e88 100644 --- a/source/game/scenes/game_scene.hpp +++ b/source/game/scenes/game_scene.hpp @@ -8,6 +8,7 @@ #include #include +#include "core/graphics/playfield_grid.hpp" #include "core/graphics/vector_text.hpp" #include "core/physics/physics_world.hpp" #include "core/rendering/sdl_manager.hpp" @@ -80,6 +81,9 @@ class GameScene final : public Scene { // Text vectorial Graphics::VectorText text_; + // Grilla de fons del playfield + Graphics::PlayfieldGrid playfield_grid_; + // [NEW] Stage system std::unique_ptr stage_config_; std::unique_ptr stage_manager_; From dc389037f801bb244e19033be6751d65f5ee005a Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 20:21:46 +0200 Subject: [PATCH 2/4] feat(grid): sub-graella amb 5 subdivisions i ajust de brillos --- source/core/defaults/grid.hpp | 5 ++++- source/core/graphics/playfield_grid.cpp | 25 ++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/source/core/defaults/grid.hpp b/source/core/defaults/grid.hpp index 4c3097e..0b2a8cd 100644 --- a/source/core/defaults/grid.hpp +++ b/source/core/defaults/grid.hpp @@ -7,6 +7,9 @@ namespace Defaults::Grid { constexpr int COLUMNS = 16; // cel·les omplen tota la PLAYAREA: cell_w = w/16 constexpr int ROWS = 8; // cel·les omplen tota la PLAYAREA: cell_h = h/8 (≈2% més altes que amples) - constexpr float BRIGHTNESS = 0.20F; // intensitat respecte al color global (border = 1.0) + constexpr float BRIGHTNESS = 0.15F; // intensitat respecte al color global (border = 1.0) + + constexpr int SUBDIVISIONS = 5; // cada cel·la principal es divideix en N subcel·les + constexpr float SUBGRID_BRIGHTNESS = 0.05F; // intensitat de la sub-graella } // namespace Defaults::Grid diff --git a/source/core/graphics/playfield_grid.cpp b/source/core/graphics/playfield_grid.cpp index cfe5c4f..abdb99a 100644 --- a/source/core/graphics/playfield_grid.cpp +++ b/source/core/graphics/playfield_grid.cpp @@ -15,19 +15,38 @@ namespace Graphics { const SDL_FRect& zona = Defaults::Zones::PLAYAREA; const float CELL_W = zona.w / static_cast(Defaults::Grid::COLUMNS); const float CELL_H = zona.h / static_cast(Defaults::Grid::ROWS); + const float SUB_W = CELL_W / static_cast(Defaults::Grid::SUBDIVISIONS); + const float SUB_H = CELL_H / static_cast(Defaults::Grid::SUBDIVISIONS); + const int SUB_VERTS = Defaults::Grid::COLUMNS * Defaults::Grid::SUBDIVISIONS; + const int SUB_HORIZ = Defaults::Grid::ROWS * Defaults::Grid::SUBDIVISIONS; 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); - // Línies verticals interiors (i=1..COLUMNS-1: no repetim el border) + // PRIMER: sub-graella (queda per dessota — pintem primer). + // Saltem les posicions que coincideixen amb la graella principal (i % SUBDIVISIONS == 0). + for (int i = 1; i < SUB_VERTS; i++) { + if (i % Defaults::Grid::SUBDIVISIONS == 0) { + continue; + } + const int X = static_cast(zona.x + (static_cast(i) * SUB_W)); + Rendering::linea(renderer_, X, Y1, X, Y2, Defaults::Grid::SUBGRID_BRIGHTNESS); + } + for (int j = 1; j < SUB_HORIZ; j++) { + if (j % Defaults::Grid::SUBDIVISIONS == 0) { + continue; + } + const int Y = static_cast(zona.y + (static_cast(j) * SUB_H)); + Rendering::linea(renderer_, X1, Y, X2, Y, Defaults::Grid::SUBGRID_BRIGHTNESS); + } + + // SEGON: graella principal (línies interiors, no repetim el border). for (int i = 1; i < Defaults::Grid::COLUMNS; i++) { const int X = static_cast(zona.x + (static_cast(i) * CELL_W)); Rendering::linea(renderer_, X, Y1, X, Y2, Defaults::Grid::BRIGHTNESS); } - - // Línies horitzontals interiors (j=1..ROWS-1: no repetim el border) for (int j = 1; j < Defaults::Grid::ROWS; j++) { const int Y = static_cast(zona.y + (static_cast(j) * CELL_H)); Rendering::linea(renderer_, X1, Y, X2, Y, Defaults::Grid::BRIGHTNESS); From 07985228b2d082ecbdf5571f66ab6779d705ccdd Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 20:44:17 +0200 Subject: [PATCH 3/4] =?UTF-8?q?feat(playfield):=20refactor=20a=20Playfield?= =?UTF-8?q?=20amb=20animaci=C3=B3=20de=20creaci=C3=B3=20durant=20l'INIT=5F?= =?UTF-8?q?HUD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/core/defaults.hpp | 2 +- source/core/defaults/grid.hpp | 15 ---- source/core/defaults/playfield.hpp | 24 +++++ source/core/graphics/playfield.cpp | 112 ++++++++++++++++++++++++ source/core/graphics/playfield.hpp | 49 +++++++++++ source/core/graphics/playfield_grid.cpp | 56 ------------ source/core/graphics/playfield_grid.hpp | 23 ----- source/game/scenes/game_scene.cpp | 12 ++- source/game/scenes/game_scene.hpp | 6 +- 9 files changed, 197 insertions(+), 102 deletions(-) delete mode 100644 source/core/defaults/grid.hpp create mode 100644 source/core/defaults/playfield.hpp create mode 100644 source/core/graphics/playfield.cpp create mode 100644 source/core/graphics/playfield.hpp delete mode 100644 source/core/graphics/playfield_grid.cpp delete mode 100644 source/core/graphics/playfield_grid.hpp diff --git a/source/core/defaults.hpp b/source/core/defaults.hpp index 8f284fb..4465086 100644 --- a/source/core/defaults.hpp +++ b/source/core/defaults.hpp @@ -17,12 +17,12 @@ #include "core/defaults/entities.hpp" #include "core/defaults/floating_score.hpp" #include "core/defaults/game.hpp" -#include "core/defaults/grid.hpp" #include "core/defaults/hud.hpp" #include "core/defaults/math.hpp" #include "core/defaults/notifier.hpp" #include "core/defaults/palette.hpp" #include "core/defaults/physics.hpp" +#include "core/defaults/playfield.hpp" #include "core/defaults/rendering.hpp" #include "core/defaults/ship.hpp" #include "core/defaults/title.hpp" diff --git a/source/core/defaults/grid.hpp b/source/core/defaults/grid.hpp deleted file mode 100644 index 0b2a8cd..0000000 --- a/source/core/defaults/grid.hpp +++ /dev/null @@ -1,15 +0,0 @@ -// grid.hpp - Configuració de la grilla de fons del playfield -// © 2026 JailDesigner - -#pragma once - -namespace Defaults::Grid { - - constexpr int COLUMNS = 16; // cel·les omplen tota la PLAYAREA: cell_w = w/16 - constexpr int ROWS = 8; // cel·les omplen tota la PLAYAREA: cell_h = h/8 (≈2% més altes que amples) - constexpr float BRIGHTNESS = 0.15F; // intensitat respecte al color global (border = 1.0) - - constexpr int SUBDIVISIONS = 5; // cada cel·la principal es divideix en N subcel·les - constexpr float SUBGRID_BRIGHTNESS = 0.05F; // intensitat de la sub-graella - -} // namespace Defaults::Grid diff --git a/source/core/defaults/playfield.hpp b/source/core/defaults/playfield.hpp new file mode 100644 index 0000000..b0c36f2 --- /dev/null +++ b/source/core/defaults/playfield.hpp @@ -0,0 +1,24 @@ +// playfield.hpp - Configuració del fons del playfield (graella, sub-graella, animació) +// © 2026 JailDesigner + +#pragma once + +namespace Defaults::Playfield { + + // Estructura de la graella (cel·les omplen tota la PLAYAREA) + constexpr int COLUMNS = 16; // cell_w = PLAYAREA.w / 16 + constexpr int ROWS = 8; // cell_h = PLAYAREA.h / 8 + constexpr int SUBDIVISIONS = 5; // cada cel·la principal es divideix en N subcel·les + + // Brillo respecte al color global (border = 1.0) + constexpr float GRID_BRIGHTNESS = 0.15F; + constexpr float SUBGRID_BRIGHTNESS = 0.05F; + + // Animació de creació amb timer intern del Playfield. + // L'animació total cobreix tot l'INIT_HUD (3 s). Cada línia es pinta en + // LINE_GROWTH_DURATION_S; l'spawn de les línies es distribueix perquè + // l'última acabe just al final de TOTAL_ANIMATION_DURATION_S. + constexpr float LINE_GROWTH_DURATION_S = 0.4F; + constexpr float TOTAL_ANIMATION_DURATION_S = 3.0F; // = Defaults::Game::INIT_HUD_DURATION + +} // namespace Defaults::Playfield diff --git a/source/core/graphics/playfield.cpp b/source/core/graphics/playfield.cpp new file mode 100644 index 0000000..d5f001a --- /dev/null +++ b/source/core/graphics/playfield.cpp @@ -0,0 +1,112 @@ +// playfield.cpp - Implementació del fons del playfield +// © 2026 JailDesigner + +#include "core/graphics/playfield.hpp" + +#include + +#include "core/defaults.hpp" +#include "core/rendering/line_renderer.hpp" + +namespace Graphics { + + Playfield::Playfield(Rendering::Renderer* renderer) + : renderer_(renderer) { + buildLines(); + total_slots_ = static_cast(lines_.size()); + + // Distribuïm els spawns de manera que la última línia acabe just a TOTAL_ANIMATION_DURATION_S. + // last_line_start = (N-1) * spawn_interval + // last_line_end = last_line_start + LINE_GROWTH_DURATION_S = TOTAL_ANIMATION_DURATION_S + if (total_slots_ > 1) { + spawn_interval_s_ = + (Defaults::Playfield::TOTAL_ANIMATION_DURATION_S - Defaults::Playfield::LINE_GROWTH_DURATION_S) / static_cast(total_slots_ - 1); + } + } + + void Playfield::update(float delta_time) { + elapsed_s_ += delta_time; + } + + void Playfield::buildLines() { + const SDL_FRect& zona = Defaults::Zones::PLAYAREA; + const float CELL_W = zona.w / static_cast(Defaults::Playfield::COLUMNS); + const float CELL_H = zona.h / static_cast(Defaults::Playfield::ROWS); + const float SUB_W = CELL_W / static_cast(Defaults::Playfield::SUBDIVISIONS); + const float SUB_H = CELL_H / static_cast(Defaults::Playfield::SUBDIVISIONS); + const int SUB_VERTS = Defaults::Playfield::COLUMNS * Defaults::Playfield::SUBDIVISIONS; + const int SUB_HORIZ = Defaults::Playfield::ROWS * Defaults::Playfield::SUBDIVISIONS; + + std::vector verticals; + std::vector horizontals; + + // Verticals: posicions i ∈ [1, SUB_VERTS-1]. Si i % SUBDIVISIONS == 0 → línia + // de la graella principal; si no, sub-graella. + for (int i = 1; i < SUB_VERTS; i++) { + const float X = zona.x + (static_cast(i) * SUB_W); + const bool IS_MAIN = (i % Defaults::Playfield::SUBDIVISIONS) == 0; + const float BRIGHTNESS = IS_MAIN + ? Defaults::Playfield::GRID_BRIGHTNESS + : Defaults::Playfield::SUBGRID_BRIGHTNESS; + verticals.push_back(Line{ + .start = {.x = X, .y = zona.y}, + .end = {.x = X, .y = zona.y + zona.h}, + .brightness = BRIGHTNESS, + .slot = 0}); + } + + // Horitzontals: posicions j ∈ [1, SUB_HORIZ-1]. Mateix criteri main/sub. + for (int j = 1; j < SUB_HORIZ; j++) { + const float Y = zona.y + (static_cast(j) * SUB_H); + const bool IS_MAIN = (j % Defaults::Playfield::SUBDIVISIONS) == 0; + const float BRIGHTNESS = IS_MAIN + ? Defaults::Playfield::GRID_BRIGHTNESS + : Defaults::Playfield::SUBGRID_BRIGHTNESS; + horizontals.push_back(Line{ + .start = {.x = zona.x, .y = Y}, + .end = {.x = zona.x + zona.w, .y = Y}, + .brightness = BRIGHTNESS, + .slot = 0}); + } + + // Verticals ja venen ordenats per x ascendent (loop sobre i). Assignem slots 0..N-1. + // Horitzontals ja venen ordenats per y ascendent. Assignem slots N..total-1. + lines_.clear(); + lines_.reserve(verticals.size() + horizontals.size()); + + int slot = 0; + for (auto& line : verticals) { + line.slot = slot++; + lines_.push_back(line); + } + for (auto& line : horizontals) { + line.slot = slot++; + lines_.push_back(line); + } + } + + auto Playfield::computeLineProgress(int slot) const -> float { + const float LINE_START = static_cast(slot) * spawn_interval_s_; + const float LINE_ELAPSED = elapsed_s_ - LINE_START; + return std::clamp(LINE_ELAPSED / Defaults::Playfield::LINE_GROWTH_DURATION_S, 0.0F, 1.0F); + } + + void Playfield::draw() const { + for (const auto& line : lines_) { + const float P = computeLineProgress(line.slot); + if (P <= 0.0F) { + continue; + } + const float END_X = line.start.x + ((line.end.x - line.start.x) * P); + const float END_Y = line.start.y + ((line.end.y - line.start.y) * P); + Rendering::linea( + renderer_, + static_cast(line.start.x), + static_cast(line.start.y), + static_cast(END_X), + static_cast(END_Y), + line.brightness); + } + } + +} // namespace Graphics diff --git a/source/core/graphics/playfield.hpp b/source/core/graphics/playfield.hpp new file mode 100644 index 0000000..2eaab55 --- /dev/null +++ b/source/core/graphics/playfield.hpp @@ -0,0 +1,49 @@ +// playfield.hpp - Fons del playfield (graella + sub-graella amb animació de creació) +// © 2026 JailDesigner +// +// La graella es construeix una sola vegada al constructor. El draw és stateless: +// rep un `creation_progress` global ∈ [0, 1] i cada línia computa quina porció +// li toca dibuixar segons el seu slot a la timeline. +// +// Disseny preparat per a futures capacitats: +// - Línies "vives" que reaccionen a explosions / pas de la nau (reaction_intensity). +// - Capes addicionals al fons (estrelles, gradients, scanlines). + +#pragma once + +#include + +#include "core/rendering/render_context.hpp" +#include "core/types.hpp" + +namespace Graphics { + + class Playfield { + public: + explicit Playfield(Rendering::Renderer* renderer); + + // Avança el timer intern de creació. + void update(float delta_time); + + // Pinta la graella. La porció dibuixada de cada línia depèn del timer intern. + void draw() const; + + private: + struct Line { + Vec2 start; // top (verticals) o left (horitzontals) + Vec2 end; // bottom (verticals) o right (horitzontals) + float brightness; // base (GRID_BRIGHTNESS o SUBGRID_BRIGHTNESS) + int slot; // posició a la timeline 0..total_slots-1 + }; + + void buildLines(); + [[nodiscard]] auto computeLineProgress(int slot) const -> float; + + Rendering::Renderer* renderer_; + std::vector lines_; // verticals primer (ordenats per x), després horitzontals (ordenats per y) + int total_slots_{0}; + float spawn_interval_s_{0.0F}; // calculat a buildLines() perquè la última línia acabi a TOTAL_ANIMATION_DURATION_S + float elapsed_s_{0.0F}; + }; + +} // namespace Graphics diff --git a/source/core/graphics/playfield_grid.cpp b/source/core/graphics/playfield_grid.cpp deleted file mode 100644 index abdb99a..0000000 --- a/source/core/graphics/playfield_grid.cpp +++ /dev/null @@ -1,56 +0,0 @@ -// playfield_grid.cpp - Implementació de la grilla de fons -// © 2026 JailDesigner - -#include "core/graphics/playfield_grid.hpp" - -#include "core/defaults.hpp" -#include "core/rendering/line_renderer.hpp" - -namespace Graphics { - - PlayfieldGrid::PlayfieldGrid(Rendering::Renderer* renderer) - : renderer_(renderer) {} - - void PlayfieldGrid::draw() const { - const SDL_FRect& zona = Defaults::Zones::PLAYAREA; - const float CELL_W = zona.w / static_cast(Defaults::Grid::COLUMNS); - const float CELL_H = zona.h / static_cast(Defaults::Grid::ROWS); - const float SUB_W = CELL_W / static_cast(Defaults::Grid::SUBDIVISIONS); - const float SUB_H = CELL_H / static_cast(Defaults::Grid::SUBDIVISIONS); - const int SUB_VERTS = Defaults::Grid::COLUMNS * Defaults::Grid::SUBDIVISIONS; - const int SUB_HORIZ = Defaults::Grid::ROWS * Defaults::Grid::SUBDIVISIONS; - - 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); - - // PRIMER: sub-graella (queda per dessota — pintem primer). - // Saltem les posicions que coincideixen amb la graella principal (i % SUBDIVISIONS == 0). - for (int i = 1; i < SUB_VERTS; i++) { - if (i % Defaults::Grid::SUBDIVISIONS == 0) { - continue; - } - const int X = static_cast(zona.x + (static_cast(i) * SUB_W)); - Rendering::linea(renderer_, X, Y1, X, Y2, Defaults::Grid::SUBGRID_BRIGHTNESS); - } - for (int j = 1; j < SUB_HORIZ; j++) { - if (j % Defaults::Grid::SUBDIVISIONS == 0) { - continue; - } - const int Y = static_cast(zona.y + (static_cast(j) * SUB_H)); - Rendering::linea(renderer_, X1, Y, X2, Y, Defaults::Grid::SUBGRID_BRIGHTNESS); - } - - // SEGON: graella principal (línies interiors, no repetim el border). - for (int i = 1; i < Defaults::Grid::COLUMNS; i++) { - const int X = static_cast(zona.x + (static_cast(i) * CELL_W)); - Rendering::linea(renderer_, X, Y1, X, Y2, Defaults::Grid::BRIGHTNESS); - } - for (int j = 1; j < Defaults::Grid::ROWS; j++) { - const int Y = static_cast(zona.y + (static_cast(j) * CELL_H)); - Rendering::linea(renderer_, X1, Y, X2, Y, Defaults::Grid::BRIGHTNESS); - } - } - -} // namespace Graphics diff --git a/source/core/graphics/playfield_grid.hpp b/source/core/graphics/playfield_grid.hpp deleted file mode 100644 index fa2a04f..0000000 --- a/source/core/graphics/playfield_grid.hpp +++ /dev/null @@ -1,23 +0,0 @@ -// playfield_grid.hpp - Grilla de fons del playfield (línies verdes fosques) -// © 2026 JailDesigner - -#pragma once - -#include "core/rendering/render_context.hpp" - -namespace Graphics { - - // Pinta una graella interior dins de Defaults::Zones::PLAYAREA. - // Cel·les quadrades; el nombre de columnes és fix (Defaults::Grid::COLUMNS), - // les files es deriven del que cap dins de l'altura. - // Color: oscil·lador global (mateix to que el border); brightness fixa al 25%. - class PlayfieldGrid { - public: - explicit PlayfieldGrid(Rendering::Renderer* renderer); - void draw() const; - - private: - Rendering::Renderer* renderer_; - }; - -} // namespace Graphics diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index 26d0a6b..b2a3be3 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -31,7 +31,7 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context) floating_score_manager_(sdl.getRenderer()), trail_manager_(sdl.getRenderer()), text_(sdl.getRenderer()), - playfield_grid_(sdl.getRenderer()) { + playfield_(sdl.getRenderer()) { // Recuperar configuración de match des del context match_config_ = context_.getMatchConfig(); @@ -182,6 +182,7 @@ void GameScene::stepPhysics(float delta_time) { bullet.postUpdate(delta_time); } trail_manager_.update(delta_time, ships_); + playfield_.update(delta_time); } void GameScene::stepShootingInput() { @@ -574,6 +575,9 @@ void GameScene::drawInitHudState() { Defaults::Game::INIT_HUD_SHIP2_RATIO_INIT, Defaults::Game::INIT_HUD_SHIP2_RATIO_END); + // Graella de fons al darrere (timer intern propi, cobreix tot l'INIT_HUD). + playfield_.draw(); + if (rect_progress > 0.0F) { if (!init_hud_rect_sound_played_) { Audio::get()->playSound(Defaults::Sound::INIT_HUD, Audio::Group::GAME); @@ -596,7 +600,7 @@ void GameScene::drawInitHudState() { } void GameScene::drawLevelStartState() { - playfield_grid_.draw(); + playfield_.draw(); drawMargins(); trail_manager_.draw(); drawActiveShipsAlive(); @@ -609,7 +613,7 @@ void GameScene::drawLevelStartState() { } void GameScene::drawPlayingState() { - playfield_grid_.draw(); + playfield_.draw(); drawMargins(); trail_manager_.draw(); drawActiveShipsAlive(); @@ -622,7 +626,7 @@ void GameScene::drawPlayingState() { } void GameScene::drawLevelCompletedState() { - playfield_grid_.draw(); + playfield_.draw(); drawMargins(); trail_manager_.draw(); drawActiveShipsAlive(); diff --git a/source/game/scenes/game_scene.hpp b/source/game/scenes/game_scene.hpp index 4774e88..e2f3164 100644 --- a/source/game/scenes/game_scene.hpp +++ b/source/game/scenes/game_scene.hpp @@ -8,7 +8,7 @@ #include #include -#include "core/graphics/playfield_grid.hpp" +#include "core/graphics/playfield.hpp" #include "core/graphics/vector_text.hpp" #include "core/physics/physics_world.hpp" #include "core/rendering/sdl_manager.hpp" @@ -81,8 +81,8 @@ class GameScene final : public Scene { // Text vectorial Graphics::VectorText text_; - // Grilla de fons del playfield - Graphics::PlayfieldGrid playfield_grid_; + // Fons del playfield (graella + futures capes) + Graphics::Playfield playfield_; // [NEW] Stage system std::unique_ptr stage_config_; From 5c8a583e24080c512cdbb99cfe145bc87cbf01be Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 22:06:02 +0200 Subject: [PATCH 4/4] tune(playfield): ona diagonal amb easing i cap brillant --- source/core/defaults/playfield.hpp | 8 ++- source/core/graphics/playfield.cpp | 96 ++++++++++++++++++++---------- source/core/graphics/playfield.hpp | 14 ++--- 3 files changed, 76 insertions(+), 42 deletions(-) diff --git a/source/core/defaults/playfield.hpp b/source/core/defaults/playfield.hpp index b0c36f2..fd0146b 100644 --- a/source/core/defaults/playfield.hpp +++ b/source/core/defaults/playfield.hpp @@ -16,9 +16,13 @@ namespace Defaults::Playfield { // Animació de creació amb timer intern del Playfield. // L'animació total cobreix tot l'INIT_HUD (3 s). Cada línia es pinta en - // LINE_GROWTH_DURATION_S; l'spawn de les línies es distribueix perquè - // l'última acabe just al final de TOTAL_ANIMATION_DURATION_S. + // LINE_GROWTH_DURATION_S; els spawns es distribueixen amb sweep des del + // centre perquè verticals i horitzontals propaguen cap als extrems. constexpr float LINE_GROWTH_DURATION_S = 0.4F; constexpr float TOTAL_ANIMATION_DURATION_S = 3.0F; // = Defaults::Game::INIT_HUD_DURATION + // Cap brillant de la línia mentre creix (extrem que avança). + constexpr float HEAD_LENGTH_PX = 8.0F; // longitud en píxels lògics del tram brillant + constexpr float HEAD_BRIGHTNESS = 0.0F; // brillo del cap (= border) + } // namespace Defaults::Playfield diff --git a/source/core/graphics/playfield.cpp b/source/core/graphics/playfield.cpp index d5f001a..145bd35 100644 --- a/source/core/graphics/playfield.cpp +++ b/source/core/graphics/playfield.cpp @@ -4,24 +4,27 @@ #include "core/graphics/playfield.hpp" #include +#include +#include #include "core/defaults.hpp" #include "core/rendering/line_renderer.hpp" namespace Graphics { + namespace { + + // Easing cubic-out: t → 1 - (1-t)^3. Decelera prop del final. + auto easeOutCubic(float t) -> float { + const float INV = 1.0F - t; + return 1.0F - (INV * INV * INV); + } + + } // namespace + Playfield::Playfield(Rendering::Renderer* renderer) : renderer_(renderer) { buildLines(); - total_slots_ = static_cast(lines_.size()); - - // Distribuïm els spawns de manera que la última línia acabe just a TOTAL_ANIMATION_DURATION_S. - // last_line_start = (N-1) * spawn_interval - // last_line_end = last_line_start + LINE_GROWTH_DURATION_S = TOTAL_ANIMATION_DURATION_S - if (total_slots_ > 1) { - spawn_interval_s_ = - (Defaults::Playfield::TOTAL_ANIMATION_DURATION_S - Defaults::Playfield::LINE_GROWTH_DURATION_S) / static_cast(total_slots_ - 1); - } } void Playfield::update(float delta_time) { @@ -40,8 +43,7 @@ namespace Graphics { std::vector verticals; std::vector horizontals; - // Verticals: posicions i ∈ [1, SUB_VERTS-1]. Si i % SUBDIVISIONS == 0 → línia - // de la graella principal; si no, sub-graella. + // Verticals: posicions i ∈ [1, SUB_VERTS-1]. for (int i = 1; i < SUB_VERTS; i++) { const float X = zona.x + (static_cast(i) * SUB_W); const bool IS_MAIN = (i % Defaults::Playfield::SUBDIVISIONS) == 0; @@ -52,10 +54,10 @@ namespace Graphics { .start = {.x = X, .y = zona.y}, .end = {.x = X, .y = zona.y + zona.h}, .brightness = BRIGHTNESS, - .slot = 0}); + .spawn_time_s = 0.0F}); } - // Horitzontals: posicions j ∈ [1, SUB_HORIZ-1]. Mateix criteri main/sub. + // Horitzontals: posicions j ∈ [1, SUB_HORIZ-1]. for (int j = 1; j < SUB_HORIZ; j++) { const float Y = zona.y + (static_cast(j) * SUB_H); const bool IS_MAIN = (j % Defaults::Playfield::SUBDIVISIONS) == 0; @@ -66,46 +68,76 @@ namespace Graphics { .start = {.x = zona.x, .y = Y}, .end = {.x = zona.x + zona.w, .y = Y}, .brightness = BRIGHTNESS, - .slot = 0}); + .spawn_time_s = 0.0F}); } - // Verticals ja venen ordenats per x ascendent (loop sobre i). Assignem slots 0..N-1. - // Horitzontals ja venen ordenats per y ascendent. Assignem slots N..total-1. + // Ona diagonal: la línia esquerra/superior naix a t=0 i les següents + // propaguen cap a la dreta/inferior, en paral·lel. Verticals i + // horitzontals comparteixen la finestra temporal així el front arriba + // a la cantonada inferior-dreta alhora. + const float SPAWN_WINDOW = + Defaults::Playfield::TOTAL_ANIMATION_DURATION_S - Defaults::Playfield::LINE_GROWTH_DURATION_S; + const int NUM_V = static_cast(verticals.size()); + const int NUM_H = static_cast(horizontals.size()); + const float INTERVAL_V = (NUM_V > 1) ? SPAWN_WINDOW / static_cast(NUM_V - 1) : 0.0F; + const float INTERVAL_H = (NUM_H > 1) ? SPAWN_WINDOW / static_cast(NUM_H - 1) : 0.0F; + lines_.clear(); lines_.reserve(verticals.size() + horizontals.size()); - int slot = 0; - for (auto& line : verticals) { - line.slot = slot++; - lines_.push_back(line); + for (int i = 0; i < NUM_V; i++) { + verticals[i].spawn_time_s = static_cast(i) * INTERVAL_V; + lines_.push_back(verticals[i]); } - for (auto& line : horizontals) { - line.slot = slot++; - lines_.push_back(line); + for (int i = 0; i < NUM_H; i++) { + horizontals[i].spawn_time_s = static_cast(i) * INTERVAL_H; + lines_.push_back(horizontals[i]); } } - auto Playfield::computeLineProgress(int slot) const -> float { - const float LINE_START = static_cast(slot) * spawn_interval_s_; - const float LINE_ELAPSED = elapsed_s_ - LINE_START; + auto Playfield::computeLineProgress(const Line& line) const -> float { + const float LINE_ELAPSED = elapsed_s_ - line.spawn_time_s; return std::clamp(LINE_ELAPSED / Defaults::Playfield::LINE_GROWTH_DURATION_S, 0.0F, 1.0F); } void Playfield::draw() const { for (const auto& line : lines_) { - const float P = computeLineProgress(line.slot); - if (P <= 0.0F) { + const float RAW_P = computeLineProgress(line); + if (RAW_P <= 0.0F) { continue; } - const float END_X = line.start.x + ((line.end.x - line.start.x) * P); - const float END_Y = line.start.y + ((line.end.y - line.start.y) * P); + const float P = easeOutCubic(RAW_P); + + const float DX = line.end.x - line.start.x; + const float DY = line.end.y - line.start.y; + const float CURRENT_X = line.start.x + (DX * P); + const float CURRENT_Y = line.start.y + (DY * P); + + // Tram base (brillo de la línia). Rendering::linea( renderer_, static_cast(line.start.x), static_cast(line.start.y), - static_cast(END_X), - static_cast(END_Y), + static_cast(CURRENT_X), + static_cast(CURRENT_Y), line.brightness); + + // Cap brillant mentre creix: l'últim tram de la línia es repinta més brillant. + if (P < 1.0F) { + const float LENGTH = std::sqrt((DX * DX) + (DY * DY)); + if (LENGTH > 0.0F) { + const float HEAD_T = std::max(0.0F, P - (Defaults::Playfield::HEAD_LENGTH_PX / LENGTH)); + const float HEAD_X = line.start.x + (DX * HEAD_T); + const float HEAD_Y = line.start.y + (DY * HEAD_T); + Rendering::linea( + renderer_, + static_cast(HEAD_X), + static_cast(HEAD_Y), + static_cast(CURRENT_X), + static_cast(CURRENT_Y), + Defaults::Playfield::HEAD_BRIGHTNESS); + } + } } } diff --git a/source/core/graphics/playfield.hpp b/source/core/graphics/playfield.hpp index 2eaab55..5baf9aa 100644 --- a/source/core/graphics/playfield.hpp +++ b/source/core/graphics/playfield.hpp @@ -30,19 +30,17 @@ namespace Graphics { private: struct Line { - Vec2 start; // top (verticals) o left (horitzontals) - Vec2 end; // bottom (verticals) o right (horitzontals) - float brightness; // base (GRID_BRIGHTNESS o SUBGRID_BRIGHTNESS) - int slot; // posició a la timeline 0..total_slots-1 + Vec2 start; // top (verticals) o left (horitzontals) + Vec2 end; // bottom (verticals) o right (horitzontals) + float brightness; // base (GRID_BRIGHTNESS o SUBGRID_BRIGHTNESS) + float spawn_time_s; // moment de naixement (verticals i horitzontals tenen ritmes independents) }; void buildLines(); - [[nodiscard]] auto computeLineProgress(int slot) const -> float; + [[nodiscard]] auto computeLineProgress(const Line& line) const -> float; Rendering::Renderer* renderer_; - std::vector lines_; // verticals primer (ordenats per x), després horitzontals (ordenats per y) - int total_slots_{0}; - float spawn_interval_s_{0.0F}; // calculat a buildLines() perquè la última línia acabi a TOTAL_ANIMATION_DURATION_S + std::vector lines_; float elapsed_s_{0.0F}; };