feat(entities): migrar bullet a data/entities/bullet/bullet.yaml
This commit is contained in:
@@ -0,0 +1,22 @@
|
|||||||
|
name: bullet
|
||||||
|
|
||||||
|
# Shape de la bala. El bounding_radius del .shp dóna el hitbox base (~3 px);
|
||||||
|
# scale el modula visualment i pel hitbox.
|
||||||
|
shape:
|
||||||
|
path: bullet.shp
|
||||||
|
scale: 1.0
|
||||||
|
collision_factor: 1.0
|
||||||
|
|
||||||
|
# Cinemàtica pura: la bala no col·lisiona físicament al PhysicsWorld
|
||||||
|
# (body_.radius = 0 al spawn), però sí participa al gameplay via
|
||||||
|
# checkCollisionSwept. La mass i l'impact_momentum_factor es fan servir
|
||||||
|
# només per calcular l'impuls que rep l'enemic en impactar.
|
||||||
|
physics:
|
||||||
|
mass: 0.5
|
||||||
|
restitution: 0.0 # irrelevant (no rebota)
|
||||||
|
linear_damping: 0.0 # movement rectilini uniforme
|
||||||
|
angular_damping: 0.0
|
||||||
|
impact_momentum_factor: 3.0 # factor de transferència de moment bala→enemic
|
||||||
|
|
||||||
|
colors:
|
||||||
|
normal: [155, 255, 175] # verd laser
|
||||||
@@ -8,8 +8,9 @@ namespace Defaults::Entities {
|
|||||||
constexpr int MAX_ORNIS = 15;
|
constexpr int MAX_ORNIS = 15;
|
||||||
constexpr int MAX_BULLETS = 50;
|
constexpr int MAX_BULLETS = 50;
|
||||||
|
|
||||||
// SHIP_RADIUS migrat a data/entities/player/player.yaml (physics.collision_radius).
|
// SHIP_RADIUS / ENEMY_RADIUS / BULLET_RADIUS han migrat: ara cada entitat
|
||||||
// ENEMY_RADIUS migrat a data/entities/<type>/<type>.yaml (physics.collision_radius).
|
// calcula el seu collision_radius com a
|
||||||
constexpr float BULLET_RADIUS = 3.0F;
|
// shape.bounding_radius × shape.scale × shape.collision_factor
|
||||||
|
// a partir del seu YAML (data/entities/<name>/<name>.yaml).
|
||||||
|
|
||||||
} // namespace Defaults::Entities
|
} // namespace Defaults::Entities
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ namespace Defaults::Palette {
|
|||||||
// brillantor perceptual sota el bloom (sense alterar la identitat de color).
|
// brillantor perceptual sota el bloom (sense alterar la identitat de color).
|
||||||
// El canal dominant es manté a 255 a cada color per maximitzar la saturació
|
// El canal dominant es manté a 255 a cada color per maximitzar la saturació
|
||||||
// visible quan el halo s'expandeix.
|
// visible quan el halo s'expandeix.
|
||||||
constexpr SDL_Color BULLET = {.r = 155, .g = 255, .b = 175, .a = 255}; // Verde laser
|
// Tots els colors d'entitats han migrat al seu YAML respectiu
|
||||||
// SHIP s'ha migrat a data/entities/player/player.yaml (colors.normal).
|
// (data/entities/<name>/<name>.yaml, secció `colors`):
|
||||||
// PENTAGON, SQUARE, PINWHEEL i WOUNDED han migrat a cada enemy YAML
|
// - SHIP → player.yaml
|
||||||
// (colors.normal i colors.wounded).
|
// - PENTAGON / SQUARE / PINWHEEL / WOUNDED → cada enemy.yaml
|
||||||
// BULLET es queda compartit fins a la migració del bullet a YAML.
|
// - BULLET → bullet.yaml
|
||||||
|
|
||||||
} // namespace Defaults::Palette
|
} // namespace Defaults::Palette
|
||||||
|
|||||||
@@ -3,64 +3,55 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
namespace Defaults::Physics {
|
// NOTA: els paràmetres del player (rotation_speed, acceleration,
|
||||||
|
// max_velocity, death_impact_factor) viuen a data/entities/player/player.yaml.
|
||||||
|
// Els paràmetres específics de la bala (mass, restitution, damping,
|
||||||
|
// impact_momentum_factor) viuen a data/entities/bullet/bullet.yaml.
|
||||||
|
// Aquest fitxer només conté els paràmetres compartits del subsistema de
|
||||||
|
// debris (explosions visuals).
|
||||||
|
|
||||||
// NOTA: els paràmetres específics de la nau del player (rotation_speed,
|
namespace Defaults::Physics::Debris {
|
||||||
// acceleration, max_velocity, death_impact_factor) viuen ara a
|
|
||||||
// data/entities/player/player.yaml. La migració d'aquests fitxers va
|
|
||||||
// començar amb la nau; els enemics i les bales són els següents.
|
|
||||||
|
|
||||||
// Bullet — impacto físico contra enemigo (impulse mass-aware).
|
constexpr float SPEED_BASE = 80.0F; // Velocidad inicial (px/s)
|
||||||
// Model: el impulse és el moment lineal de la bala (m·v) multiplicat per
|
constexpr float VARIACIO_SPEED = 40.0F; // ±variació aleatòria (px/s)
|
||||||
// un factor de transferència [0..1]. 1.0 = transfereix tot el moment
|
constexpr float ACCELERACIO = -60.0F; // Fricció/desacceleració (px/s²)
|
||||||
// (col·lisió perfectament inelàstica), 0.5 = transfereix la meitat.
|
constexpr float ROTATION_MIN = 0.1F; // Rotación mínima (rad/s ~5.7°/s)
|
||||||
namespace Bullet {
|
constexpr float ROTATION_MAX = 0.3F; // Rotación màxima (rad/s ~17.2°/s)
|
||||||
constexpr float IMPACT_MOMENTUM_FACTOR = 3.0F; // Factor de transferència de moment bala→enemic
|
constexpr float TEMPS_VIDA = 2.0F; // Vida mínima garantida (s) — després pot morir per velocitat baixa
|
||||||
} // namespace Bullet
|
constexpr float TEMPS_VIDA_NAU = 3.0F; // Ship debris min lifetime (matches DEATH_DURATION)
|
||||||
|
constexpr float SHRINK_RATE = 1.0F; // Reducció de mida (1.0 = encoge a 0 al final del min_lifetime)
|
||||||
|
|
||||||
// Explosions (debris physics)
|
// Política de mort: passat el min_lifetime, el fragment mor quan la
|
||||||
namespace Debris {
|
// seva velocity cau per sota d'aquest llindar. Així els fragments
|
||||||
constexpr float SPEED_BASE = 80.0F; // Velocidad inicial (px/s)
|
// ràpids no "popen" en moviment.
|
||||||
constexpr float VARIACIO_SPEED = 40.0F; // ±variació aleatòria (px/s)
|
constexpr float MIN_SPEED_TO_DIE = 5.0F; // px/s — al cuadrat per evitar sqrt en update
|
||||||
constexpr float ACCELERACIO = -60.0F; // Fricció/desacceleració (px/s²)
|
constexpr float MIN_SPEED_TO_DIE_SQ = MIN_SPEED_TO_DIE * MIN_SPEED_TO_DIE;
|
||||||
constexpr float ROTATION_MIN = 0.1F; // Rotación mínima (rad/s ~5.7°/s)
|
|
||||||
constexpr float ROTATION_MAX = 0.3F; // Rotación màxima (rad/s ~17.2°/s)
|
|
||||||
constexpr float TEMPS_VIDA = 2.0F; // Vida mínima garantida (s) — després pot morir per velocitat baixa
|
|
||||||
constexpr float TEMPS_VIDA_NAU = 3.0F; // Ship debris min lifetime (matches DEATH_DURATION)
|
|
||||||
constexpr float SHRINK_RATE = 1.0F; // Reducció de mida (1.0 = encoge a 0 al final del min_lifetime)
|
|
||||||
|
|
||||||
// Política de mort: passat el min_lifetime, el fragment mor quan la
|
// Rebot contra els límits del PLAYAREA (mateix patró que enemics/ship).
|
||||||
// seva velocity cau per sota d'aquest llindar. Així els fragments
|
// 0.7 = 70% de l'energia conservada al rebot.
|
||||||
// ràpids no "popen" en moviment.
|
constexpr float RESTITUTION_BOUNDS = 0.7F;
|
||||||
constexpr float MIN_SPEED_TO_DIE = 5.0F; // px/s — al cuadrat per evitar sqrt en update
|
|
||||||
constexpr float MIN_SPEED_TO_DIE_SQ = MIN_SPEED_TO_DIE * MIN_SPEED_TO_DIE;
|
|
||||||
|
|
||||||
// Rebot contra els límits del PLAYAREA (mateix patró que enemics/ship).
|
// Herència de velocity angular (trayectorias curvas)
|
||||||
// 0.7 = 70% de l'energia conservada al rebot.
|
constexpr float INHERITANCE_FACTOR_MIN = 0.7F; // Mínimo 70% del drotacio heredat
|
||||||
constexpr float RESTITUTION_BOUNDS = 0.7F;
|
constexpr float INHERITANCE_FACTOR_MAX = 1.0F; // Màxim 100% del drotacio heredat
|
||||||
|
constexpr float FRICCIO_ANGULAR = 0.5F; // Desacceleració angular (rad/s²)
|
||||||
|
|
||||||
// Herència de velocity angular (trayectorias curvas)
|
// Velocity heredada de la nau a l'explosió (80% del feel original).
|
||||||
constexpr float INHERITANCE_FACTOR_MIN = 0.7F; // Mínimo 70% del drotacio heredat
|
constexpr float SHIP_VELOCITY_INHERITANCE = 0.8F;
|
||||||
constexpr float INHERITANCE_FACTOR_MAX = 1.0F; // Màxim 100% del drotacio heredat
|
|
||||||
constexpr float FRICCIO_ANGULAR = 0.5F; // Desacceleració angular (rad/s²)
|
|
||||||
|
|
||||||
// Velocity heredada de la nau a l'explosió (80% del feel original).
|
// Velocity heredada de l'enemic a l'explosió (palanca per a tuneo).
|
||||||
constexpr float SHIP_VELOCITY_INHERITANCE = 0.8F;
|
// 1.0 = inèrcia completa; >1.0 amplifica la deriva; <1.0 la atenua.
|
||||||
|
constexpr float ENEMY_VELOCITY_INHERITANCE = 1.0F;
|
||||||
|
|
||||||
// Velocity heredada de l'enemic a l'explosió (palanca per a tuneo).
|
// Tuneig específic de l'explosió d'enemic (overrides als defaults
|
||||||
// 1.0 = inèrcia completa; >1.0 amplifica la deriva; <1.0 la atenua.
|
// que es passen com a paràmetres opcionals a explode()).
|
||||||
constexpr float ENEMY_VELOCITY_INHERITANCE = 1.0F;
|
constexpr float ENEMY_LIFETIME = 2.5F; // Vida mínima del debris (s) — els que segueixen movent-se viuen més
|
||||||
|
constexpr float ENEMY_FRICTION = -30.0F; // Fricció més suau perquè s'estenguin més
|
||||||
|
constexpr int ENEMY_SEGMENT_MULTIPLIER = 1; // Sense còpies (5 cares = 5 trossos); >1 produeix grups sincronitzats
|
||||||
|
|
||||||
// Tuneig específic de l'explosió d'enemic (overrides als defaults
|
// Angular velocity sin for trajectory inheritance
|
||||||
// que es passen com a paràmetres opcionals a explode()).
|
// Excess above this threshold is converted to tangential linear velocity
|
||||||
constexpr float ENEMY_LIFETIME = 2.5F; // Vida mínima del debris (s) — els que segueixen movent-se viuen més
|
// Prevents "vortex trap" problem with high-rotation enemies
|
||||||
constexpr float ENEMY_FRICTION = -30.0F; // Fricció més suau perquè s'estenguin més
|
constexpr float SPEED_ROT_MAX = 1.5F; // rad/s (~86°/s)
|
||||||
constexpr int ENEMY_SEGMENT_MULTIPLIER = 1; // Sense còpies (5 cares = 5 trossos); >1 produeix grups sincronitzats
|
|
||||||
|
|
||||||
// Angular velocity sin for trajectory inheritance
|
} // namespace Defaults::Physics::Debris
|
||||||
// Excess above this threshold is converted to tangential linear velocity
|
|
||||||
// Prevents "vortex trap" problem with high-rotation enemies
|
|
||||||
constexpr float SPEED_ROT_MAX = 1.5F; // rad/s (~86°/s)
|
|
||||||
} // namespace Debris
|
|
||||||
|
|
||||||
} // namespace Defaults::Physics
|
|
||||||
|
|||||||
@@ -14,38 +14,39 @@
|
|||||||
#include "core/rendering/shape_renderer.hpp"
|
#include "core/rendering/shape_renderer.hpp"
|
||||||
#include "core/types.hpp"
|
#include "core/types.hpp"
|
||||||
#include "game/constants.hpp"
|
#include "game/constants.hpp"
|
||||||
|
#include "game/entities/bullet_config.hpp"
|
||||||
|
#include "game/entities/bullet_registry.hpp"
|
||||||
|
|
||||||
Bullet::Bullet(Rendering::Renderer* renderer)
|
Bullet::Bullet(Rendering::Renderer* renderer)
|
||||||
: Entity(renderer) {
|
: Entity(renderer),
|
||||||
// Brightness específico para balas
|
config_(&BulletRegistry::get()) {
|
||||||
brightness_ = Defaults::Brightness::BALA;
|
brightness_ = Defaults::Brightness::BALA;
|
||||||
|
|
||||||
// Configuración del cuerpo físico.
|
// Cinemàtiques pures: no col·lisionen al PhysicsWorld (body_.radius = 0).
|
||||||
// Las balas son cinemáticas: no colisionan con otros bodies ni paredes.
|
// El gameplay (GameScene) gestiona els hits via checkCollisionSwept i la
|
||||||
// El gameplay (GameScene) gestiona los hits con check_collision y la
|
// sortida del PLAYAREA.
|
||||||
// salida del PLAYAREA. Por eso radius=0 en el world (no participa en
|
body_.setMass(config_->physics.mass);
|
||||||
// resolveBodyCollisions ni resolveBoundsCollisions).
|
body_.radius = 0.0F;
|
||||||
body_.setMass(0.5F); // Ligera (no afecta a nadie, pero por consistencia)
|
body_.restitution = config_->physics.restitution;
|
||||||
body_.radius = 0.0F; // Sin colisión física (cinemática pura)
|
body_.linear_damping = config_->physics.linear_damping;
|
||||||
body_.restitution = 0.0F; // Irrelevante (no rebota)
|
body_.angular_damping = config_->physics.angular_damping;
|
||||||
body_.linear_damping = 0.0F; // Sin fricción (movimiento rectilíneo uniforme)
|
|
||||||
body_.angular_damping = 0.0F;
|
|
||||||
|
|
||||||
// Cargar shape compartida desde archivo
|
shape_ = Graphics::ShapeLoader::load(config_->shape.path);
|
||||||
shape_ = Graphics::ShapeLoader::load("bullet.shp");
|
|
||||||
if (!shape_ || !shape_->isValid()) {
|
if (!shape_ || !shape_->isValid()) {
|
||||||
std::cerr << "[Bullet] Error: no s'ha pogut load bullet.shp" << '\n';
|
std::cerr << "[Bullet] Error: no s'ha pogut carregar " << config_->shape.path << '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Radi de col·lisió derivat del cercle circumscrit de la shape.
|
||||||
|
const float BOUNDING = (shape_ != nullptr) ? shape_->getBoundingRadius() : 0.0F;
|
||||||
|
collision_radius_ = BOUNDING * config_->shape.scale * config_->shape.collision_factor;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bullet::init() {
|
void Bullet::init() {
|
||||||
// Inicialment inactiva
|
|
||||||
is_active_ = false;
|
is_active_ = false;
|
||||||
center_ = {.x = 0.0F, .y = 0.0F};
|
center_ = {.x = 0.0F, .y = 0.0F};
|
||||||
prev_position_ = {.x = 0.0F, .y = 0.0F};
|
prev_position_ = {.x = 0.0F, .y = 0.0F};
|
||||||
angle_ = 0.0F;
|
angle_ = 0.0F;
|
||||||
|
|
||||||
// Reset del cuerpo físico
|
|
||||||
body_.position = Vec2{};
|
body_.position = Vec2{};
|
||||||
body_.velocity = Vec2{};
|
body_.velocity = Vec2{};
|
||||||
body_.angle = 0.0F;
|
body_.angle = 0.0F;
|
||||||
@@ -54,19 +55,15 @@ void Bullet::init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Bullet::fire(const Vec2& position, float angle, uint8_t owner_id, float bullet_speed) {
|
void Bullet::fire(const Vec2& position, float angle, uint8_t owner_id, float bullet_speed) {
|
||||||
// Activar bullet
|
|
||||||
is_active_ = true;
|
is_active_ = true;
|
||||||
|
|
||||||
// Almacenar propietario (0=P1, 1=P2)
|
|
||||||
owner_id_ = owner_id;
|
owner_id_ = owner_id;
|
||||||
|
|
||||||
// Posición y orientación iniciales = ship
|
|
||||||
center_ = position;
|
center_ = position;
|
||||||
prev_position_ = position; // Al spawn no hi ha moviment encara: swept degenera a punt-cercle
|
prev_position_ = position; // spawn: swept degenera a punt-cercle
|
||||||
angle_ = angle;
|
angle_ = angle;
|
||||||
|
|
||||||
// Sincronizar el body físico: posición + velocidad cartesiana
|
// Sincronizar el body físic: posició + velocitat cartesiana.
|
||||||
// angle - PI/2 porque angle=0 apunta hacia arriba (eje Y negativo SDL)
|
// angle - PI/2 perquè angle=0 apunta cap amunt (eje Y negatiu SDL).
|
||||||
body_.position = position;
|
body_.position = position;
|
||||||
body_.angle = angle;
|
body_.angle = angle;
|
||||||
const float DIR_X = std::cos(angle - (Constants::PI / 2.0F));
|
const float DIR_X = std::cos(angle - (Constants::PI / 2.0F));
|
||||||
@@ -75,7 +72,6 @@ void Bullet::fire(const Vec2& position, float angle, uint8_t owner_id, float bul
|
|||||||
body_.angular_velocity = 0.0F;
|
body_.angular_velocity = 0.0F;
|
||||||
body_.clearAccumulators();
|
body_.clearAccumulators();
|
||||||
|
|
||||||
// Reproducir sonido de disparo láser
|
|
||||||
Audio::get()->playSound(Defaults::Sound::LASER, Audio::Group::GAME);
|
Audio::get()->playSound(Defaults::Sound::LASER, Audio::Group::GAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,24 +83,18 @@ void Bullet::update(float /*delta_time*/) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Bullet::postUpdate(float /*delta_time*/) {
|
void Bullet::postUpdate(float /*delta_time*/) {
|
||||||
// Captura la posició al final del frame anterior abans de sobreescriure center_;
|
|
||||||
// així el sistema de col·lisions pot fer swept (segment-vs-cercle) entre prev_position_
|
|
||||||
// i la nova center_, evitant tunneling a velocitats altes.
|
|
||||||
prev_position_ = center_;
|
prev_position_ = center_;
|
||||||
center_ = body_.position;
|
center_ = body_.position;
|
||||||
// angle_ no cambia (las balas no rotan visualmente).
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bullet::desactivar() {
|
void Bullet::desactivar() {
|
||||||
is_active_ = false;
|
is_active_ = false;
|
||||||
// Detener el cuerpo físico para que no acumule deriva mientras inactiva.
|
|
||||||
body_.velocity = Vec2{};
|
body_.velocity = Vec2{};
|
||||||
body_.angular_velocity = 0.0F;
|
body_.angular_velocity = 0.0F;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bullet::draw() const {
|
void Bullet::draw() const {
|
||||||
if (is_active_ && shape_) {
|
if (is_active_ && shape_) {
|
||||||
// Les bales roten segons l'angle de trayectòria (estático tras disparo)
|
Rendering::renderShape(renderer_, shape_, center_, angle_, 1.0F, 1.0F, brightness_, config_->colors.normal);
|
||||||
Rendering::renderShape(renderer_, shape_, center_, angle_, 1.0F, 1.0F, brightness_, Defaults::Palette::BULLET);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,12 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
#include "core/defaults.hpp"
|
|
||||||
#include "core/entities/entity.hpp"
|
#include "core/entities/entity.hpp"
|
||||||
#include "core/types.hpp"
|
#include "core/types.hpp"
|
||||||
|
|
||||||
|
// Forward declaration — la definició completa s'inclou només al .cpp.
|
||||||
|
struct BulletConfig;
|
||||||
|
|
||||||
class Bullet : public Entities::Entity {
|
class Bullet : public Entities::Entity {
|
||||||
public:
|
public:
|
||||||
Bullet()
|
Bullet()
|
||||||
@@ -25,25 +27,25 @@ class Bullet : public Entities::Entity {
|
|||||||
// Override: Interfaz de Entity
|
// Override: Interfaz de Entity
|
||||||
[[nodiscard]] auto isActive() const -> bool override { return is_active_; }
|
[[nodiscard]] auto isActive() const -> bool override { return is_active_; }
|
||||||
|
|
||||||
// Override: Interfaz de colisión (gameplay-level: PLAYAREA bounds-check)
|
// Override: Interfaz de colisión (radi derivat al ctor des del shape).
|
||||||
[[nodiscard]] auto getCollisionRadius() const -> float override {
|
[[nodiscard]] auto getCollisionRadius() const -> float override { return collision_radius_; }
|
||||||
return Defaults::Entities::BULLET_RADIUS;
|
|
||||||
}
|
|
||||||
[[nodiscard]] auto isCollidable() const -> bool override {
|
[[nodiscard]] auto isCollidable() const -> bool override {
|
||||||
return is_active_;
|
return is_active_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Configuració associada (vàlida des del ctor — la bala l'agafa del BulletRegistry).
|
||||||
|
[[nodiscard]] auto getConfig() const -> const BulletConfig& { return *config_; }
|
||||||
|
|
||||||
// Getters (API pública sin cambios)
|
// Getters (API pública sin cambios)
|
||||||
[[nodiscard]] auto getOwnerId() const -> uint8_t { return owner_id_; }
|
[[nodiscard]] auto getOwnerId() const -> uint8_t { return owner_id_; }
|
||||||
// Posició al final del frame anterior, per a CCD segment-vs-cercle.
|
|
||||||
[[nodiscard]] auto getPrevPosition() const -> const Vec2& { return prev_position_; }
|
[[nodiscard]] auto getPrevPosition() const -> const Vec2& { return prev_position_; }
|
||||||
void desactivar();
|
void desactivar();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Miembros específicos de Bullet (heredados: renderer_, shape_, center_, angle_, brightness_, body_).
|
const BulletConfig* config_{nullptr}; // apunta al BulletRegistry; vàlid post-ctor
|
||||||
// Inicializados en la declaración para que tanto el ctor por defecto como el que toma renderer
|
float collision_radius_{0.0F}; // derivat: shape.bounding_radius × scale × collision_factor
|
||||||
// dejen el objeto en estado coherente (proyectil inactivo, sin owner).
|
|
||||||
bool is_active_{false};
|
bool is_active_{false};
|
||||||
uint8_t owner_id_{0}; // 0=P1, 1=P2
|
uint8_t owner_id_{0}; // 0=P1, 1=P2
|
||||||
Vec2 prev_position_{}; // Posició al final del frame anterior (per a swept collision)
|
Vec2 prev_position_{}; // posició al final del frame anterior (swept CCD)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
// bullet_config.cpp - Implementació del parser de BulletConfig
|
||||||
|
// © 2026 JailDesigner
|
||||||
|
|
||||||
|
#include "game/entities/bullet_config.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <exception>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
auto parseColor(const fkyaml::node& node, SDL_Color& out) -> bool {
|
||||||
|
if (!node.is_sequence() || node.size() != 3) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto R = node[0].get_value<uint32_t>();
|
||||||
|
const auto G = node[1].get_value<uint32_t>();
|
||||||
|
const auto B = node[2].get_value<uint32_t>();
|
||||||
|
out = SDL_Color{
|
||||||
|
.r = static_cast<uint8_t>(R),
|
||||||
|
.g = static_cast<uint8_t>(G),
|
||||||
|
.b = static_cast<uint8_t>(B),
|
||||||
|
.a = 255};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parseShape(const fkyaml::node& node, BulletConfig::ShapeCfg& out) -> bool {
|
||||||
|
if (!node.contains("shape") || !node["shape"].contains("path")) {
|
||||||
|
std::cerr << "[BulletConfig] Error: falta 'shape.path'\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto& s = node["shape"];
|
||||||
|
out.path = s["path"].get_value<std::string>();
|
||||||
|
out.scale = s.contains("scale") ? s["scale"].get_value<float>() : 1.0F;
|
||||||
|
out.collision_factor = s.contains("collision_factor")
|
||||||
|
? s["collision_factor"].get_value<float>()
|
||||||
|
: 1.0F;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parsePhysics(const fkyaml::node& node, BulletConfig::PhysicsCfg& out) -> bool {
|
||||||
|
if (!node.contains("physics")) {
|
||||||
|
std::cerr << "[BulletConfig] Error: falta 'physics'\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto& p = node["physics"];
|
||||||
|
out.mass = p["mass"].get_value<float>();
|
||||||
|
out.restitution = p["restitution"].get_value<float>();
|
||||||
|
out.linear_damping = p["linear_damping"].get_value<float>();
|
||||||
|
out.angular_damping = p["angular_damping"].get_value<float>();
|
||||||
|
out.impact_momentum_factor = p["impact_momentum_factor"].get_value<float>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parseColors(const fkyaml::node& node, BulletConfig::ColorsCfg& out) -> bool {
|
||||||
|
if (!node.contains("colors") || !parseColor(node["colors"]["normal"], out.normal)) {
|
||||||
|
std::cerr << "[BulletConfig] Error: 'colors.normal' no és [r,g,b]\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
auto BulletConfig::fromYaml(const fkyaml::node& node) -> std::optional<BulletConfig> {
|
||||||
|
try {
|
||||||
|
BulletConfig cfg;
|
||||||
|
cfg.name = node.contains("name") ? node["name"].get_value<std::string>() : "bullet";
|
||||||
|
|
||||||
|
if (!parseShape(node, cfg.shape)) { return std::nullopt; }
|
||||||
|
if (!parsePhysics(node, cfg.physics)) { return std::nullopt; }
|
||||||
|
if (!parseColors(node, cfg.colors)) { return std::nullopt; }
|
||||||
|
|
||||||
|
return cfg;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "[BulletConfig] Excepció parsejant: " << e.what() << '\n';
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
// bullet_config.hpp - Configuració de la bala carregada des de YAML
|
||||||
|
// © 2026 JailDesigner
|
||||||
|
//
|
||||||
|
// Paral·lel a Player/EnemyConfig. Una sola instància a tot el joc (per ara);
|
||||||
|
// es comparteix entre totes les bales actives via BulletRegistry.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "external/fkyaml_node.hpp"
|
||||||
|
|
||||||
|
struct BulletConfig {
|
||||||
|
struct ShapeCfg {
|
||||||
|
std::string path;
|
||||||
|
float scale;
|
||||||
|
float collision_factor;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PhysicsCfg {
|
||||||
|
float mass;
|
||||||
|
float restitution;
|
||||||
|
float linear_damping;
|
||||||
|
float angular_damping;
|
||||||
|
float impact_momentum_factor; // factor de transferència bullet→enemic
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ColorsCfg {
|
||||||
|
SDL_Color normal;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
ShapeCfg shape;
|
||||||
|
PhysicsCfg physics;
|
||||||
|
ColorsCfg colors;
|
||||||
|
|
||||||
|
static auto fromYaml(const fkyaml::node& node) -> std::optional<BulletConfig>;
|
||||||
|
};
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// bullet_registry.cpp - Implementació del registre de bala
|
||||||
|
// © 2026 JailDesigner
|
||||||
|
|
||||||
|
#include "game/entities/bullet_registry.hpp"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "core/entities/entity_loader.hpp"
|
||||||
|
|
||||||
|
BulletConfig BulletRegistry::config;
|
||||||
|
bool BulletRegistry::loaded = false;
|
||||||
|
|
||||||
|
auto BulletRegistry::load() -> bool {
|
||||||
|
auto yaml = Entities::EntityLoader::load("bullet");
|
||||||
|
if (!yaml) {
|
||||||
|
std::cerr << "[BulletRegistry] Error: no s'ha pogut carregar bullet.yaml\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto cfg = BulletConfig::fromYaml(*yaml);
|
||||||
|
if (!cfg) {
|
||||||
|
std::cerr << "[BulletRegistry] Error: format invàlid a bullet.yaml\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
config = *cfg;
|
||||||
|
loaded = true;
|
||||||
|
std::cout << "[BulletRegistry] Configuració de bala carregada.\n";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto BulletRegistry::get() -> const BulletConfig& {
|
||||||
|
if (!loaded) {
|
||||||
|
std::cerr << "[BulletRegistry] FATAL: get() abans de load()\n";
|
||||||
|
std::exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// bullet_registry.hpp - Registre estàtic de la configuració de la bala
|
||||||
|
// © 2026 JailDesigner
|
||||||
|
//
|
||||||
|
// Una única instància per a tota la sessió. Es manté el patró registry
|
||||||
|
// (paral·lel a EnemyRegistry) tot i ser una sola entitat: si el dia de demà
|
||||||
|
// hi ha més tipus de bala (laser/plasma/etc.) només cal estendre-ho.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "game/entities/bullet_config.hpp"
|
||||||
|
|
||||||
|
class BulletRegistry {
|
||||||
|
public:
|
||||||
|
BulletRegistry() = delete;
|
||||||
|
|
||||||
|
// Carrega data/entities/bullet/bullet.yaml. Retorna false si falla.
|
||||||
|
static auto load() -> bool;
|
||||||
|
|
||||||
|
// Accés a la configuració. Avorta amb log fatal si load() no s'ha cridat
|
||||||
|
// o ha fallat.
|
||||||
|
static auto get() -> const BulletConfig&;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static BulletConfig config;
|
||||||
|
static bool loaded;
|
||||||
|
};
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
#include "core/locale/locale.hpp"
|
#include "core/locale/locale.hpp"
|
||||||
#include "core/system/scene_context.hpp"
|
#include "core/system/scene_context.hpp"
|
||||||
#include "core/system/service_menu.hpp"
|
#include "core/system/service_menu.hpp"
|
||||||
|
#include "game/entities/bullet_registry.hpp"
|
||||||
#include "game/entities/enemy_registry.hpp"
|
#include "game/entities/enemy_registry.hpp"
|
||||||
#include "game/entities/player_config.hpp"
|
#include "game/entities/player_config.hpp"
|
||||||
#include "game/stage_system/stage_loader.hpp"
|
#include "game/stage_system/stage_loader.hpp"
|
||||||
@@ -72,6 +73,13 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context)
|
|||||||
std::exit(EXIT_FAILURE);
|
std::exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Carregar la configuració de la bala. Cal abans de construir el pool de
|
||||||
|
// bullets, ja que cada Bullet llegeix el registry al seu ctor.
|
||||||
|
if (!BulletRegistry::load()) {
|
||||||
|
std::cerr << "[GameScene] FATAL: no s'ha pogut carregar bullet.yaml\n";
|
||||||
|
std::exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
// Inicialitzar naves: P1 amb el shape del YAML, P2 amb override visual.
|
// Inicialitzar naves: P1 amb el shape del YAML, P2 amb override visual.
|
||||||
ships_[0] = Ship(sdl.getRenderer(), *player_config); // Jugador 1: nau estàndard
|
ships_[0] = Ship(sdl.getRenderer(), *player_config); // Jugador 1: nau estàndard
|
||||||
ships_[1] = Ship(sdl.getRenderer(), *player_config, "ship2.shp"); // Jugador 2: interceptor amb ales
|
ships_[1] = Ship(sdl.getRenderer(), *player_config, "ship2.shp"); // Jugador 2: interceptor amb ales
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "core/physics/collision.hpp"
|
#include "core/physics/collision.hpp"
|
||||||
#include "core/types.hpp"
|
#include "core/types.hpp"
|
||||||
#include "game/constants.hpp"
|
#include "game/constants.hpp"
|
||||||
|
#include "game/entities/bullet_config.hpp"
|
||||||
#include "game/entities/enemy_config.hpp"
|
#include "game/entities/enemy_config.hpp"
|
||||||
|
|
||||||
namespace Systems::Collision {
|
namespace Systems::Collision {
|
||||||
@@ -80,7 +81,7 @@ namespace Systems::Collision {
|
|||||||
0.0F, // sense velocity angular heretada
|
0.0F, // sense velocity angular heretada
|
||||||
0.0F, // sense rotació visual heretada
|
0.0F, // sense rotació visual heretada
|
||||||
Defaults::Sound::HIT,
|
Defaults::Sound::HIT,
|
||||||
Defaults::Palette::BULLET,
|
bullet.getConfig().colors.normal,
|
||||||
Defaults::Physics::Debris::TEMPS_VIDA,
|
Defaults::Physics::Debris::TEMPS_VIDA,
|
||||||
Defaults::Physics::Debris::ACCELERACIO,
|
Defaults::Physics::Debris::ACCELERACIO,
|
||||||
1); // sense duplicat de segments
|
1); // sense duplicat de segments
|
||||||
@@ -96,7 +97,7 @@ namespace Systems::Collision {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (auto& enemy : ctx.enemies) {
|
for (auto& enemy : ctx.enemies) {
|
||||||
if (!Physics::checkCollisionSwept(bullet.getPrevPosition(), bullet.getCenter(), Defaults::Entities::BULLET_RADIUS, enemy, AMPLIFIER)) {
|
if (!Physics::checkCollisionSwept(bullet.getPrevPosition(), bullet.getCenter(), bullet.getCollisionRadius(), enemy, AMPLIFIER)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +106,7 @@ namespace Systems::Collision {
|
|||||||
// (m·v) multiplicat pel factor de transferència. Direcció = vector
|
// (m·v) multiplicat pel factor de transferència. Direcció = vector
|
||||||
// velocity de la bala (cap a on viatjava).
|
// velocity de la bala (cap a on viatjava).
|
||||||
const Vec2 IMPULSE = bullet.getBody().velocity *
|
const Vec2 IMPULSE = bullet.getBody().velocity *
|
||||||
(bullet.getBody().mass * Defaults::Physics::Bullet::IMPACT_MOMENTUM_FACTOR);
|
(bullet.getBody().mass * bullet.getConfig().physics.impact_momentum_factor);
|
||||||
enemy.applyImpulse(IMPULSE);
|
enemy.applyImpulse(IMPULSE);
|
||||||
|
|
||||||
const uint8_t SHOOTER = bullet.getOwnerId();
|
const uint8_t SHOOTER = bullet.getOwnerId();
|
||||||
@@ -244,7 +245,7 @@ namespace Systems::Collision {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Physics::checkCollisionSwept(bullet.getPrevPosition(), bullet.getCenter(), Defaults::Entities::BULLET_RADIUS, ctx.ships[player_id], AMPLIFIER)) {
|
if (!Physics::checkCollisionSwept(bullet.getPrevPosition(), bullet.getCenter(), bullet.getCollisionRadius(), ctx.ships[player_id], AMPLIFIER)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +254,7 @@ namespace Systems::Collision {
|
|||||||
// de la bala a la nau ABANS de on_player_hit perquè tocado()
|
// de la bala a la nau ABANS de on_player_hit perquè tocado()
|
||||||
// captura la velocitat per als debris (si no, queden quiets).
|
// captura la velocitat per als debris (si no, queden quiets).
|
||||||
const Vec2 BULLET_IMPULSE = bullet.getBody().velocity *
|
const Vec2 BULLET_IMPULSE = bullet.getBody().velocity *
|
||||||
(bullet.getBody().mass * Defaults::Physics::Bullet::IMPACT_MOMENTUM_FACTOR);
|
(bullet.getBody().mass * bullet.getConfig().physics.impact_momentum_factor);
|
||||||
ctx.ships[player_id].getBody().applyImpulse(BULLET_IMPULSE);
|
ctx.ships[player_id].getBody().applyImpulse(BULLET_IMPULSE);
|
||||||
ctx.on_player_hit(player_id);
|
ctx.on_player_hit(player_id);
|
||||||
ctx.lives_per_player[BULLET_OWNER]++;
|
ctx.lives_per_player[BULLET_OWNER]++;
|
||||||
@@ -280,12 +281,12 @@ namespace Systems::Collision {
|
|||||||
float min_y;
|
float min_y;
|
||||||
float max_y;
|
float max_y;
|
||||||
Constants::getPlayAreaBounds(min_x, max_x, min_y, max_y);
|
Constants::getPlayAreaBounds(min_x, max_x, min_y, max_y);
|
||||||
constexpr float R = Defaults::Entities::BULLET_RADIUS;
|
|
||||||
|
|
||||||
for (auto& bullet : bullets) {
|
for (auto& bullet : bullets) {
|
||||||
if (!bullet.isActive()) {
|
if (!bullet.isActive()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
const float R = bullet.getCollisionRadius();
|
||||||
const Vec2& pos = bullet.getCenter();
|
const Vec2& pos = bullet.getCenter();
|
||||||
if (pos.x < min_x + R || pos.x > max_x - R ||
|
if (pos.x < min_x + R || pos.x > max_x - R ||
|
||||||
pos.y < min_y + R || pos.y > max_y - R) {
|
pos.y < min_y + R || pos.y > max_y - R) {
|
||||||
|
|||||||
Reference in New Issue
Block a user