486 lines
14 KiB
C++
486 lines
14 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),
|
|
speed_(config.speed),
|
|
play_area_(config.play_area),
|
|
sound_(config.sound) {
|
|
switch (type_) {
|
|
case Type::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_ = 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;
|
|
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();
|
|
}
|
|
|
|
// 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 (frame-based)
|
|
void Balloon::move() {
|
|
if (isStopped()) {
|
|
return;
|
|
}
|
|
|
|
handleHorizontalMovement();
|
|
handleVerticalMovement();
|
|
applyGravity();
|
|
}
|
|
|
|
// 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() {
|
|
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::handleHorizontalMovement(float deltaTime) {
|
|
// Convertir deltaTime (milisegundos) a factor de frame (asumiendo 60fps)
|
|
float frameFactor = deltaTime / (1000.0f / 60.0f);
|
|
x_ += vx_ * speed_ * frameFactor;
|
|
|
|
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();
|
|
}
|
|
|
|
void Balloon::handleVerticalMovement(float deltaTime) {
|
|
// Convertir deltaTime (milisegundos) a factor de frame (asumiendo 60fps)
|
|
float frameFactor = deltaTime / (1000.0f / 60.0f);
|
|
y_ += vy_ * speed_ * frameFactor;
|
|
|
|
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::applyGravity(float deltaTime) {
|
|
// Convertir deltaTime (milisegundos) a factor de frame (asumiendo 60fps)
|
|
float frameFactor = deltaTime / (1000.0f / 60.0f);
|
|
|
|
travel_y_ += speed_ * frameFactor;
|
|
|
|
if (travel_y_ >= 1.0F) {
|
|
travel_y_ -= 1.0F;
|
|
vy_ += gravity_;
|
|
}
|
|
}
|
|
|
|
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 (frame-based)
|
|
void Balloon::update() {
|
|
move();
|
|
updateState();
|
|
updateBounceEffect();
|
|
shiftSprite();
|
|
shiftColliders();
|
|
sprite_->update();
|
|
++counter_;
|
|
}
|
|
|
|
// 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);
|
|
// Convertir deltaTime (milisegundos) a factor de frame (asumiendo 60fps)
|
|
float frameFactor = deltaTime / (1000.0f / 60.0f);
|
|
counter_ += frameFactor;
|
|
}
|
|
|
|
// Actualiza los estados del globo (frame-based)
|
|
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 (static_cast<int>(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();
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
// Convertir deltaTime (milisegundos) a factor de frame (asumiendo 60fps)
|
|
float frameFactor = deltaTime / (1000.0f / 60.0f);
|
|
|
|
// Desplaza lentamente el globo hacia abajo y hacia un lado
|
|
// Cada 10 frames (aproximadamente cada 166ms a 60fps)
|
|
movement_accumulator_ += frameFactor;
|
|
|
|
if (movement_accumulator_ >= 10.0f) {
|
|
movement_accumulator_ -= 10.0f;
|
|
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_ -= frameFactor;
|
|
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
|
|
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_);
|
|
}
|
|
|
|
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;
|
|
} |