feat(playfield): refactor a Playfield amb animació de creació durant l'INIT_HUD
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,112 @@
|
||||
// playfield.cpp - Implementació del fons del playfield
|
||||
// © 2026 JailDesigner
|
||||
|
||||
#include "core/graphics/playfield.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "core/defaults.hpp"
|
||||
#include "core/rendering/line_renderer.hpp"
|
||||
|
||||
namespace Graphics {
|
||||
|
||||
Playfield::Playfield(Rendering::Renderer* renderer)
|
||||
: renderer_(renderer) {
|
||||
buildLines();
|
||||
total_slots_ = static_cast<int>(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<float>(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<float>(Defaults::Playfield::COLUMNS);
|
||||
const float CELL_H = zona.h / static_cast<float>(Defaults::Playfield::ROWS);
|
||||
const float SUB_W = CELL_W / static_cast<float>(Defaults::Playfield::SUBDIVISIONS);
|
||||
const float SUB_H = CELL_H / static_cast<float>(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<Line> verticals;
|
||||
std::vector<Line> 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<float>(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<float>(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<float>(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<int>(line.start.x),
|
||||
static_cast<int>(line.start.y),
|
||||
static_cast<int>(END_X),
|
||||
static_cast<int>(END_Y),
|
||||
line.brightness);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Graphics
|
||||
@@ -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 <vector>
|
||||
|
||||
#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<Line> 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
|
||||
@@ -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<float>(Defaults::Grid::COLUMNS);
|
||||
const float CELL_H = zona.h / static_cast<float>(Defaults::Grid::ROWS);
|
||||
const float SUB_W = CELL_W / static_cast<float>(Defaults::Grid::SUBDIVISIONS);
|
||||
const float SUB_H = CELL_H / static_cast<float>(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<int>(zona.x);
|
||||
const int Y1 = static_cast<int>(zona.y);
|
||||
const int X2 = static_cast<int>(zona.x + zona.w);
|
||||
const int Y2 = static_cast<int>(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<int>(zona.x + (static_cast<float>(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<int>(zona.y + (static_cast<float>(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<int>(zona.x + (static_cast<float>(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<int>(zona.y + (static_cast<float>(j) * CELL_H));
|
||||
Rendering::linea(renderer_, X1, Y, X2, Y, Defaults::Grid::BRIGHTNESS);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Graphics
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#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<StageSystem::StageSystemConfig> stage_config_;
|
||||
|
||||
Reference in New Issue
Block a user