feat(border): refactor a Graphics::Border amb bumps i flash verd clar per impactes contra les parets
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
// IWYU pragma: begin_exports
|
// IWYU pragma: begin_exports
|
||||||
#include "core/defaults/audio.hpp"
|
#include "core/defaults/audio.hpp"
|
||||||
|
#include "core/defaults/border.hpp"
|
||||||
#include "core/defaults/brightness.hpp"
|
#include "core/defaults/brightness.hpp"
|
||||||
#include "core/defaults/controls.hpp"
|
#include "core/defaults/controls.hpp"
|
||||||
#include "core/defaults/effects.hpp"
|
#include "core/defaults/effects.hpp"
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// border.hpp - Configuració del border del playfield (estàtic + reaccions)
|
||||||
|
// © 2026 JailDesigner
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Defaults::Border {
|
||||||
|
|
||||||
|
// Desplaçament del border per impactes
|
||||||
|
constexpr float MAX_DISPLACEMENT_PX = 6.0F; // tope màxim de separació respecte la posició natural
|
||||||
|
constexpr float DISPLACEMENT_RECOVERY_PER_S = 30.0F; // px/s tornant cap a 0 (ease lineal)
|
||||||
|
|
||||||
|
// Flash al impacte. Intensitat proporcional al desplaçament:
|
||||||
|
// max displacement → color = FLASH_COLOR pur
|
||||||
|
// 0 displacement → color = oscil·lador (base verd)
|
||||||
|
// La línia es dibuixa amb el color resultant del lerp; no hi ha sobreposició.
|
||||||
|
constexpr bool FLASH_ENABLED = true;
|
||||||
|
constexpr unsigned char FLASH_COLOR_R = 180;
|
||||||
|
constexpr unsigned char FLASH_COLOR_G = 255;
|
||||||
|
constexpr unsigned char FLASH_COLOR_B = 180;
|
||||||
|
|
||||||
|
// Conversió velocitat d'impacte → strength del bump
|
||||||
|
constexpr float BUMP_VELOCITY_REFERENCE = 120.0F; // px/s donen strength 1.0
|
||||||
|
constexpr float BUMP_MIN_VELOCITY = 20.0F; // sota d'açò no genera bump (filtrar fregaments)
|
||||||
|
|
||||||
|
} // namespace Defaults::Border
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
// border.cpp - Implementació del border del playfield
|
||||||
|
// © 2026 JailDesigner
|
||||||
|
|
||||||
|
#include "core/graphics/border.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include "core/defaults.hpp"
|
||||||
|
#include "core/rendering/line_renderer.hpp"
|
||||||
|
|
||||||
|
namespace Graphics {
|
||||||
|
|
||||||
|
Border::Border(Rendering::Renderer* renderer)
|
||||||
|
: renderer_(renderer) {}
|
||||||
|
|
||||||
|
void Border::update(float delta_time) {
|
||||||
|
for (auto& side : sides_) {
|
||||||
|
// Desplaçament decau cap a 0 amb ritme constant (lineal).
|
||||||
|
const float DEC = Defaults::Border::DISPLACEMENT_RECOVERY_PER_S * delta_time;
|
||||||
|
side.displacement_px = std::max(0.0F, side.displacement_px - DEC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Border::bumpAt(Vec2 contact_point, float strength) {
|
||||||
|
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
|
||||||
|
const std::array<float, SIDE_COUNT> DISTANCES = {
|
||||||
|
/* TOP */ std::abs(contact_point.y - zona.y),
|
||||||
|
/* RIGHT */ std::abs((zona.x + zona.w) - contact_point.x),
|
||||||
|
/* BOTTOM */ std::abs((zona.y + zona.h) - contact_point.y),
|
||||||
|
/* LEFT */ std::abs(contact_point.x - zona.x)};
|
||||||
|
|
||||||
|
int closest_idx = 0;
|
||||||
|
float closest_dist = DISTANCES[0];
|
||||||
|
for (int i = 1; i < SIDE_COUNT; i++) {
|
||||||
|
if (DISTANCES[i] < closest_dist) {
|
||||||
|
closest_dist = DISTANCES[i];
|
||||||
|
closest_idx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
applyBump(closest_idx, strength);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Border::applyBump(int side_idx, float strength) {
|
||||||
|
const float S = std::clamp(strength, 0.0F, 1.0F);
|
||||||
|
SideState& side = sides_[static_cast<std::size_t>(side_idx)];
|
||||||
|
side.displacement_px = std::min(
|
||||||
|
Defaults::Border::MAX_DISPLACEMENT_PX,
|
||||||
|
side.displacement_px + (S * Defaults::Border::MAX_DISPLACEMENT_PX));
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Lerp de l'oscil·lador (color base actual) cap a un color "flash" en
|
||||||
|
// funció de f ∈ [0, 1]. Retorna sempre amb alpha>0 perquè el line_renderer
|
||||||
|
// l'use directament (sense barrejar amb el global).
|
||||||
|
auto lerpColor(SDL_Color flash, float f) -> SDL_Color {
|
||||||
|
const float CLAMPED = std::clamp(f, 0.0F, 1.0F);
|
||||||
|
const SDL_Color BASE = Rendering::getLineColor();
|
||||||
|
const auto LERP_U8 = [&](unsigned char a, unsigned char b) {
|
||||||
|
const float OUT = (static_cast<float>(a) * (1.0F - CLAMPED)) + (static_cast<float>(b) * CLAMPED);
|
||||||
|
return static_cast<unsigned char>(OUT);
|
||||||
|
};
|
||||||
|
return SDL_Color{
|
||||||
|
.r = LERP_U8(BASE.r, flash.r),
|
||||||
|
.g = LERP_U8(BASE.g, flash.g),
|
||||||
|
.b = LERP_U8(BASE.b, flash.b),
|
||||||
|
.a = 255};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void Border::draw() const {
|
||||||
|
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
|
||||||
|
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);
|
||||||
|
|
||||||
|
const int OFF_TOP = static_cast<int>(sides_[SIDE_TOP].displacement_px);
|
||||||
|
const int OFF_RIGHT = static_cast<int>(sides_[SIDE_RIGHT].displacement_px);
|
||||||
|
const int OFF_BOTTOM = static_cast<int>(sides_[SIDE_BOTTOM].displacement_px);
|
||||||
|
const int OFF_LEFT = static_cast<int>(sides_[SIDE_LEFT].displacement_px);
|
||||||
|
|
||||||
|
// Color per costat: lerp(oscil·lador → flash) en funció del desplaçament.
|
||||||
|
const SDL_Color FLASH = {
|
||||||
|
.r = Defaults::Border::FLASH_COLOR_R,
|
||||||
|
.g = Defaults::Border::FLASH_COLOR_G,
|
||||||
|
.b = Defaults::Border::FLASH_COLOR_B,
|
||||||
|
.a = 255};
|
||||||
|
const float MAX_D = Defaults::Border::MAX_DISPLACEMENT_PX;
|
||||||
|
const bool DO_FLASH = Defaults::Border::FLASH_ENABLED;
|
||||||
|
|
||||||
|
const SDL_Color C_TOP = DO_FLASH ? lerpColor(FLASH, sides_[SIDE_TOP].displacement_px / MAX_D) : SDL_Color{};
|
||||||
|
const SDL_Color C_RIGHT = DO_FLASH ? lerpColor(FLASH, sides_[SIDE_RIGHT].displacement_px / MAX_D) : SDL_Color{};
|
||||||
|
const SDL_Color C_BOTTOM = DO_FLASH ? lerpColor(FLASH, sides_[SIDE_BOTTOM].displacement_px / MAX_D) : SDL_Color{};
|
||||||
|
const SDL_Color C_LEFT = DO_FLASH ? lerpColor(FLASH, sides_[SIDE_LEFT].displacement_px / MAX_D) : SDL_Color{};
|
||||||
|
|
||||||
|
// Una sola línia per costat (brillo 1.0). Si DO_FLASH = false → alpha = 0 → usa
|
||||||
|
// el color global de l'oscil·lador.
|
||||||
|
Rendering::linea(renderer_, X1, Y1 - OFF_TOP, X2, Y1 - OFF_TOP, 1.0F, 0.0F, C_TOP);
|
||||||
|
Rendering::linea(renderer_, X2 + OFF_RIGHT, Y1, X2 + OFF_RIGHT, Y2, 1.0F, 0.0F, C_RIGHT);
|
||||||
|
Rendering::linea(renderer_, X1, Y2 + OFF_BOTTOM, X2, Y2 + OFF_BOTTOM, 1.0F, 0.0F, C_BOTTOM);
|
||||||
|
Rendering::linea(renderer_, X1 - OFF_LEFT, Y1, X1 - OFF_LEFT, Y2, 1.0F, 0.0F, C_LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Graphics
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// border.hpp - Border del playfield amb estat (desplaçaments i flash per impactes)
|
||||||
|
// © 2026 JailDesigner
|
||||||
|
//
|
||||||
|
// Substitueix el `drawMargins()` inline de GameScene. Cada un dels 4 costats
|
||||||
|
// té estat propi (desplaçament perpendicular outward + intensitat de flash blanc)
|
||||||
|
// que decau cap a 0. Esdeveniments externs (col·lisions contra els bounds, etc.)
|
||||||
|
// criden `bumpAt()` per generar reaccions.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "core/rendering/render_context.hpp"
|
||||||
|
#include "core/types.hpp"
|
||||||
|
|
||||||
|
namespace Graphics {
|
||||||
|
|
||||||
|
class Border {
|
||||||
|
public:
|
||||||
|
explicit Border(Rendering::Renderer* renderer);
|
||||||
|
|
||||||
|
// Decae desplaçaments i flash cap a 0.
|
||||||
|
void update(float delta_time);
|
||||||
|
|
||||||
|
// Dibuixa els 4 costats amb el seu estat actual.
|
||||||
|
void draw() const;
|
||||||
|
|
||||||
|
// Aplica un bump al costat més proper al punt de contacte.
|
||||||
|
// strength ∈ [0, 1]; valors superiors es retallen.
|
||||||
|
void bumpAt(Vec2 contact_point, float strength);
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum : std::uint8_t {
|
||||||
|
SIDE_TOP = 0,
|
||||||
|
SIDE_RIGHT = 1,
|
||||||
|
SIDE_BOTTOM = 2,
|
||||||
|
SIDE_LEFT = 3,
|
||||||
|
SIDE_COUNT = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SideState {
|
||||||
|
float displacement_px{0.0F}; // outward (sempre ≥ 0); el flash es deriva d'aquí
|
||||||
|
};
|
||||||
|
|
||||||
|
void applyBump(int side_idx, float strength);
|
||||||
|
|
||||||
|
Rendering::Renderer* renderer_;
|
||||||
|
std::array<SideState, SIDE_COUNT> sides_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Graphics
|
||||||
@@ -66,6 +66,51 @@ namespace Physics {
|
|||||||
|
|
||||||
// Rebote contra los 4 bordes del rectángulo bounds_.
|
// Rebote contra los 4 bordes del rectángulo bounds_.
|
||||||
// Refleja la componente normal de la velocidad por la restitución.
|
// Refleja la componente normal de la velocidad por la restitución.
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Resol col·lisió contra un parell paret-axis (mín i màx).
|
||||||
|
// pos/vel són les referències al component de l'axis actiu (x o y);
|
||||||
|
// contact_perp és la coordenada del component perpendicular (la fixa de la
|
||||||
|
// paret a l'eix actiu — usada per al contact_point).
|
||||||
|
void resolveAxis(float& pos,
|
||||||
|
float& vel,
|
||||||
|
float radius,
|
||||||
|
float min_val,
|
||||||
|
float max_val,
|
||||||
|
float restitution,
|
||||||
|
bool axis_is_x,
|
||||||
|
float contact_perp,
|
||||||
|
const PhysicsWorld::BoundsHitCallback& callback) {
|
||||||
|
// Cara mínima (esquerra o superior)
|
||||||
|
if (pos - radius < min_val) {
|
||||||
|
pos = min_val + radius;
|
||||||
|
if (vel < 0.0F) {
|
||||||
|
if (callback) {
|
||||||
|
const Vec2 CONTACT = axis_is_x
|
||||||
|
? Vec2{.x = min_val, .y = contact_perp}
|
||||||
|
: Vec2{.x = contact_perp, .y = min_val};
|
||||||
|
callback(BoundsHit{.contact_point = CONTACT, .impact_speed = -vel});
|
||||||
|
}
|
||||||
|
vel = -vel * restitution;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Cara màxima (dreta o inferior)
|
||||||
|
if (pos + radius > max_val) {
|
||||||
|
pos = max_val - radius;
|
||||||
|
if (vel > 0.0F) {
|
||||||
|
if (callback) {
|
||||||
|
const Vec2 CONTACT = axis_is_x
|
||||||
|
? Vec2{.x = max_val, .y = contact_perp}
|
||||||
|
: Vec2{.x = contact_perp, .y = max_val};
|
||||||
|
callback(BoundsHit{.contact_point = CONTACT, .impact_speed = vel});
|
||||||
|
}
|
||||||
|
vel = -vel * restitution;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void PhysicsWorld::resolveBoundsCollisions() {
|
void PhysicsWorld::resolveBoundsCollisions() {
|
||||||
const float MIN_X = bounds_.x;
|
const float MIN_X = bounds_.x;
|
||||||
const float MAX_X = bounds_.x + bounds_.w;
|
const float MAX_X = bounds_.x + bounds_.w;
|
||||||
@@ -76,36 +121,10 @@ namespace Physics {
|
|||||||
if (body == nullptr || body->isStatic()) {
|
if (body == nullptr || body->isStatic()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const float R = body->radius;
|
// Eix X (esquerra/dreta): contact_perp = y actual del cos.
|
||||||
|
resolveAxis(body->position.x, body->velocity.x, body->radius, MIN_X, MAX_X, body->restitution, /*axis_is_x=*/true, body->position.y, bounds_hit_callback_);
|
||||||
// Pared izquierda
|
// Eix Y (superior/inferior): contact_perp = x actual (ja clampejada en l'eix X).
|
||||||
if (body->position.x - R < MIN_X) {
|
resolveAxis(body->position.y, body->velocity.y, body->radius, MIN_Y, MAX_Y, body->restitution, /*axis_is_x=*/false, body->position.x, bounds_hit_callback_);
|
||||||
body->position.x = MIN_X + R;
|
|
||||||
if (body->velocity.x < 0.0F) {
|
|
||||||
body->velocity.x = -body->velocity.x * body->restitution;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Pared derecha
|
|
||||||
if (body->position.x + R > MAX_X) {
|
|
||||||
body->position.x = MAX_X - R;
|
|
||||||
if (body->velocity.x > 0.0F) {
|
|
||||||
body->velocity.x = -body->velocity.x * body->restitution;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Pared superior
|
|
||||||
if (body->position.y - R < MIN_Y) {
|
|
||||||
body->position.y = MIN_Y + R;
|
|
||||||
if (body->velocity.y < 0.0F) {
|
|
||||||
body->velocity.y = -body->velocity.y * body->restitution;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Pared inferior
|
|
||||||
if (body->position.y + R > MAX_Y) {
|
|
||||||
body->position.y = MAX_Y - R;
|
|
||||||
if (body->velocity.y > 0.0F) {
|
|
||||||
body->velocity.y = -body->velocity.y * body->restitution;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,14 +13,27 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "core/types.hpp"
|
||||||
|
|
||||||
namespace Physics {
|
namespace Physics {
|
||||||
|
|
||||||
struct RigidBody;
|
struct RigidBody;
|
||||||
|
|
||||||
|
// Notificació d'impacte contra un dels bounds del PLAYAREA. impact_speed és
|
||||||
|
// la magnitud de la component de velocity perpendicular a la paret (≥ 0).
|
||||||
|
struct BoundsHit {
|
||||||
|
Vec2 contact_point;
|
||||||
|
float impact_speed;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PhysicsWorld {
|
||||||
|
public:
|
||||||
|
using BoundsHitCallback = std::function<void(const BoundsHit&)>;
|
||||||
|
|
||||||
class PhysicsWorld {
|
|
||||||
public:
|
|
||||||
PhysicsWorld() = default;
|
PhysicsWorld() = default;
|
||||||
|
|
||||||
// Añade un cuerpo al mundo (no toma ownership).
|
// Añade un cuerpo al mundo (no toma ownership).
|
||||||
@@ -41,6 +54,13 @@ class PhysicsWorld {
|
|||||||
}
|
}
|
||||||
void clearBounds() { has_bounds_ = false; }
|
void clearBounds() { has_bounds_ = false; }
|
||||||
|
|
||||||
|
// Callback opcional invocat cada vegada que un cos impacta contra
|
||||||
|
// un dels bounds del PLAYAREA. S'invoca abans de la reflexió de
|
||||||
|
// velocity perquè impact_speed sigui la magnitud entrant.
|
||||||
|
void setBoundsHitCallback(BoundsHitCallback callback) {
|
||||||
|
bounds_hit_callback_ = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
// Avanza la simulación dt segundos:
|
// Avanza la simulación dt segundos:
|
||||||
// 1. Integra cada cuerpo (semi-implicit Euler + damping)
|
// 1. Integra cada cuerpo (semi-implicit Euler + damping)
|
||||||
// 2. Resuelve colisiones contra los bounds (si configurados)
|
// 2. Resuelve colisiones contra los bounds (si configurados)
|
||||||
@@ -51,10 +71,11 @@ class PhysicsWorld {
|
|||||||
[[nodiscard]] auto getBodyCount() const -> std::size_t { return bodies_.size(); }
|
[[nodiscard]] auto getBodyCount() const -> std::size_t { return bodies_.size(); }
|
||||||
[[nodiscard]] auto getBodies() const -> const std::vector<RigidBody*>& { return bodies_; }
|
[[nodiscard]] auto getBodies() const -> const std::vector<RigidBody*>& { return bodies_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<RigidBody*> bodies_;
|
std::vector<RigidBody*> bodies_;
|
||||||
SDL_FRect bounds_{0.0F, 0.0F, 0.0F, 0.0F};
|
SDL_FRect bounds_{0.0F, 0.0F, 0.0F, 0.0F};
|
||||||
bool has_bounds_{false};
|
bool has_bounds_{false};
|
||||||
|
BoundsHitCallback bounds_hit_callback_;
|
||||||
|
|
||||||
void integrate(float dt);
|
void integrate(float dt);
|
||||||
void resolveBoundsCollisions();
|
void resolveBoundsCollisions();
|
||||||
@@ -62,6 +83,6 @@ class PhysicsWorld {
|
|||||||
// Resol un únic parell (a, b): correcció posicional + impulso elàstic.
|
// Resol un únic parell (a, b): correcció posicional + impulso elàstic.
|
||||||
// Estàtic: només toca els dos cossos rebuts, no consulta el world.
|
// Estàtic: només toca els dos cossos rebuts, no consulta el world.
|
||||||
static void resolveBodyPair(RigidBody& a, RigidBody& b);
|
static void resolveBodyPair(RigidBody& a, RigidBody& b);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Physics
|
} // namespace Physics
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ namespace Rendering {
|
|||||||
|
|
||||||
void setLineColor(SDL_Color color) { g_current_line_color = color; }
|
void setLineColor(SDL_Color color) { g_current_line_color = color; }
|
||||||
|
|
||||||
|
auto getLineColor() -> SDL_Color { return g_current_line_color; }
|
||||||
|
|
||||||
void setLineThickness(float thickness) {
|
void setLineThickness(float thickness) {
|
||||||
if (thickness > 0.0F) {
|
if (thickness > 0.0F) {
|
||||||
g_current_line_thickness = thickness;
|
g_current_line_thickness = thickness;
|
||||||
|
|||||||
@@ -9,27 +9,32 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/rendering/render_context.hpp"
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include "core/rendering/render_context.hpp"
|
||||||
|
|
||||||
namespace Rendering {
|
namespace Rendering {
|
||||||
|
|
||||||
// Dibuja una línea entre dos puntos en coordenadas lógicas (1280×720).
|
// Dibuja una línea entre dos puntos en coordenadas lógicas (1280×720).
|
||||||
// brightness: factor de brillo (0.0..1.0, default 1.0 = brillo máximo).
|
// brightness: factor de brillo (0.0..1.0, default 1.0 = brillo máximo).
|
||||||
// thickness: grosor en píxeles lógicos. Si <= 0 usa g_current_line_thickness.
|
// thickness: grosor en píxeles lógicos. Si <= 0 usa g_current_line_thickness.
|
||||||
// color: si alpha==0, se usa el color global del oscilador; si alpha>0 se
|
// color: si alpha==0, se usa el color global del oscilador; si alpha>0 se
|
||||||
// usa este color directo (paleta semántica por entidad).
|
// usa este color directo (paleta semántica por entidad).
|
||||||
void linea(Renderer* renderer,
|
void linea(Renderer* renderer,
|
||||||
int x1, int y1, int x2, int y2,
|
int x1,
|
||||||
float brightness = 1.0F,
|
int y1,
|
||||||
float thickness = 0.0F,
|
int x2,
|
||||||
SDL_Color color = {0, 0, 0, 0});
|
int y2,
|
||||||
|
float brightness = 1.0F,
|
||||||
|
float thickness = 0.0F,
|
||||||
|
SDL_Color color = {0, 0, 0, 0});
|
||||||
|
|
||||||
// Color global de las líneas (lo actualiza ColorOscillator vía SDLManager).
|
// Color global de las líneas (lo actualiza ColorOscillator vía SDLManager).
|
||||||
void setLineColor(SDL_Color color);
|
void setLineColor(SDL_Color color);
|
||||||
|
[[nodiscard]] auto getLineColor() -> SDL_Color;
|
||||||
|
|
||||||
// Grosor global por defecto (en píxeles lógicos). Default: 1.5.
|
// Grosor global por defecto (en píxeles lógicos). Default: 1.5.
|
||||||
void setLineThickness(float thickness);
|
void setLineThickness(float thickness);
|
||||||
[[nodiscard]] auto getLineThickness() -> float;
|
[[nodiscard]] auto getLineThickness() -> float;
|
||||||
|
|
||||||
} // namespace Rendering
|
} // namespace Rendering
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
#include "core/audio/audio.hpp"
|
#include "core/audio/audio.hpp"
|
||||||
#include "core/input/input.hpp"
|
#include "core/input/input.hpp"
|
||||||
#include "core/rendering/line_renderer.hpp"
|
|
||||||
#include "core/system/scene_context.hpp"
|
#include "core/system/scene_context.hpp"
|
||||||
#include "game/stage_system/stage_loader.hpp"
|
#include "game/stage_system/stage_loader.hpp"
|
||||||
#include "game/systems/collision_system.hpp"
|
#include "game/systems/collision_system.hpp"
|
||||||
@@ -31,7 +30,8 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context)
|
|||||||
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()) {
|
playfield_(sdl.getRenderer()),
|
||||||
|
border_(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();
|
||||||
|
|
||||||
@@ -64,6 +64,17 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context)
|
|||||||
physics_world_.clear();
|
physics_world_.clear();
|
||||||
physics_world_.setBounds(Defaults::Zones::PLAYAREA);
|
physics_world_.setBounds(Defaults::Zones::PLAYAREA);
|
||||||
|
|
||||||
|
// Connectar els impactes contra les parets al border (bump + flash).
|
||||||
|
physics_world_.setBoundsHitCallback([this](const Physics::BoundsHit& hit) {
|
||||||
|
if (hit.impact_speed < Defaults::Border::BUMP_MIN_VELOCITY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const float STRENGTH = std::min(
|
||||||
|
1.0F,
|
||||||
|
hit.impact_speed / Defaults::Border::BUMP_VELOCITY_REFERENCE);
|
||||||
|
border_.bumpAt(hit.contact_point, STRENGTH);
|
||||||
|
});
|
||||||
|
|
||||||
// Load stage configuration
|
// Load stage configuration
|
||||||
stage_config_ = StageSystem::StageLoader::load("data/stages/stages.yaml");
|
stage_config_ = StageSystem::StageLoader::load("data/stages/stages.yaml");
|
||||||
if (!stage_config_) {
|
if (!stage_config_) {
|
||||||
@@ -183,6 +194,7 @@ void GameScene::stepPhysics(float delta_time) {
|
|||||||
}
|
}
|
||||||
trail_manager_.update(delta_time, ships_);
|
trail_manager_.update(delta_time, ships_);
|
||||||
playfield_.update(delta_time);
|
playfield_.update(delta_time);
|
||||||
|
border_.update(delta_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameScene::stepShootingInput() {
|
void GameScene::stepShootingInput() {
|
||||||
@@ -519,7 +531,7 @@ void GameScene::drawActiveShipsAlive() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GameScene::drawContinueState() {
|
void GameScene::drawContinueState() {
|
||||||
drawMargins();
|
border_.draw();
|
||||||
drawEnemies();
|
drawEnemies();
|
||||||
drawBullets();
|
drawBullets();
|
||||||
debris_manager_.draw();
|
debris_manager_.draw();
|
||||||
@@ -530,7 +542,7 @@ void GameScene::drawContinueState() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GameScene::drawGameOverState() {
|
void GameScene::drawGameOverState() {
|
||||||
drawMargins();
|
border_.draw();
|
||||||
drawEnemies();
|
drawEnemies();
|
||||||
drawBullets();
|
drawBullets();
|
||||||
debris_manager_.draw();
|
debris_manager_.draw();
|
||||||
@@ -601,7 +613,7 @@ void GameScene::drawInitHudState() {
|
|||||||
|
|
||||||
void GameScene::drawLevelStartState() {
|
void GameScene::drawLevelStartState() {
|
||||||
playfield_.draw();
|
playfield_.draw();
|
||||||
drawMargins();
|
border_.draw();
|
||||||
trail_manager_.draw();
|
trail_manager_.draw();
|
||||||
drawActiveShipsAlive();
|
drawActiveShipsAlive();
|
||||||
drawBullets();
|
drawBullets();
|
||||||
@@ -614,7 +626,7 @@ void GameScene::drawLevelStartState() {
|
|||||||
|
|
||||||
void GameScene::drawPlayingState() {
|
void GameScene::drawPlayingState() {
|
||||||
playfield_.draw();
|
playfield_.draw();
|
||||||
drawMargins();
|
border_.draw();
|
||||||
trail_manager_.draw();
|
trail_manager_.draw();
|
||||||
drawActiveShipsAlive();
|
drawActiveShipsAlive();
|
||||||
drawEnemies();
|
drawEnemies();
|
||||||
@@ -627,7 +639,7 @@ void GameScene::drawPlayingState() {
|
|||||||
|
|
||||||
void GameScene::drawLevelCompletedState() {
|
void GameScene::drawLevelCompletedState() {
|
||||||
playfield_.draw();
|
playfield_.draw();
|
||||||
drawMargins();
|
border_.draw();
|
||||||
trail_manager_.draw();
|
trail_manager_.draw();
|
||||||
drawActiveShipsAlive();
|
drawActiveShipsAlive();
|
||||||
drawBullets();
|
drawBullets();
|
||||||
@@ -679,23 +691,6 @@ void GameScene::tocado(uint8_t player_id) {
|
|||||||
// Phase 3 is handled in update() when hit_timer_per_player_ >= DEATH_DURATION
|
// Phase 3 is handled in update() when hit_timer_per_player_ >= DEATH_DURATION
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameScene::drawMargins() const {
|
|
||||||
// Dibuixar rectangle de la zona de juego
|
|
||||||
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
|
|
||||||
|
|
||||||
// Coordenades dels cantons
|
|
||||||
int x1 = static_cast<int>(zona.x);
|
|
||||||
int y1 = static_cast<int>(zona.y);
|
|
||||||
int x2 = static_cast<int>(zona.x + zona.w);
|
|
||||||
int y2 = static_cast<int>(zona.y + zona.h);
|
|
||||||
|
|
||||||
// 4 línies per formar el rectangle
|
|
||||||
Rendering::linea(sdl_.getRenderer(), x1, y1, x2, y1); // Top
|
|
||||||
Rendering::linea(sdl_.getRenderer(), x1, y2, x2, y2); // Bottom
|
|
||||||
Rendering::linea(sdl_.getRenderer(), x1, y1, x1, y2); // Left
|
|
||||||
Rendering::linea(sdl_.getRenderer(), x2, y1, x2, y2); // Right
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameScene::drawScoreboard() {
|
void GameScene::drawScoreboard() {
|
||||||
// Construir text del marcador
|
// Construir text del marcador
|
||||||
std::string text = buildScoreboard();
|
std::string text = buildScoreboard();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "core/graphics/border.hpp"
|
||||||
#include "core/graphics/playfield.hpp"
|
#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"
|
||||||
@@ -84,6 +85,9 @@ class GameScene final : public Scene {
|
|||||||
// Fons del playfield (graella + futures capes)
|
// Fons del playfield (graella + futures capes)
|
||||||
Graphics::Playfield playfield_;
|
Graphics::Playfield playfield_;
|
||||||
|
|
||||||
|
// Border del playfield (4 línies amb desplaçaments i flash per impactes)
|
||||||
|
Graphics::Border border_;
|
||||||
|
|
||||||
// [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_;
|
||||||
@@ -93,7 +97,6 @@ class GameScene final : public Scene {
|
|||||||
|
|
||||||
// Funciones privades
|
// Funciones privades
|
||||||
void tocado(uint8_t player_id);
|
void tocado(uint8_t player_id);
|
||||||
void drawMargins() const; // Dibuixar vores de la zona de juego
|
|
||||||
void drawScoreboard(); // Dibuixar marcador de puntuación
|
void drawScoreboard(); // Dibuixar marcador de puntuación
|
||||||
void fireBullet(uint8_t player_id); // Shoot bullet from player
|
void fireBullet(uint8_t player_id); // Shoot bullet from player
|
||||||
[[nodiscard]] auto getSpawnPoint(uint8_t player_id) const -> Vec2; // Get spawn position for player
|
[[nodiscard]] auto getSpawnPoint(uint8_t player_id) const -> Vec2; // Get spawn position for player
|
||||||
|
|||||||
Reference in New Issue
Block a user