Fase 6e: migrar Bullet al sistema de fisica vectorial
Las balas pasan a ser cinematicas dentro del PhysicsWorld: - body_.setMass(0.5), radius=0 (no colisionan fisicamente) - disparar() setea body_.position + body_.velocity cartesiana (140 px/s) - update() detecta salida del PLAYAREA via body_.position y desactiva - postUpdate() sincroniza center_ desde body_.position - desactivar() detiene el body para evitar deriva mientras inactiva GameScene registra los bodies en init() y llama postUpdate(). El gameplay sigue gestionando colisiones bullet-enemy/bullet-ship con check_collision (el radio gameplay es BULLET_RADIUS=3, expuesto via getCollisionRadius). Renames a camelBack (clang-tidy): get_owner_id->getOwnerId, get_grace_timer->getGraceTimer. MIGRATION_PLAN.md actualizado: Fase 6e cerrada, Fase 7 (SDL3 GPU) siguiente. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
c50ca23135
commit
9993b2d98c
4 files changed
+128
-109
No files matched your search
@@ -1,4 +1,4 @@
|
||||
// bullet.cpp - Implementació de projectils de la ship
|
||||
// bullet.cpp - Implementación de projectils de la ship
|
||||
// © 1999 Visente i Sergi (versión Pascal)
|
||||
// © 2025 Port a C++20 con SDL3
|
||||
|
||||
@@ -17,18 +17,33 @@
|
||||
#include "core/types.hpp"
|
||||
#include "game/constants.hpp"
|
||||
|
||||
namespace {
|
||||
// Velocidad escalar de las balas (px/s). Conserva el feel del Pascal original
|
||||
// (7 px/frame × 20 FPS = 140 px/s).
|
||||
constexpr float BULLET_SPEED = 140.0F;
|
||||
} // namespace
|
||||
|
||||
Bullet::Bullet(SDL_Renderer* renderer)
|
||||
: Entity(renderer),
|
||||
velocity_(0.0F),
|
||||
esta_(false),
|
||||
owner_id_(0),
|
||||
grace_timer_(0.0F) {
|
||||
// [NUEVO] Brightness específic per balas
|
||||
// Brightness específico para balas
|
||||
brightness_ = Defaults::Brightness::BALA;
|
||||
|
||||
// [NUEVO] Carregar shape compartida desde file
|
||||
shape_ = Graphics::ShapeLoader::load("bullet.shp");
|
||||
// Configuración del cuerpo físico.
|
||||
// Las balas son cinemáticas: no colisionan con otros bodies ni paredes.
|
||||
// El gameplay (GameScene) gestiona los hits con check_collision y la
|
||||
// salida del PLAYAREA. Por eso radius=0 en el world (no participa en
|
||||
// resolveBodyCollisions ni resolveBoundsCollisions).
|
||||
body_.setMass(0.5F); // Ligera (no afecta a nadie, pero por consistencia)
|
||||
body_.radius = 0.0F; // Sin colisión física (cinemática pura)
|
||||
body_.restitution = 0.0F; // Irrelevante (no rebota)
|
||||
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("bullet.shp");
|
||||
if (!shape_ || !shape_->isValid()) {
|
||||
std::cerr << "[Bullet] Error: no s'ha pogut load bullet.shp" << '\n';
|
||||
}
|
||||
@@ -39,77 +54,57 @@ void Bullet::init() {
|
||||
esta_ = false;
|
||||
center_ = {.x = 0.0F, .y = 0.0F};
|
||||
angle_ = 0.0F;
|
||||
velocity_ = 0.0F;
|
||||
grace_timer_ = 0.0F;
|
||||
|
||||
// Reset del cuerpo físico
|
||||
body_.position = Vec2{};
|
||||
body_.velocity = Vec2{};
|
||||
body_.angle = 0.0F;
|
||||
body_.angular_velocity = 0.0F;
|
||||
body_.clearAccumulators();
|
||||
}
|
||||
|
||||
void Bullet::disparar(const Vec2& position, float angle, uint8_t owner_id) {
|
||||
// Activar bullet i posicionar-la a la ship
|
||||
// Basat en joc_asteroides.cpp línies 188-200
|
||||
|
||||
// Activar bullet
|
||||
esta_ = true;
|
||||
|
||||
// Posición inicial = centro de la ship
|
||||
center_.x = position.x;
|
||||
center_.y = position.y;
|
||||
|
||||
// Angle = angle de la ship (dispara en la direcció que apunta)
|
||||
angle_ = angle;
|
||||
|
||||
// Almacenar propietario (0=P1, 1=P2)
|
||||
owner_id_ = owner_id;
|
||||
|
||||
// Velocidad alta (el juego Pascal original usava 7 px/frame)
|
||||
// 7 px/frame × 20 FPS = 140 px/s
|
||||
velocity_ = 140.0F;
|
||||
|
||||
// Activar grace period (prevents instant self-collision)
|
||||
grace_timer_ = Defaults::Game::BULLET_GRACE_PERIOD;
|
||||
|
||||
// Posición y orientación iniciales = ship
|
||||
center_ = position;
|
||||
angle_ = angle;
|
||||
|
||||
// Sincronizar el body físico: posición + velocidad cartesiana
|
||||
// angle - PI/2 porque angle=0 apunta hacia arriba (eje Y negativo SDL)
|
||||
body_.position = position;
|
||||
body_.angle = angle;
|
||||
const float DIR_X = std::cos(angle - (Constants::PI / 2.0F));
|
||||
const float DIR_Y = std::sin(angle - (Constants::PI / 2.0F));
|
||||
body_.velocity = Vec2{.x = DIR_X * BULLET_SPEED, .y = DIR_Y * BULLET_SPEED};
|
||||
body_.angular_velocity = 0.0F;
|
||||
body_.clearAccumulators();
|
||||
|
||||
// Reproducir sonido de disparo láser
|
||||
Audio::get()->playSound(Defaults::Sound::LASER, Audio::Group::GAME);
|
||||
}
|
||||
|
||||
void Bullet::update(float delta_time) {
|
||||
if (esta_) {
|
||||
// Decrementar grace timer
|
||||
if (grace_timer_ > 0.0F) {
|
||||
grace_timer_ -= delta_time;
|
||||
grace_timer_ = std::max(grace_timer_, 0.0F);
|
||||
}
|
||||
|
||||
mou(delta_time);
|
||||
if (!esta_) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Bullet::draw() const {
|
||||
if (esta_ && shape_) {
|
||||
// [NUEVO] Usar render_shape en lloc de rota_pol
|
||||
// Les balas roten segons l'angle de trajectòria
|
||||
Rendering::render_shape(renderer_, shape_, center_, angle_, 1.0F, 1.0F, brightness_);
|
||||
// Decrementar grace timer
|
||||
if (grace_timer_ > 0.0F) {
|
||||
grace_timer_ -= delta_time;
|
||||
grace_timer_ = std::max(grace_timer_, 0.0F);
|
||||
}
|
||||
}
|
||||
|
||||
void Bullet::mou(float delta_time) {
|
||||
// Moviment rectilini de la bullet
|
||||
// Basat en el codi Pascal original: procedure mou_bales
|
||||
// Copiat EXACTAMENT de joc_asteroides.cpp línies 396-419
|
||||
|
||||
// Calcular nueva posición (movement polar time-based)
|
||||
// velocity ya está en px/s (140 px/s), solo necesario multiplicar per delta_time
|
||||
float velocitat_efectiva = velocity_ * delta_time;
|
||||
|
||||
// Calcular desplaçament (angle-PI/2 perquè angle=0 apunta amunt)
|
||||
float dy = velocitat_efectiva * std::sin(angle_ - (Constants::PI / 2.0F));
|
||||
float dx = velocitat_efectiva * std::cos(angle_ - (Constants::PI / 2.0F));
|
||||
|
||||
// Acumulació directa con precisió subpíxel
|
||||
center_.y += dy;
|
||||
center_.x += dx;
|
||||
|
||||
// Desactivar si surt de la zona de juego (no rebota como los ORNIs)
|
||||
// CORRECCIÓ: Usar límits segurs con radi de la bullet
|
||||
// El movimiento real lo hace PhysicsWorld::update() (integración).
|
||||
// Aquí solo lógica de estado: detectar salida del PLAYAREA y desactivar.
|
||||
float min_x;
|
||||
float max_x;
|
||||
float min_y;
|
||||
@@ -120,8 +115,28 @@ void Bullet::mou(float delta_time) {
|
||||
min_y,
|
||||
max_y);
|
||||
|
||||
if (center_.x < min_x || center_.x > max_x ||
|
||||
center_.y < min_y || center_.y > max_y) {
|
||||
esta_ = false;
|
||||
if (body_.position.x < min_x || body_.position.x > max_x ||
|
||||
body_.position.y < min_y || body_.position.y > max_y) {
|
||||
desactivar();
|
||||
}
|
||||
}
|
||||
|
||||
void Bullet::postUpdate(float /*delta_time*/) {
|
||||
// Sincronizar mirror desde body_ tras la integración del world.
|
||||
center_ = body_.position;
|
||||
// angle_ no cambia (las balas no rotan visualmente).
|
||||
}
|
||||
|
||||
void Bullet::desactivar() {
|
||||
esta_ = false;
|
||||
// Detener el cuerpo físico para que no acumule deriva mientras inactiva.
|
||||
body_.velocity = Vec2{};
|
||||
body_.angular_velocity = 0.0F;
|
||||
}
|
||||
|
||||
void Bullet::draw() const {
|
||||
if (esta_ && shape_) {
|
||||
// Les bales roten segons l'angle de trayectòria (estático tras disparo)
|
||||
Rendering::render_shape(renderer_, shape_, center_, angle_, 1.0F, 1.0F, brightness_);
|
||||
}
|
||||
}
|
||||
@@ -20,31 +20,29 @@ class Bullet : public Entities::Entity {
|
||||
void init() override;
|
||||
void disparar(const Vec2& position, float angle, uint8_t owner_id);
|
||||
void update(float delta_time) override;
|
||||
void postUpdate(float delta_time) override;
|
||||
void draw() const override;
|
||||
|
||||
// Override: Interfície d'Entity
|
||||
[[nodiscard]] bool isActive() const override { return esta_; }
|
||||
// Override: Interfaz de Entity
|
||||
[[nodiscard]] auto isActive() const -> bool override { return esta_; }
|
||||
|
||||
// Override: Interfície de colisión
|
||||
[[nodiscard]] float getCollisionRadius() const override {
|
||||
// Override: Interfaz de colisión (gameplay-level: PLAYAREA bounds-check)
|
||||
[[nodiscard]] auto getCollisionRadius() const -> float override {
|
||||
return Defaults::Entities::BULLET_RADIUS;
|
||||
}
|
||||
[[nodiscard]] bool isCollidable() const override {
|
||||
[[nodiscard]] auto isCollidable() const -> bool override {
|
||||
return esta_ && grace_timer_ <= 0.0F;
|
||||
}
|
||||
|
||||
// Getters (API pública sin canvis)
|
||||
[[nodiscard]] bool esta_activa() const { return esta_; }
|
||||
[[nodiscard]] uint8_t get_owner_id() const { return owner_id_; }
|
||||
[[nodiscard]] float get_grace_timer() const { return grace_timer_; }
|
||||
void desactivar() { esta_ = false; }
|
||||
// Getters (API pública sin cambios)
|
||||
[[nodiscard]] auto esta_activa() const -> bool { return esta_; }
|
||||
[[nodiscard]] auto getOwnerId() const -> uint8_t { return owner_id_; }
|
||||
[[nodiscard]] auto getGraceTimer() const -> float { return grace_timer_; }
|
||||
void desactivar();
|
||||
|
||||
private:
|
||||
// Membres específics de Bullet (heretats: renderer_, shape_, center_, angle_, brightness_)
|
||||
float velocity_;
|
||||
// Miembros específicos de Bullet (heredados: renderer_, shape_, center_, angle_, brightness_, body_)
|
||||
bool esta_;
|
||||
uint8_t owner_id_; // 0=P1, 1=P2
|
||||
float grace_timer_; // Grace period timer (0.0 = vulnerable)
|
||||
|
||||
void mou(float delta_time);
|
||||
};
|
||||
@@ -205,9 +205,13 @@ void GameScene::init() {
|
||||
// DON'T call enemy.init() here - stage system handles spawning
|
||||
}
|
||||
|
||||
// Inicialitzar balas (now 6 instead of 3)
|
||||
// Inicialitzar balas (now 6 instead of 3).
|
||||
// Se registran en el physics_world para integración cinemática.
|
||||
// Como su body_.radius=0, no colisionan físicamente con nadie (las
|
||||
// colisiones de gameplay se gestionan en detectar_col·lisions_*).
|
||||
for (auto& bullet : bullets_) {
|
||||
bullet.init();
|
||||
physics_world_.addBody(&bullet.getBody());
|
||||
}
|
||||
|
||||
// [ELIMINAT] Iniciar música de juego (ara es gestiona en stage_manager)
|
||||
@@ -230,6 +234,9 @@ void GameScene::update(float delta_time) {
|
||||
for (auto& enemy : enemies_) {
|
||||
enemy.postUpdate(delta_time);
|
||||
}
|
||||
for (auto& bullet : bullets_) {
|
||||
bullet.postUpdate(delta_time);
|
||||
}
|
||||
|
||||
// Processar disparos (state-based, no event-based)
|
||||
if (game_over_state_ == GameOverState::NONE) {
|
||||
@@ -996,7 +1003,7 @@ void GameScene::detectar_col·lisions_bales_enemics() {
|
||||
}
|
||||
|
||||
// 2. Add score to the player who shot it
|
||||
uint8_t owner_id = bullet.get_owner_id();
|
||||
uint8_t owner_id = bullet.getOwnerId();
|
||||
score_per_player_[owner_id] += points;
|
||||
|
||||
// 3. Create floating score number
|
||||
@@ -1078,11 +1085,11 @@ void GameScene::detectar_col·lisions_bales_jugadors() {
|
||||
}
|
||||
|
||||
// Skip bullets in grace period (prevents instant self-collision)
|
||||
if (bullet.get_grace_timer() > 0.0F) {
|
||||
if (bullet.getGraceTimer() > 0.0F) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t bullet_owner = bullet.get_owner_id();
|
||||
uint8_t bullet_owner = bullet.getOwnerId();
|
||||
|
||||
// Check collision with BOTH players
|
||||
for (uint8_t player_id = 0; player_id < 2; player_id++) {
|
||||
|
||||
Reference in new issue
Block a user