#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(float x, float y, Type type, Size size, float vel_x, float speed, Uint16 creation_timer, SDL_FRect play_area, std::shared_ptr texture, const std::vector &animation) : sprite_(std::make_unique(texture, animation)), x_(x), y_(y), vx_(vel_x), being_created_(creation_timer > 0), invulnerable_(creation_timer > 0), stopped_(creation_timer > 0), creation_counter_(creation_timer), creation_counter_ini_(creation_timer), type_(type), size_(size), speed_(speed), play_area_(play_area) { 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); bouncing_sound_ = BOUNCING_SOUND.at(INDEX); popping_sound_ = POPPING_SOUND.at(INDEX); break; } case Type::FLOATER: { default_vy_ = max_vy_ = vy_ = 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); bouncing_sound_ = BOUNCING_SOUND.at(INDEX); popping_sound_ = POPPING_SOUND.at(INDEX); break; } case Type::POWERBALL: { constexpr int INDEX = 3; h_ = w_ = WIDTH.at(4); bouncing_sound_ = BOUNCING_SOUND.at(3); popping_sound_ = "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(creation_timer <= 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(); } // Centra el globo en la posición X void Balloon::alignTo(int x) { x_ = static_cast(x - (w_ / 2)); const int MIN_X = play_area_.x; const int MAX_X = play_area_.w - w_; x_ = std::clamp(x_, static_cast(MIN_X), static_cast(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 void Balloon::move() { if (isStopped()) { return; } handleHorizontalMovement(); handleVerticalMovement(); applyGravity(); } void Balloon::handleHorizontalMovement() { x_ += vx_ * speed_; 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() { y_ += vy_ * speed_; 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() { /* Para aplicar la gravedad, el diseño original la aplicaba en cada iteración del bucle Al añadir el modificador de velocidad se reduce la distancia que recorre el objeto y por tanto recibe mas gravedad. Para solucionarlo se va a aplicar la gravedad cuando se haya recorrido una distancia igual a la velocidad en Y, que era el cálculo inicial */ travel_y_ += speed_; if (travel_y_ >= 1.0F) { travel_y_ -= 1.0F; vy_ += gravity_; } } void Balloon::playBouncingSound() { if (bouncing_sound_enabled_) { playSound(bouncing_sound_); } } // Actualiza al globo a su posicion, animación y controla los contadores void Balloon::update() { move(); updateState(); updateBounceEffect(); shiftSprite(); shiftColliders(); sprite_->update(); ++counter_; } // Actualiza los estados del globo void Balloon::updateState() { // 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 if (creation_counter_ % 10 == 0) { y_++; x_ += vx_; // 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_; vx_ = -vx_; } } --creation_counter_; } 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 if (use_reversed_colors_) { sprite_->setCurrentAnimation(creating_animation); } else { sprite_->setCurrentAnimation(isBeingCreated() ? creating_animation : normal_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(); } // Reproduce sonido void Balloon::playSound(const std::string &name) const { if (!sound_enabled_) { return; } static auto *audio_ = Audio::get(); audio_->playSound(name); } // Explota el globo void Balloon::pop(bool should_sound) { if (should_sound) { if (poping_sound_enabled_) { playSound(popping_sound_); } } enabled_ = false; }