diff --git a/source/core/defaults.hpp b/source/core/defaults.hpp index 3353f94..4465086 100644 --- a/source/core/defaults.hpp +++ b/source/core/defaults.hpp @@ -22,6 +22,7 @@ #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/playfield.hpp b/source/core/defaults/playfield.hpp new file mode 100644 index 0000000..fd0146b --- /dev/null +++ b/source/core/defaults/playfield.hpp @@ -0,0 +1,28 @@ +// 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; 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 new file mode 100644 index 0000000..145bd35 --- /dev/null +++ b/source/core/graphics/playfield.cpp @@ -0,0 +1,144 @@ +// playfield.cpp - Implementació del fons del playfield +// © 2026 JailDesigner + +#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(); + } + + 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]. + 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, + .spawn_time_s = 0.0F}); + } + + // 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; + 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, + .spawn_time_s = 0.0F}); + } + + // 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()); + + for (int i = 0; i < NUM_V; i++) { + verticals[i].spawn_time_s = static_cast(i) * INTERVAL_V; + lines_.push_back(verticals[i]); + } + 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(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 RAW_P = computeLineProgress(line); + if (RAW_P <= 0.0F) { + continue; + } + 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(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); + } + } + } + } + +} // namespace Graphics diff --git a/source/core/graphics/playfield.hpp b/source/core/graphics/playfield.hpp new file mode 100644 index 0000000..5baf9aa --- /dev/null +++ b/source/core/graphics/playfield.hpp @@ -0,0 +1,47 @@ +// 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) + float spawn_time_s; // moment de naixement (verticals i horitzontals tenen ritmes independents) + }; + + void buildLines(); + [[nodiscard]] auto computeLineProgress(const Line& line) const -> float; + + Rendering::Renderer* renderer_; + std::vector lines_; + float elapsed_s_{0.0F}; + }; + +} // namespace Graphics diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index e6f9417..b2a3be3 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_(sdl.getRenderer()) { // Recuperar configuración de match des del context match_config_ = context_.getMatchConfig(); @@ -181,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() { @@ -573,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); @@ -595,6 +600,7 @@ void GameScene::drawInitHudState() { } void GameScene::drawLevelStartState() { + playfield_.draw(); drawMargins(); trail_manager_.draw(); drawActiveShipsAlive(); @@ -607,6 +613,7 @@ void GameScene::drawLevelStartState() { } void GameScene::drawPlayingState() { + playfield_.draw(); drawMargins(); trail_manager_.draw(); drawActiveShipsAlive(); @@ -619,6 +626,7 @@ void GameScene::drawPlayingState() { } void GameScene::drawLevelCompletedState() { + playfield_.draw(); drawMargins(); trail_manager_.draw(); drawActiveShipsAlive(); diff --git a/source/game/scenes/game_scene.hpp b/source/game/scenes/game_scene.hpp index 6b99b5b..e2f3164 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.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_; + // Fons del playfield (graella + futures capes) + Graphics::Playfield playfield_; + // [NEW] Stage system std::unique_ptr stage_config_; std::unique_ptr stage_manager_;