Files
coffee_crisis_arcade_edition/source/balloon.cpp
2025-07-19 22:38:01 +02:00

386 lines
11 KiB
C++

#include "balloon.h"
#include <algorithm> // Para clamp
#include <array> // Para array
#include <cmath> // 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, BalloonType type, BalloonSize size, float vel_x, float speed, Uint16 creation_timer, SDL_FRect play_area, std::shared_ptr<Texture> texture, const std::vector<std::string> &animation)
: sprite_(std::make_unique<AnimatedSprite>(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 BalloonType::BALLOON: {
vy_ = 0;
max_vy_ = 3.0f;
const int INDEX = static_cast<int>(size_);
gravity_ = param.balloon.settings.at(INDEX).grav;
default_vy_ = param.balloon.settings.at(INDEX).vel;
h_ = w_ = BALLOON_SIZE[INDEX];
power_ = BALLOON_POWER[INDEX];
menace_ = BALLOON_MENACE[INDEX];
score_ = BALLOON_SCORE[INDEX];
bouncing_sound_ = BALLOON_BOUNCING_SOUND[INDEX];
popping_sound_ = BALLOON_POPPING_SOUND[INDEX];
break;
}
case BalloonType::FLOATER: {
default_vy_ = max_vy_ = vy_ = fabs(vx_ * 2.0f);
gravity_ = 0.00f;
const int INDEX = static_cast<int>(size_);
h_ = w_ = BALLOON_SIZE[INDEX];
power_ = BALLOON_POWER[INDEX];
menace_ = BALLOON_MENACE[INDEX];
score_ = BALLOON_SCORE[INDEX];
bouncing_sound_ = BALLOON_BOUNCING_SOUND[INDEX];
popping_sound_ = BALLOON_POPPING_SOUND[INDEX];
break;
}
case BalloonType::POWERBALL: {
constexpr int INDEX = 3;
h_ = w_ = BALLOON_SIZE[4];
bouncing_sound_ = BALLOON_BOUNCING_SOUND[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<float>(x - (w_ / 2));
const int MIN_X = play_area_.x;
const int MAX_X = play_area_.w - w_;
x_ = std::clamp(x_, static_cast<float>(MIN_X), static_cast<float>(MAX_X));
}
// Pinta el globo en la pantalla
void Balloon::render() {
if (type_ == BalloonType::POWERBALL) {
// Renderiza el fondo azul
{
auto sp = std::make_unique<Sprite>(sprite_->getTexture(), sprite_->getPosition());
sp->setSpriteClip(0, 0, BALLOON_SIZE[4], BALLOON_SIZE[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>(sprite_->getTexture(), sprite_->getPosition());
sp->setSpriteClip(BALLOON_SIZE[4] * 2, 0, BALLOON_SIZE[4], BALLOON_SIZE[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() {
// 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 = play_area_.x - CLIP;
const float MAX_X = play_area_.x + play_area_.w - w_ + CLIP;
if (x_ < MIN_X || x_ > MAX_X) {
if (bouncing_sound_enabled_)
playSound(bouncing_sound_);
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 solo si el globo va de subida
if (vy_ < 0) {
const int MIN_Y = play_area_.y;
if (y_ < MIN_Y) {
if (bouncing_sound_enabled_)
playSound(bouncing_sound_);
y_ = MIN_Y;
vy_ = -vy_;
enableBounce();
}
}
// Colisión en la parte inferior de la zona de juego
const int MAX_Y = play_area_.y + play_area_.h - h_;
if (y_ > MAX_Y) {
if (bouncing_sound_enabled_)
playSound(bouncing_sound_);
y_ = MAX_Y;
vy_ = -default_vy_;
if (type_ != BalloonType::POWERBALL) {
enableBounce();
} else {
setInvulnerable(false);
}
}
/*
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_;
}
}
}
// 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 = 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 BalloonType::POWERBALL:
creating_animation = "powerball";
normal_animation = "powerball";
break;
case BalloonType::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<int>(x_) + collider_.r;
collider_.y = static_cast<int>(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);
}
// 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();
if (++bouncing_.counter / bouncing_.speed >= MAX_BOUNCE) {
disableBounce();
}
}
}
// 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) {
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;
}