#include "balloon.h" #include // Para clamp #include // Para fabs #include "animated_sprite.h" // Para AnimatedSprite #include "param.h" // Para Param, param, ParamBalloon, ParamGame #include "sprite.h" // Para Sprite #include "texture.h" // Para Texture // Constructor Balloon::Balloon(float x, float y, BalloonType type, BalloonSize size, float vel_x, float speed, Uint16 creation_timer, 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) { switch (type_) { case BalloonType::BALLOON: { vy_ = 0; max_vy_ = 3.0f; const int index = static_cast(size_); gravity_ = param.balloon.at(index).grav; default_vy_ = param.balloon.at(index).vel; h_ = w_ = BALLOON_SIZE[index]; power_ = BALLOON_POWER[index]; menace_ = BALLOON_MENACE[index]; score_ = BALLOON_SCORE[index]; break; } case BalloonType::FLOATER: { default_vy_ = max_vy_ = vy_ = fabs(vx_ * 2.0f); gravity_ = 0.00f; const int index = static_cast(size_); h_ = w_ = BALLOON_SIZE[index]; power_ = BALLOON_POWER[index]; menace_ = BALLOON_MENACE[index]; score_ = BALLOON_SCORE[index]; break; } case BalloonType::POWERBALL: { const int index = 3; h_ = w_ = BALLOON_SIZE[index]; power_ = score_ = menace_ = 0; vy_ = 0; max_vy_ = 3.0f; gravity_ = param.balloon.at(index).grav; default_vy_ = param.balloon.at(index).vel; sprite_->disableRotate(); sprite_->setRotateSpeed(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 = param.game.play_area.rect.x; const int max_x = param.game.play_area.rect.w - w_; x_ = std::clamp(x_, static_cast(min_x), static_cast(max_x)); } // Pinta el globo en la pantalla void Balloon::render() { if (isBeingCreated()) { // Aplica alpha blending sprite_->getTexture()->setAlpha(255 - (int)((float)creation_counter_ * (255.0f / (float)creation_counter_ini_))); sprite_->render(); sprite_->getTexture()->setAlpha(255); } else { if (bouncing_.enabled) { // Aplica efecto de bouncing sprite_->setPos(x_ + bouncing_.despX, y_ + bouncing_.despY); sprite_->render(); sprite_->setPos(x_ - bouncing_.despX, y_ - bouncing_.despY); } else sprite_->render(); } // Añade la máscara del borde a la PowerBall if (type_ == BalloonType::POWERBALL && !isBeingCreated()) { auto sp = std::make_unique(sprite_->getTexture(), sprite_->getPosition()); sp->setSpriteClip(BALLOON_SIZE[3], 0, BALLOON_SIZE[3], BALLOON_SIZE[3]); sp->render(); } } // Actualiza la posición y estados del globo void Balloon::move() { // Comprueba si se puede mover if (!isStopped()) { // Mueve el globo en horizontal x_ += vx_ * speed_; // Colisión en las partes laterales de la zona de juego const int clip = 2; const float min_x = param.game.play_area.rect.x - clip; const float max_x = param.game.play_area.rect.w - w_ + clip; if (x_ < min_x || x_ > max_x) { x_ = std::clamp(x_, min_x, max_x); vx_ = -vx_; // Activa el efecto de rebote o invierte la rotación if (type_ == BalloonType::POWERBALL) sprite_->switchRotate(); else enableBounce(); } // Mueve el globo en vertical y_ += vy_ * speed_; // Colisión en la parte superior de la zona de juego const int min_y = param.game.play_area.rect.y; if (y_ < min_y) { y_ = min_y; vy_ = -vy_; if (type_ != BalloonType::POWERBALL) enableBounce(); } // Colisión en la parte inferior de la zona de juego const int max_y = param.game.play_area.rect.h - h_; if (y_ > max_y) { y_ = max_y; vy_ = -default_vy_; if (type_ != BalloonType::POWERBALL) enableBounce(); } /* 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 */ // Incrementa la variable que calcula la distancia acumulada en Y travel_y_ += speed_; // Si la distancia acumulada en Y es igual a la velocidad, se aplica la gravedad if (travel_y_ >= 1.0f) { // Quita el excedente travel_y_ -= 1.0f; // Aplica la gravedad al objeto sin pasarse de una velocidad máxima vy_ += gravity_; } } } // Deshabilita el globo y pone a cero todos los valores void Balloon::disable() { enabled_ = false; } // Explosiona el globo void Balloon::pop() { disable(); } // Actualiza al globo a su posicion, animación y controla los contadores void Balloon::update() { move(); updateState(); updateBounce(); 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 = param.game.play_area.rect.x; const int max_x = param.game.play_area.rect.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 BalloonType::POWERBALL: creating_animation = "powerball"; normal_animation = "powerball"; break; case BalloonType::FLOATER: creating_animation = "red"; normal_animation = "green"; break; default: creating_animation = "blue"; normal_animation = "orange"; break; } // Establece el frame de animación if (use_reversed_colors_) sprite_->setCurrentAnimation(creating_animation); else sprite_->setCurrentAnimation(isBeingCreated() ? creating_animation : normal_animation); } // Comprueba si el globo está habilitado bool Balloon::isEnabled() const { return enabled_; } // Obtiene del valor de la variable float Balloon::getPosX() const { return x_; } // Obtiene del valor de la variable float Balloon::getPosY() const { return y_; } // Obtiene del valor de la variable int Balloon::getWidth() const { return w_; } // Obtiene del valor de la variable int Balloon::getHeight() const { return h_; } // Establece el valor de la variable void Balloon::setVelY(float vel_y) { vy_ = vel_y; } // Establece el valor de la variable void Balloon::setSpeed(float speed) { speed_ = speed; } // Obtiene el tamaño del globo BalloonSize Balloon::getSize() const { return size_; } // Obtiene el tipo de globo BalloonType Balloon::getType() const { return type_; } // Detiene el globo void Balloon::stop() { stopped_ = true; if (type_ == BalloonType::POWERBALL) sprite_->disableRotate(); } // Pone el globo en movimiento void Balloon::start() { stopped_ = false; if (type_ == BalloonType::POWERBALL) sprite_->enableRotate(); } // Obtiene del valor de la variable bool Balloon::isStopped() const { return stopped_; } // Establece el valor de la variable void Balloon::setInvulnerable(bool value) { invulnerable_ = value; } // Obtiene del valor de la variable bool Balloon::isInvulnerable() const { return invulnerable_; } // Obtiene del valor de la variable bool Balloon::isBeingCreated() const { return being_created_; } // Obtiene del valor de la variable Uint16 Balloon::getScore() const { return score_; } // Obtiene el circulo de colisión Circle &Balloon::getCollider() { return collider_; } // 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_); } // Establece el nivel de zoom del sprite void Balloon::zoomSprite() { sprite_->setZoomW(bouncing_.zoomW); sprite_->setZoomH(bouncing_.zoomH); } // Obtiene le valor de la variable Uint8 Balloon::getMenace() const { return isEnabled() ? menace_ : 0; } // Obtiene le valor de la variable Uint8 Balloon::getPower() const { return power_; } // Activa el efecto void Balloon::enableBounce() { bouncing_.enabled = true; bouncing_.reset(); zoomSprite(); } // Detiene el efecto void Balloon::disableBounce() { bouncing_.enabled = false; bouncing_.reset(); zoomSprite(); } // Aplica el efecto void Balloon::updateBounce() { if (bouncing_.enabled) { const int index = bouncing_.counter / bouncing_.speed; bouncing_.zoomW = bouncing_.w[index]; bouncing_.zoomH = bouncing_.h[index]; zoomSprite(); const auto spriteClip = sprite_->getSpriteClip(); bouncing_.despX = spriteClip.w * (1.0f - bouncing_.zoomW); bouncing_.despY = spriteClip.h * (1.0f - bouncing_.zoomH); if (++bouncing_.counter / bouncing_.speed >= MAX_BOUNCE) disableBounce(); } } // Indica si el globo se puede explotar bool Balloon::canBePopped() const { return isEnabled() && !isBeingCreated(); } // Indica si el globo se puede destruir bool Balloon::canBeDestroyed() const { return isEnabled(); } // 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(); } // Indica si está usando el color alternativo bool Balloon::isUsingReversedColor() { return use_reversed_colors_; }