// bullet.cpp - Implementación de projectils de la ship // © 2026 JailDesigner #include "game/entities/bullet.hpp" #include #include #include #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::fire(const Vec2& position, float angle, uint8_t owner_id, float bullet_speed) { // 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 * 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*/) { // No-op: la desactivació per fora-de-zone viu a // Systems::Collision::desactivateOutOfBoundsBullets() perquè així té accés // al DebrisManager i pot generar el "trencament" visual de la bala alhora. // El moviment l'integra PhysicsWorld; postUpdate sincronitza center_ i prev_position_. } 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); } }