Files
orni-attack/source/game/entities/bullet.cpp
T

127 lines
4.5 KiB
C++

// bullet.cpp - Implementación de projectils de la ship
// © 2026 JailDesigner
#include "game/entities/bullet.hpp"
#include <cmath>
#include <cstdint>
#include <iostream>
#include "core/audio/audio.hpp"
#include "core/defaults.hpp"
#include "core/entities/entity.hpp"
#include "core/graphics/shape_loader.hpp"
#include "core/rendering/shape_renderer.hpp"
#include "core/types.hpp"
#include "game/constants.hpp"
Bullet::Bullet(Rendering::Renderer* renderer)
: Entity(renderer) {
// Brightness específico para balas
brightness_ = Defaults::Brightness::BALA;
// 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';
}
}
void Bullet::init() {
// Inicialment inactiva
is_active_ = false;
center_ = {.x = 0.0F, .y = 0.0F};
prev_position_ = {.x = 0.0F, .y = 0.0F};
angle_ = 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
is_active_ = true;
// Almacenar propietario (0=P1, 1=P2)
owner_id_ = owner_id;
// Posición y orientación iniciales = ship
center_ = position;
prev_position_ = position; // Al spawn no hi ha moviment encara: swept degenera a punt-cercle
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 * Defaults::Game::BULLET_SPEED, .y = DIR_Y * Defaults::Game::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 (!is_active_) {
return;
}
// El movimiento real lo hace PhysicsWorld::update() (integración).
// Aquí solo lógica de estado: detectar salida del PLAYAREA i desactivar.
// Sense marge de seguretat: la bala mor quan la seva aresta toca el border visual
// (centre a BULLET_RADIUS del límit). El MARGE_SEGURETAT de getSafePlayAreaBounds
// és per a spawn d'enemics, no per a desactivació de bales.
float min_x;
float max_x;
float min_y;
float max_y;
Constants::getPlayAreaBounds(min_x, max_x, min_y, max_y);
constexpr float R = Defaults::Entities::BULLET_RADIUS;
if (body_.position.x < min_x + R || body_.position.x > max_x - R ||
body_.position.y < min_y + R || body_.position.y > max_y - R) {
desactivar();
}
}
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_;
center_ = body_.position;
// angle_ no cambia (las balas no rotan visualmente).
}
void Bullet::desactivar() {
is_active_ = 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 (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_, Defaults::Palette::BULLET);
}
}