From 07985228b2d082ecbdf5571f66ab6779d705ccdd Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 21 May 2026 20:44:17 +0200 Subject: [PATCH] =?UTF-8?q?feat(playfield):=20refactor=20a=20Playfield=20a?= =?UTF-8?q?mb=20animaci=C3=B3=20de=20creaci=C3=B3=20durant=20l'INIT=5FHUD?= 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_;