Files
coffee_crisis_arcade_edition/source/balloon.cpp

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(const Config& config)
: sprite_(std::make_unique<AnimatedSprite>(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 * 60.0F; // Convert from frames to seconds (180 pixels/s)
const int INDEX = static_cast<int>(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<int>(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 * 60.0F; // Convert from frames to seconds (180 pixels/s)
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 ? 120.0 : -120.0); // Convert from 2 degrees/frame to 120 degrees/second
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>(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>(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 en segundos: velocidad (pixels/s) * tempo * tiempo (s)
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 en segundos: velocidad (pixels/s) * tempo * tiempo (s)
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 en segundos: aceleración (pixels/s²) * tempo * tiempo (s)
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 en segundos
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 10/60 segundos (equivalente a 10 frames a 60fps)
movement_accumulator_ += deltaTime;
constexpr float MOVEMENT_INTERVAL_S = 10.0f / 60.0f; // 10 frames = ~0.167s
if (movement_accumulator_ >= MOVEMENT_INTERVAL_S) {
movement_accumulator_ -= MOVEMENT_INTERVAL_S;
y_++;
x_ += vx_ / 60.0f; // Convierte de pixels/segundo a pixels/frame para movimiento discreto
// 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_ / 60.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<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_);
}
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;
}