Merge branch 'feat/playfield-grid': fons playfield amb graella animada
This commit is contained in:
@@ -22,6 +22,7 @@
|
|||||||
#include "core/defaults/notifier.hpp"
|
#include "core/defaults/notifier.hpp"
|
||||||
#include "core/defaults/palette.hpp"
|
#include "core/defaults/palette.hpp"
|
||||||
#include "core/defaults/physics.hpp"
|
#include "core/defaults/physics.hpp"
|
||||||
|
#include "core/defaults/playfield.hpp"
|
||||||
#include "core/defaults/rendering.hpp"
|
#include "core/defaults/rendering.hpp"
|
||||||
#include "core/defaults/ship.hpp"
|
#include "core/defaults/ship.hpp"
|
||||||
#include "core/defaults/title.hpp"
|
#include "core/defaults/title.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
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
// playfield.cpp - Implementació del fons del playfield
|
||||||
|
// © 2026 JailDesigner
|
||||||
|
|
||||||
|
#include "core/graphics/playfield.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#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<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].
|
||||||
|
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,
|
||||||
|
.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<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,
|
||||||
|
.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<int>(verticals.size());
|
||||||
|
const int NUM_H = static_cast<int>(horizontals.size());
|
||||||
|
const float INTERVAL_V = (NUM_V > 1) ? SPAWN_WINDOW / static_cast<float>(NUM_V - 1) : 0.0F;
|
||||||
|
const float INTERVAL_H = (NUM_H > 1) ? SPAWN_WINDOW / static_cast<float>(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<float>(i) * INTERVAL_V;
|
||||||
|
lines_.push_back(verticals[i]);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < NUM_H; i++) {
|
||||||
|
horizontals[i].spawn_time_s = static_cast<float>(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<int>(line.start.x),
|
||||||
|
static_cast<int>(line.start.y),
|
||||||
|
static_cast<int>(CURRENT_X),
|
||||||
|
static_cast<int>(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<int>(HEAD_X),
|
||||||
|
static_cast<int>(HEAD_Y),
|
||||||
|
static_cast<int>(CURRENT_X),
|
||||||
|
static_cast<int>(CURRENT_Y),
|
||||||
|
Defaults::Playfield::HEAD_BRIGHTNESS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Graphics
|
||||||
@@ -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 <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)
|
||||||
|
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<Line> lines_;
|
||||||
|
float elapsed_s_{0.0F};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Graphics
|
||||||
@@ -30,7 +30,8 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context)
|
|||||||
firework_manager_(sdl.getRenderer()),
|
firework_manager_(sdl.getRenderer()),
|
||||||
floating_score_manager_(sdl.getRenderer()),
|
floating_score_manager_(sdl.getRenderer()),
|
||||||
trail_manager_(sdl.getRenderer()),
|
trail_manager_(sdl.getRenderer()),
|
||||||
text_(sdl.getRenderer()) {
|
text_(sdl.getRenderer()),
|
||||||
|
playfield_(sdl.getRenderer()) {
|
||||||
// Recuperar configuración de match des del context
|
// Recuperar configuración de match des del context
|
||||||
match_config_ = context_.getMatchConfig();
|
match_config_ = context_.getMatchConfig();
|
||||||
|
|
||||||
@@ -181,6 +182,7 @@ void GameScene::stepPhysics(float delta_time) {
|
|||||||
bullet.postUpdate(delta_time);
|
bullet.postUpdate(delta_time);
|
||||||
}
|
}
|
||||||
trail_manager_.update(delta_time, ships_);
|
trail_manager_.update(delta_time, ships_);
|
||||||
|
playfield_.update(delta_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameScene::stepShootingInput() {
|
void GameScene::stepShootingInput() {
|
||||||
@@ -573,6 +575,9 @@ void GameScene::drawInitHudState() {
|
|||||||
Defaults::Game::INIT_HUD_SHIP2_RATIO_INIT,
|
Defaults::Game::INIT_HUD_SHIP2_RATIO_INIT,
|
||||||
Defaults::Game::INIT_HUD_SHIP2_RATIO_END);
|
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 (rect_progress > 0.0F) {
|
||||||
if (!init_hud_rect_sound_played_) {
|
if (!init_hud_rect_sound_played_) {
|
||||||
Audio::get()->playSound(Defaults::Sound::INIT_HUD, Audio::Group::GAME);
|
Audio::get()->playSound(Defaults::Sound::INIT_HUD, Audio::Group::GAME);
|
||||||
@@ -595,6 +600,7 @@ void GameScene::drawInitHudState() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GameScene::drawLevelStartState() {
|
void GameScene::drawLevelStartState() {
|
||||||
|
playfield_.draw();
|
||||||
drawMargins();
|
drawMargins();
|
||||||
trail_manager_.draw();
|
trail_manager_.draw();
|
||||||
drawActiveShipsAlive();
|
drawActiveShipsAlive();
|
||||||
@@ -607,6 +613,7 @@ void GameScene::drawLevelStartState() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GameScene::drawPlayingState() {
|
void GameScene::drawPlayingState() {
|
||||||
|
playfield_.draw();
|
||||||
drawMargins();
|
drawMargins();
|
||||||
trail_manager_.draw();
|
trail_manager_.draw();
|
||||||
drawActiveShipsAlive();
|
drawActiveShipsAlive();
|
||||||
@@ -619,6 +626,7 @@ void GameScene::drawPlayingState() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GameScene::drawLevelCompletedState() {
|
void GameScene::drawLevelCompletedState() {
|
||||||
|
playfield_.draw();
|
||||||
drawMargins();
|
drawMargins();
|
||||||
trail_manager_.draw();
|
trail_manager_.draw();
|
||||||
drawActiveShipsAlive();
|
drawActiveShipsAlive();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "core/graphics/playfield.hpp"
|
||||||
#include "core/graphics/vector_text.hpp"
|
#include "core/graphics/vector_text.hpp"
|
||||||
#include "core/physics/physics_world.hpp"
|
#include "core/physics/physics_world.hpp"
|
||||||
#include "core/rendering/sdl_manager.hpp"
|
#include "core/rendering/sdl_manager.hpp"
|
||||||
@@ -80,6 +81,9 @@ class GameScene final : public Scene {
|
|||||||
// Text vectorial
|
// Text vectorial
|
||||||
Graphics::VectorText text_;
|
Graphics::VectorText text_;
|
||||||
|
|
||||||
|
// Fons del playfield (graella + futures capes)
|
||||||
|
Graphics::Playfield playfield_;
|
||||||
|
|
||||||
// [NEW] Stage system
|
// [NEW] Stage system
|
||||||
std::unique_ptr<StageSystem::StageSystemConfig> stage_config_;
|
std::unique_ptr<StageSystem::StageSystemConfig> stage_config_;
|
||||||
std::unique_ptr<StageSystem::StageManager> stage_manager_;
|
std::unique_ptr<StageSystem::StageManager> stage_manager_;
|
||||||
|
|||||||
Reference in New Issue
Block a user