Merge branch 'feat/border-bumps': border amb reaccions a impactes i explosions
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
|
||||
// IWYU pragma: begin_exports
|
||||
#include "core/defaults/audio.hpp"
|
||||
#include "core/defaults/border.hpp"
|
||||
#include "core/defaults/brightness.hpp"
|
||||
#include "core/defaults/controls.hpp"
|
||||
#include "core/defaults/effects.hpp"
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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)
|
||||
|
||||
// Bump generat per explosions properes a la paret.
|
||||
constexpr float EXPLOSION_FALLOFF_PX = 80.0F; // més enllà d'aquesta distància, sense bump
|
||||
constexpr float EXPLOSION_BASE_STRENGTH = 0.7F; // strength màxim (a 0 px de la paret)
|
||||
|
||||
} // 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_.
|
||||
// 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() {
|
||||
const float MIN_X = bounds_.x;
|
||||
const float MAX_X = bounds_.x + bounds_.w;
|
||||
@@ -76,36 +121,10 @@ namespace Physics {
|
||||
if (body == nullptr || body->isStatic()) {
|
||||
continue;
|
||||
}
|
||||
const float R = body->radius;
|
||||
|
||||
// Pared izquierda
|
||||
if (body->position.x - R < MIN_X) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
// 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_);
|
||||
// Eix Y (superior/inferior): contact_perp = x actual (ja clampejada en l'eix 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_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,14 +13,27 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "core/types.hpp"
|
||||
|
||||
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;
|
||||
|
||||
// Añade un cuerpo al mundo (no toma ownership).
|
||||
@@ -41,6 +54,13 @@ class PhysicsWorld {
|
||||
}
|
||||
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:
|
||||
// 1. Integra cada cuerpo (semi-implicit Euler + damping)
|
||||
// 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 getBodies() const -> const std::vector<RigidBody*>& { return bodies_; }
|
||||
|
||||
private:
|
||||
private:
|
||||
std::vector<RigidBody*> bodies_;
|
||||
SDL_FRect bounds_{0.0F, 0.0F, 0.0F, 0.0F};
|
||||
bool has_bounds_{false};
|
||||
BoundsHitCallback bounds_hit_callback_;
|
||||
|
||||
void integrate(float dt);
|
||||
void resolveBoundsCollisions();
|
||||
@@ -62,6 +83,6 @@ class PhysicsWorld {
|
||||
// Resol un únic parell (a, b): correcció posicional + impulso elàstic.
|
||||
// Estàtic: només toca els dos cossos rebuts, no consulta el world.
|
||||
static void resolveBodyPair(RigidBody& a, RigidBody& b);
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace Physics
|
||||
|
||||
@@ -47,6 +47,8 @@ namespace Rendering {
|
||||
|
||||
void setLineColor(SDL_Color color) { g_current_line_color = color; }
|
||||
|
||||
auto getLineColor() -> SDL_Color { return g_current_line_color; }
|
||||
|
||||
void setLineThickness(float thickness) {
|
||||
if (thickness > 0.0F) {
|
||||
g_current_line_thickness = thickness;
|
||||
|
||||
@@ -9,27 +9,32 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/rendering/render_context.hpp"
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "core/rendering/render_context.hpp"
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
// 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).
|
||||
// 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
|
||||
// usa este color directo (paleta semántica por entidad).
|
||||
void linea(Renderer* renderer,
|
||||
int x1, int y1, int x2, int y2,
|
||||
float brightness = 1.0F,
|
||||
float thickness = 0.0F,
|
||||
SDL_Color color = {0, 0, 0, 0});
|
||||
// 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).
|
||||
// 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
|
||||
// usa este color directo (paleta semántica por entidad).
|
||||
void linea(Renderer* renderer,
|
||||
int x1,
|
||||
int y1,
|
||||
int x2,
|
||||
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).
|
||||
void setLineColor(SDL_Color color);
|
||||
// Color global de las líneas (lo actualiza ColorOscillator vía SDLManager).
|
||||
void setLineColor(SDL_Color color);
|
||||
[[nodiscard]] auto getLineColor() -> SDL_Color;
|
||||
|
||||
// Grosor global por defecto (en píxeles lógicos). Default: 1.5.
|
||||
void setLineThickness(float thickness);
|
||||
[[nodiscard]] auto getLineThickness() -> float;
|
||||
// Grosor global por defecto (en píxeles lógicos). Default: 1.5.
|
||||
void setLineThickness(float thickness);
|
||||
[[nodiscard]] auto getLineThickness() -> float;
|
||||
|
||||
} // namespace Rendering
|
||||
|
||||
@@ -65,6 +65,11 @@ namespace Effects {
|
||||
// Reproducir sonido de explosión
|
||||
Audio::get()->playSound(sound, Audio::Group::GAME);
|
||||
|
||||
// Notificar als subscriptors (border, playfield, etc.).
|
||||
if (explosion_callback_) {
|
||||
explosion_callback_(centro);
|
||||
}
|
||||
|
||||
const Vec2& shape_centre = shape->getCenter();
|
||||
|
||||
// Multiplier: cada segment s'emet N vegades amb direccions aleatòries
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -22,8 +23,17 @@ namespace Effects {
|
||||
// Manté un pool de objectes Debris i gestiona el seu cicle de vida
|
||||
class DebrisManager {
|
||||
public:
|
||||
// Notificació opcional cada vegada que es genera una explosió. El
|
||||
// consumidor pot usar-la per fer reaccionar elements del fons (border
|
||||
// bumps, pulse del playfield, etc.).
|
||||
using ExplosionCallback = std::function<void(Vec2 center)>;
|
||||
|
||||
explicit DebrisManager(Rendering::Renderer* renderer);
|
||||
|
||||
void setExplosionCallback(ExplosionCallback callback) {
|
||||
explosion_callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
// Crear explosión a partir de una shape
|
||||
// - shape: shape vectorial a explode
|
||||
// - centro: posición del centro de l'objecte
|
||||
@@ -66,6 +76,7 @@ namespace Effects {
|
||||
|
||||
private:
|
||||
Rendering::Renderer* renderer_;
|
||||
ExplosionCallback explosion_callback_;
|
||||
|
||||
// Pool de fragments (màxim concurrent)
|
||||
// Pentàgon 5 línies × 15 enemics × multiplier 3 = 225 trossos només d'enemics.
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
#include "core/audio/audio.hpp"
|
||||
#include "core/input/input.hpp"
|
||||
#include "core/rendering/line_renderer.hpp"
|
||||
#include "core/system/scene_context.hpp"
|
||||
#include "game/stage_system/stage_loader.hpp"
|
||||
#include "game/systems/collision_system.hpp"
|
||||
@@ -31,7 +30,8 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context)
|
||||
floating_score_manager_(sdl.getRenderer()),
|
||||
trail_manager_(sdl.getRenderer()),
|
||||
text_(sdl.getRenderer()),
|
||||
playfield_(sdl.getRenderer()) {
|
||||
playfield_(sdl.getRenderer()),
|
||||
border_(sdl.getRenderer()) {
|
||||
// Recuperar configuración de match des del context
|
||||
match_config_ = context_.getMatchConfig();
|
||||
|
||||
@@ -64,6 +64,32 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context)
|
||||
physics_world_.clear();
|
||||
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);
|
||||
});
|
||||
|
||||
// Explosions properes a una paret també generen bump (falloff lineal amb la distància).
|
||||
debris_manager_.setExplosionCallback([this](Vec2 center) {
|
||||
const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
|
||||
const float DIST_LEFT = std::abs(center.x - zona.x);
|
||||
const float DIST_RIGHT = std::abs((zona.x + zona.w) - center.x);
|
||||
const float DIST_TOP = std::abs(center.y - zona.y);
|
||||
const float DIST_BOTTOM = std::abs((zona.y + zona.h) - center.y);
|
||||
const float MIN_DIST = std::min({DIST_LEFT, DIST_RIGHT, DIST_TOP, DIST_BOTTOM});
|
||||
if (MIN_DIST > Defaults::Border::EXPLOSION_FALLOFF_PX) {
|
||||
return;
|
||||
}
|
||||
const float FALLOFF = 1.0F - (MIN_DIST / Defaults::Border::EXPLOSION_FALLOFF_PX);
|
||||
border_.bumpAt(center, Defaults::Border::EXPLOSION_BASE_STRENGTH * FALLOFF);
|
||||
});
|
||||
|
||||
// Load stage configuration
|
||||
stage_config_ = StageSystem::StageLoader::load("data/stages/stages.yaml");
|
||||
if (!stage_config_) {
|
||||
@@ -183,6 +209,7 @@ void GameScene::stepPhysics(float delta_time) {
|
||||
}
|
||||
trail_manager_.update(delta_time, ships_);
|
||||
playfield_.update(delta_time);
|
||||
border_.update(delta_time);
|
||||
}
|
||||
|
||||
void GameScene::stepShootingInput() {
|
||||
@@ -519,7 +546,7 @@ void GameScene::drawActiveShipsAlive() const {
|
||||
}
|
||||
|
||||
void GameScene::drawContinueState() {
|
||||
drawMargins();
|
||||
border_.draw();
|
||||
drawEnemies();
|
||||
drawBullets();
|
||||
debris_manager_.draw();
|
||||
@@ -530,7 +557,7 @@ void GameScene::drawContinueState() {
|
||||
}
|
||||
|
||||
void GameScene::drawGameOverState() {
|
||||
drawMargins();
|
||||
border_.draw();
|
||||
drawEnemies();
|
||||
drawBullets();
|
||||
debris_manager_.draw();
|
||||
@@ -601,7 +628,7 @@ void GameScene::drawInitHudState() {
|
||||
|
||||
void GameScene::drawLevelStartState() {
|
||||
playfield_.draw();
|
||||
drawMargins();
|
||||
border_.draw();
|
||||
trail_manager_.draw();
|
||||
drawActiveShipsAlive();
|
||||
drawBullets();
|
||||
@@ -614,7 +641,7 @@ void GameScene::drawLevelStartState() {
|
||||
|
||||
void GameScene::drawPlayingState() {
|
||||
playfield_.draw();
|
||||
drawMargins();
|
||||
border_.draw();
|
||||
trail_manager_.draw();
|
||||
drawActiveShipsAlive();
|
||||
drawEnemies();
|
||||
@@ -627,7 +654,7 @@ void GameScene::drawPlayingState() {
|
||||
|
||||
void GameScene::drawLevelCompletedState() {
|
||||
playfield_.draw();
|
||||
drawMargins();
|
||||
border_.draw();
|
||||
trail_manager_.draw();
|
||||
drawActiveShipsAlive();
|
||||
drawBullets();
|
||||
@@ -679,23 +706,6 @@ void GameScene::tocado(uint8_t player_id) {
|
||||
// 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() {
|
||||
// Construir text del marcador
|
||||
std::string text = buildScoreboard();
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "core/graphics/border.hpp"
|
||||
#include "core/graphics/playfield.hpp"
|
||||
#include "core/graphics/vector_text.hpp"
|
||||
#include "core/physics/physics_world.hpp"
|
||||
@@ -84,6 +85,9 @@ class GameScene final : public Scene {
|
||||
// Fons del playfield (graella + futures capes)
|
||||
Graphics::Playfield playfield_;
|
||||
|
||||
// Border del playfield (4 línies amb desplaçaments i flash per impactes)
|
||||
Graphics::Border border_;
|
||||
|
||||
// [NEW] Stage system
|
||||
std::unique_ptr<StageSystem::StageSystemConfig> stage_config_;
|
||||
std::unique_ptr<StageSystem::StageManager> stage_manager_;
|
||||
@@ -93,7 +97,6 @@ class GameScene final : public Scene {
|
||||
|
||||
// Funciones privades
|
||||
void tocado(uint8_t player_id);
|
||||
void drawMargins() const; // Dibuixar vores de la zona de juego
|
||||
void drawScoreboard(); // Dibuixar marcador de puntuación
|
||||
void fireBullet(uint8_t player_id); // Shoot bullet from player
|
||||
[[nodiscard]] auto getSpawnPoint(uint8_t player_id) const -> Vec2; // Get spawn position for player
|
||||
|
||||
Reference in New Issue
Block a user