reestructuració
This commit is contained in:
386
source/game/entities/balloon.cpp
Normal file
386
source/game/entities/balloon.cpp
Normal file
@@ -0,0 +1,386 @@
|
||||
#include "balloon.hpp"
|
||||
|
||||
#include <algorithm> // Para clamp
|
||||
#include <array> // Para array
|
||||
#include <cmath> // Para fabs
|
||||
|
||||
#include "animated_sprite.hpp" // Para AnimatedSprite
|
||||
#include "audio.hpp" // Para Audio
|
||||
#include "param.hpp" // Para Param, ParamBalloon, param
|
||||
#include "sprite.hpp" // Para Sprite
|
||||
#include "texture.hpp" // 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 = {.x = 24.0F, .y = 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 - static_cast<int>(creation_counter_ * (255.0F / 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 delta_time) {
|
||||
if (isStopped()) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleHorizontalMovement(delta_time);
|
||||
handleVerticalMovement(delta_time);
|
||||
applyGravity(delta_time);
|
||||
}
|
||||
|
||||
void Balloon::handleHorizontalMovement(float delta_time) {
|
||||
// DeltaTime en segundos: velocidad (pixels/s) * tempo * tiempo (s)
|
||||
x_ += vx_ * game_tempo_ * delta_time;
|
||||
|
||||
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 delta_time) {
|
||||
// DeltaTime en segundos: velocidad (pixels/s) * tempo * tiempo (s)
|
||||
y_ += vy_ * game_tempo_ * delta_time;
|
||||
|
||||
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 delta_time) {
|
||||
// DeltaTime en segundos: aceleración (pixels/s²) * tempo * tiempo (s)
|
||||
vy_ += gravity_ * game_tempo_ * delta_time;
|
||||
}
|
||||
|
||||
void Balloon::playBouncingSound() const {
|
||||
if (sound_.enabled && sound_.bouncing_enabled) {
|
||||
Audio::get()->playSound(sound_.bouncing_file);
|
||||
}
|
||||
}
|
||||
|
||||
void Balloon::playPoppingSound() const {
|
||||
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 delta_time) {
|
||||
move(delta_time);
|
||||
updateState(delta_time);
|
||||
updateBounceEffect();
|
||||
shiftSprite();
|
||||
shiftColliders();
|
||||
sprite_->update(delta_time);
|
||||
// Contador interno con deltaTime en segundos
|
||||
counter_ += delta_time;
|
||||
}
|
||||
|
||||
// Actualiza los estados del globo (time-based)
|
||||
void Balloon::updateState(float delta_time) {
|
||||
// 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_ += delta_time;
|
||||
|
||||
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_ -= delta_time;
|
||||
creation_counter_ = std::max<float>(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;
|
||||
}
|
||||
305
source/game/entities/balloon.hpp
Normal file
305
source/game/entities/balloon.hpp
Normal file
@@ -0,0 +1,305 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para Uint8, Uint16, SDL_FRect, Uint32
|
||||
|
||||
#include <array> // Para array
|
||||
#include <memory> // Para allocator, shared_ptr, unique_ptr
|
||||
#include <string> // Para basic_string, string
|
||||
#include <string_view> // Para string_view
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "animated_sprite.hpp" // Para AnimatedSprite
|
||||
#include "utils.hpp" // Para Circle
|
||||
|
||||
class Texture;
|
||||
|
||||
// --- Clase Balloon ---
|
||||
class Balloon {
|
||||
public:
|
||||
// --- Constantes relacionadas con globos ---
|
||||
static constexpr int MAX_BOUNCE = 10; // Cantidad de elementos del vector de deformación
|
||||
|
||||
static constexpr std::array<int, 4> SCORE = {50, 100, 200, 400};
|
||||
static constexpr std::array<int, 4> POWER = {1, 3, 7, 15};
|
||||
static constexpr std::array<int, 4> MENACE = {1, 2, 4, 8};
|
||||
static constexpr std::array<int, 5> WIDTH = {10, 16, 26, 48, 49};
|
||||
|
||||
static constexpr std::array<std::string_view, 4> BOUNCING_SOUND = {
|
||||
"balloon_bounce0.wav",
|
||||
"balloon_bounce1.wav",
|
||||
"balloon_bounce2.wav",
|
||||
"balloon_bounce3.wav"};
|
||||
|
||||
static constexpr std::array<std::string_view, 4> POPPING_SOUND = {
|
||||
"balloon_pop0.wav",
|
||||
"balloon_pop1.wav",
|
||||
"balloon_pop2.wav",
|
||||
"balloon_pop3.wav"};
|
||||
|
||||
// Velocidades horizontales en pixels/segundo (convertidas desde 0.7 pixels/frame a 60fps)
|
||||
static constexpr float VELX_POSITIVE = 0.7F * 60.0F; // 42 pixels/segundo
|
||||
static constexpr float VELX_NEGATIVE = -0.7F * 60.0F; // -42 pixels/segundo
|
||||
|
||||
// Multiplicadores de tempo del juego (sin cambios, son puros multiplicadores)
|
||||
static constexpr std::array<float, 5> GAME_TEMPO = {0.60F, 0.70F, 0.80F, 0.90F, 1.00F};
|
||||
|
||||
static constexpr int POWERBALL_SCREENPOWER_MINIMUM = 10;
|
||||
static constexpr int POWERBALL_COUNTER = 8;
|
||||
|
||||
// --- Enums ---
|
||||
enum class Size : Uint8 {
|
||||
SMALL = 0, // Tamaño pequeño
|
||||
MEDIUM = 1, // Tamaño mediano
|
||||
LARGE = 2, // Tamaño grande
|
||||
EXTRALARGE = 3, // Tamaño extra grande
|
||||
};
|
||||
|
||||
enum class Type : Uint8 {
|
||||
BALLOON = 0, // Globo normal
|
||||
FLOATER = 1, // Globo flotante
|
||||
POWERBALL = 2, // Globo de poder
|
||||
};
|
||||
|
||||
// --- Estructura para manejo de sonido ---
|
||||
struct Sound {
|
||||
std::string bouncing_file; // Archivo de sonido al rebotar
|
||||
std::string popping_file; // Archivo de sonido al explotar
|
||||
bool bouncing_enabled = false; // Si debe sonar el globo al rebotar
|
||||
bool poping_enabled = true; // Si debe sonar el globo al explotar
|
||||
bool enabled = true; // Indica si los globos deben hacer algun sonido
|
||||
};
|
||||
|
||||
// --- Estructura de configuración para inicialización ---
|
||||
struct Config {
|
||||
float x = 0.0F;
|
||||
float y = 0.0F;
|
||||
Type type = Type::BALLOON;
|
||||
Size size = Size::EXTRALARGE;
|
||||
float vel_x = VELX_POSITIVE;
|
||||
float game_tempo = GAME_TEMPO.at(0);
|
||||
float creation_counter = 0.0F;
|
||||
SDL_FRect play_area = {.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F};
|
||||
std::shared_ptr<Texture> texture = nullptr;
|
||||
std::vector<std::string> animation;
|
||||
Sound sound;
|
||||
};
|
||||
|
||||
// --- Constructores y destructor ---
|
||||
Balloon(const Config& config);
|
||||
~Balloon() = default;
|
||||
|
||||
// --- Métodos principales ---
|
||||
void alignTo(int x); // Centra el globo en la posición X
|
||||
void render(); // Pinta el globo en la pantalla
|
||||
void move(float delta_time); // Actualiza la posición y estados del globo (time-based)
|
||||
void update(float delta_time); // Actualiza el globo (posición, animación, contadores) (time-based)
|
||||
void stop(); // Detiene el globo
|
||||
void start(); // Pone el globo en movimiento
|
||||
void pop(bool should_sound = false); // Explota el globo
|
||||
|
||||
// --- Métodos de color ---
|
||||
void useReverseColor(); // Pone el color alternativo en el globo
|
||||
void useNormalColor(); // Pone el color normal en el globo
|
||||
|
||||
// --- Getters ---
|
||||
[[nodiscard]] auto getPosX() const -> float { return x_; }
|
||||
[[nodiscard]] auto getPosY() const -> float { return y_; }
|
||||
[[nodiscard]] auto getWidth() const -> int { return w_; }
|
||||
[[nodiscard]] auto getHeight() const -> int { return h_; }
|
||||
[[nodiscard]] auto getSize() const -> Size { return size_; }
|
||||
[[nodiscard]] auto getType() const -> Type { return type_; }
|
||||
[[nodiscard]] auto getScore() const -> Uint16 { return score_; }
|
||||
auto getCollider() -> Circle& { return collider_; }
|
||||
[[nodiscard]] auto getMenace() const -> Uint8 { return isEnabled() ? menace_ : 0; }
|
||||
[[nodiscard]] auto getPower() const -> Uint8 { return power_; }
|
||||
[[nodiscard]] auto isStopped() const -> bool { return stopped_; }
|
||||
[[nodiscard]] auto isPowerBall() const -> bool { return type_ == Type::POWERBALL; }
|
||||
[[nodiscard]] auto isInvulnerable() const -> bool { return invulnerable_; }
|
||||
[[nodiscard]] auto isBeingCreated() const -> bool { return being_created_; }
|
||||
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; }
|
||||
[[nodiscard]] auto isUsingReversedColor() const -> bool { return use_reversed_colors_; }
|
||||
[[nodiscard]] auto canBePopped() const -> bool { return !isBeingCreated(); }
|
||||
|
||||
// --- Setters ---
|
||||
void setVelY(float vel_y) { vy_ = vel_y; }
|
||||
void setVelX(float vel_x) { vx_ = vel_x; }
|
||||
void alterVelX(float percent) { vx_ *= percent; }
|
||||
void setGameTempo(float tempo) { game_tempo_ = tempo; }
|
||||
void setInvulnerable(bool value) { invulnerable_ = value; }
|
||||
void setBouncingSound(bool value) { sound_.bouncing_enabled = value; }
|
||||
void setPoppingSound(bool value) { sound_.poping_enabled = value; }
|
||||
void setSound(bool value) { sound_.enabled = value; }
|
||||
|
||||
private:
|
||||
// --- Estructura para el efecto de rebote ---
|
||||
struct BounceEffect {
|
||||
private:
|
||||
static constexpr int BOUNCE_FRAMES = 10; // Cantidad de elementos del vector de deformación
|
||||
|
||||
// Tablas de valores predefinidos para el efecto de rebote
|
||||
static constexpr std::array<float, BOUNCE_FRAMES> HORIZONTAL_ZOOM_VALUES = {
|
||||
1.10F,
|
||||
1.05F,
|
||||
1.00F,
|
||||
0.95F,
|
||||
0.90F,
|
||||
0.95F,
|
||||
1.00F,
|
||||
1.02F,
|
||||
1.05F,
|
||||
1.02F};
|
||||
|
||||
static constexpr std::array<float, BOUNCE_FRAMES> VERTICAL_ZOOM_VALUES = {
|
||||
0.90F,
|
||||
0.95F,
|
||||
1.00F,
|
||||
1.05F,
|
||||
1.10F,
|
||||
1.05F,
|
||||
1.00F,
|
||||
0.98F,
|
||||
0.95F,
|
||||
0.98F};
|
||||
|
||||
// Estado del efecto
|
||||
bool enabled_ = false; // Si el efecto está activo
|
||||
Uint8 counter_ = 0; // Contador para el efecto
|
||||
Uint8 speed_ = 2; // Velocidad del efecto
|
||||
|
||||
// Valores actuales de transformación
|
||||
float horizontal_zoom_ = 1.0F; // Zoom en anchura
|
||||
float verical_zoom_ = 1.0F; // Zoom en altura
|
||||
float x_offset_ = 0.0F; // Desplazamiento X antes de pintar
|
||||
float y_offset_ = 0.0F; // Desplazamiento Y antes de pintar
|
||||
|
||||
public:
|
||||
// Constructor por defecto
|
||||
BounceEffect() = default;
|
||||
|
||||
// Reinicia el efecto a sus valores iniciales
|
||||
void reset() {
|
||||
counter_ = 0;
|
||||
horizontal_zoom_ = 1.0F;
|
||||
verical_zoom_ = 1.0F;
|
||||
x_offset_ = 0.0F;
|
||||
y_offset_ = 0.0F;
|
||||
}
|
||||
|
||||
// Aplica la deformación visual al sprite
|
||||
void apply(AnimatedSprite* sprite) const {
|
||||
if (sprite != nullptr) {
|
||||
sprite->setHorizontalZoom(horizontal_zoom_);
|
||||
sprite->setVerticalZoom(verical_zoom_);
|
||||
}
|
||||
}
|
||||
|
||||
// Activa el efecto de rebote
|
||||
void enable(AnimatedSprite* sprite, Size balloon_size) {
|
||||
// Los globos pequeños no tienen efecto de rebote
|
||||
if (balloon_size == Size::SMALL) {
|
||||
return;
|
||||
}
|
||||
enabled_ = true;
|
||||
reset();
|
||||
apply(sprite);
|
||||
}
|
||||
|
||||
// Detiene el efecto de rebote
|
||||
void disable(AnimatedSprite* sprite) {
|
||||
enabled_ = false;
|
||||
reset();
|
||||
apply(sprite);
|
||||
}
|
||||
|
||||
// Actualiza el efecto en cada frame
|
||||
void update(AnimatedSprite* sprite) {
|
||||
if (!enabled_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calcula el índice basado en el contador y velocidad
|
||||
const int INDEX = counter_ / speed_;
|
||||
|
||||
// Actualiza los valores de zoom desde las tablas predefinidas
|
||||
horizontal_zoom_ = HORIZONTAL_ZOOM_VALUES.at(INDEX);
|
||||
verical_zoom_ = VERTICAL_ZOOM_VALUES.at(INDEX);
|
||||
|
||||
// Aplica la deformación al sprite
|
||||
apply(sprite);
|
||||
|
||||
// Incrementa el contador y verifica si el efecto debe terminar
|
||||
if (++counter_ / speed_ >= BOUNCE_FRAMES) {
|
||||
disable(sprite);
|
||||
}
|
||||
}
|
||||
|
||||
// Getters para acceso a los valores actuales
|
||||
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; }
|
||||
[[nodiscard]] auto getHorizontalZoom() const -> float { return horizontal_zoom_; }
|
||||
[[nodiscard]] auto getVerticalZoom() const -> float { return verical_zoom_; }
|
||||
[[nodiscard]] auto getXOffset() const -> float { return x_offset_; }
|
||||
[[nodiscard]] auto getYOffset() const -> float { return y_offset_; }
|
||||
};
|
||||
|
||||
// --- Objetos y punteros ---
|
||||
std::unique_ptr<AnimatedSprite> sprite_; // Sprite del objeto globo
|
||||
|
||||
// --- Variables de estado y físicas ---
|
||||
float x_; // Posición X
|
||||
float y_; // Posición Y
|
||||
float w_; // Ancho
|
||||
float h_; // Alto
|
||||
float vx_; // Velocidad X
|
||||
float vy_; // Velocidad Y
|
||||
float gravity_; // Aceleración en Y
|
||||
float default_vy_; // Velocidad inicial al rebotar
|
||||
float max_vy_; // Máxima velocidad en Y
|
||||
bool being_created_; // Si el globo se está creando
|
||||
bool enabled_ = true; // Si el globo está activo
|
||||
bool invulnerable_; // Si el globo es invulnerable
|
||||
bool stopped_; // Si el globo está parado
|
||||
bool use_reversed_colors_ = false; // Si se usa el color alternativo
|
||||
Circle collider_; // Círculo de colisión
|
||||
float creation_counter_; // Temporizador de creación
|
||||
float creation_counter_ini_; // Valor inicial del temporizador de creación
|
||||
Uint16 score_; // Puntos al destruir el globo
|
||||
Type type_; // Tipo de globo
|
||||
Size size_; // Tamaño de globo
|
||||
Uint8 menace_; // Amenaza que genera el globo
|
||||
Uint32 counter_ = 0; // Contador interno
|
||||
float game_tempo_; // Multiplicador de tempo del juego
|
||||
float movement_accumulator_ = 0.0F; // Acumulador para movimiento durante creación (deltaTime)
|
||||
Uint8 power_; // Poder que alberga el globo
|
||||
SDL_FRect play_area_; // Zona de movimiento del globo
|
||||
Sound sound_; // Configuración de sonido del globo
|
||||
BounceEffect bounce_effect_; // Efecto de rebote
|
||||
|
||||
// --- Posicionamiento y transformación ---
|
||||
void shiftColliders(); // Alinea el círculo de colisión con el sprite
|
||||
void shiftSprite(); // Alinea el sprite en pantalla
|
||||
|
||||
// --- Animación y sonido ---
|
||||
void setAnimation(); // Establece la animación correspondiente
|
||||
void playBouncingSound() const; // Reproduce el sonido de rebote
|
||||
void playPoppingSound() const; // Reproduce el sonido de reventar
|
||||
|
||||
// --- Movimiento y física ---
|
||||
void handleHorizontalMovement(float delta_time); // Maneja el movimiento horizontal (time-based)
|
||||
void handleVerticalMovement(float delta_time); // Maneja el movimiento vertical (time-based)
|
||||
void applyGravity(float delta_time); // Aplica la gravedad al objeto (time-based)
|
||||
|
||||
// --- Rebote ---
|
||||
void enableBounceEffect(); // Activa el efecto de rebote
|
||||
void disableBounceEffect(); // Detiene el efecto de rebote
|
||||
void updateBounceEffect(); // Actualiza el estado del rebote
|
||||
void handleHorizontalBounce(float min_x, float max_x); // Maneja el rebote horizontal dentro de límites
|
||||
|
||||
// --- Colisiones ---
|
||||
[[nodiscard]] auto isOutOfHorizontalBounds(float min_x, float max_x) const -> bool; // Verifica si está fuera de los límites horizontales
|
||||
[[nodiscard]] auto shouldCheckTopCollision() const -> bool; // Determina si debe comprobarse la colisión superior
|
||||
void handleTopCollision(); // Maneja la colisión superior
|
||||
void handleBottomCollision(); // Maneja la colisión inferior
|
||||
|
||||
// --- Lógica de estado ---
|
||||
void updateState(float delta_time); // Actualiza los estados del globo (time-based)
|
||||
};
|
||||
130
source/game/entities/bullet.cpp
Normal file
130
source/game/entities/bullet.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
#include "bullet.hpp"
|
||||
|
||||
#include <memory> // Para unique_ptr, make_unique
|
||||
#include <string> // Para basic_string, string
|
||||
|
||||
#include "param.hpp" // Para Param, ParamGame, param
|
||||
#include "resource.hpp" // Para Resource
|
||||
|
||||
// Constructor
|
||||
Bullet::Bullet(float x, float y, Type type, Color color, int owner)
|
||||
: sprite_(std::make_unique<AnimatedSprite>(Resource::get()->getTexture("bullet.png"), Resource::get()->getAnimation("bullet.ani"))),
|
||||
type_(type),
|
||||
owner_(owner),
|
||||
pos_x_(x),
|
||||
pos_y_(y) {
|
||||
vel_x_ = calculateVelocity(type_);
|
||||
sprite_->setCurrentAnimation(buildAnimationString(type_, color));
|
||||
|
||||
collider_.r = WIDTH / 2;
|
||||
shiftColliders();
|
||||
}
|
||||
|
||||
// Calcula la velocidad horizontal de la bala basada en su tipo
|
||||
auto Bullet::calculateVelocity(Type type) -> float {
|
||||
switch (type) {
|
||||
case Type::LEFT:
|
||||
return VEL_X_LEFT;
|
||||
case Type::RIGHT:
|
||||
return VEL_X_RIGHT;
|
||||
default:
|
||||
return VEL_X_CENTER;
|
||||
}
|
||||
}
|
||||
|
||||
// Construye el string de animación basado en el tipo de bala y color específico
|
||||
auto Bullet::buildAnimationString(Type type, Color color) -> std::string {
|
||||
std::string animation_string;
|
||||
|
||||
// Mapear color a string específico
|
||||
switch (color) {
|
||||
case Color::YELLOW:
|
||||
animation_string = "yellow_";
|
||||
break;
|
||||
case Color::GREEN:
|
||||
animation_string = "green_";
|
||||
break;
|
||||
case Color::RED:
|
||||
animation_string = "red_";
|
||||
break;
|
||||
case Color::PURPLE:
|
||||
animation_string = "purple_";
|
||||
break;
|
||||
}
|
||||
|
||||
// Añadir dirección
|
||||
switch (type) {
|
||||
case Type::UP:
|
||||
animation_string += "up";
|
||||
break;
|
||||
case Type::LEFT:
|
||||
animation_string += "left";
|
||||
break;
|
||||
case Type::RIGHT:
|
||||
animation_string += "right";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return animation_string;
|
||||
}
|
||||
|
||||
// Implementación de render
|
||||
void Bullet::render() {
|
||||
if (type_ != Type::NONE) {
|
||||
sprite_->render();
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza el estado del objeto
|
||||
auto Bullet::update(float delta_time) -> MoveStatus {
|
||||
sprite_->update(delta_time);
|
||||
return move(delta_time);
|
||||
}
|
||||
|
||||
// Implementación del movimiento usando MoveStatus
|
||||
auto Bullet::move(float delta_time) -> MoveStatus {
|
||||
pos_x_ += vel_x_ * delta_time;
|
||||
if (pos_x_ < param.game.play_area.rect.x - WIDTH || pos_x_ > param.game.play_area.rect.w) {
|
||||
disable();
|
||||
return MoveStatus::OUT;
|
||||
}
|
||||
|
||||
pos_y_ += VEL_Y * delta_time;
|
||||
if (pos_y_ < param.game.play_area.rect.y - HEIGHT) {
|
||||
disable();
|
||||
return MoveStatus::OUT;
|
||||
}
|
||||
|
||||
shiftSprite();
|
||||
shiftColliders();
|
||||
|
||||
return MoveStatus::OK;
|
||||
}
|
||||
|
||||
auto Bullet::isEnabled() const -> bool {
|
||||
return type_ != Type::NONE;
|
||||
}
|
||||
|
||||
void Bullet::disable() {
|
||||
type_ = Type::NONE;
|
||||
}
|
||||
|
||||
auto Bullet::getOwner() const -> int {
|
||||
return owner_;
|
||||
}
|
||||
|
||||
auto Bullet::getCollider() -> Circle& {
|
||||
return collider_;
|
||||
}
|
||||
|
||||
void Bullet::shiftColliders() {
|
||||
collider_.x = pos_x_ + collider_.r;
|
||||
collider_.y = pos_y_ + collider_.r;
|
||||
}
|
||||
|
||||
void Bullet::shiftSprite() {
|
||||
sprite_->setX(pos_x_);
|
||||
sprite_->setY(pos_y_);
|
||||
}
|
||||
76
source/game/entities/bullet.hpp
Normal file
76
source/game/entities/bullet.hpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para Uint8
|
||||
|
||||
#include <memory> // Para unique_ptr
|
||||
#include <string> // Para string
|
||||
|
||||
#include "animated_sprite.hpp" // Para AnimatedSprite
|
||||
#include "utils.hpp" // Para Circle
|
||||
|
||||
// --- Clase Bullet: representa una bala del jugador ---
|
||||
class Bullet {
|
||||
public:
|
||||
// --- Constantes ---
|
||||
static constexpr float WIDTH = 12.0F; // Anchura de la bala
|
||||
static constexpr float HEIGHT = 12.0F; // Altura de la bala
|
||||
|
||||
// --- Enums ---
|
||||
enum class Type : Uint8 {
|
||||
UP, // Bala hacia arriba
|
||||
LEFT, // Bala hacia la izquierda
|
||||
RIGHT, // Bala hacia la derecha
|
||||
NONE // Sin bala
|
||||
};
|
||||
|
||||
enum class MoveStatus : Uint8 {
|
||||
OK = 0, // Movimiento normal
|
||||
OUT = 1 // Fuera de los límites
|
||||
};
|
||||
|
||||
enum class Color : Uint8 {
|
||||
YELLOW,
|
||||
GREEN,
|
||||
RED,
|
||||
PURPLE
|
||||
};
|
||||
|
||||
// --- Constructor y destructor ---
|
||||
Bullet(float x, float y, Type type, Color color, int owner); // Constructor principal
|
||||
~Bullet() = default; // Destructor
|
||||
|
||||
// --- Métodos principales ---
|
||||
void render(); // Dibuja la bala en pantalla
|
||||
auto update(float delta_time) -> MoveStatus; // Actualiza el estado del objeto (time-based)
|
||||
void disable(); // Desactiva la bala
|
||||
|
||||
// --- Getters ---
|
||||
[[nodiscard]] auto isEnabled() const -> bool; // Comprueba si está activa
|
||||
[[nodiscard]] auto getOwner() const -> int; // Devuelve el identificador del dueño
|
||||
auto getCollider() -> Circle&; // Devuelve el círculo de colisión
|
||||
|
||||
private:
|
||||
// --- Constantes ---
|
||||
static constexpr float VEL_Y = -180.0F; // Velocidad vertical (pixels/segundo) - era -0.18F pixels/ms
|
||||
static constexpr float VEL_X_LEFT = -120.0F; // Velocidad izquierda (pixels/segundo) - era -0.12F pixels/ms
|
||||
static constexpr float VEL_X_RIGHT = 120.0F; // Velocidad derecha (pixels/segundo) - era 0.12F pixels/ms
|
||||
static constexpr float VEL_X_CENTER = 0.0F; // Velocidad central
|
||||
|
||||
// --- Objetos y punteros ---
|
||||
std::unique_ptr<AnimatedSprite> sprite_; // Sprite con los gráficos
|
||||
|
||||
// --- Variables de estado ---
|
||||
Circle collider_; // Círculo de colisión
|
||||
Type type_; // Tipo de bala
|
||||
int owner_; // Identificador del jugador
|
||||
float pos_x_; // Posición en el eje X
|
||||
float pos_y_; // Posición en el eje Y
|
||||
float vel_x_; // Velocidad en el eje X
|
||||
|
||||
// --- Métodos internos ---
|
||||
void shiftColliders(); // Ajusta el círculo de colisión
|
||||
void shiftSprite(); // Ajusta el sprite
|
||||
auto move(float delta_time) -> MoveStatus; // Mueve la bala y devuelve su estado (time-based)
|
||||
static auto calculateVelocity(Type type) -> float; // Calcula la velocidad horizontal de la bala
|
||||
static auto buildAnimationString(Type type, Color color) -> std::string; // Construye el string de animación
|
||||
};
|
||||
58
source/game/entities/explosions.cpp
Normal file
58
source/game/entities/explosions.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#include "explosions.hpp"
|
||||
|
||||
#include <utility> // Para std::cmp_less
|
||||
|
||||
#include "animated_sprite.hpp" // Para AnimatedSprite
|
||||
|
||||
class Texture; // lines 4-4
|
||||
|
||||
// Actualiza la lógica de la clase (time-based)
|
||||
void Explosions::update(float delta_time) {
|
||||
for (auto& explosion : explosions_) {
|
||||
explosion->update(delta_time);
|
||||
}
|
||||
|
||||
// Vacia el vector de elementos finalizados
|
||||
freeExplosions();
|
||||
}
|
||||
|
||||
// Dibuja el objeto en pantalla
|
||||
void Explosions::render() {
|
||||
for (auto& explosion : explosions_) {
|
||||
explosion->render();
|
||||
}
|
||||
}
|
||||
|
||||
// Añade texturas al objeto
|
||||
void Explosions::addTexture(int size, const std::shared_ptr<Texture>& texture, const std::vector<std::string>& animation) {
|
||||
textures_.emplace_back(size, texture, animation);
|
||||
}
|
||||
|
||||
// Añade una explosión
|
||||
void Explosions::add(int x, int y, int size) {
|
||||
const auto INDEX = getIndexBySize(size);
|
||||
explosions_.emplace_back(std::make_unique<AnimatedSprite>(textures_[INDEX].texture, textures_[INDEX].animation));
|
||||
explosions_.back()->setPos(x, y);
|
||||
}
|
||||
|
||||
// Vacia el vector de elementos finalizados
|
||||
void Explosions::freeExplosions() {
|
||||
if (!explosions_.empty()) {
|
||||
for (int i = explosions_.size() - 1; i >= 0; --i) {
|
||||
if (explosions_[i]->animationIsCompleted()) {
|
||||
explosions_.erase(explosions_.begin() + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Busca una textura a partir del tamaño
|
||||
auto Explosions::getIndexBySize(int size) -> int {
|
||||
for (int i = 0; std::cmp_less(i, textures_.size()); ++i) {
|
||||
if (size == textures_[i].size) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
47
source/game/entities/explosions.hpp
Normal file
47
source/game/entities/explosions.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory> // Para unique_ptr, shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <utility> // Para move
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "animated_sprite.hpp" // Para AnimatedSprite
|
||||
|
||||
class Texture;
|
||||
|
||||
// --- Estructura ExplosionTexture: almacena información de una textura de explosión ---
|
||||
struct ExplosionTexture {
|
||||
int size; // Tamaño de la explosión
|
||||
std::shared_ptr<Texture> texture; // Textura para la explosión
|
||||
std::vector<std::string> animation; // Animación para la textura
|
||||
|
||||
ExplosionTexture(int sz, std::shared_ptr<Texture> tex, const std::vector<std::string>& anim)
|
||||
: size(sz),
|
||||
texture(std::move(tex)),
|
||||
animation(anim) {}
|
||||
};
|
||||
|
||||
// --- Clase Explosions: gestor de explosiones ---
|
||||
class Explosions {
|
||||
public:
|
||||
// --- Constructor y destructor ---
|
||||
Explosions() = default; // Constructor por defecto
|
||||
~Explosions() = default; // Destructor por defecto
|
||||
|
||||
// --- Métodos principales ---
|
||||
void update(float delta_time); // Actualiza la lógica de la clase (time-based)
|
||||
void render(); // Dibuja el objeto en pantalla
|
||||
|
||||
// --- Configuración ---
|
||||
void addTexture(int size, const std::shared_ptr<Texture>& texture, const std::vector<std::string>& animation); // Añade texturas al objeto
|
||||
void add(int x, int y, int size); // Añade una explosión
|
||||
|
||||
private:
|
||||
// --- Variables de estado ---
|
||||
std::vector<ExplosionTexture> textures_; // Vector con las texturas a utilizar
|
||||
std::vector<std::unique_ptr<AnimatedSprite>> explosions_; // Lista con todas las explosiones
|
||||
|
||||
// --- Métodos internos ---
|
||||
void freeExplosions(); // Vacía el vector de elementos finalizados
|
||||
auto getIndexBySize(int size) -> int; // Busca una textura a partir del tamaño
|
||||
};
|
||||
228
source/game/entities/item.cpp
Normal file
228
source/game/entities/item.cpp
Normal file
@@ -0,0 +1,228 @@
|
||||
#include "item.hpp"
|
||||
|
||||
#include <algorithm> // Para clamp
|
||||
#include <cmath> // Para fmod
|
||||
#include <cstdlib> // Para rand
|
||||
|
||||
#include "animated_sprite.hpp" // Para AnimatedSprite
|
||||
#include "param.hpp" // Para Param, ParamGame, param
|
||||
|
||||
class Texture; // lines 6-6
|
||||
|
||||
Item::Item(ItemType type, float x, float y, SDL_FRect& play_area, const std::shared_ptr<Texture>& texture, const std::vector<std::string>& animation)
|
||||
: sprite_(std::make_unique<AnimatedSprite>(texture, animation)),
|
||||
play_area_(play_area),
|
||||
type_(type) {
|
||||
switch (type) {
|
||||
case ItemType::COFFEE_MACHINE: {
|
||||
width_ = COFFEE_MACHINE_WIDTH;
|
||||
height_ = COFFEE_MACHINE_HEIGHT;
|
||||
pos_x_ = getCoffeeMachineSpawn(x, width_, play_area_.w);
|
||||
pos_y_ = y;
|
||||
vel_x_ = ((rand() % 3) - 1) * COFFEE_MACHINE_VEL_X_FACTOR;
|
||||
vel_y_ = COFFEE_MACHINE_VEL_Y;
|
||||
accel_y_ = COFFEE_MACHINE_ACCEL_Y;
|
||||
collider_.r = 10;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
pos_x_ = x;
|
||||
pos_y_ = y;
|
||||
// 6 velocidades: 3 negativas (-1.0, -0.66, -0.33) y 3 positivas (0.33, 0.66, 1.0)
|
||||
const int DIRECTION = rand() % 6;
|
||||
if (DIRECTION < 3) {
|
||||
// Velocidades negativas: -1.0, -0.66, -0.33
|
||||
vel_x_ = -ITEM_VEL_X_BASE + (DIRECTION * ITEM_VEL_X_STEP);
|
||||
rotate_speed_ = -720.0F;
|
||||
} else {
|
||||
// Velocidades positivas: 0.33, 0.66, 1.0
|
||||
vel_x_ = ITEM_VEL_X_STEP + ((DIRECTION - 3) * ITEM_VEL_X_STEP);
|
||||
rotate_speed_ = 720.0F;
|
||||
}
|
||||
vel_y_ = ITEM_VEL_Y;
|
||||
accel_y_ = ITEM_ACCEL_Y;
|
||||
collider_.r = width_ / 2;
|
||||
sprite_->startRotate();
|
||||
sprite_->setRotateAmount(rotate_speed_);
|
||||
sprite_->setCurrentAnimation("no-blink");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza el sprite
|
||||
shiftSprite();
|
||||
shiftColliders();
|
||||
}
|
||||
|
||||
void Item::alignTo(int x) {
|
||||
const float MIN_X = param.game.play_area.rect.x + 1;
|
||||
const float MAX_X = play_area_.w - width_ - 1;
|
||||
|
||||
pos_x_ = x - (width_ / 2);
|
||||
|
||||
// Ajusta para que no quede fuera de la zona de juego
|
||||
pos_x_ = std::clamp(pos_x_, MIN_X, MAX_X);
|
||||
|
||||
// Actualiza el sprite
|
||||
shiftSprite();
|
||||
shiftColliders();
|
||||
}
|
||||
|
||||
void Item::render() {
|
||||
if (enabled_) {
|
||||
// Muestra normalmente hasta los últimos ~3.3 segundos
|
||||
constexpr float BLINK_START_S = LIFETIME_DURATION_S - 3.33F;
|
||||
|
||||
if (lifetime_timer_ < BLINK_START_S) {
|
||||
sprite_->render();
|
||||
} else {
|
||||
// Efecto de parpadeo en los últimos segundos (cada ~0.33 segundos)
|
||||
constexpr float BLINK_INTERVAL_S = 0.33F;
|
||||
const float PHASE = std::fmod(lifetime_timer_, BLINK_INTERVAL_S);
|
||||
const float HALF_INTERVAL = BLINK_INTERVAL_S / 2.0F;
|
||||
|
||||
if (PHASE < HALF_INTERVAL) {
|
||||
sprite_->render();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Item::move(float delta_time) {
|
||||
floor_collision_ = false;
|
||||
|
||||
// Calcula la nueva posición usando deltaTime (velocidad en pixels/segundo)
|
||||
pos_x_ += vel_x_ * delta_time;
|
||||
pos_y_ += vel_y_ * delta_time;
|
||||
|
||||
// Aplica las aceleraciones a la velocidad usando deltaTime (aceleración en pixels/segundo²)
|
||||
vel_x_ += accel_x_ * delta_time;
|
||||
vel_y_ += accel_y_ * delta_time;
|
||||
|
||||
// Comprueba los laterales de la zona de juego
|
||||
const float MIN_X = param.game.play_area.rect.x;
|
||||
const float MAX_X = play_area_.w - width_;
|
||||
pos_x_ = std::clamp(pos_x_, MIN_X, MAX_X);
|
||||
|
||||
// Si toca el borde lateral
|
||||
if (pos_x_ == MIN_X || pos_x_ == MAX_X) {
|
||||
vel_x_ = -vel_x_; // Invierte la velocidad horizontal
|
||||
sprite_->scaleRotateAmount(-1.0F); // Invierte la rotación
|
||||
}
|
||||
|
||||
// Si colisiona por arriba, rebota (excepto la máquina de café)
|
||||
if ((pos_y_ < param.game.play_area.rect.y) && !(type_ == ItemType::COFFEE_MACHINE)) {
|
||||
// Corrige
|
||||
pos_y_ = param.game.play_area.rect.y;
|
||||
|
||||
// Fuerza la velocidad hacia abajo para evitar oscilaciones
|
||||
vel_y_ = std::abs(vel_y_);
|
||||
}
|
||||
|
||||
// Si colisiona con la parte inferior
|
||||
if (pos_y_ > play_area_.h - height_) {
|
||||
pos_y_ = play_area_.h - height_; // Corrige la posición
|
||||
sprite_->scaleRotateAmount(0.5F); // Reduce la rotación
|
||||
sprite_->stopRotate(300.0F); // Detiene la rotacion
|
||||
|
||||
switch (type_) {
|
||||
case ItemType::COFFEE_MACHINE:
|
||||
// La máquina de café es mas pesada y tiene una fisica diferente, ademas hace ruido
|
||||
floor_collision_ = true;
|
||||
if (std::abs(vel_y_) < BOUNCE_VEL_THRESHOLD) {
|
||||
// Si la velocidad vertical es baja, detiene el objeto
|
||||
vel_y_ = vel_x_ = accel_x_ = accel_y_ = 0;
|
||||
} else {
|
||||
// Si la velocidad vertical es alta, el objeto rebota y pierde velocidad
|
||||
vel_y_ *= COFFEE_BOUNCE_DAMPING;
|
||||
vel_x_ *= HORIZONTAL_DAMPING;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Si no es una máquina de café
|
||||
if (std::abs(vel_y_) < BOUNCE_VEL_THRESHOLD) {
|
||||
// Si la velocidad vertical es baja, detiene el objeto
|
||||
vel_y_ = vel_x_ = accel_x_ = accel_y_ = 0;
|
||||
sprite_->setCurrentAnimation("blink");
|
||||
} else {
|
||||
// Si la velocidad vertical es alta, el objeto rebota y pierde velocidad
|
||||
vel_y_ *= ITEM_BOUNCE_DAMPING;
|
||||
vel_x_ *= HORIZONTAL_DAMPING;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza la posición del sprite
|
||||
shiftSprite();
|
||||
shiftColliders();
|
||||
}
|
||||
|
||||
void Item::disable() { enabled_ = false; }
|
||||
|
||||
void Item::update(float delta_time) {
|
||||
move(delta_time);
|
||||
sprite_->update(delta_time);
|
||||
updateTimeToLive(delta_time);
|
||||
}
|
||||
|
||||
void Item::updateTimeToLive(float delta_time) {
|
||||
lifetime_timer_ += delta_time;
|
||||
if (lifetime_timer_ >= LIFETIME_DURATION_S) {
|
||||
disable();
|
||||
}
|
||||
}
|
||||
|
||||
void Item::shiftColliders() {
|
||||
collider_.x = static_cast<int>(pos_x_ + (width_ / 2));
|
||||
collider_.y = static_cast<int>(pos_y_ + (height_ / 2));
|
||||
}
|
||||
|
||||
void Item::shiftSprite() {
|
||||
sprite_->setPosX(pos_x_);
|
||||
sprite_->setPosY(pos_y_);
|
||||
}
|
||||
|
||||
// Calcula la zona de aparición de la máquina de café
|
||||
auto Item::getCoffeeMachineSpawn(int player_x, int item_width, int area_width, int margin) -> int {
|
||||
// Distancia mínima del jugador (ajusta según necesites)
|
||||
const int MIN_DISTANCE_FROM_PLAYER = area_width / 2;
|
||||
|
||||
const int LEFT_BOUND = margin;
|
||||
const int RIGHT_BOUND = area_width - item_width - margin;
|
||||
|
||||
// Calcular zona de exclusión alrededor del jugador
|
||||
int exclude_left = player_x - MIN_DISTANCE_FROM_PLAYER;
|
||||
int exclude_right = player_x + MIN_DISTANCE_FROM_PLAYER;
|
||||
|
||||
// Verificar si hay espacio suficiente a la izquierda
|
||||
bool can_spawn_left = (exclude_left > LEFT_BOUND) && (exclude_left - LEFT_BOUND > item_width);
|
||||
|
||||
// Verificar si hay espacio suficiente a la derecha
|
||||
bool can_spawn_right = (exclude_right < RIGHT_BOUND) && (RIGHT_BOUND - exclude_right > item_width);
|
||||
|
||||
if (can_spawn_left && can_spawn_right) {
|
||||
// Ambos lados disponibles, elegir aleatoriamente
|
||||
if (rand() % 2 == 0) {
|
||||
// Lado izquierdo
|
||||
return (rand() % (exclude_left - LEFT_BOUND)) + LEFT_BOUND;
|
||||
} // Lado derecho
|
||||
return (rand() % (RIGHT_BOUND - exclude_right)) + exclude_right;
|
||||
}
|
||||
if (can_spawn_left) {
|
||||
// Solo lado izquierdo disponible
|
||||
return (rand() % (exclude_left - LEFT_BOUND)) + LEFT_BOUND;
|
||||
}
|
||||
if (can_spawn_right) {
|
||||
// Solo lado derecho disponible
|
||||
return (rand() % (RIGHT_BOUND - exclude_right)) + exclude_right;
|
||||
} // No hay espacio suficiente lejos del jugador
|
||||
// Por ahora, intentar spawn en el extremo más lejano posible
|
||||
int distance_to_left = abs(player_x - LEFT_BOUND);
|
||||
int distance_to_right = abs(RIGHT_BOUND - player_x);
|
||||
|
||||
if (distance_to_left > distance_to_right) {
|
||||
return LEFT_BOUND;
|
||||
}
|
||||
return RIGHT_BOUND - item_width;
|
||||
}
|
||||
100
source/game/entities/item.hpp
Normal file
100
source/game/entities/item.hpp
Normal file
@@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FRect, Uint16
|
||||
|
||||
#include <memory> // Para shared_ptr, unique_ptr
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "animated_sprite.hpp" // Para AnimatedSprite
|
||||
#include "utils.hpp" // Para Circle
|
||||
|
||||
class Texture;
|
||||
|
||||
// --- Enums ---
|
||||
enum class ItemType : int {
|
||||
DISK = 1, // Disco
|
||||
GAVINA = 2, // Gavina
|
||||
PACMAR = 3, // Pacman
|
||||
CLOCK = 4, // Reloj
|
||||
COFFEE = 5, // Café
|
||||
DEBIAN = 6, // Debian
|
||||
COFFEE_MACHINE = 7, // Máquina de café
|
||||
NONE = 8, // Ninguno
|
||||
};
|
||||
|
||||
// --- Clase Item: representa un objeto en el juego ---
|
||||
class Item {
|
||||
public:
|
||||
// --- Constantes ---
|
||||
static constexpr float WIDTH = 20.0F; // Anchura del item
|
||||
static constexpr float HEIGHT = 20.0F; // ALtura del item
|
||||
static constexpr int COFFEE_MACHINE_WIDTH = 30; // Anchura de la máquina de café
|
||||
static constexpr int COFFEE_MACHINE_HEIGHT = 39; // Altura de la máquina de café
|
||||
static constexpr float LIFETIME_DURATION_S = 10.0F; // Duración de vida del ítem en segundos
|
||||
|
||||
// Velocidades base (pixels/segundo) - Coffee Machine
|
||||
static constexpr float COFFEE_MACHINE_VEL_X_FACTOR = 30.0F; // Factor para velocidad X de máquina de café (0.5*60fps)
|
||||
static constexpr float COFFEE_MACHINE_VEL_Y = -6.0F; // Velocidad Y inicial de máquina de café (-0.1*60fps)
|
||||
static constexpr float COFFEE_MACHINE_ACCEL_Y = 360.0F; // Aceleración Y de máquina de café (0.1*60²fps = 360 pixels/segundo²)
|
||||
|
||||
// Velocidades base (pixels/segundo) - Items normales
|
||||
static constexpr float ITEM_VEL_X_BASE = 60.0F; // Velocidad X base para items (1.0F*60fps)
|
||||
static constexpr float ITEM_VEL_X_STEP = 20.0F; // Incremento de velocidad X (0.33F*60fps)
|
||||
static constexpr float ITEM_VEL_Y = -240.0F; // Velocidad Y inicial de items (-4.0F*60fps)
|
||||
static constexpr float ITEM_ACCEL_Y = 720.0F; // Aceleración Y de items (0.2*60²fps = 720 pixels/segundo²)
|
||||
|
||||
// Constantes de física de rebote
|
||||
static constexpr float BOUNCE_VEL_THRESHOLD = 60.0F; // Umbral de velocidad para parar (1.0F*60fps)
|
||||
static constexpr float COFFEE_BOUNCE_DAMPING = -0.20F; // Factor de rebote Y para máquina de café
|
||||
static constexpr float ITEM_BOUNCE_DAMPING = -0.5F; // Factor de rebote Y para items normales
|
||||
static constexpr float HORIZONTAL_DAMPING = 0.75F; // Factor de amortiguación horizontal
|
||||
|
||||
// --- Constructor y destructor ---
|
||||
Item(ItemType type, float x, float y, SDL_FRect& play_area, const std::shared_ptr<Texture>& texture, const std::vector<std::string>& animation); // Constructor principal
|
||||
~Item() = default; // Destructor
|
||||
|
||||
// --- Métodos principales ---
|
||||
void alignTo(int x); // Centra el objeto en la posición X indicada
|
||||
void render(); // Renderiza el objeto en pantalla
|
||||
void disable(); // Desactiva el objeto
|
||||
void update(float delta_time); // Actualiza la posición, animación y contadores (time-based)
|
||||
|
||||
// --- Getters ---
|
||||
[[nodiscard]] auto getPosX() const -> float { return pos_x_; } // Obtiene la posición X
|
||||
[[nodiscard]] auto getPosY() const -> float { return pos_y_; } // Obtiene la posición Y
|
||||
[[nodiscard]] auto getWidth() const -> int { return width_; } // Obtiene la anchura
|
||||
[[nodiscard]] auto getHeight() const -> int { return height_; } // Obtiene la altura
|
||||
[[nodiscard]] auto getType() const -> ItemType { return type_; } // Obtiene el tipo
|
||||
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; } // Verifica si está habilitado
|
||||
[[nodiscard]] auto isOnFloor() const -> bool { return floor_collision_; } // Verifica si está en el suelo
|
||||
auto getCollider() -> Circle& { return collider_; } // Obtiene el colisionador
|
||||
|
||||
private:
|
||||
// --- Objetos y punteros ---
|
||||
std::unique_ptr<AnimatedSprite> sprite_; // Sprite con los gráficos del objeto
|
||||
|
||||
// --- Variables de estado ---
|
||||
SDL_FRect play_area_; // Rectángulo con la zona de juego
|
||||
Circle collider_; // Círculo de colisión del objeto
|
||||
ItemType type_; // Tipo de objeto
|
||||
float pos_x_ = 0.0F; // Posición X del objeto
|
||||
float pos_y_ = 0.0F; // Posición Y del objeto
|
||||
float vel_x_ = 0.0F; // Velocidad en el eje X
|
||||
float vel_y_ = 0.0F; // Velocidad en el eje Y
|
||||
float accel_x_ = 0.0F; // Aceleración en el eje X
|
||||
float accel_y_ = 0.0F; // Aceleración en el eje Y
|
||||
float width_ = WIDTH; // Ancho del objeto
|
||||
float height_ = HEIGHT; // Alto del objeto
|
||||
float rotate_speed_ = 0.0F; // Velocidad de rotacion
|
||||
float lifetime_timer_ = 0.0F; // Acumulador de tiempo de vida del ítem (segundos)
|
||||
bool floor_collision_ = false; // Indica si el objeto colisiona con el suelo
|
||||
bool enabled_ = true; // Indica si el objeto está habilitado
|
||||
|
||||
// --- Métodos internos ---
|
||||
void shiftColliders(); // Alinea el círculo de colisión con la posición del objeto
|
||||
void shiftSprite(); // Coloca el sprite en la posición del objeto
|
||||
void move(float delta_time); // Actualiza la posición y estados del objeto (time-based)
|
||||
void updateTimeToLive(float delta_time); // Actualiza el contador de tiempo de vida (time-based)
|
||||
static auto getCoffeeMachineSpawn(int player_x, int item_width, int area_width, int margin = 2) -> int; // Calcula la zona de aparición de la máquina de café
|
||||
};
|
||||
1192
source/game/entities/player.cpp
Normal file
1192
source/game/entities/player.cpp
Normal file
File diff suppressed because it is too large
Load Diff
405
source/game/entities/player.hpp
Normal file
405
source/game/entities/player.hpp
Normal file
@@ -0,0 +1,405 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FRect, SDL_FlipMode
|
||||
|
||||
#include <cstddef> // Para size_t
|
||||
#include <iterator> // Para pair
|
||||
#include <memory> // Para shared_ptr, unique_ptr
|
||||
#include <string> // Para basic_string, string
|
||||
#include <utility> // Para move, pair
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "animated_sprite.hpp" // for AnimatedSprite
|
||||
#include "bullet.hpp" // for Bullet
|
||||
#include "cooldown.hpp"
|
||||
#include "enter_name.hpp" // for EnterName
|
||||
#include "input.hpp" // for Input
|
||||
#include "manage_hiscore_table.hpp" // for Table
|
||||
#include "scoreboard.hpp" // for Scoreboard
|
||||
#include "utils.hpp" // for Circle
|
||||
|
||||
class IStageInfo;
|
||||
class Texture;
|
||||
|
||||
// --- Clase Player: jugador principal del juego ---
|
||||
//
|
||||
// Esta clase gestiona todos los aspectos de un jugador durante el juego,
|
||||
// incluyendo movimiento, disparos, animaciones y estados especiales.
|
||||
//
|
||||
// Funcionalidades principales:
|
||||
// • Sistema de disparo de dos líneas: funcional (cooldown) + visual (animaciones)
|
||||
// • Estados de animación: normal → aiming → recoiling → threat_pose → normal
|
||||
// • Movimiento time-based: compatibilidad con deltaTime para fluidez variable
|
||||
// • Power-ups e invulnerabilidad: coffee machine, extra hits, parpadeos
|
||||
// • Sistema de puntuación: multipliers, high scores, entrada de nombres
|
||||
// • Estados de juego: playing, rolling, continue, entering_name, etc.
|
||||
//
|
||||
// El sistema de disparo utiliza duraciones configurables mediante constantes
|
||||
// para facilitar el ajuste del gameplay y la sensación de disparo.
|
||||
class Player {
|
||||
public:
|
||||
// --- Constantes ---
|
||||
static constexpr int WIDTH = 32; // Anchura
|
||||
static constexpr int HEIGHT = 32; // Altura
|
||||
|
||||
// --- Estructuras ---
|
||||
struct BulletColorPair {
|
||||
Bullet::Color normal_color; // Color de bala sin power-up
|
||||
Bullet::Color powered_color; // Color de bala con power-up
|
||||
};
|
||||
|
||||
// --- Enums ---
|
||||
enum class Id : int {
|
||||
NO_PLAYER = -1, // Sin jugador
|
||||
BOTH_PLAYERS = 0, // Ambos jugadores
|
||||
PLAYER1 = 1, // Jugador 1
|
||||
PLAYER2 = 2 // Jugador 2
|
||||
};
|
||||
|
||||
enum class State {
|
||||
// Estados de movimiento
|
||||
WALKING_LEFT, // Caminando hacia la izquierda
|
||||
WALKING_RIGHT, // Caminando hacia la derecha
|
||||
WALKING_STOP, // Parado, sin moverse
|
||||
|
||||
// Estados de disparo
|
||||
FIRING_UP, // Disparando hacia arriba
|
||||
FIRING_LEFT, // Disparando hacia la izquierda
|
||||
FIRING_RIGHT, // Disparando hacia la derecha
|
||||
FIRING_NONE, // No está disparando
|
||||
|
||||
// Estados de retroceso tras disparar
|
||||
RECOILING_UP, // Retroceso tras disparar hacia arriba
|
||||
RECOILING_LEFT, // Retroceso tras disparar hacia la izquierda
|
||||
RECOILING_RIGHT, // Retroceso tras disparar hacia la derecha
|
||||
|
||||
// Estados de enfriamiento tras disparar
|
||||
COOLING_UP, // Enfriando tras disparar hacia arriba
|
||||
COOLING_LEFT, // Enfriando tras disparar hacia la izquierda
|
||||
COOLING_RIGHT, // Enfriando tras disparar hacia la derecha
|
||||
|
||||
// Estados generales del jugador
|
||||
PLAYING, // Está jugando activamente
|
||||
CONTINUE, // Cuenta atrás para continuar tras perder
|
||||
CONTINUE_TIME_OUT, // Se ha terminado la cuenta atras para continuar y se retira al jugador de la zona de juego
|
||||
WAITING, // Esperando para entrar a jugar
|
||||
ENTERING_NAME, // Introduciendo nombre para la tabla de puntuaciones
|
||||
SHOWING_NAME, // Mostrando el nombre introducido
|
||||
ROLLING, // El jugador está dando vueltas y rebotando
|
||||
LYING_ON_THE_FLOOR_FOREVER, // El jugador está inconsciente para siempre en el suelo (demo)
|
||||
GAME_OVER, // Fin de la partida, no puede jugar
|
||||
CELEBRATING, // Celebrando victoria (pose de victoria)
|
||||
ENTERING_NAME_GAME_COMPLETED, // Introduciendo nombre tras completar el juego
|
||||
LEAVING_SCREEN, // Saliendo de la pantalla (animación)
|
||||
ENTERING_SCREEN, // Entrando a la pantalla (animación)
|
||||
CREDITS, // Estado para mostrar los créditos del juego
|
||||
TITLE_ANIMATION, // Animacion para el titulo
|
||||
TITLE_HIDDEN, // Animacion para el titulo
|
||||
RECOVER, // Al aceptar continuar
|
||||
RESPAWNING, // Tras continuar y dar las gracias, otorga inmunidad y vuelve al juego
|
||||
};
|
||||
|
||||
// --- Estructuras ---
|
||||
struct Config {
|
||||
Id id; // Identificador del jugador
|
||||
float x; // Posición X inicial
|
||||
int y; // Posición Y inicial
|
||||
bool demo; // Modo demo
|
||||
SDL_FRect* play_area; // Área de juego (puntero para mantener referencia)
|
||||
std::vector<std::shared_ptr<Texture>> texture; // Texturas del jugador
|
||||
std::vector<std::vector<std::string>> animations; // Animaciones del jugador
|
||||
Table* hi_score_table; // Tabla de puntuaciones (puntero para referencia)
|
||||
int* glowing_entry; // Entrada brillante (puntero para mantener referencia)
|
||||
IStageInfo* stage_info; // Gestor de pantallas (puntero)
|
||||
};
|
||||
|
||||
// --- Constructor y destructor ---
|
||||
Player(const Config& config);
|
||||
~Player() = default;
|
||||
|
||||
// --- Inicialización y ciclo de vida ---
|
||||
void init(); // Inicializa el jugador
|
||||
void update(float delta_time); // Actualiza estado, animación y contadores (time-based)
|
||||
void render(); // Dibuja el jugador en pantalla
|
||||
|
||||
// --- Entrada y control ---
|
||||
void setInput(Input::Action action); // Procesa entrada general
|
||||
void setInputPlaying(Input::Action action); // Procesa entrada en modo jugando
|
||||
void setInputEnteringName(Input::Action action); // Procesa entrada al introducir nombre
|
||||
|
||||
// --- Movimiento y animación ---
|
||||
void move(float delta_time); // Mueve el jugador (time-based)
|
||||
void setAnimation(float delta_time); // Establece la animación según el estado (time-based)
|
||||
|
||||
// --- Texturas y animaciones ---
|
||||
void setPlayerTextures(const std::vector<std::shared_ptr<Texture>>& texture); // NOLINT(readability-avoid-const-params-in-decls) Cambia las texturas del jugador
|
||||
|
||||
// --- Gameplay: Puntuación y power-ups ---
|
||||
void addScore(int score, int lowest_hi_score_entry); // Añade puntos
|
||||
void incScoreMultiplier(); // Incrementa el multiplicador
|
||||
void decScoreMultiplier(); // Decrementa el multiplicador
|
||||
|
||||
// --- Estados de juego ---
|
||||
void setPlayingState(State state); // Cambia el estado de juego
|
||||
void setInvulnerable(bool value); // Establece el valor del estado de invulnerabilidad
|
||||
void setPowerUp(); // Activa el modo PowerUp
|
||||
void updatePowerUp(float delta_time); // Actualiza el valor de PowerUp
|
||||
void giveExtraHit(); // Concede un toque extra al jugador
|
||||
void removeExtraHit(); // Quita el toque extra al jugador
|
||||
void decContinueCounter(); // Decrementa el contador de continuar
|
||||
void setWalkingState(State state) { walking_state_ = state; } // Establece el estado de caminar
|
||||
void startFiringSystem(int cooldown_frames); // Inicia el sistema de disparo
|
||||
void setScoreBoardPanel(Scoreboard::Id panel) { scoreboard_panel_ = panel; } // Establece el panel del marcador
|
||||
void addCredit();
|
||||
void passShowingName();
|
||||
|
||||
// --- Estado del juego: Consultas (is* methods) ---
|
||||
[[nodiscard]] auto isLyingOnTheFloorForever() const -> bool { return playing_state_ == State::LYING_ON_THE_FLOOR_FOREVER; }
|
||||
[[nodiscard]] auto isCelebrating() const -> bool { return playing_state_ == State::CELEBRATING; }
|
||||
[[nodiscard]] auto isContinue() const -> bool { return playing_state_ == State::CONTINUE; }
|
||||
[[nodiscard]] auto isDying() const -> bool { return playing_state_ == State::ROLLING; }
|
||||
[[nodiscard]] auto isEnteringName() const -> bool { return playing_state_ == State::ENTERING_NAME; }
|
||||
[[nodiscard]] auto isShowingName() const -> bool { return playing_state_ == State::SHOWING_NAME; }
|
||||
[[nodiscard]] auto isEnteringNameGameCompleted() const -> bool { return playing_state_ == State::ENTERING_NAME_GAME_COMPLETED; }
|
||||
[[nodiscard]] auto isLeavingScreen() const -> bool { return playing_state_ == State::LEAVING_SCREEN; }
|
||||
[[nodiscard]] auto isGameOver() const -> bool { return playing_state_ == State::GAME_OVER; }
|
||||
[[nodiscard]] auto isPlaying() const -> bool { return playing_state_ == State::PLAYING; }
|
||||
[[nodiscard]] auto isWaiting() const -> bool { return playing_state_ == State::WAITING; }
|
||||
[[nodiscard]] auto isTitleHidden() const -> bool { return playing_state_ == State::TITLE_HIDDEN; }
|
||||
|
||||
// --- Estados específicos: Consultas adicionales ---
|
||||
[[nodiscard]] auto canFire() const -> bool { return can_fire_new_system_; } // Usa nuevo sistema
|
||||
[[nodiscard]] auto hasExtraHit() const -> bool { return extra_hit_; }
|
||||
[[nodiscard]] auto isCooling() const -> bool { return firing_state_ == State::COOLING_LEFT || firing_state_ == State::COOLING_UP || firing_state_ == State::COOLING_RIGHT; }
|
||||
[[nodiscard]] auto isRecoiling() const -> bool { return firing_state_ == State::RECOILING_LEFT || firing_state_ == State::RECOILING_UP || firing_state_ == State::RECOILING_RIGHT; }
|
||||
[[nodiscard]] auto qualifiesForHighScore() const -> bool { return qualifies_for_high_score_; }
|
||||
[[nodiscard]] auto isInvulnerable() const -> bool { return invulnerable_; }
|
||||
[[nodiscard]] auto isPowerUp() const -> bool { return power_up_; }
|
||||
[[nodiscard]] auto isInBulletColorToggleMode() const -> bool { return in_power_up_ending_phase_; }
|
||||
|
||||
// --- Getters: Propiedades y valores ---
|
||||
// Posición y dimensiones
|
||||
[[nodiscard]] auto getPosX() const -> int { return static_cast<int>(pos_x_); }
|
||||
[[nodiscard]] auto getPosY() const -> int { return pos_y_; }
|
||||
[[nodiscard]] static auto getWidth() -> int { return WIDTH; }
|
||||
[[nodiscard]] static auto getHeight() -> int { return HEIGHT; }
|
||||
// Jugador y identificación
|
||||
[[nodiscard]] auto getId() const -> Player::Id { return id_; }
|
||||
[[nodiscard]] auto getName() const -> const std::string& { return name_; }
|
||||
[[nodiscard]] auto getPlayingState() const -> State { return playing_state_; }
|
||||
auto getCollider() -> Circle& { return collider_; }
|
||||
[[nodiscard]] auto getZOrder() const -> size_t { return z_order_; }
|
||||
void setZOrder(size_t z_order) { z_order_ = z_order; }
|
||||
|
||||
// Puntuación y juego
|
||||
[[nodiscard]] auto getScore() const -> int { return score_; }
|
||||
[[nodiscard]] auto getScoreMultiplier() const -> float { return score_multiplier_; }
|
||||
[[nodiscard]] auto get1CC() const -> bool { return game_completed_ && credits_used_ <= 1; }
|
||||
[[nodiscard]] auto getScoreBoardPanel() const -> Scoreboard::Id { return scoreboard_panel_; }
|
||||
|
||||
// Power-ups y estado especial
|
||||
[[nodiscard]] auto getCoffees() const -> int { return coffees_; }
|
||||
[[nodiscard]] auto getPowerUpCounter() const -> int { return power_up_counter_; }
|
||||
[[nodiscard]] auto getInvulnerableCounter() const -> int { return invulnerable_counter_; }
|
||||
[[nodiscard]] auto getBulletColor() const -> Bullet::Color; // Devuelve el color actual de bala según el estado
|
||||
auto getNextBulletColor() -> Bullet::Color; // Devuelve el color para la próxima bala (alterna si está en modo toggle)
|
||||
void setBulletColors(Bullet::Color normal, Bullet::Color powered); // Establece los colores de bala para este jugador
|
||||
[[nodiscard]] auto getBulletSoundFile() const -> std::string { return bullet_sound_file_; } // Devuelve el archivo de sonido de bala
|
||||
void setBulletSoundFile(const std::string& filename); // Establece el archivo de sonido de bala para este jugador
|
||||
|
||||
// Contadores y timers
|
||||
[[nodiscard]] auto getContinueCounter() const -> int { return continue_counter_; }
|
||||
[[nodiscard]] auto getRecordName() const -> std::string { return enter_name_ ? enter_name_->getFinalName() : "xxx"; }
|
||||
[[nodiscard]] auto getLastEnterName() const -> std::string { return last_enter_name_; }
|
||||
|
||||
// --- Configuración e interfaz externa ---
|
||||
void setName(const std::string& name) { name_ = name; }
|
||||
void setGamepad(std::shared_ptr<Input::Gamepad> gamepad) { gamepad_ = std::move(gamepad); }
|
||||
[[nodiscard]] auto getGamepad() const -> std::shared_ptr<Input::Gamepad> { return gamepad_; }
|
||||
void setUsesKeyboard(bool value) { uses_keyboard_ = value; }
|
||||
[[nodiscard]] auto getUsesKeyboard() const -> bool { return uses_keyboard_; }
|
||||
[[nodiscard]] auto getController() const -> int { return controller_index_; }
|
||||
|
||||
// Demo file management
|
||||
[[nodiscard]] auto getDemoFile() const -> size_t { return demo_file_; }
|
||||
void setDemoFile(size_t demo_file) { demo_file_ = demo_file; }
|
||||
|
||||
private:
|
||||
// --- Constantes de física y movimiento ---
|
||||
static constexpr float BASE_SPEED = 90.0F; // Velocidad base del jugador (pixels/segundo)
|
||||
|
||||
// --- Constantes de power-ups y estados especiales ---
|
||||
static constexpr int POWERUP_COUNTER = 1500; // Duración del estado PowerUp (frames)
|
||||
static constexpr int INVULNERABLE_COUNTER = 200; // Duración del estado invulnerable (frames)
|
||||
static constexpr size_t INVULNERABLE_TEXTURE = 3; // Textura usada durante invulnerabilidad
|
||||
|
||||
// --- Constantes del sistema de disparo (obsoletas - usar nuevo sistema) ---
|
||||
static constexpr int COOLING_DURATION = 50; // Duración del enfriamiento tras disparar
|
||||
static constexpr int COOLING_COMPLETE = 0; // Valor que indica enfriamiento completado
|
||||
|
||||
// --- Constantes de estados de espera ---
|
||||
static constexpr int WAITING_COUNTER = 1000; // Tiempo de espera en estado de espera
|
||||
|
||||
// --- Constantes del nuevo sistema de disparo de dos líneas ---
|
||||
static constexpr float AIMING_DURATION_FACTOR = 0.5F; // 50% del cooldown funcional
|
||||
static constexpr float RECOILING_DURATION_MULTIPLIER = 4.0F; // 4 veces la duración de aiming
|
||||
static constexpr float THREAT_POSE_DURATION = 50.0F / 60.0F; // 50 frames = ~0.833s (duración base)
|
||||
static constexpr float MIN_THREAT_POSE_DURATION = 6.0F / 60.0F; // 6 frames = ~0.1s (duración mínima)
|
||||
|
||||
// --- Objetos y punteros ---
|
||||
std::unique_ptr<AnimatedSprite> player_sprite_; // Sprite para dibujar el jugador
|
||||
std::unique_ptr<AnimatedSprite> power_sprite_; // Sprite para dibujar el aura del jugador con el poder a tope
|
||||
std::unique_ptr<EnterName> enter_name_; // Clase utilizada para introducir el nombre
|
||||
std::unique_ptr<Cooldown> cooldown_ = nullptr; // Objeto para gestionar cooldowns de teclado
|
||||
std::shared_ptr<Input::Gamepad> gamepad_ = nullptr; // Dispositivo asociado
|
||||
Table* hi_score_table_ = nullptr; // Tabla de máximas puntuaciones
|
||||
int* glowing_entry_ = nullptr; // Entrada de la tabla de puntuaciones para hacerla brillar
|
||||
IStageInfo* stage_info_; // Informacion de la pantalla actual
|
||||
|
||||
// --- Variables de estado ---
|
||||
SDL_FRect play_area_; // Rectángulo con la zona de juego
|
||||
Circle collider_ = Circle(0, 0, 9); // Círculo de colisión del jugador
|
||||
std::string name_; // Nombre del jugador
|
||||
std::string last_enter_name_; // Último nombre introducido en la tabla de puntuaciones
|
||||
Scoreboard::Id scoreboard_panel_ = Scoreboard::Id::LEFT; // Panel del marcador asociado al jugador
|
||||
Id id_; // Identificador para el jugador
|
||||
State walking_state_ = State::WALKING_STOP; // Estado del jugador al moverse
|
||||
State firing_state_ = State::FIRING_NONE; // Estado del jugador al disparar
|
||||
State playing_state_ = State::WAITING; // Estado del jugador en el juego
|
||||
BulletColorPair bullet_colors_ = {.normal_color = Bullet::Color::YELLOW, .powered_color = Bullet::Color::GREEN}; // Par de colores de balas para este jugador
|
||||
std::string bullet_sound_file_ = "bullet1p.wav"; // Archivo de sonido de bala para este jugador
|
||||
|
||||
float pos_x_ = 0.0F; // Posición en el eje X
|
||||
float default_pos_x_; // Posición inicial para el jugador
|
||||
float vel_x_ = 0.0F; // Cantidad de píxeles a desplazarse en el eje X
|
||||
float score_multiplier_ = 1.0F; // Multiplicador de puntos
|
||||
int pos_y_ = 0; // Posición en el eje Y
|
||||
int default_pos_y_; // Posición inicial para el jugador
|
||||
int vel_y_ = 0; // Cantidad de píxeles a desplazarse en el eje Y
|
||||
float invulnerable_time_accumulator_ = 0.0F; // Acumulador de tiempo para invulnerabilidad (time-based)
|
||||
float power_up_time_accumulator_ = 0.0F; // Acumulador de tiempo para power-up (time-based)
|
||||
float continue_time_accumulator_ = 0.0F; // Acumulador de tiempo para continue counter (time-based)
|
||||
float name_entry_time_accumulator_ = 0.0F; // Acumulador de tiempo para name entry counter (time-based)
|
||||
float showing_name_time_accumulator_ = 0.0F; // Acumulador de tiempo para showing name (time-based)
|
||||
float waiting_time_accumulator_ = 0.0F; // Acumulador de tiempo para waiting movement (time-based)
|
||||
float step_time_accumulator_ = 0.0F; // Acumulador de tiempo para step counter (time-based)
|
||||
|
||||
// ========================================
|
||||
// NUEVO SISTEMA DE DISPARO DE DOS LÍNEAS
|
||||
// ========================================
|
||||
|
||||
// LÍNEA 1: SISTEMA FUNCIONAL (CanFire)
|
||||
float fire_cooldown_timer_ = 0.0F; // Tiempo restante hasta poder disparar otra vez
|
||||
bool can_fire_new_system_ = true; // true si puede disparar ahora mismo
|
||||
|
||||
// LÍNEA 2: SISTEMA VISUAL (Animaciones)
|
||||
enum class VisualFireState {
|
||||
NORMAL, // Brazo en posición neutral
|
||||
AIMING, // Brazo alzado (disparando)
|
||||
RECOILING, // Brazo en retroceso
|
||||
THREAT_POSE // Posición amenazante
|
||||
};
|
||||
|
||||
VisualFireState visual_fire_state_ = VisualFireState::NORMAL;
|
||||
float visual_state_timer_ = 0.0F; // Tiempo en el estado visual actual
|
||||
float aiming_duration_ = 0.0F; // Duración del estado AIMING
|
||||
float recoiling_duration_ = 0.0F; // Duración del estado RECOILING
|
||||
|
||||
int invulnerable_counter_ = INVULNERABLE_COUNTER; // Contador para la invulnerabilidad
|
||||
int score_ = 0; // Puntos del jugador
|
||||
int coffees_ = 0; // Indica cuántos cafés lleva acumulados
|
||||
int power_up_counter_ = POWERUP_COUNTER; // Temporizador para el modo PowerUp
|
||||
int power_up_x_offset_ = 0; // Desplazamiento del sprite de PowerUp respecto al sprite del jugador
|
||||
int continue_counter_ = 10; // Contador para poder continuar
|
||||
int controller_index_ = 0; // Índice del array de mandos que utilizará para moverse
|
||||
size_t demo_file_ = 0; // Indice del fichero de datos para el modo demo
|
||||
size_t z_order_ = 0; // Orden de dibujado en la pantalla
|
||||
float name_entry_idle_time_accumulator_ = 0.0F; // Tiempo idle acumulado para poner nombre (milisegundos)
|
||||
float name_entry_total_time_accumulator_ = 0.0F; // Tiempo total acumulado poniendo nombre (milisegundos)
|
||||
int step_counter_ = 0; // Cuenta los pasos para los estados en los que camina automáticamente
|
||||
int credits_used_ = 0; // Indica el número de veces que ha continuado
|
||||
int waiting_counter_ = 0; // Contador para el estado de espera
|
||||
bool qualifies_for_high_score_ = false; // Indica si tiene una puntuación que le permite entrar en la tabla de records
|
||||
bool invulnerable_ = true; // Indica si el jugador es invulnerable
|
||||
bool extra_hit_ = false; // Indica si el jugador tiene un toque extra
|
||||
bool power_up_ = false; // Indica si el jugador tiene activo el modo PowerUp
|
||||
bool power_sprite_visible_ = false; // Indica si el sprite de power-up debe ser visible
|
||||
bool in_power_up_ending_phase_ = false; // Indica si está en la fase final del power-up (alternando colores)
|
||||
bool bullet_color_toggle_ = false; // Para alternar entre verde y amarillo en fase final
|
||||
bool demo_ = false; // Para que el jugador sepa si está en el modo demostración
|
||||
bool game_completed_ = false; // Indica si ha completado el juego
|
||||
bool uses_keyboard_ = false; // Indica si usa el teclado como dispositivo de control
|
||||
bool recover_sound_triggered_ = false; // Indica si ya ha sonado el sonido en el estado RECOVER
|
||||
|
||||
// --- Métodos internos ---
|
||||
void shiftColliders(); // Actualiza el círculo de colisión a la posición del jugador
|
||||
void shiftSprite(); // Recoloca el sprite
|
||||
|
||||
// --- Setters internos ---
|
||||
void setController(int index) { controller_index_ = index; }
|
||||
void setFiringState(State state) { firing_state_ = state; }
|
||||
void setInvulnerableCounter(int value) { invulnerable_counter_ = value; }
|
||||
void setPowerUpCounter(int value) { power_up_counter_ = value; }
|
||||
void setScore(int score) { score_ = score; }
|
||||
void setScoreMultiplier(float value) { score_multiplier_ = value; }
|
||||
|
||||
// --- Actualizadores de estado (time-based) ---
|
||||
void updateInvulnerable(float delta_time); // Monitoriza el estado de invulnerabilidad
|
||||
void updateContinueCounter(float delta_time); // Actualiza el contador de continue
|
||||
void updateEnterNameCounter(float delta_time); // Actualiza el contador de entrar nombre
|
||||
void updateShowingName(float delta_time); // Actualiza el estado SHOWING_NAME
|
||||
void decNameEntryCounter(); // Decrementa el contador de entrar nombre
|
||||
|
||||
// --- Utilidades generales ---
|
||||
void updateScoreboard(); // Actualiza el panel del marcador
|
||||
void setScoreboardMode(Scoreboard::Mode mode) const; // Cambia el modo del marcador
|
||||
void playSound(const std::string& name) const; // Hace sonar un sonido
|
||||
[[nodiscard]] auto isRenderable() const -> bool; // Indica si se puede dibujar el objeto
|
||||
void addScoreToScoreBoard() const; // Añade una puntuación a la tabla de records
|
||||
|
||||
// --- Sistema de disparo (nuevo - dos líneas) ---
|
||||
void updateFireSystem(float delta_time); // Método principal del nuevo sistema de disparo
|
||||
void updateFunctionalLine(float delta_time); // Actualiza la línea funcional (CanFire)
|
||||
void updateVisualLine(float delta_time); // Actualiza la línea visual (Animaciones)
|
||||
void updateFiringStateFromVisual(); // Sincroniza firing_state_ con visual_fire_state_
|
||||
void transitionToRecoilingNew(); // Transición AIMING → RECOILING
|
||||
void transitionToThreatPose(); // Transición RECOILING → THREAT_POSE
|
||||
void transitionToNormalNew(); // Transición THREAT_POSE → NORMAL
|
||||
|
||||
// --- Manejadores de movimiento ---
|
||||
void handlePlayingMovement(float delta_time); // Gestiona el movimiento durante el juego
|
||||
void handleRecoverMovement(); // Comprueba si ha acabado la animación de recuperación
|
||||
void updateStepCounter(float delta_time); // Incrementa o ajusta el contador de pasos
|
||||
void setInputBasedOnPlayerId(); // Asocia las entradas de control según el jugador
|
||||
|
||||
// --- Manejadores de estados especiales ---
|
||||
void handleRollingMovement(); // Actualiza la lógica de movimiento de "rodar"
|
||||
void handleRollingBoundaryCollision(); // Detecta colisiones con límites durante rodamiento
|
||||
void handleRollingGroundCollision(); // Gestiona interacción con el suelo durante rodamiento
|
||||
void handleRollingStop(); // Detiene el movimiento del objeto rodante
|
||||
void handleRollingBounce(); // Aplica lógica de rebote durante rodamiento
|
||||
void handleContinueTimeOut(); // Gestiona tiempo de espera en pantalla "Continuar"
|
||||
|
||||
// --- Manejadores de transiciones de pantalla ---
|
||||
void handleTitleAnimation(float delta_time); // Ejecuta animación del título
|
||||
void handleLeavingScreen(float delta_time); // Lógica para salir de pantalla
|
||||
void handleEnteringScreen(float delta_time); // Lógica para entrar en pantalla
|
||||
void handlePlayer1Entering(float delta_time); // Entrada del Jugador 1
|
||||
void handlePlayer2Entering(float delta_time); // Entrada del Jugador 2
|
||||
|
||||
// --- Manejadores de pantallas especiales ---
|
||||
void handleCreditsMovement(float delta_time); // Movimiento en pantalla de créditos
|
||||
void handleCreditsRightMovement(); // Movimiento hacia la derecha en créditos
|
||||
void handleCreditsLeftMovement(); // Movimiento hacia la izquierda en créditos
|
||||
void handleWaitingMovement(float delta_time); // Animación del jugador saludando
|
||||
void updateWalkingStateForCredits(); // Actualiza estado de caminata en créditos
|
||||
|
||||
// --- Introducción de nombre ---
|
||||
void handleNameCharacterAddition();
|
||||
void handleNameCharacterRemoval();
|
||||
void handleNameSelectionMove(Input::Action action);
|
||||
void confirmNameEntry();
|
||||
|
||||
// --- Utilidades de animación ---
|
||||
[[nodiscard]] auto computeAnimation() const -> std::pair<std::string, SDL_FlipMode>; // Calcula animación de movimiento y disparo
|
||||
};
|
||||
225
source/game/entities/tabe.cpp
Normal file
225
source/game/entities/tabe.cpp
Normal file
@@ -0,0 +1,225 @@
|
||||
// IWYU pragma: no_include <bits/std_abs.h>
|
||||
#include "tabe.hpp"
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FlipMode, SDL_GetTicks
|
||||
|
||||
#include <algorithm> // Para max
|
||||
#include <array> // Para array
|
||||
#include <cstdlib> // Para rand, abs
|
||||
#include <string> // Para basic_string
|
||||
|
||||
#include "audio.hpp" // Para Audio
|
||||
#include "param.hpp" // Para Param, param, ParamGame, ParamTabe
|
||||
#include "resource.hpp" // Para Resource
|
||||
#include "utils.hpp" // Para Zone
|
||||
|
||||
// Constructor
|
||||
Tabe::Tabe()
|
||||
: sprite_(std::make_unique<AnimatedSprite>(Resource::get()->getTexture("tabe.png"), Resource::get()->getAnimation("tabe.ani"))),
|
||||
timer_(Timer(param.tabe.min_spawn_time, param.tabe.max_spawn_time)) {}
|
||||
|
||||
// Actualiza la lógica (time-based)
|
||||
void Tabe::update(float delta_time) {
|
||||
if (enabled_ && !timer_.is_paused) {
|
||||
sprite_->update(delta_time);
|
||||
move(delta_time);
|
||||
updateState(delta_time);
|
||||
}
|
||||
|
||||
timer_.update();
|
||||
if (timer_.shouldSpawn()) {
|
||||
enable();
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja el objeto
|
||||
void Tabe::render() {
|
||||
if (enabled_) {
|
||||
sprite_->render();
|
||||
}
|
||||
}
|
||||
|
||||
// Mueve el objeto (time-based)
|
||||
void Tabe::move(float delta_time) {
|
||||
const int X = static_cast<int>(x_);
|
||||
speed_ += accel_ * delta_time;
|
||||
x_ += speed_ * delta_time;
|
||||
fly_distance_ -= std::abs(X - static_cast<int>(x_));
|
||||
|
||||
// Comprueba si sale por los bordes
|
||||
const float MIN_X = param.game.game_area.rect.x - WIDTH;
|
||||
const float MAX_X = param.game.game_area.rect.x + param.game.game_area.rect.w;
|
||||
switch (destiny_) {
|
||||
case Direction::TO_THE_LEFT: {
|
||||
if (x_ < MIN_X) {
|
||||
disable();
|
||||
}
|
||||
if (x_ > param.game.game_area.rect.x + param.game.game_area.rect.w - WIDTH && direction_ == Direction::TO_THE_RIGHT) {
|
||||
setRandomFlyPath(Direction::TO_THE_LEFT, 80);
|
||||
x_ = param.game.game_area.rect.x + param.game.game_area.rect.w - WIDTH;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Direction::TO_THE_RIGHT: {
|
||||
if (x_ > MAX_X) {
|
||||
disable();
|
||||
}
|
||||
if (x_ < param.game.game_area.rect.x && direction_ == Direction::TO_THE_LEFT) {
|
||||
setRandomFlyPath(Direction::TO_THE_RIGHT, 80);
|
||||
x_ = param.game.game_area.rect.x;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (fly_distance_ <= 0) {
|
||||
if (waiting_counter_ > 0) {
|
||||
accel_ = speed_ = 0.0F;
|
||||
waiting_counter_ -= delta_time;
|
||||
waiting_counter_ = std::max<float>(waiting_counter_, 0);
|
||||
} else {
|
||||
constexpr int CHOICES = 4;
|
||||
const std::array<Direction, CHOICES> LEFT = {
|
||||
Direction::TO_THE_LEFT,
|
||||
Direction::TO_THE_LEFT,
|
||||
Direction::TO_THE_LEFT,
|
||||
Direction::TO_THE_RIGHT};
|
||||
|
||||
const std::array<Direction, CHOICES> RIGHT = {
|
||||
Direction::TO_THE_LEFT,
|
||||
Direction::TO_THE_RIGHT,
|
||||
Direction::TO_THE_RIGHT,
|
||||
Direction::TO_THE_RIGHT};
|
||||
|
||||
const Direction DIRECTION = destiny_ == Direction::TO_THE_LEFT
|
||||
? LEFT[rand() % CHOICES]
|
||||
: RIGHT[rand() % CHOICES];
|
||||
|
||||
setRandomFlyPath(DIRECTION, 20 + (rand() % 40));
|
||||
}
|
||||
}
|
||||
|
||||
shiftSprite();
|
||||
}
|
||||
|
||||
// Habilita el objeto
|
||||
void Tabe::enable() {
|
||||
if (!enabled_) {
|
||||
enabled_ = true;
|
||||
has_bonus_ = true;
|
||||
hit_counter_ = 0;
|
||||
number_of_hits_ = 0;
|
||||
y_ = param.game.game_area.rect.y + 20.0F;
|
||||
|
||||
// Establece una dirección aleatoria
|
||||
destiny_ = direction_ = rand() % 2 == 0 ? Direction::TO_THE_LEFT : Direction::TO_THE_RIGHT;
|
||||
|
||||
// Establece la posición inicial
|
||||
x_ = (direction_ == Direction::TO_THE_LEFT) ? param.game.game_area.rect.x + param.game.game_area.rect.w : param.game.game_area.rect.x - WIDTH;
|
||||
|
||||
// Crea una ruta de vuelo
|
||||
setRandomFlyPath(direction_, 60);
|
||||
shiftSprite();
|
||||
}
|
||||
}
|
||||
|
||||
// Establece un vuelo aleatorio
|
||||
void Tabe::setRandomFlyPath(Direction direction, int length) {
|
||||
direction_ = direction;
|
||||
fly_distance_ = length;
|
||||
waiting_counter_ = 0.083F + ((rand() % 15) * 0.0167F); // 5-20 frames converted to seconds (5/60 to 20/60)
|
||||
Audio::get()->playSound("tabe.wav");
|
||||
|
||||
constexpr float SPEED = 120.0F; // 2 pixels/frame * 60fps = 120 pixels/second
|
||||
|
||||
switch (direction) {
|
||||
case Direction::TO_THE_LEFT: {
|
||||
speed_ = -1.0F * SPEED;
|
||||
accel_ = -1.0F * (1 + (rand() % 10)) * 2.0F; // Converted from frame-based to seconds
|
||||
sprite_->setFlip(SDL_FLIP_NONE);
|
||||
break;
|
||||
}
|
||||
|
||||
case Direction::TO_THE_RIGHT: {
|
||||
speed_ = SPEED;
|
||||
accel_ = (1 + (rand() % 10)) * 2.0F; // Converted from frame-based to seconds
|
||||
sprite_->setFlip(SDL_FLIP_HORIZONTAL);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Establece el estado
|
||||
void Tabe::setState(State state) {
|
||||
if (enabled_) {
|
||||
state_ = state;
|
||||
|
||||
switch (state) {
|
||||
case State::FLY:
|
||||
sprite_->setCurrentAnimation("fly");
|
||||
break;
|
||||
|
||||
case State::HIT:
|
||||
sprite_->setCurrentAnimation("hit");
|
||||
hit_counter_ = 0.083F; // 5 frames converted to seconds (5/60)
|
||||
++number_of_hits_;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza el estado (time-based)
|
||||
void Tabe::updateState(float delta_time) {
|
||||
if (state_ == State::HIT) {
|
||||
hit_counter_ -= delta_time;
|
||||
if (hit_counter_ <= 0) {
|
||||
setState(State::FLY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Intenta obtener el bonus
|
||||
auto Tabe::tryToGetBonus() -> bool {
|
||||
if (has_bonus_ && rand() % std::max(1, 15 - number_of_hits_) == 0) {
|
||||
has_bonus_ = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Actualiza el temporizador
|
||||
void Tabe::updateTimer() {
|
||||
timer_.current_time = SDL_GetTicks();
|
||||
timer_.delta_time = timer_.current_time - timer_.last_time;
|
||||
timer_.last_time = timer_.current_time;
|
||||
}
|
||||
|
||||
// Deshabilita el objeto
|
||||
void Tabe::disable() {
|
||||
enabled_ = false;
|
||||
timer_.reset();
|
||||
}
|
||||
|
||||
// Detiene/activa el timer
|
||||
void Tabe::pauseTimer(bool value) {
|
||||
timer_.setPaused(value);
|
||||
}
|
||||
|
||||
// Deshabilita el spawning permanentemente
|
||||
void Tabe::disableSpawning() {
|
||||
timer_.setSpawnDisabled(true);
|
||||
}
|
||||
|
||||
// Habilita el spawning nuevamente
|
||||
void Tabe::enableSpawning() {
|
||||
timer_.setSpawnDisabled(false);
|
||||
}
|
||||
150
source/game/entities/tabe.hpp
Normal file
150
source/game/entities/tabe.hpp
Normal file
@@ -0,0 +1,150 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para Uint32, SDL_GetTicks, SDL_FRect
|
||||
|
||||
#include <cstdlib> // Para rand
|
||||
#include <memory> // Para unique_ptr
|
||||
|
||||
#include "animated_sprite.hpp" // Para AnimatedSprite
|
||||
|
||||
// --- Clase Tabe ---
|
||||
class Tabe {
|
||||
public:
|
||||
// --- Enumeraciones para dirección y estado ---
|
||||
enum class Direction : int {
|
||||
TO_THE_LEFT = 0,
|
||||
TO_THE_RIGHT = 1,
|
||||
};
|
||||
|
||||
enum class State : int {
|
||||
FLY = 0,
|
||||
HIT = 1,
|
||||
};
|
||||
|
||||
// --- Constructores y destructor ---
|
||||
Tabe();
|
||||
~Tabe() = default;
|
||||
|
||||
// --- Métodos principales ---
|
||||
void update(float delta_time); // Actualiza la lógica (time-based)
|
||||
void render(); // Dibuja el objeto
|
||||
void enable(); // Habilita el objeto
|
||||
void setState(State state); // Establece el estado
|
||||
auto tryToGetBonus() -> bool; // Intenta obtener el bonus
|
||||
void pauseTimer(bool value); // Detiene/activa el timer
|
||||
void disableSpawning(); // Deshabilita el spawning permanentemente
|
||||
void enableSpawning(); // Habilita el spawning nuevamente
|
||||
|
||||
// --- Getters ---
|
||||
auto getCollider() -> SDL_FRect& { return sprite_->getRect(); } // Obtiene el área de colisión
|
||||
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; } // Indica si el objeto está activo
|
||||
|
||||
private:
|
||||
// --- Constantes ---
|
||||
static constexpr int WIDTH = 32;
|
||||
static constexpr int HEIGHT = 32;
|
||||
|
||||
// --- Estructura para el temporizador del Tabe ---
|
||||
struct Timer {
|
||||
private:
|
||||
static constexpr Uint32 MINUTES_TO_MILLISECONDS = 60000; // Factor de conversión de minutos a milisegundos
|
||||
|
||||
public:
|
||||
Uint32 time_until_next_spawn; // Tiempo restante para la próxima aparición
|
||||
Uint32 min_spawn_time; // Tiempo mínimo entre apariciones (en milisegundos)
|
||||
Uint32 max_spawn_time; // Tiempo máximo entre apariciones (en milisegundos)
|
||||
Uint32 current_time; // Tiempo actual
|
||||
Uint32 delta_time; // Diferencia de tiempo desde la última actualización
|
||||
Uint32 last_time; // Tiempo de la última actualización
|
||||
bool is_paused{false}; // Indica si el temporizador está pausado (por pausa de juego)
|
||||
bool spawn_disabled{false}; // Indica si el spawning está deshabilitado permanentemente
|
||||
|
||||
// Constructor - los parámetros min_time y max_time están en mintos
|
||||
Timer(float min_time, float max_time)
|
||||
: min_spawn_time(static_cast<Uint32>(min_time * MINUTES_TO_MILLISECONDS)),
|
||||
max_spawn_time(static_cast<Uint32>(max_time * MINUTES_TO_MILLISECONDS)),
|
||||
current_time(SDL_GetTicks()) {
|
||||
reset();
|
||||
}
|
||||
|
||||
// Restablece el temporizador con un nuevo tiempo hasta la próxima aparición
|
||||
void reset() {
|
||||
Uint32 range = max_spawn_time - min_spawn_time;
|
||||
time_until_next_spawn = min_spawn_time + (rand() % (range + 1));
|
||||
last_time = SDL_GetTicks();
|
||||
}
|
||||
|
||||
// Actualiza el temporizador, decrementando el tiempo hasta la próxima aparición
|
||||
void update() {
|
||||
current_time = SDL_GetTicks();
|
||||
|
||||
// Solo actualizar si no está pausado (ni por juego ni por spawn deshabilitado)
|
||||
if (!is_paused && !spawn_disabled) {
|
||||
delta_time = current_time - last_time;
|
||||
|
||||
if (time_until_next_spawn > delta_time) {
|
||||
time_until_next_spawn -= delta_time;
|
||||
} else {
|
||||
time_until_next_spawn = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Siempre actualizar last_time para evitar saltos de tiempo al despausar
|
||||
last_time = current_time;
|
||||
}
|
||||
|
||||
// Pausa o reanuda el temporizador
|
||||
void setPaused(bool paused) {
|
||||
if (is_paused != paused) {
|
||||
is_paused = paused;
|
||||
// Al despausar, actualizar last_time para evitar saltos
|
||||
if (!paused) {
|
||||
last_time = SDL_GetTicks();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pausa o reanuda el spawning
|
||||
void setSpawnDisabled(bool disabled) {
|
||||
if (spawn_disabled != disabled) {
|
||||
spawn_disabled = disabled;
|
||||
// Al reactivar, actualizar last_time para evitar saltos
|
||||
if (!disabled) {
|
||||
last_time = SDL_GetTicks();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Indica si el temporizador ha finalizado
|
||||
[[nodiscard]] auto shouldSpawn() const -> bool {
|
||||
return time_until_next_spawn == 0 && !is_paused && !spawn_disabled;
|
||||
}
|
||||
};
|
||||
|
||||
// --- Objetos y punteros ---
|
||||
std::unique_ptr<AnimatedSprite> sprite_; // Sprite con los gráficos y animaciones
|
||||
|
||||
// --- Variables de estado ---
|
||||
float x_ = 0; // Posición X
|
||||
float y_ = 0; // Posición Y
|
||||
float speed_ = 0.0F; // Velocidad de movimiento
|
||||
float accel_ = 0.0F; // Aceleración
|
||||
int fly_distance_ = 0; // Distancia de vuelo
|
||||
float waiting_counter_ = 0; // Tiempo que pasa quieto
|
||||
bool enabled_ = false; // Indica si el objeto está activo
|
||||
Direction direction_ = Direction::TO_THE_LEFT; // Dirección actual
|
||||
Direction destiny_ = Direction::TO_THE_LEFT; // Destino
|
||||
State state_ = State::FLY; // Estado actual
|
||||
float hit_counter_ = 0; // Contador para el estado HIT
|
||||
int number_of_hits_ = 0; // Cantidad de disparos recibidos
|
||||
bool has_bonus_ = true; // Indica si aún tiene el bonus para soltar
|
||||
Timer timer_; // Temporizador para gestionar la aparición
|
||||
|
||||
// --- Métodos internos ---
|
||||
void move(float delta_time); // Mueve el objeto (time-based)
|
||||
void shiftSprite() { sprite_->setPos(x_, y_); } // Actualiza la posición del sprite
|
||||
void setRandomFlyPath(Direction direction, int length); // Establece un vuelo aleatorio
|
||||
void updateState(float delta_time); // Actualiza el estado (time-based)
|
||||
void updateTimer(); // Actualiza el temporizador
|
||||
void disable(); // Deshabilita el objeto
|
||||
};
|
||||
Reference in New Issue
Block a user