#include "balloon.h" #include // Para clamp #include // Para array #include // Para fabs #include "animated_sprite.h" // Para AnimatedSprite #include "audio.h" // Para Audio #include "param.h" // Para Param, ParamBalloon, param #include "sprite.h" // Para Sprite #include "texture.h" // Para Texture // Constructor Balloon::Balloon(const Config& config) : sprite_(std::make_unique(config.texture, config.animation)), x_(config.x), y_(config.y), vx_(config.vel_x), being_created_(config.creation_counter > 0), invulnerable_(config.creation_counter > 0), stopped_(config.creation_counter > 0), creation_counter_(config.creation_counter), creation_counter_ini_(config.creation_counter), type_(config.type), size_(config.size), game_tempo_(config.game_tempo), play_area_(config.play_area), sound_(config.sound) { switch (type_) { case Type::BALLOON: { vy_ = 0; max_vy_ = 3.0F; const int INDEX = static_cast(size_); gravity_ = param.balloon.settings.at(INDEX).grav; default_vy_ = param.balloon.settings.at(INDEX).vel; h_ = w_ = WIDTH.at(INDEX); power_ = POWER.at(INDEX); menace_ = MENACE.at(INDEX); score_ = SCORE.at(INDEX); sound_.bouncing_file = BOUNCING_SOUND.at(INDEX); sound_.popping_file = POPPING_SOUND.at(INDEX); break; } case Type::FLOATER: { default_vy_ = max_vy_ = vy_ = std::fabs(vx_ * 2.0F); gravity_ = 0.00F; const int INDEX = static_cast(size_); h_ = w_ = WIDTH.at(INDEX); power_ = POWER.at(INDEX); menace_ = MENACE.at(INDEX); score_ = SCORE.at(INDEX); sound_.bouncing_file = BOUNCING_SOUND.at(INDEX); sound_.popping_file = POPPING_SOUND.at(INDEX); break; } case Type::POWERBALL: { constexpr int INDEX = 3; h_ = w_ = WIDTH.at(4); sound_.bouncing_file = BOUNCING_SOUND.at(3); sound_.popping_file = "power_ball_explosion.wav"; power_ = score_ = menace_ = 0; vy_ = 0; max_vy_ = 3.0F; gravity_ = param.balloon.settings.at(INDEX).grav; default_vy_ = param.balloon.settings.at(INDEX).vel; sprite_->setRotate(config.creation_counter <= 0); sprite_->setRotateAmount(vx_ > 0.0F ? 2.0 : -2.0); break; } default: break; } // Configura el sprite sprite_->setWidth(w_); sprite_->setHeight(h_); shiftSprite(); // Alinea el circulo de colisión con el objeto collider_.r = w_ / 2; shiftColliders(); // Establece la animación a usar setAnimation(); // Si no se está creando (creation_counter = 0), asegurar estado activo if (!being_created_) { start(); setInvulnerable(false); } } // Centra el globo en la posición X void Balloon::alignTo(int x) { const float MIN_X = play_area_.x; const float MAX_X = play_area_.w - w_; x_ = std::clamp(x - (w_ / 2), MIN_X, MAX_X); } // Pinta el globo en la pantalla void Balloon::render() { if (type_ == Type::POWERBALL) { // Renderiza el fondo azul { auto sp = std::make_unique(sprite_->getTexture(), sprite_->getPosition()); sp->setSpriteClip(0, 0, WIDTH.at(4), WIDTH.at(4)); sp->render(); } // Renderiza la estrella if (!invulnerable_) { SDL_FPoint p = {24.0F, 24.0F}; sprite_->setRotatingCenter(p); sprite_->render(); } // Añade la máscara del borde y los reflejos { auto sp = std::make_unique(sprite_->getTexture(), sprite_->getPosition()); sp->setSpriteClip(WIDTH.at(4) * 2, 0, WIDTH.at(4), WIDTH.at(4)); sp->render(); } } else { // Renderizado para el resto de globos if (isBeingCreated()) { // Renderizado con transparencia sprite_->getTexture()->setAlpha(255 - (int)((float)creation_counter_ * (255.0F / (float)creation_counter_ini_))); sprite_->render(); sprite_->getTexture()->setAlpha(255); } else { // Renderizado normal sprite_->render(); } } } // Actualiza la posición y estados del globo (time-based) void Balloon::move(float deltaTime) { if (isStopped()) { return; } handleHorizontalMovement(deltaTime); handleVerticalMovement(deltaTime); applyGravity(deltaTime); } void Balloon::handleHorizontalMovement(float deltaTime) { // DeltaTime puro: velocidad (pixels/ms) * tempo * tiempo (ms) x_ += vx_ * game_tempo_ * deltaTime; const int CLIP = 2; const float MIN_X = play_area_.x - CLIP; const float MAX_X = play_area_.x + play_area_.w - w_ + CLIP; if (isOutOfHorizontalBounds(MIN_X, MAX_X)) { handleHorizontalBounce(MIN_X, MAX_X); } } void Balloon::handleVerticalMovement(float deltaTime) { // DeltaTime puro: velocidad (pixels/ms) * tempo * tiempo (ms) y_ += vy_ * game_tempo_ * deltaTime; if (shouldCheckTopCollision()) { handleTopCollision(); } handleBottomCollision(); } auto Balloon::isOutOfHorizontalBounds(float min_x, float max_x) const -> bool { return x_ < min_x || x_ > max_x; } void Balloon::handleHorizontalBounce(float min_x, float max_x) { playBouncingSound(); x_ = std::clamp(x_, min_x, max_x); vx_ = -vx_; if (type_ == Type::POWERBALL) { sprite_->switchRotate(); } else { enableBounceEffect(); } } auto Balloon::shouldCheckTopCollision() const -> bool { // Colisión en la parte superior solo si el globo va de subida return vy_ < 0; } void Balloon::handleTopCollision() { const int MIN_Y = play_area_.y; if (y_ < MIN_Y) { playBouncingSound(); y_ = MIN_Y; vy_ = -vy_; enableBounceEffect(); } } void Balloon::handleBottomCollision() { const int MAX_Y = play_area_.y + play_area_.h - h_; if (y_ > MAX_Y) { playBouncingSound(); y_ = MAX_Y; vy_ = -default_vy_; if (type_ != Type::POWERBALL) { enableBounceEffect(); } else { setInvulnerable(false); } } } void Balloon::applyGravity(float deltaTime) { // DeltaTime puro: aceleración (pixels/ms²) * tempo * tiempo (ms) vy_ += gravity_ * game_tempo_ * deltaTime; } void Balloon::playBouncingSound() { if (sound_.enabled && sound_.bouncing_enabled) { Audio::get()->playSound(sound_.bouncing_file); } } void Balloon::playPoppingSound() { if (sound_.enabled && sound_.poping_enabled) { Audio::get()->playSound(sound_.popping_file); } } // Actualiza al globo a su posicion, animación y controla los contadores (time-based) void Balloon::update(float deltaTime) { move(deltaTime); updateState(deltaTime); updateBounceEffect(); shiftSprite(); shiftColliders(); sprite_->update(deltaTime); // Contador interno con deltaTime puro counter_ += deltaTime; } // Actualiza los estados del globo (time-based) void Balloon::updateState(float deltaTime) { // Si se está creando if (isBeingCreated()) { // Actualiza el valor de las variables stop(); setInvulnerable(true); if (creation_counter_ > 0) { // Desplaza lentamente el globo hacia abajo y hacia un lado // Cada 166ms (equivalente a 10 frames a 60fps) movement_accumulator_ += deltaTime; if (movement_accumulator_ >= 166.0f) { movement_accumulator_ -= 166.0f; y_++; x_ += vx_ * 10.0f; // Movimiento equivalente a 10 frames de velocidad horizontal // Comprueba no se salga por los laterales const int MIN_X = play_area_.x; const int MAX_X = play_area_.w - w_; if (x_ < MIN_X || x_ > MAX_X) { // Corrige y cambia el sentido de la velocidad x_ -= vx_ * 10.0f; vx_ = -vx_; } } creation_counter_ -= deltaTime; if (creation_counter_ < 0) creation_counter_ = 0; } else { // El contador ha llegado a cero being_created_ = false; start(); setInvulnerable(false); setAnimation(); } } } // Establece la animación correspondiente al estado void Balloon::setAnimation() { std::string creating_animation; std::string normal_animation; switch (type_) { case Type::POWERBALL: creating_animation = "powerball"; normal_animation = "powerball"; break; case Type::FLOATER: creating_animation = param.balloon.color.at(2); normal_animation = param.balloon.color.at(3); break; default: creating_animation = param.balloon.color.at(0); normal_animation = param.balloon.color.at(1); break; } // Establece el frame de animación std::string chosen_animation; if (use_reversed_colors_) { chosen_animation = creating_animation; } else { chosen_animation = isBeingCreated() ? creating_animation : normal_animation; } sprite_->setCurrentAnimation(chosen_animation); } // Detiene el globo void Balloon::stop() { stopped_ = true; if (isPowerBall()) { sprite_->setRotate(!stopped_); } } // Pone el globo en movimiento void Balloon::start() { stopped_ = false; if (isPowerBall()) { sprite_->setRotate(!stopped_); } } // Alinea el circulo de colisión con la posición del objeto globo void Balloon::shiftColliders() { collider_.x = static_cast(x_) + collider_.r; collider_.y = static_cast(y_) + collider_.r; } // Alinea el sprite con la posición del objeto globo void Balloon::shiftSprite() { sprite_->setPosX(x_); sprite_->setPosY(y_); } void Balloon::enableBounceEffect() { bounce_effect_.enable(sprite_.get(), size_); } void Balloon::disableBounceEffect() { bounce_effect_.disable(sprite_.get()); } void Balloon::updateBounceEffect() { bounce_effect_.update(sprite_.get()); } // Pone el color alternativo en el globo void Balloon::useReverseColor() { if (!isBeingCreated()) { use_reversed_colors_ = true; setAnimation(); } } // Pone el color normal en el globo void Balloon::useNormalColor() { use_reversed_colors_ = false; setAnimation(); } // Explota el globo void Balloon::pop(bool should_sound) { if (should_sound) { playPoppingSound(); } enabled_ = false; }