Compare commits

2 Commits

Author SHA1 Message Date
b3f6e2fcf0 Metal post-processing pipeline implementation
- Add custom Metal render target creation
- Implement post-processing without GPU-CPU-GPU copies
- Create Metal texture extraction system
- Add Metal CRT shader pipeline integration
- Modify screen rendering to use Metal when available
- Enable shaders by default for testing

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-16 07:42:03 +02:00
7f00942517 treballant en metal 2025-09-10 20:44:10 +02:00
54 changed files with 1200 additions and 2065 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,4 @@
.vscode .vscode
.claude
build/ build/
data/config/config.txt data/config/config.txt
*.DS_Store *.DS_Store

View File

@@ -10,8 +10,8 @@ game.play_area.rect.x 0 # Posición X de la zona jugable
game.play_area.rect.y 0 # Posición Y de la zona jugable game.play_area.rect.y 0 # Posición Y de la zona jugable
game.play_area.rect.w 320 # Ancho de la zona jugable game.play_area.rect.w 320 # Ancho de la zona jugable
game.play_area.rect.h 200 # Alto de la zona jugable game.play_area.rect.h 200 # Alto de la zona jugable
game.name_entry_idle_time 10000 # Segundos para introducir el nombre al finalizar la partida si no se pulsa nada game.name_entry_idle_time 10 # Segundos para introducir el nombre al finalizar la partida si no se pulsa nada
game.name_entry_total_time 60000 # Segundos totales para introducir el nombre al finalizar la partida game.name_entry_total_time 60 # Segundos totales para introducir el nombre al finalizar la partida
game.hit_stop false # Indica si debe haber un paro cuando el jugador es golpeado por un globo game.hit_stop false # Indica si debe haber un paro cuando el jugador es golpeado por un globo
game.hit_stop_ms 500 # Cantidad de milisegundos que dura el hit_stop game.hit_stop_ms 500 # Cantidad de milisegundos que dura el hit_stop
@@ -39,11 +39,11 @@ scoreboard.text_color2 FFFFFF # Color secundario del texto del marca
scoreboard.skip_countdown_value 8 # Valor para saltar la cuenta atrás (segundos) scoreboard.skip_countdown_value 8 # Valor para saltar la cuenta atrás (segundos)
# --- TITLE --- # --- TITLE ---
title.press_start_position 180 # Posición Y del texto "Press Start" title.press_start_position 180 # Posición Y del texto "Press Start"
title.title_duration 14000 # Duración de la pantalla de título (milisegundos) title.title_duration 800 # Duración de la pantalla de título (frames)
title.arcade_edition_position 123 # Posición Y del subtítulo "Arcade Edition" title.arcade_edition_position 123 # Posición Y del subtítulo "Arcade Edition"
title.title_c_c_position 80 # Posición Y del título principal title.title_c_c_position 80 # Posición Y del título principal
title.bg_color 41526F # Color de fondo en la sección titulo title.bg_color 41526F # Color de fondo en la sección titulo
# --- BACKGROUND --- # --- BACKGROUND ---
background.attenuate_color FFFFFF00 # Color de atenuación del fondo (RGBA hexadecimal) background.attenuate_color FFFFFF00 # Color de atenuación del fondo (RGBA hexadecimal)

View File

@@ -10,8 +10,8 @@ game.play_area.rect.x 0 # Posición X de la zona jugable
game.play_area.rect.y 0 # Posición Y de la zona jugable game.play_area.rect.y 0 # Posición Y de la zona jugable
game.play_area.rect.w 320 # Ancho de la zona jugable game.play_area.rect.w 320 # Ancho de la zona jugable
game.play_area.rect.h 216 # Alto de la zona jugable game.play_area.rect.h 216 # Alto de la zona jugable
game.name_entry_idle_time 10000 # Segundos para introducir el nombre al finalizar la partida si no se pulsa nada game.name_entry_idle_time 10 # Segundos para introducir el nombre al finalizar la partida si no se pulsa nada
game.name_entry_total_time 60000 # Segundos totales para introducir el nombre al finalizar la partida game.name_entry_total_time 60 # Segundos totales para introducir el nombre al finalizar la partida
game.hit_stop false # Indica si debe haber un paro cuando el jugador es golpeado por un globo game.hit_stop false # Indica si debe haber un paro cuando el jugador es golpeado por un globo
game.hit_stop_ms 500 # Cantidad de milisegundos que dura el hit_stop game.hit_stop_ms 500 # Cantidad de milisegundos que dura el hit_stop
@@ -39,11 +39,11 @@ scoreboard.text_color2 FFFFFF # Color secundario del texto del marca
scoreboard.skip_countdown_value 8 # Valor para saltar la cuenta atrás (segundos) scoreboard.skip_countdown_value 8 # Valor para saltar la cuenta atrás (segundos)
# --- TITLE --- # --- TITLE ---
title.press_start_position 180 # Posición Y del texto "Press Start" title.press_start_position 180 # Posición Y del texto "Press Start"
title.title_duration 14000 # Duración de la pantalla de título (milisegundos) title.title_duration 800 # Duración de la pantalla de título (frames)
title.arcade_edition_position 123 # Posición Y del subtítulo "Arcade Edition" title.arcade_edition_position 123 # Posición Y del subtítulo "Arcade Edition"
title.title_c_c_position 80 # Posición Y del título principal title.title_c_c_position 80 # Posición Y del título principal
title.bg_color 41526F # Color de fondo en la sección titulo title.bg_color 41526F # Color de fondo en la sección titulo
# --- BACKGROUND --- # --- BACKGROUND ---
background.attenuate_color FFFFFF00 # Color de atenuación del fondo (RGBA hexadecimal) background.attenuate_color FFFFFF00 # Color de atenuación del fondo (RGBA hexadecimal)

View File

@@ -43,10 +43,6 @@ auto loadAnimationsFromFile(const std::string& file_path) -> AnimationsFileBuffe
std::vector<std::string> buffer; std::vector<std::string> buffer;
std::string line; std::string line;
while (std::getline(input_stream, line)) { while (std::getline(input_stream, line)) {
// Eliminar caracteres de retorno de carro (\r) al final de la línea
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
if (!line.empty()) { if (!line.empty()) {
buffer.push_back(line); buffer.push_back(line);
} }
@@ -86,7 +82,7 @@ auto AnimatedSprite::getAnimationIndex(const std::string& name) -> int {
return -1; return -1;
} }
// Calcula el frame correspondiente a la animación (frame-based) // Calcula el frame correspondiente a la animación
void AnimatedSprite::animate() { void AnimatedSprite::animate() {
if (animations_[current_animation_].speed == 0 || animations_[current_animation_].paused) { if (animations_[current_animation_].speed == 0 || animations_[current_animation_].paused) {
return; return;
@@ -116,39 +112,6 @@ void AnimatedSprite::animate() {
} }
} }
// Calcula el frame correspondiente a la animación (time-based)
void AnimatedSprite::animate(float deltaTime) {
if (animations_[current_animation_].speed == 0 || animations_[current_animation_].paused) {
return;
}
// Convertir speed (frames) a tiempo: speed frames = speed * (1000ms/60fps) milisegundos
float frameTime = static_cast<float>(animations_[current_animation_].speed) * (1000.0f / 60.0f);
// Acumular tiempo transcurrido
animations_[current_animation_].time_accumulator += deltaTime;
// Verificar si es momento de cambiar frame
if (animations_[current_animation_].time_accumulator >= frameTime) {
animations_[current_animation_].time_accumulator -= frameTime;
animations_[current_animation_].current_frame++;
// Si alcanza el final de la animación
if (animations_[current_animation_].current_frame >= animations_[current_animation_].frames.size()) {
if (animations_[current_animation_].loop == -1) { // Si no hay loop, deja el último frame
animations_[current_animation_].current_frame = animations_[current_animation_].frames.size() - 1;
animations_[current_animation_].completed = true;
} else { // Si hay loop, vuelve al frame indicado
animations_[current_animation_].time_accumulator = 0.0f;
animations_[current_animation_].current_frame = animations_[current_animation_].loop;
}
}
// Actualizar el sprite clip
updateSpriteClip();
}
}
// Comprueba si ha terminado la animación // Comprueba si ha terminado la animación
auto AnimatedSprite::animationIsCompleted() -> bool { auto AnimatedSprite::animationIsCompleted() -> bool {
return animations_[current_animation_].completed; return animations_[current_animation_].completed;
@@ -163,12 +126,10 @@ void AnimatedSprite::setCurrentAnimation(const std::string& name, bool reset) {
if (reset) { if (reset) {
animations_[current_animation_].current_frame = 0; animations_[current_animation_].current_frame = 0;
animations_[current_animation_].counter = 0; animations_[current_animation_].counter = 0;
animations_[current_animation_].time_accumulator = 0.0f;
animations_[current_animation_].completed = false; animations_[current_animation_].completed = false;
} else { } else {
animations_[current_animation_].current_frame = std::min(animations_[OLD_ANIMATION].current_frame, animations_[current_animation_].frames.size() - 1); animations_[current_animation_].current_frame = std::min(animations_[OLD_ANIMATION].current_frame, animations_[current_animation_].frames.size() - 1);
animations_[current_animation_].counter = animations_[OLD_ANIMATION].counter; animations_[current_animation_].counter = animations_[OLD_ANIMATION].counter;
animations_[current_animation_].time_accumulator = animations_[OLD_ANIMATION].time_accumulator;
animations_[current_animation_].completed = animations_[OLD_ANIMATION].completed; animations_[current_animation_].completed = animations_[OLD_ANIMATION].completed;
} }
updateSpriteClip(); updateSpriteClip();
@@ -184,35 +145,26 @@ void AnimatedSprite::setCurrentAnimation(int index, bool reset) {
if (reset) { if (reset) {
animations_[current_animation_].current_frame = 0; animations_[current_animation_].current_frame = 0;
animations_[current_animation_].counter = 0; animations_[current_animation_].counter = 0;
animations_[current_animation_].time_accumulator = 0.0f;
animations_[current_animation_].completed = false; animations_[current_animation_].completed = false;
} else { } else {
animations_[current_animation_].current_frame = std::min(animations_[OLD_ANIMATION].current_frame, animations_[current_animation_].frames.size()); animations_[current_animation_].current_frame = std::min(animations_[OLD_ANIMATION].current_frame, animations_[current_animation_].frames.size());
animations_[current_animation_].counter = animations_[OLD_ANIMATION].counter; animations_[current_animation_].counter = animations_[OLD_ANIMATION].counter;
animations_[current_animation_].time_accumulator = animations_[OLD_ANIMATION].time_accumulator;
animations_[current_animation_].completed = animations_[OLD_ANIMATION].completed; animations_[current_animation_].completed = animations_[OLD_ANIMATION].completed;
} }
updateSpriteClip(); updateSpriteClip();
} }
} }
// Actualiza las variables del objeto (frame-based) // Actualiza las variables del objeto
void AnimatedSprite::update() { void AnimatedSprite::update() {
animate(); animate();
MovingSprite::update(); MovingSprite::update();
} }
// Actualiza las variables del objeto (time-based)
void AnimatedSprite::update(float deltaTime) {
animate(deltaTime);
MovingSprite::update(deltaTime);
}
// Reinicia la animación // Reinicia la animación
void AnimatedSprite::resetAnimation() { void AnimatedSprite::resetAnimation() {
animations_[current_animation_].current_frame = 0; animations_[current_animation_].current_frame = 0;
animations_[current_animation_].counter = 0; animations_[current_animation_].counter = 0;
animations_[current_animation_].time_accumulator = 0.0f;
animations_[current_animation_].completed = false; animations_[current_animation_].completed = false;
animations_[current_animation_].paused = false; animations_[current_animation_].paused = false;
updateSpriteClip(); updateSpriteClip();

View File

@@ -21,12 +21,11 @@ struct Animation {
std::string name; // Nombre de la animación std::string name; // Nombre de la animación
std::vector<SDL_FRect> frames; // Frames que componen la animación std::vector<SDL_FRect> frames; // Frames que componen la animación
int speed{DEFAULT_SPEED}; // Velocidad de reproducción (frame-based) int speed{DEFAULT_SPEED}; // Velocidad de reproducción
int loop{0}; // Frame de vuelta al terminar (-1 para no repetir) int loop{0}; // Frame de vuelta al terminar (-1 para no repetir)
bool completed{false}; // Indica si la animación ha finalizado bool completed{false}; // Indica si la animación ha finalizado
size_t current_frame{0}; // Frame actual en reproducción size_t current_frame{0}; // Frame actual en reproducción
int counter{0}; // Contador para la animación (frame-based) int counter{0}; // Contador para la animación
float time_accumulator{0.0f}; // Acumulador de tiempo para animaciones time-based
bool paused{false}; // La animación no avanza bool paused{false}; // La animación no avanza
Animation() = default; Animation() = default;
@@ -56,8 +55,7 @@ class AnimatedSprite : public MovingSprite {
~AnimatedSprite() override = default; ~AnimatedSprite() override = default;
// --- Métodos principales --- // --- Métodos principales ---
void update() override; // Actualiza la animación (frame-based) void update() override; // Actualiza la animación
void update(float deltaTime); // Actualiza la animación (time-based)
// --- Control de animaciones --- // --- Control de animaciones ---
void setCurrentAnimation(const std::string& name = "default", bool reset = true); // Establece la animación por nombre void setCurrentAnimation(const std::string& name = "default", bool reset = true); // Establece la animación por nombre
@@ -80,8 +78,7 @@ class AnimatedSprite : public MovingSprite {
int current_animation_ = 0; // Índice de la animación activa int current_animation_ = 0; // Índice de la animación activa
// --- Métodos internos --- // --- Métodos internos ---
void animate(); // Calcula el frame correspondiente a la animación (frame-based) void animate(); // Calcula el frame correspondiente a la animación
void animate(float deltaTime); // Calcula el frame correspondiente a la animación (time-based)
void loadFromAnimationsFileBuffer(const AnimationsFileBuffer& source); // Carga la animación desde un vector de cadenas void loadFromAnimationsFileBuffer(const AnimationsFileBuffer& source); // Carga la animación desde un vector de cadenas
void processConfigLine(const std::string& line, AnimationConfig& config); // Procesa una línea de configuración void processConfigLine(const std::string& line, AnimationConfig& config); // Procesa una línea de configuración
void updateFrameCalculations(AnimationConfig& config); // Actualiza los cálculos basados en las dimensiones del frame void updateFrameCalculations(AnimationConfig& config); // Actualiza los cálculos basados en las dimensiones del frame

View File

@@ -126,14 +126,7 @@ void Background::initializeTextures() {
} }
// Actualiza la lógica del objeto // Actualiza la lógica del objeto
// Actualiza la lógica del objeto (compatibilidad)
void Background::update() { void Background::update() {
constexpr float FRAME_TIME_MS = 1000.0f / 60.0f; // 16.67ms por frame a 60 FPS
update(FRAME_TIME_MS);
}
// Actualiza la lógica del objeto
void Background::update(float delta_time) {
// Actualiza la progresión y calcula transiciones // Actualiza la progresión y calcula transiciones
if (!manual_mode_) { if (!manual_mode_) {
updateProgression(); updateProgression();

View File

@@ -31,8 +31,7 @@ class Background {
~Background(); // Destructor ~Background(); // Destructor
// --- Métodos principales --- // --- Métodos principales ---
void update(); // Actualiza la lógica del objeto (compatibilidad) void update(); // Actualiza la lógica del objeto
void update(float delta_time); // Actualiza la lógica del objeto
void render(); // Dibuja el objeto void render(); // Dibuja el objeto
void reset(); // Reinicia la progresión void reset(); // Reinicia la progresión

View File

@@ -135,7 +135,7 @@ void Balloon::render() {
} }
} }
// Actualiza la posición y estados del globo (frame-based) // Actualiza la posición y estados del globo
void Balloon::move() { void Balloon::move() {
if (isStopped()) { if (isStopped()) {
return; return;
@@ -146,17 +146,6 @@ void Balloon::move() {
applyGravity(); 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() { void Balloon::handleHorizontalMovement() {
x_ += vx_ * speed_; x_ += vx_ * speed_;
@@ -169,20 +158,6 @@ void Balloon::handleHorizontalMovement() {
} }
} }
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() { void Balloon::handleVerticalMovement() {
y_ += vy_ * speed_; y_ += vy_ * speed_;
@@ -193,18 +168,6 @@ void Balloon::handleVerticalMovement() {
handleBottomCollision(); 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 { auto Balloon::isOutOfHorizontalBounds(float min_x, float max_x) const -> bool {
return x_ < min_x || x_ > max_x; return x_ < min_x || x_ > max_x;
} }
@@ -267,18 +230,6 @@ void Balloon::applyGravity() {
} }
} }
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() { void Balloon::playBouncingSound() {
if (sound_.enabled && sound_.bouncing_enabled) { if (sound_.enabled && sound_.bouncing_enabled) {
Audio::get()->playSound(sound_.bouncing_file); Audio::get()->playSound(sound_.bouncing_file);
@@ -291,7 +242,7 @@ void Balloon::playPoppingSound() {
} }
} }
// Actualiza al globo a su posicion, animación y controla los contadores (frame-based) // Actualiza al globo a su posicion, animación y controla los contadores
void Balloon::update() { void Balloon::update() {
move(); move();
updateState(); updateState();
@@ -302,20 +253,7 @@ void Balloon::update() {
++counter_; ++counter_;
} }
// Actualiza al globo a su posicion, animación y controla los contadores (time-based) // Actualiza los estados del globo
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() { void Balloon::updateState() {
// Si se está creando // Si se está creando
if (isBeingCreated()) { if (isBeingCreated()) {
@@ -325,7 +263,7 @@ void Balloon::updateState() {
if (creation_counter_ > 0) { if (creation_counter_ > 0) {
// Desplaza lentamente el globo hacia abajo y hacia un lado // Desplaza lentamente el globo hacia abajo y hacia un lado
if (static_cast<int>(creation_counter_) % 10 == 0) { if (creation_counter_ % 10 == 0) {
y_++; y_++;
x_ += vx_; x_ += vx_;
@@ -352,51 +290,6 @@ void Balloon::updateState() {
} }
} }
// 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 // Establece la animación correspondiente al estado
void Balloon::setAnimation() { void Balloon::setAnimation() {
std::string creating_animation; std::string creating_animation;

View File

@@ -87,13 +87,11 @@ class Balloon {
~Balloon() = default; ~Balloon() = default;
// --- Métodos principales --- // --- Métodos principales ---
void alignTo(int x); // Centra el globo en la posición X void alignTo(int x); // Centra el globo en la posición X
void render(); // Pinta el globo en la pantalla void render(); // Pinta el globo en la pantalla
void move(); // Actualiza la posición y estados del globo (frame-based) void move(); // Actualiza la posición y estados del globo
void move(float deltaTime); // Actualiza la posición y estados del globo (time-based) void update(); // Actualiza el globo (posición, animación, contadores)
void update(); // Actualiza el globo (posición, animación, contadores) (frame-based) void stop(); // Detiene el globo
void update(float deltaTime); // Actualiza el globo (posición, animación, contadores) (time-based)
void stop(); // Detiene el globo
void start(); // Pone el globo en movimiento void start(); // Pone el globo en movimiento
void pop(bool should_sound = false); // Explota el globo void pop(bool should_sound = false); // Explota el globo
@@ -258,8 +256,8 @@ class Balloon {
bool stopped_; // Si el globo está parado bool stopped_; // Si el globo está parado
bool use_reversed_colors_ = false; // Si se usa el color alternativo bool use_reversed_colors_ = false; // Si se usa el color alternativo
Circle collider_; // Círculo de colisión Circle collider_; // Círculo de colisión
float creation_counter_; // Temporizador de creación Uint16 creation_counter_; // Temporizador de creación
float creation_counter_ini_; // Valor inicial del temporizador de creación Uint16 creation_counter_ini_; // Valor inicial del temporizador de creación
Uint16 score_; // Puntos al destruir el globo Uint16 score_; // Puntos al destruir el globo
Type type_; // Tipo de globo Type type_; // Tipo de globo
Size size_; // Tamaño de globo Size size_; // Tamaño de globo
@@ -267,7 +265,6 @@ class Balloon {
Uint32 counter_ = 0; // Contador interno Uint32 counter_ = 0; // Contador interno
float travel_y_ = 1.0F; // Distancia a recorrer en Y antes de aplicar gravedad float travel_y_ = 1.0F; // Distancia a recorrer en Y antes de aplicar gravedad
float speed_; // Velocidad del globo float speed_; // Velocidad del globo
float movement_accumulator_ = 0.0f; // Acumulador para movimiento durante creación (deltaTime)
Uint8 power_; // Poder que alberga el globo Uint8 power_; // Poder que alberga el globo
SDL_FRect play_area_; // Zona de movimiento del globo SDL_FRect play_area_; // Zona de movimiento del globo
Sound sound_; // Configuración de sonido del globo Sound sound_; // Configuración de sonido del globo
@@ -283,12 +280,9 @@ class Balloon {
void playPoppingSound(); // Reproduce el sonido de reventar void playPoppingSound(); // Reproduce el sonido de reventar
// --- Movimiento y física --- // --- Movimiento y física ---
void handleHorizontalMovement(); // Maneja el movimiento horizontal (frame-based) void handleHorizontalMovement(); // Maneja el movimiento horizontal
void handleHorizontalMovement(float deltaTime); // Maneja el movimiento horizontal (time-based) void handleVerticalMovement(); // Maneja el movimiento vertical
void handleVerticalMovement(); // Maneja el movimiento vertical (frame-based) void applyGravity(); // Aplica la gravedad al objeto
void handleVerticalMovement(float deltaTime); // Maneja el movimiento vertical (time-based)
void applyGravity(); // Aplica la gravedad al objeto (frame-based)
void applyGravity(float deltaTime); // Aplica la gravedad al objeto (time-based)
// --- Rebote --- // --- Rebote ---
void enableBounceEffect(); // Activa el efecto de rebote void enableBounceEffect(); // Activa el efecto de rebote
@@ -303,6 +297,5 @@ class Balloon {
void handleBottomCollision(); // Maneja la colisión inferior void handleBottomCollision(); // Maneja la colisión inferior
// --- Lógica de estado --- // --- Lógica de estado ---
void updateState(); // Actualiza los estados del globo (frame-based) void updateState(); // Actualiza los estados del globo
void updateState(float deltaTime); // Actualiza los estados del globo (time-based)
}; };

View File

@@ -62,7 +62,7 @@ void BalloonManager::init() {
explosions_->addTexture(3, explosions_textures_.at(3), explosions_animations_.at(3)); explosions_->addTexture(3, explosions_textures_.at(3), explosions_animations_.at(3));
} }
// Actualiza (frame-based) // Actualiza
void BalloonManager::update() { void BalloonManager::update() {
for (const auto &balloon : balloons_) { for (const auto &balloon : balloons_) {
balloon->update(); balloon->update();
@@ -71,15 +71,6 @@ void BalloonManager::update() {
explosions_->update(); explosions_->update();
} }
// Actualiza (time-based)
void BalloonManager::update(float deltaTime) {
for (const auto &balloon : balloons_) {
balloon->update(deltaTime);
}
updateBalloonDeployCounter(deltaTime);
explosions_->update(deltaTime);
}
// Renderiza los objetos // Renderiza los objetos
void BalloonManager::render() { void BalloonManager::render() {
for (auto &balloon : balloons_) { for (auto &balloon : balloons_) {
@@ -171,23 +162,13 @@ void BalloonManager::freeBalloons() {
balloons_.erase(result.begin(), balloons_.end()); balloons_.erase(result.begin(), balloons_.end());
} }
// Actualiza la variable enemyDeployCounter (frame-based) // Actualiza la variable enemyDeployCounter
void BalloonManager::updateBalloonDeployCounter() { void BalloonManager::updateBalloonDeployCounter() {
if (balloon_deploy_counter_ > 0) { if (balloon_deploy_counter_ > 0) {
--balloon_deploy_counter_; --balloon_deploy_counter_;
} }
} }
// Actualiza la variable enemyDeployCounter (time-based)
void BalloonManager::updateBalloonDeployCounter(float deltaTime) {
if (balloon_deploy_counter_ > 0) {
// Convertir deltaTime (milisegundos) a factor de frame (asumiendo 60fps)
float frameFactor = deltaTime / (1000.0f / 60.0f);
balloon_deploy_counter_ -= frameFactor;
if (balloon_deploy_counter_ < 0) balloon_deploy_counter_ = 0;
}
}
// Indica si se puede crear una powerball // Indica si se puede crear una powerball
auto BalloonManager::canPowerBallBeCreated() -> bool { return (!power_ball_enabled_) && (calculateScreenPower() > Balloon::POWERBALL_SCREENPOWER_MINIMUM) && (power_ball_counter_ == 0); } auto BalloonManager::canPowerBallBeCreated() -> bool { return (!power_ball_enabled_) && (calculateScreenPower() > Balloon::POWERBALL_SCREENPOWER_MINIMUM) && (power_ball_counter_ == 0); }

View File

@@ -28,9 +28,8 @@ class BalloonManager {
~BalloonManager() = default; ~BalloonManager() = default;
// --- Métodos principales --- // --- Métodos principales ---
void update(); // Actualiza el estado de los globos (frame-based) void update(); // Actualiza el estado de los globos
void update(float deltaTime); // Actualiza el estado de los globos (time-based) void render(); // Renderiza los globos en pantalla
void render(); // Renderiza los globos en pantalla
// --- Gestión de globos --- // --- Gestión de globos ---
void freeBalloons(); // Libera globos que ya no sirven void freeBalloons(); // Libera globos que ya no sirven
@@ -50,8 +49,7 @@ class BalloonManager {
void setBalloonSpeed(float speed); // Ajusta la velocidad de los globos void setBalloonSpeed(float speed); // Ajusta la velocidad de los globos
void setDefaultBalloonSpeed(float speed) { default_balloon_speed_ = speed; }; // Establece la velocidad base void setDefaultBalloonSpeed(float speed) { default_balloon_speed_ = speed; }; // Establece la velocidad base
void resetBalloonSpeed() { setBalloonSpeed(default_balloon_speed_); }; // Restablece la velocidad de los globos void resetBalloonSpeed() { setBalloonSpeed(default_balloon_speed_); }; // Restablece la velocidad de los globos
void updateBalloonDeployCounter(); // Actualiza el contador de despliegue (frame-based) void updateBalloonDeployCounter(); // Actualiza el contador de despliegue
void updateBalloonDeployCounter(float deltaTime); // Actualiza el contador de despliegue (time-based)
auto canPowerBallBeCreated() -> bool; // Indica si se puede crear una PowerBall auto canPowerBallBeCreated() -> bool; // Indica si se puede crear una PowerBall
auto calculateScreenPower() -> int; // Calcula el poder de los globos en pantalla auto calculateScreenPower() -> int; // Calcula el poder de los globos en pantalla
@@ -100,7 +98,7 @@ class BalloonManager {
SDL_FRect play_area_ = param.game.play_area.rect; SDL_FRect play_area_ = param.game.play_area.rect;
float balloon_speed_ = Balloon::SPEED.at(0); float balloon_speed_ = Balloon::SPEED.at(0);
float default_balloon_speed_ = Balloon::SPEED.at(0); float default_balloon_speed_ = Balloon::SPEED.at(0);
float balloon_deploy_counter_ = 0; int balloon_deploy_counter_ = 0;
int power_ball_counter_ = 0; int power_ball_counter_ = 0;
int last_balloon_deploy_ = 0; int last_balloon_deploy_ = 0;
bool power_ball_enabled_ = false; bool power_ball_enabled_ = false;

View File

@@ -60,19 +60,13 @@ void Bullet::render() {
} }
} }
// Actualiza el estado del objeto (frame-based) // Actualiza el estado del objeto
auto Bullet::update() -> BulletMoveStatus { auto Bullet::update() -> BulletMoveStatus {
sprite_->update(); sprite_->update();
return move(); return move();
} }
// Actualiza el estado del objeto (time-based) // Implementación del movimiento usando BulletMoveStatus
auto Bullet::update(float deltaTime) -> BulletMoveStatus {
sprite_->update(deltaTime);
return move(deltaTime);
}
// Implementación del movimiento usando BulletMoveStatus (frame-based)
auto Bullet::move() -> BulletMoveStatus { auto Bullet::move() -> BulletMoveStatus {
pos_x_ += vel_x_; pos_x_ += vel_x_;
if (pos_x_ < param.game.play_area.rect.x - WIDTH || pos_x_ > param.game.play_area.rect.w) { if (pos_x_ < param.game.play_area.rect.x - WIDTH || pos_x_ > param.game.play_area.rect.w) {
@@ -92,29 +86,6 @@ auto Bullet::move() -> BulletMoveStatus {
return BulletMoveStatus::OK; return BulletMoveStatus::OK;
} }
// Implementación del movimiento usando BulletMoveStatus (time-based)
auto Bullet::move(float deltaTime) -> BulletMoveStatus {
// Convertir deltaTime (milisegundos) a factor de frame (asumiendo 60fps)
float frameFactor = deltaTime / (1000.0f / 60.0f);
pos_x_ += vel_x_ * frameFactor;
if (pos_x_ < param.game.play_area.rect.x - WIDTH || pos_x_ > param.game.play_area.rect.w) {
disable();
return BulletMoveStatus::OUT;
}
pos_y_ += VEL_Y * frameFactor;
if (pos_y_ < param.game.play_area.rect.y - HEIGHT) {
disable();
return BulletMoveStatus::OUT;
}
shiftSprite();
shiftColliders();
return BulletMoveStatus::OK;
}
auto Bullet::isEnabled() const -> bool { auto Bullet::isEnabled() const -> bool {
return bullet_type_ != BulletType::NONE; return bullet_type_ != BulletType::NONE;
} }

View File

@@ -34,10 +34,9 @@ class Bullet {
~Bullet() = default; // Destructor ~Bullet() = default; // Destructor
// --- Métodos principales --- // --- Métodos principales ---
void render(); // Dibuja la bala en pantalla void render(); // Dibuja la bala en pantalla
auto update() -> BulletMoveStatus; // Actualiza el estado del objeto (frame-based) auto update() -> BulletMoveStatus; // Actualiza el estado del objeto
auto update(float deltaTime) -> BulletMoveStatus; // Actualiza el estado del objeto (time-based) void disable(); // Desactiva la bala
void disable(); // Desactiva la bala
// --- Getters --- // --- Getters ---
[[nodiscard]] auto isEnabled() const -> bool; // Comprueba si está activa [[nodiscard]] auto isEnabled() const -> bool; // Comprueba si está activa
@@ -65,8 +64,7 @@ class Bullet {
// --- Métodos internos --- // --- Métodos internos ---
void shiftColliders(); // Ajusta el círculo de colisión void shiftColliders(); // Ajusta el círculo de colisión
void shiftSprite(); // Ajusta el sprite void shiftSprite(); // Ajusta el sprite
auto move() -> BulletMoveStatus; // Mueve la bala y devuelve su estado (frame-based) auto move() -> BulletMoveStatus; // Mueve la bala y devuelve su estado
auto move(float deltaTime) -> BulletMoveStatus; // Mueve la bala y devuelve su estado (time-based)
static auto calculateVelocity(BulletType bullet_type) -> float; // Calcula la velocidad horizontal de la bala static auto calculateVelocity(BulletType bullet_type) -> float; // Calcula la velocidad horizontal de la bala
static auto buildAnimationString(BulletType bullet_type, bool powered) -> std::string; // Construye el string de animación static auto buildAnimationString(BulletType bullet_type, bool powered) -> std::string; // Construye el string de animación
}; };

View File

@@ -15,8 +15,8 @@ namespace Game {
constexpr float WIDTH = 320.0F; constexpr float WIDTH = 320.0F;
constexpr float HEIGHT = 256.0F; constexpr float HEIGHT = 256.0F;
constexpr float ITEM_SIZE = 20.0F; constexpr float ITEM_SIZE = 20.0F;
constexpr int NAME_ENTRY_IDLE_TIME = 10000; // 10 segundos en milisegundos constexpr int NAME_ENTRY_IDLE_TIME = 10;
constexpr int NAME_ENTRY_TOTAL_TIME = 60000; // 60 segundos en milisegundos constexpr int NAME_ENTRY_TOTAL_TIME = 60;
constexpr bool HIT_STOP = false; constexpr bool HIT_STOP = false;
constexpr int HIT_STOP_MS = 500; constexpr int HIT_STOP_MS = 500;
constexpr const char* ITEM_TEXT_OUTLINE_COLOR = "FFFFFF00"; // 255, 255, 255, 0 constexpr const char* ITEM_TEXT_OUTLINE_COLOR = "FFFFFF00"; // 255, 255, 255, 0
@@ -58,7 +58,7 @@ constexpr int SKIP_COUNTDOWN_VALUE = 8;
// --- TITLE --- // --- TITLE ---
namespace Title { namespace Title {
constexpr int PRESS_START_POSITION = 180; constexpr int PRESS_START_POSITION = 180;
constexpr float DURATION = 14000; constexpr int DURATION = 800;
constexpr int ARCADE_EDITION_POSITION = 123; constexpr int ARCADE_EDITION_POSITION = 123;
constexpr int TITLE_C_C_POSITION = 80; constexpr int TITLE_C_C_POSITION = 80;
constexpr const char* BG_COLOR = "41526F"; constexpr const char* BG_COLOR = "41526F";
@@ -219,7 +219,7 @@ constexpr SDL_ScaleMode VIDEO_SCALE_MODE = SDL_ScaleMode::SDL_SCALEMODE_NEAREST;
constexpr bool VIDEO_FULLSCREEN = false; constexpr bool VIDEO_FULLSCREEN = false;
constexpr bool VIDEO_VSYNC = true; constexpr bool VIDEO_VSYNC = true;
constexpr bool VIDEO_INTEGER_SCALE = true; constexpr bool VIDEO_INTEGER_SCALE = true;
constexpr bool VIDEO_SHADERS = false; constexpr bool VIDEO_SHADERS = true;
// Music // Music
constexpr bool MUSIC_ENABLED = true; constexpr bool MUSIC_ENABLED = true;

View File

@@ -42,7 +42,7 @@ Director::Director(int argc, std::span<char *> argv) {
Section::name = Section::Name::GAME; Section::name = Section::Name::GAME;
Section::options = Section::Options::GAME_PLAY_1P; Section::options = Section::Options::GAME_PLAY_1P;
#elif _DEBUG #elif _DEBUG
Section::name = Section::Name::GAME; Section::name = Section::Name::HI_SCORE_TABLE;
Section::options = Section::Options::GAME_PLAY_1P; Section::options = Section::Options::GAME_PLAY_1P;
#else // NORMAL GAME #else // NORMAL GAME
Section::name = Section::Name::LOGO; Section::name = Section::Name::LOGO;

View File

@@ -6,7 +6,7 @@
class Texture; // lines 4-4 class Texture; // lines 4-4
// Actualiza la lógica de la clase (frame-based) // Actualiza la lógica de la clase
void Explosions::update() { void Explosions::update() {
for (auto &explosion : explosions_) { for (auto &explosion : explosions_) {
explosion->update(); explosion->update();
@@ -16,16 +16,6 @@ void Explosions::update() {
freeExplosions(); freeExplosions();
} }
// Actualiza la lógica de la clase (time-based)
void Explosions::update(float deltaTime) {
for (auto &explosion : explosions_) {
explosion->update(deltaTime);
}
// Vacia el vector de elementos finalizados
freeExplosions();
}
// Dibuja el objeto en pantalla // Dibuja el objeto en pantalla
void Explosions::render() { void Explosions::render() {
for (auto &explosion : explosions_) { for (auto &explosion : explosions_) {

View File

@@ -29,9 +29,8 @@ class Explosions {
~Explosions() = default; // Destructor por defecto ~Explosions() = default; // Destructor por defecto
// --- Métodos principales --- // --- Métodos principales ---
void update(); // Actualiza la lógica de la clase (frame-based) void update(); // Actualiza la lógica de la clase
void update(float deltaTime); // Actualiza la lógica de la clase (time-based) void render(); // Dibuja el objeto en pantalla
void render(); // Dibuja el objeto en pantalla
// --- Configuración --- // --- Configuración ---
void addTexture(int size, const std::shared_ptr<Texture> &texture, const std::vector<std::string> &animation); // Añade texturas al objeto void addTexture(int size, const std::shared_ptr<Texture> &texture, const std::vector<std::string> &animation); // Añade texturas al objeto

View File

@@ -4,6 +4,20 @@
#include <string> // Para basic_string, string #include <string> // Para basic_string, string
namespace shader { namespace shader {
bool init(SDL_Window *ventana, SDL_Texture *texturaBackBuffer, const std::string &vertexShader, const std::string &fragmentShader = ""); bool init(SDL_Window *ventana, SDL_Texture *texturaBackBuffer, const std::string &shaderSource, const std::string &fragmentShader = "");
void render(); void render();
void cleanup();
bool isUsingOpenGL();
#ifdef __APPLE__
namespace metal {
bool initMetal(SDL_Window* window, SDL_Texture* backBuffer, const std::string& shaderFilename);
SDL_Texture* createMetalRenderTarget(SDL_Renderer* renderer, int width, int height);
void updateMetalTexture(SDL_Texture* backBuffer);
void renderMetal();
void renderWithPostProcessing(SDL_Renderer* renderer, SDL_Texture* sourceTexture);
void cleanupMetal();
}
#endif
} // namespace shader } // namespace shader

387
source/external/jail_shader_metal.mm vendored Normal file
View File

@@ -0,0 +1,387 @@
#include "jail_shader.h"
#ifdef __APPLE__
#include <SDL3/SDL.h>
#include <SDL3/SDL_metal.h>
#include <Metal/Metal.h>
#include <QuartzCore/CAMetalLayer.h>
#include <CoreFoundation/CoreFoundation.h>
#include <stdexcept>
#include <vector>
#include "../asset.h"
namespace shader {
namespace metal {
// Metal objects
id<MTLDevice> device = nullptr;
id<MTLRenderPipelineState> pipelineState = nullptr;
id<MTLBuffer> vertexBuffer = nullptr;
id<MTLTexture> backBufferTexture = nullptr;
id<MTLTexture> gameCanvasTexture = nullptr; // Our custom render target texture
id<MTLSamplerState> sampler = nullptr;
// SDL objects (references from main shader module)
SDL_Window* win = nullptr;
SDL_Renderer* renderer = nullptr;
SDL_Texture* backBuffer = nullptr;
// Vertex data for fullscreen quad
struct Vertex {
float position[4]; // x, y, z, w
float texcoord[2]; // u, v
};
const Vertex quadVertices[] = {
// Position (x, y, z, w) // TexCoord (u, v) - Standard OpenGL-style coordinates
{{-1.0f, -1.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}, // Bottom-left
{{ 1.0f, -1.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, // Bottom-right
{{-1.0f, 1.0f, 0.0f, 1.0f}, {0.0f, 0.0f}}, // Top-left
{{ 1.0f, 1.0f, 0.0f, 1.0f}, {1.0f, 0.0f}}, // Top-right
};
std::string loadMetalShader(const std::string& filename) {
// Try to load the .metal file from the same location as GLSL files
auto data = Asset::get()->loadData(filename);
if (!data.empty()) {
return std::string(data.begin(), data.end());
}
return "";
}
SDL_Texture* createMetalRenderTarget(SDL_Renderer* renderer, int width, int height) {
if (!renderer) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "createMetalRenderTarget: No renderer provided");
return nullptr;
}
// Crear textura Metal como render target
SDL_Texture* metalTexture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, width, height);
if (!metalTexture) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create Metal render target texture: %s", SDL_GetError());
return nullptr;
}
// Configurar filtrado nearest neighbor para look pixelado
SDL_SetTextureScaleMode(metalTexture, SDL_SCALEMODE_NEAREST);
// Try to extract and store the Metal texture directly
SDL_PropertiesID props = SDL_GetTextureProperties(metalTexture);
if (props != 0) {
const char* propertyNames[] = {
"SDL.texture.metal.texture",
"SDL.renderer.metal.texture",
"metal.texture",
"texture.metal",
"MTLTexture",
"texture"
};
for (const char* propName : propertyNames) {
gameCanvasTexture = (__bridge id<MTLTexture>)SDL_GetPointerProperty(props, propName, nullptr);
if (gameCanvasTexture) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Successfully extracted Metal texture via property '%s' (size: %lux%lu)",
propName, [gameCanvasTexture width], [gameCanvasTexture height]);
break;
}
}
}
if (!gameCanvasTexture) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Could not extract Metal texture from SDL texture - shaders may not work");
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Created Metal render target texture (%dx%d)", width, height);
return metalTexture;
}
bool initMetal(SDL_Window* window, SDL_Texture* backBufferTexture, const std::string& shaderFilename) {
// Store references
win = window;
backBuffer = backBufferTexture;
renderer = SDL_GetRenderer(window);
if (!renderer) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to get SDL renderer");
return false;
}
// Get Metal layer from SDL renderer and extract device from it
CAMetalLayer* metalLayer = (__bridge CAMetalLayer*)SDL_GetRenderMetalLayer(renderer);
if (!metalLayer) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to get Metal layer from SDL renderer");
return false;
}
device = metalLayer.device;
if (!device) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to get Metal device from layer");
return false;
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Got Metal device from SDL layer: %s", [[device name] UTF8String]);
// Note: We no longer need our own texture - we'll use the backBuffer directly
// Load and compile shaders
std::string metalShaderSource = loadMetalShader(shaderFilename);
if (metalShaderSource.empty()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load Metal shader: %s", shaderFilename.c_str());
return false;
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Loaded Metal shader %s (length: %zu)",
shaderFilename.c_str(), metalShaderSource.length());
NSString* shaderNSString = [NSString stringWithUTF8String:metalShaderSource.c_str()];
NSError* error = nil;
id<MTLLibrary> library = [device newLibraryWithSource:shaderNSString options:nil error:&error];
if (!library || error) {
if (error) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to compile Metal shader: %s",
[[error localizedDescription] UTF8String]);
}
return false;
}
id<MTLFunction> vertexFunction = [library newFunctionWithName:@"vertex_main"];
id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"fragment_main"];
if (!vertexFunction || !fragmentFunction) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load Metal shader functions");
return false;
}
// Create render pipeline
MTLRenderPipelineDescriptor* pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineDescriptor.vertexFunction = vertexFunction;
pipelineDescriptor.fragmentFunction = fragmentFunction;
pipelineDescriptor.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
// Set up vertex descriptor
MTLVertexDescriptor* vertexDescriptor = [[MTLVertexDescriptor alloc] init];
vertexDescriptor.attributes[0].format = MTLVertexFormatFloat4;
vertexDescriptor.attributes[0].offset = 0;
vertexDescriptor.attributes[0].bufferIndex = 0;
vertexDescriptor.attributes[1].format = MTLVertexFormatFloat2;
vertexDescriptor.attributes[1].offset = 4 * sizeof(float);
vertexDescriptor.attributes[1].bufferIndex = 0;
vertexDescriptor.layouts[0].stride = sizeof(Vertex);
vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
pipelineDescriptor.vertexDescriptor = vertexDescriptor;
pipelineState = [device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error];
if (!pipelineState || error) {
if (error) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create Metal render pipeline: %s",
[[error localizedDescription] UTF8String]);
}
return false;
}
// Create vertex buffer
vertexBuffer = [device newBufferWithBytes:quadVertices
length:sizeof(quadVertices)
options:MTLResourceOptionCPUCacheModeDefault];
// Create sampler state for nearest neighbor filtering (pixelated look)
MTLSamplerDescriptor* samplerDescriptor = [[MTLSamplerDescriptor alloc] init];
samplerDescriptor.minFilter = MTLSamplerMinMagFilterNearest;
samplerDescriptor.magFilter = MTLSamplerMinMagFilterNearest;
samplerDescriptor.sAddressMode = MTLSamplerAddressModeClampToEdge;
samplerDescriptor.tAddressMode = MTLSamplerAddressModeClampToEdge;
sampler = [device newSamplerStateWithDescriptor:samplerDescriptor];
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "** Metal shader system initialized successfully");
return true;
}
void updateMetalTexture(SDL_Texture* backBuffer) {
if (!device || !backBuffer) return;
// Only log this occasionally to avoid spam
static int attemptCount = 0;
static bool hasLogged = false;
// Try multiple property names that SDL3 might use
SDL_PropertiesID props = SDL_GetTextureProperties(backBuffer);
if (props != 0) {
const char* propertyNames[] = {
"SDL.texture.metal.texture",
"SDL.renderer.metal.texture",
"metal.texture",
"texture.metal",
"MTLTexture",
"texture"
};
for (const char* propName : propertyNames) {
id<MTLTexture> sdlMetalTexture = (__bridge id<MTLTexture>)SDL_GetPointerProperty(props, propName, nullptr);
if (sdlMetalTexture) {
backBufferTexture = sdlMetalTexture;
if (!hasLogged) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Got Metal texture via property '%s' (size: %lux%lu)",
propName, [backBufferTexture width], [backBufferTexture height]);
hasLogged = true;
}
return;
}
}
}
// If we can't get the texture after several attempts, log once and continue
if (!hasLogged && attemptCount++ > 10) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Could not access SDL Metal texture after %d attempts - shader will be skipped", attemptCount);
hasLogged = true;
}
}
void renderMetal() {
if (!renderer || !device || !pipelineState) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Metal render failed: missing components");
return;
}
// Correct pipeline: backBuffer → Metal shaders → screen (no double rendering)
// Try to get the Metal texture directly from the SDL backBuffer texture
updateMetalTexture(backBuffer);
if (!backBufferTexture) {
static int fallbackLogCount = 0;
if (fallbackLogCount++ < 3) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Metal texture not available - falling back to normal SDL rendering (attempt %d)", fallbackLogCount);
}
// Fallback: render without shaders using normal SDL path
SDL_SetRenderTarget(renderer, nullptr);
SDL_RenderTexture(renderer, backBuffer, nullptr, nullptr);
SDL_RenderPresent(renderer);
return;
}
// Apply Metal CRT shader directly: backBuffer texture → screen
SDL_SetRenderTarget(renderer, nullptr);
// Get Metal command encoder to render directly to screen
void* encoder_ptr = SDL_GetRenderMetalCommandEncoder(renderer);
if (encoder_ptr) {
id<MTLRenderCommandEncoder> encoder = (__bridge id<MTLRenderCommandEncoder>)encoder_ptr;
static int debugCount = 0;
if (debugCount++ < 5) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Metal render attempt %d: encoder=%p, pipeline=%p, texture=%p",
debugCount, encoder, pipelineState, backBufferTexture);
}
// Apply CRT shader effect directly to backBuffer texture
[encoder setRenderPipelineState:pipelineState];
[encoder setVertexBuffer:vertexBuffer offset:0 atIndex:0];
[encoder setFragmentTexture:backBufferTexture atIndex:0];
[encoder setFragmentSamplerState:sampler atIndex:0];
// Draw fullscreen quad with CRT effect directly to screen
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
static int successCount = 0;
if (successCount++ < 5) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Applied CRT shader to backBuffer (attempt %d) - texture size: %lux%lu",
successCount, [backBufferTexture width], [backBufferTexture height]);
}
} else {
// Fallback: render normally without shaders
SDL_RenderTexture(renderer, backBuffer, nullptr, nullptr);
static int fallbackCount = 0;
if (fallbackCount++ < 3) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Failed to get Metal command encoder - fallback rendering");
}
}
SDL_RenderPresent(renderer);
}
void renderWithPostProcessing(SDL_Renderer* renderer, SDL_Texture* sourceTexture) {
if (!renderer || !sourceTexture || !device || !pipelineState) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Metal post-processing failed: missing components");
// Fallback: render normally without shaders
SDL_SetRenderTarget(renderer, nullptr);
SDL_RenderTexture(renderer, sourceTexture, nullptr, nullptr);
SDL_RenderPresent(renderer);
return;
}
// Use our stored Metal texture if available
id<MTLTexture> metalTexture = gameCanvasTexture;
if (!metalTexture) {
static int fallbackLogCount = 0;
if (fallbackLogCount++ < 3) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "gameCanvasTexture not available - falling back to normal rendering (attempt %d)", fallbackLogCount);
}
// Fallback: render normally without shaders
SDL_SetRenderTarget(renderer, nullptr);
SDL_RenderTexture(renderer, sourceTexture, nullptr, nullptr);
SDL_RenderPresent(renderer);
return;
}
// Apply Metal CRT shader post-processing: sourceTexture → screen
SDL_SetRenderTarget(renderer, nullptr);
// Get Metal command encoder to render directly to screen
void* encoder_ptr = SDL_GetRenderMetalCommandEncoder(renderer);
if (encoder_ptr) {
id<MTLRenderCommandEncoder> encoder = (__bridge id<MTLRenderCommandEncoder>)encoder_ptr;
static int debugCount = 0;
if (debugCount++ < 3) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Metal post-processing attempt %d: encoder=%p, pipeline=%p, texture=%p",
debugCount, encoder, pipelineState, metalTexture);
}
// Apply CRT shader effect to sourceTexture
[encoder setRenderPipelineState:pipelineState];
[encoder setVertexBuffer:vertexBuffer offset:0 atIndex:0];
[encoder setFragmentTexture:metalTexture atIndex:0];
[encoder setFragmentSamplerState:sampler atIndex:0];
// Draw fullscreen quad with CRT effect directly to screen
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
static int successCount = 0;
if (successCount++ < 3) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Applied CRT post-processing (attempt %d) - texture size: %lux%lu",
successCount, [metalTexture width], [metalTexture height]);
}
} else {
// Fallback: render normally without shaders
SDL_RenderTexture(renderer, sourceTexture, nullptr, nullptr);
static int fallbackCount = 0;
if (fallbackCount++ < 3) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Failed to get Metal command encoder for post-processing - fallback rendering");
}
}
SDL_RenderPresent(renderer);
}
void cleanupMetal() {
// Release Metal objects (ARC handles most of this automatically)
pipelineState = nullptr;
backBufferTexture = nullptr;
gameCanvasTexture = nullptr;
vertexBuffer = nullptr;
sampler = nullptr;
device = nullptr;
renderer = nullptr;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Metal shader system cleaned up");
}
} // namespace metal
} // namespace shader
#endif // __APPLE__

View File

@@ -82,11 +82,6 @@ void Fade::update() {
} }
} }
// Compatibilidad delta-time (ignora el parámetro ya que usa SDL_GetTicks)
void Fade::update(float delta_time) {
update(); // Llama al método original
}
void Fade::updatePreState() { void Fade::updatePreState() {
// Sistema basado en tiempo únicamente // Sistema basado en tiempo únicamente
Uint32 elapsed_time = SDL_GetTicks() - pre_start_time_; Uint32 elapsed_time = SDL_GetTicks() - pre_start_time_;

View File

@@ -37,11 +37,10 @@ class Fade {
~Fade(); ~Fade();
// --- Métodos principales --- // --- Métodos principales ---
void reset(); // Resetea variables para reutilizar el fade void reset(); // Resetea variables para reutilizar el fade
void render(); // Dibuja la transición en pantalla void render(); // Dibuja la transición en pantalla
void update(); // Actualiza el estado interno (ya usa tiempo real) void update(); // Actualiza el estado interno
void update(float delta_time); // Compatibilidad delta-time (ignora el parámetro) void activate(); // Activa el fade
void activate(); // Activa el fade
// --- Configuración --- // --- Configuración ---
void setColor(Uint8 r, Uint8 g, Uint8 b); // Establece el color RGB del fade void setColor(Uint8 r, Uint8 g, Uint8 b); // Establece el color RGB del fade

View File

@@ -45,7 +45,6 @@ void GameLogo::init() {
arcade_edition_status_ = Status::DISABLED; arcade_edition_status_ = Status::DISABLED;
shake_.init(1, 2, 8, XP); shake_.init(1, 2, 8, XP);
zoom_ = 3.0F * ZOOM_FACTOR; zoom_ = 3.0F * ZOOM_FACTOR;
post_finished_time_accumulator_ = 0.0f;
// Inicializa el bitmap de 'Coffee' // Inicializa el bitmap de 'Coffee'
coffee_sprite_->setPosX(XP); coffee_sprite_->setPosX(XP);
@@ -113,20 +112,13 @@ void GameLogo::render() {
} }
} }
// Actualiza la lógica de la clase (frame-based) // Actualiza la lógica de la clase
void GameLogo::update() { void GameLogo::update() {
updateCoffeeCrisis(); updateCoffeeCrisis();
updateArcadeEdition(); updateArcadeEdition();
updatePostFinishedCounter(); updatePostFinishedCounter();
} }
// Actualiza la lógica de la clase (time-based)
void GameLogo::update(float deltaTime) {
updateCoffeeCrisis(deltaTime);
updateArcadeEdition(deltaTime);
updatePostFinishedCounter(deltaTime);
}
void GameLogo::updateCoffeeCrisis() { void GameLogo::updateCoffeeCrisis() {
switch (coffee_crisis_status_) { switch (coffee_crisis_status_) {
case Status::MOVING: case Status::MOVING:
@@ -143,22 +135,6 @@ void GameLogo::updateCoffeeCrisis() {
} }
} }
void GameLogo::updateCoffeeCrisis(float deltaTime) {
switch (coffee_crisis_status_) {
case Status::MOVING:
handleCoffeeCrisisMoving(deltaTime);
break;
case Status::SHAKING:
handleCoffeeCrisisShaking(deltaTime);
break;
case Status::FINISHED:
handleCoffeeCrisisFinished(deltaTime);
break;
default:
break;
}
}
void GameLogo::updateArcadeEdition() { void GameLogo::updateArcadeEdition() {
switch (arcade_edition_status_) { switch (arcade_edition_status_) {
case Status::MOVING: case Status::MOVING:
@@ -172,19 +148,6 @@ void GameLogo::updateArcadeEdition() {
} }
} }
void GameLogo::updateArcadeEdition(float deltaTime) {
switch (arcade_edition_status_) {
case Status::MOVING:
handleArcadeEditionMoving(deltaTime);
break;
case Status::SHAKING:
handleArcadeEditionShaking(deltaTime);
break;
default:
break;
}
}
void GameLogo::handleCoffeeCrisisMoving() { void GameLogo::handleCoffeeCrisisMoving() {
coffee_sprite_->update(); coffee_sprite_->update();
crisis_sprite_->update(); crisis_sprite_->update();
@@ -195,16 +158,6 @@ void GameLogo::handleCoffeeCrisisMoving() {
} }
} }
void GameLogo::handleCoffeeCrisisMoving(float deltaTime) {
coffee_sprite_->update(deltaTime);
crisis_sprite_->update(deltaTime);
if (coffee_sprite_->hasFinished() && crisis_sprite_->hasFinished()) {
coffee_crisis_status_ = Status::SHAKING;
playTitleEffects();
}
}
void GameLogo::handleCoffeeCrisisShaking() { void GameLogo::handleCoffeeCrisisShaking() {
if (shake_.remaining > 0) { if (shake_.remaining > 0) {
processShakeEffect(coffee_sprite_.get(), crisis_sprite_.get()); processShakeEffect(coffee_sprite_.get(), crisis_sprite_.get());
@@ -215,24 +168,10 @@ void GameLogo::handleCoffeeCrisisShaking() {
updateDustSprites(); updateDustSprites();
} }
void GameLogo::handleCoffeeCrisisShaking(float deltaTime) {
if (shake_.remaining > 0) {
processShakeEffect(coffee_sprite_.get(), crisis_sprite_.get(), deltaTime);
} else {
finishCoffeeCrisisShaking();
}
updateDustSprites(deltaTime);
}
void GameLogo::handleCoffeeCrisisFinished() { void GameLogo::handleCoffeeCrisisFinished() {
updateDustSprites(); updateDustSprites();
} }
void GameLogo::handleCoffeeCrisisFinished(float deltaTime) {
updateDustSprites(deltaTime);
}
void GameLogo::handleArcadeEditionMoving() { void GameLogo::handleArcadeEditionMoving() {
zoom_ -= 0.1F * ZOOM_FACTOR; zoom_ -= 0.1F * ZOOM_FACTOR;
arcade_edition_sprite_->setZoom(zoom_); arcade_edition_sprite_->setZoom(zoom_);
@@ -242,16 +181,6 @@ void GameLogo::handleArcadeEditionMoving() {
} }
} }
void GameLogo::handleArcadeEditionMoving(float deltaTime) {
// Convertir 0.1F * ZOOM_FACTOR por frame a por milisegundo (asumiendo 60fps)
zoom_ -= (0.1F * ZOOM_FACTOR) * deltaTime / (1000.0F / 60.0F);
arcade_edition_sprite_->setZoom(zoom_);
if (zoom_ <= 1.0F) {
finishArcadeEditionMoving();
}
}
void GameLogo::handleArcadeEditionShaking() { void GameLogo::handleArcadeEditionShaking() {
if (shake_.remaining > 0) { if (shake_.remaining > 0) {
processArcadeEditionShake(); processArcadeEditionShake();
@@ -261,15 +190,6 @@ void GameLogo::handleArcadeEditionShaking() {
} }
} }
void GameLogo::handleArcadeEditionShaking(float deltaTime) {
if (shake_.remaining > 0) {
processArcadeEditionShake(deltaTime);
} else {
arcade_edition_sprite_->setX(shake_.origin);
arcade_edition_status_ = Status::FINISHED;
}
}
void GameLogo::processShakeEffect(SmartSprite* primary_sprite, SmartSprite* secondary_sprite) { void GameLogo::processShakeEffect(SmartSprite* primary_sprite, SmartSprite* secondary_sprite) {
if (shake_.counter > 0) { if (shake_.counter > 0) {
shake_.counter--; shake_.counter--;
@@ -284,23 +204,6 @@ void GameLogo::processShakeEffect(SmartSprite* primary_sprite, SmartSprite* seco
} }
} }
void GameLogo::processShakeEffect(SmartSprite* primary_sprite, SmartSprite* secondary_sprite, float deltaTime) {
// Convertir delay (frames) a tiempo: delay frames = delay * (1000ms/60fps)
float delayTime = static_cast<float>(shake_.delay) * (1000.0f / 60.0f);
shake_.time_accumulator += deltaTime;
if (shake_.time_accumulator >= delayTime) {
shake_.time_accumulator -= delayTime;
const auto DISPLACEMENT = calculateShakeDisplacement();
primary_sprite->setPosX(shake_.origin + DISPLACEMENT);
if (secondary_sprite != nullptr) {
secondary_sprite->setPosX(shake_.origin + DISPLACEMENT + 15);
}
shake_.remaining--;
}
}
void GameLogo::processArcadeEditionShake() { void GameLogo::processArcadeEditionShake() {
if (shake_.counter > 0) { if (shake_.counter > 0) {
shake_.counter--; shake_.counter--;
@@ -312,20 +215,6 @@ void GameLogo::processArcadeEditionShake() {
} }
} }
void GameLogo::processArcadeEditionShake(float deltaTime) {
// Convertir delay (frames) a tiempo: delay frames = delay * (1000ms/60fps)
float delayTime = static_cast<float>(shake_.delay) * (1000.0f / 60.0f);
shake_.time_accumulator += deltaTime;
if (shake_.time_accumulator >= delayTime) {
shake_.time_accumulator -= delayTime;
const auto DISPLACEMENT = calculateShakeDisplacement();
arcade_edition_sprite_->setX(shake_.origin + DISPLACEMENT);
shake_.remaining--;
}
}
auto GameLogo::calculateShakeDisplacement() const -> int { auto GameLogo::calculateShakeDisplacement() const -> int {
return shake_.remaining % 2 == 0 ? shake_.desp * (-1) : shake_.desp; return shake_.remaining % 2 == 0 ? shake_.desp * (-1) : shake_.desp;
} }
@@ -356,11 +245,6 @@ void GameLogo::updateDustSprites() {
dust_left_sprite_->update(); dust_left_sprite_->update();
} }
void GameLogo::updateDustSprites(float deltaTime) {
dust_right_sprite_->update(deltaTime);
dust_left_sprite_->update(deltaTime);
}
void GameLogo::updatePostFinishedCounter() { void GameLogo::updatePostFinishedCounter() {
if (coffee_crisis_status_ == Status::FINISHED && if (coffee_crisis_status_ == Status::FINISHED &&
arcade_edition_status_ == Status::FINISHED && arcade_edition_status_ == Status::FINISHED &&
@@ -369,23 +253,6 @@ void GameLogo::updatePostFinishedCounter() {
} }
} }
void GameLogo::updatePostFinishedCounter(float deltaTime) {
if (coffee_crisis_status_ == Status::FINISHED &&
arcade_edition_status_ == Status::FINISHED &&
post_finished_counter_ > 0) {
// Convertir 1 frame a tiempo: 1 frame = 1000ms/60fps = 16.67ms
float frameTime = 1000.0f / 60.0f;
post_finished_time_accumulator_ += deltaTime;
if (post_finished_time_accumulator_ >= frameTime) {
post_finished_time_accumulator_ -= frameTime;
--post_finished_counter_;
}
}
}
// Activa la clase // Activa la clase
void GameLogo::enable() { void GameLogo::enable() {
init(); init();

View File

@@ -16,10 +16,9 @@ class GameLogo {
~GameLogo() = default; ~GameLogo() = default;
// --- Métodos principales --- // --- Métodos principales ---
void render(); // Pinta la clase en pantalla void render(); // Pinta la clase en pantalla
void update(); // Actualiza la lógica de la clase (frame-based) void update(); // Actualiza la lógica de la clase
void update(float deltaTime); // Actualiza la lógica de la clase (time-based) void enable(); // Activa la clase
void enable(); // Activa la clase
// --- Getters --- // --- Getters ---
[[nodiscard]] auto hasFinished() const -> bool; // Indica si ha terminado la animación [[nodiscard]] auto hasFinished() const -> bool; // Indica si ha terminado la animación
@@ -35,13 +34,12 @@ class GameLogo {
// --- Estructuras privadas --- // --- Estructuras privadas ---
struct Shake { struct Shake {
int desp = 1; // Pixels de desplazamiento para agitar la pantalla en el eje x int desp = 1; // Pixels de desplazamiento para agitar la pantalla en el eje x
int delay = 2; // Retraso entre cada desplazamiento de la pantalla al agitarse (frame-based) int delay = 2; // Retraso entre cada desplazamiento de la pantalla al agitarse
int length = 8; // Cantidad de desplazamientos a realizar int length = 8; // Cantidad de desplazamientos a realizar
int remaining = length; // Cantidad de desplazamientos pendientes a realizar int remaining = length; // Cantidad de desplazamientos pendientes a realizar
int counter = delay; // Contador para el retraso (frame-based) int counter = delay; // Contador para el retraso
float time_accumulator = 0.0f; // Acumulador de tiempo para deltaTime int origin = 0; // Valor inicial de la pantalla para dejarla igual tras el desplazamiento
int origin = 0; // Valor inicial de la pantalla para dejarla igual tras el desplazamiento
Shake() = default; Shake() = default;
Shake(int d, int de, int l, int o) Shake(int d, int de, int l, int o)
@@ -58,7 +56,6 @@ class GameLogo {
length = l; length = l;
remaining = l; remaining = l;
counter = de; counter = de;
time_accumulator = 0.0f;
origin = o; origin = o;
} }
}; };
@@ -82,44 +79,32 @@ class GameLogo {
float x_; // Posición X del logo float x_; // Posición X del logo
float y_; // Posición Y del logo float y_; // Posición Y del logo
float zoom_ = 1.0F; // Zoom aplicado al texto "ARCADE EDITION" float zoom_ = 1.0F; // Zoom aplicado al texto "ARCADE EDITION"
int post_finished_counter_ = 1; // Contador final tras animaciones (frame-based) int post_finished_counter_ = 1; // Contador final tras animaciones
float post_finished_time_accumulator_ = 0.0f; // Acumulador de tiempo para post_finished_counter
// --- Inicialización --- // --- Inicialización ---
void init(); // Inicializa las variables void init(); // Inicializa las variables
[[nodiscard]] auto getInitialVerticalDesp() const -> int; // Calcula el desplazamiento vertical inicial [[nodiscard]] auto getInitialVerticalDesp() const -> int; // Calcula el desplazamiento vertical inicial
// --- Actualización de estados específicos --- // --- Actualización de estados específicos ---
void updateCoffeeCrisis(); // Actualiza el estado de "Coffee Crisis" (frame-based) void updateCoffeeCrisis(); // Actualiza el estado de "Coffee Crisis"
void updateCoffeeCrisis(float deltaTime); // Actualiza el estado de "Coffee Crisis" (time-based) void updateArcadeEdition(); // Actualiza el estado de "Arcade Edition"
void updateArcadeEdition(); // Actualiza el estado de "Arcade Edition" (frame-based) void updatePostFinishedCounter(); // Actualiza el contador tras finalizar una animación
void updateArcadeEdition(float deltaTime); // Actualiza el estado de "Arcade Edition" (time-based)
void updatePostFinishedCounter(); // Actualiza el contador tras finalizar una animación (frame-based)
void updatePostFinishedCounter(float deltaTime); // Actualiza el contador tras finalizar una animación (time-based)
// --- Efectos visuales: movimiento y sacudidas --- // --- Efectos visuales: movimiento y sacudidas ---
void handleCoffeeCrisisMoving(); // Maneja el movimiento de "Coffee Crisis" (frame-based) void handleCoffeeCrisisMoving(); // Maneja el movimiento de "Coffee Crisis"
void handleCoffeeCrisisMoving(float deltaTime); // Maneja el movimiento de "Coffee Crisis" (time-based) void handleCoffeeCrisisShaking(); // Maneja la sacudida de "Coffee Crisis"
void handleCoffeeCrisisShaking(); // Maneja la sacudida de "Coffee Crisis" (frame-based) void handleArcadeEditionMoving(); // Maneja el movimiento de "Arcade Edition"
void handleCoffeeCrisisShaking(float deltaTime); // Maneja la sacudida de "Coffee Crisis" (time-based) void handleArcadeEditionShaking(); // Maneja la sacudida de "Arcade Edition"
void handleArcadeEditionMoving(); // Maneja el movimiento de "Arcade Edition" (frame-based) void processShakeEffect(SmartSprite* primary_sprite, SmartSprite* secondary_sprite = nullptr); // Procesa el efecto de sacudida en sprites
void handleArcadeEditionMoving(float deltaTime); // Maneja el movimiento de "Arcade Edition" (time-based) void processArcadeEditionShake(); // Procesa la sacudida específica de "Arcade Edition"
void handleArcadeEditionShaking(); // Maneja la sacudida de "Arcade Edition" (frame-based) [[nodiscard]] auto calculateShakeDisplacement() const -> int; // Calcula el desplazamiento de la sacudida
void handleArcadeEditionShaking(float deltaTime); // Maneja la sacudida de "Arcade Edition" (time-based)
void processShakeEffect(SmartSprite* primary_sprite, SmartSprite* secondary_sprite = nullptr); // Procesa el efecto de sacudida en sprites (frame-based)
void processShakeEffect(SmartSprite* primary_sprite, SmartSprite* secondary_sprite, float deltaTime); // Procesa el efecto de sacudida en sprites (time-based)
void processArcadeEditionShake(); // Procesa la sacudida específica de "Arcade Edition" (frame-based)
void processArcadeEditionShake(float deltaTime); // Procesa la sacudida específica de "Arcade Edition" (time-based)
[[nodiscard]] auto calculateShakeDisplacement() const -> int; // Calcula el desplazamiento de la sacudida
// --- Gestión de finalización de efectos --- // --- Gestión de finalización de efectos ---
void handleCoffeeCrisisFinished(); // Maneja el final de la animación "Coffee Crisis" (frame-based) void handleCoffeeCrisisFinished(); // Maneja el final de la animación "Coffee Crisis"
void handleCoffeeCrisisFinished(float deltaTime); // Maneja el final de la animación "Coffee Crisis" (time-based) void finishCoffeeCrisisShaking(); // Finaliza la sacudida de "Coffee Crisis"
void finishCoffeeCrisisShaking(); // Finaliza la sacudida de "Coffee Crisis" void finishArcadeEditionMoving(); // Finaliza el movimiento de "Arcade Edition"
void finishArcadeEditionMoving(); // Finaliza el movimiento de "Arcade Edition"
// --- Utilidades --- // --- Utilidades ---
static void playTitleEffects(); // Reproduce efectos visuales/sonoros del título static void playTitleEffects(); // Reproduce efectos visuales/sonoros del título
void updateDustSprites(); // Actualiza los sprites de polvo (frame-based) void updateDustSprites(); // Actualiza los sprites de polvo
void updateDustSprites(float deltaTime); // Actualiza los sprites de polvo (time-based)
}; };

View File

@@ -53,7 +53,7 @@ void MovingSprite::stop() {
flip_ = SDL_FLIP_NONE; // Establece como se ha de voltear el sprite flip_ = SDL_FLIP_NONE; // Establece como se ha de voltear el sprite
} }
// Mueve el sprite (frame-based) // Mueve el sprite
void MovingSprite::move() { void MovingSprite::move() {
x_ += vx_; x_ += vx_;
y_ += vy_; y_ += vy_;
@@ -65,39 +65,16 @@ void MovingSprite::move() {
pos_.y = static_cast<int>(y_); pos_.y = static_cast<int>(y_);
} }
// Mueve el sprite (time-based) // Actualiza las variables internas del objeto
void MovingSprite::move(float deltaTime) {
// Convertir deltaTime (milisegundos) a factor de frame (asumiendo 60fps)
float frameFactor = deltaTime / (1000.0f / 60.0f);
x_ += vx_ * frameFactor;
y_ += vy_ * frameFactor;
vx_ += ax_ * frameFactor;
vy_ += ay_ * frameFactor;
pos_.x = static_cast<int>(x_);
pos_.y = static_cast<int>(y_);
}
// Actualiza las variables internas del objeto (frame-based)
void MovingSprite::update() { void MovingSprite::update() {
move(); move();
rotate(); rotate();
} }
// Actualiza las variables internas del objeto (time-based)
void MovingSprite::update(float deltaTime) {
move(deltaTime);
rotate(deltaTime);
}
// Muestra el sprite por pantalla // Muestra el sprite por pantalla
void MovingSprite::render() { void MovingSprite::render() { getTexture()->render(pos_.x, pos_.y, &sprite_clip_, horizontal_zoom_, vertical_zoom_, rotate_.angle, &rotate_.center, flip_); }
getTexture()->render(pos_.x, pos_.y, &sprite_clip_, horizontal_zoom_, vertical_zoom_, rotate_.angle, &rotate_.center, flip_);
}
// Establece la rotacion (frame-based) // Establece la rotacion
void MovingSprite::rotate() { void MovingSprite::rotate() {
if (rotate_.enabled) { if (rotate_.enabled) {
++rotate_.counter; ++rotate_.counter;
@@ -108,15 +85,6 @@ void MovingSprite::rotate() {
} }
} }
// Establece la rotacion (time-based)
void MovingSprite::rotate(float deltaTime) {
if (rotate_.enabled) {
// Convertir speed (frames) a tiempo: speed frames = speed * (1000ms/60fps) milisegundos
float rotationFrameTime = static_cast<float>(rotate_.speed) * (1000.0f / 60.0f);
rotate_.angle += rotate_.amount * (deltaTime / rotationFrameTime);
}
}
// Activa o desactiva el efecto de rotación // Activa o desactiva el efecto de rotación
void MovingSprite::setRotate(bool enable) { void MovingSprite::setRotate(bool enable) {
rotate_.enabled = enable; rotate_.enabled = enable;

View File

@@ -29,11 +29,10 @@ class MovingSprite : public Sprite {
~MovingSprite() override = default; ~MovingSprite() override = default;
// --- Métodos principales --- // --- Métodos principales ---
virtual void update(); // Actualiza las variables internas del objeto (frame-based) virtual void update(); // Actualiza las variables internas del objeto
virtual void update(float deltaTime); // Actualiza las variables internas del objeto (time-based) void clear() override; // Reinicia todas las variables a cero
void clear() override; // Reinicia todas las variables a cero void stop(); // Elimina el movimiento del sprite
void stop(); // Elimina el movimiento del sprite void render() override; // Muestra el sprite por pantalla
void render() override; // Muestra el sprite por pantalla
// --- Configuración --- // --- Configuración ---
void setPos(SDL_FRect rect); // Establece la posición y el tamaño del objeto void setPos(SDL_FRect rect); // Establece la posición y el tamaño del objeto
@@ -80,8 +79,6 @@ class MovingSprite : public Sprite {
// --- Métodos internos --- // --- Métodos internos ---
void updateAngle() { rotate_.angle += rotate_.amount; } // Incrementa el valor del ángulo void updateAngle() { rotate_.angle += rotate_.amount; } // Incrementa el valor del ángulo
void move(); // Mueve el sprite según velocidad y aceleración (frame-based) void move(); // Mueve el sprite según velocidad y aceleración
void move(float deltaTime); // Mueve el sprite según velocidad y aceleración (time-based) void rotate(); // Rota el sprite según los parámetros de rotación
void rotate(); // Rota el sprite según los parámetros de rotación (frame-based)
void rotate(float deltaTime); // Rota el sprite según los parámetros de rotación (time-based)
}; };

View File

@@ -106,6 +106,7 @@ auto setParams(const std::string& var, const std::string& value) -> bool {
{"scoreboard.rect.h", [](const std::string& v) { param.scoreboard.rect.h = std::stoi(v); }}, {"scoreboard.rect.h", [](const std::string& v) { param.scoreboard.rect.h = std::stoi(v); }},
{"scoreboard.skip_countdown_value", [](const std::string& v) { param.scoreboard.skip_countdown_value = std::stoi(v); }}, {"scoreboard.skip_countdown_value", [](const std::string& v) { param.scoreboard.skip_countdown_value = std::stoi(v); }},
{"title.press_start_position", [](const std::string& v) { param.title.press_start_position = std::stoi(v); }}, {"title.press_start_position", [](const std::string& v) { param.title.press_start_position = std::stoi(v); }},
{"title.title_duration", [](const std::string& v) { param.title.title_duration = std::stoi(v); }},
{"title.arcade_edition_position", [](const std::string& v) { param.title.arcade_edition_position = std::stoi(v); }}, {"title.arcade_edition_position", [](const std::string& v) { param.title.arcade_edition_position = std::stoi(v); }},
{"title.title_c_c_position", [](const std::string& v) { param.title.title_c_c_position = std::stoi(v); }}, {"title.title_c_c_position", [](const std::string& v) { param.title.title_c_c_position = std::stoi(v); }},
{"intro.text_distance_from_bottom", [](const std::string& v) { param.intro.text_distance_from_bottom = std::stoi(v); }}}; {"intro.text_distance_from_bottom", [](const std::string& v) { param.intro.text_distance_from_bottom = std::stoi(v); }}};
@@ -181,7 +182,6 @@ auto setParams(const std::string& var, const std::string& value) -> bool {
{"balloon.settings[3].grav", [](const std::string& v) { param.balloon.settings.at(3).grav = std::stof(v); }}, {"balloon.settings[3].grav", [](const std::string& v) { param.balloon.settings.at(3).grav = std::stof(v); }},
{"tabe.min_spawn_time", [](const std::string& v) { param.tabe.min_spawn_time = std::stof(v); }}, {"tabe.min_spawn_time", [](const std::string& v) { param.tabe.min_spawn_time = std::stof(v); }},
{"tabe.max_spawn_time", [](const std::string& v) { param.tabe.max_spawn_time = std::stof(v); }}, {"tabe.max_spawn_time", [](const std::string& v) { param.tabe.max_spawn_time = std::stof(v); }},
{"title.title_duration", [](const std::string& v) { param.title.title_duration = std::stof(v); }},
{"service_menu.window_message.padding", [](const std::string& v) { param.service_menu.window_message.padding = std::stof(v); }}, {"service_menu.window_message.padding", [](const std::string& v) { param.service_menu.window_message.padding = std::stof(v); }},
{"service_menu.window_message.line_spacing", [](const std::string& v) { param.service_menu.window_message.line_spacing = std::stof(v); }}, {"service_menu.window_message.line_spacing", [](const std::string& v) { param.service_menu.window_message.line_spacing = std::stof(v); }},
{"service_menu.window_message.title_separator_spacing", [](const std::string& v) { param.service_menu.window_message.title_separator_spacing = std::stof(v); }}, {"service_menu.window_message.title_separator_spacing", [](const std::string& v) { param.service_menu.window_message.title_separator_spacing = std::stof(v); }},

View File

@@ -38,7 +38,7 @@ struct ParamFade {
// --- Parámetros de la pantalla de título --- // --- Parámetros de la pantalla de título ---
struct ParamTitle { struct ParamTitle {
int press_start_position = GameDefaults::Title::PRESS_START_POSITION; int press_start_position = GameDefaults::Title::PRESS_START_POSITION;
float title_duration = GameDefaults::Title::DURATION; int title_duration = GameDefaults::Title::DURATION;
int arcade_edition_position = GameDefaults::Title::ARCADE_EDITION_POSITION; int arcade_edition_position = GameDefaults::Title::ARCADE_EDITION_POSITION;
int title_c_c_position = GameDefaults::Title::TITLE_C_C_POSITION; int title_c_c_position = GameDefaults::Title::TITLE_C_C_POSITION;
Color bg_color = Color::fromHex(GameDefaults::Title::BG_COLOR); Color bg_color = Color::fromHex(GameDefaults::Title::BG_COLOR);

View File

@@ -4,13 +4,6 @@
#include <functional> // Para function #include <functional> // Para function
#include <utility> // Para move #include <utility> // Para move
// Constructor para paths por puntos (compatibilidad)
Path::Path(const std::vector<SDL_FPoint> &spots_init, int waiting_counter_init)
: spots(spots_init), is_point_path(true) {
constexpr float FRAME_TIME_MS = 1000.0f / 60.0f;
waiting_time_ms = static_cast<float>(waiting_counter_init) * FRAME_TIME_MS;
}
// Devuelve un vector con los puntos que conforman la ruta // Devuelve un vector con los puntos que conforman la ruta
auto createPath(float start, float end, PathType type, float fixed_pos, int steps, const std::function<double(double)> &easing_function) -> std::vector<SDL_FPoint> { auto createPath(float start, float end, PathType type, float fixed_pos, int steps, const std::function<double(double)> &easing_function) -> std::vector<SDL_FPoint> {
std::vector<SDL_FPoint> v; std::vector<SDL_FPoint> v;
@@ -39,16 +32,10 @@ auto createPath(float start, float end, PathType type, float fixed_pos, int step
return v; return v;
} }
// Actualiza la posición y comprueba si ha llegado a su destino (compatibilidad)
void PathSprite::update() {
constexpr float FRAME_TIME_MS = 1000.0f / 60.0f; // 16.67ms por frame a 60 FPS
update(FRAME_TIME_MS);
}
// Actualiza la posición y comprueba si ha llegado a su destino // Actualiza la posición y comprueba si ha llegado a su destino
void PathSprite::update(float delta_time) { void PathSprite::update() {
if (enabled_ && !has_finished_) { if (enabled_ && !has_finished_) {
moveThroughCurrentPath(delta_time); moveThroughCurrentPath();
goToNextPathOrDie(); goToNextPathOrDie();
} }
} }
@@ -92,13 +79,7 @@ void PathSprite::addPath(Path path, bool centered) {
// Añade un recorrido // Añade un recorrido
void PathSprite::addPath(int start, int end, PathType type, int fixed_pos, int steps, const std::function<double(double)> &easing_function, int waiting_counter) { void PathSprite::addPath(int start, int end, PathType type, int fixed_pos, int steps, const std::function<double(double)> &easing_function, int waiting_counter) {
// Convertir frames a milisegundos paths_.emplace_back(createPath(start, end, type, fixed_pos, steps, easing_function), waiting_counter);
constexpr float FRAME_TIME_MS = 1000.0f / 60.0f;
float duration_ms = static_cast<float>(steps) * FRAME_TIME_MS;
float waiting_ms = static_cast<float>(waiting_counter) * FRAME_TIME_MS;
paths_.emplace_back(static_cast<float>(start), static_cast<float>(end), type, static_cast<float>(fixed_pos),
duration_ms, waiting_ms, easing_function);
} }
// Añade un recorrido // Añade un recorrido
@@ -114,78 +95,35 @@ void PathSprite::enable() {
enabled_ = true; enabled_ = true;
// Establece la posición inicial // Establece la posición
auto &path = paths_.at(current_path_); auto &path = paths_.at(current_path_);
if (path.is_point_path) { const auto &p = path.spots.at(path.counter);
const auto &p = path.spots.at(path.counter); setPosition(p);
setPosition(p);
} else {
// Para paths generados, establecer posición inicial
SDL_FPoint initial_pos;
if (path.type == PathType::HORIZONTAL) {
initial_pos = {path.start_pos, path.fixed_pos};
} else {
initial_pos = {path.fixed_pos, path.start_pos};
}
setPosition(initial_pos);
}
} }
// Coloca el sprite en los diferentes puntos del recorrido // Coloca el sprite en los diferentes puntos del recorrido
void PathSprite::moveThroughCurrentPath(float delta_time) { void PathSprite::moveThroughCurrentPath() {
auto &path = paths_.at(current_path_); auto &path = paths_.at(current_path_);
if (path.is_point_path) { // Establece la posición
// Lógica para paths por puntos (compatibilidad) const auto &p = path.spots.at(path.counter);
const auto &p = path.spots.at(path.counter); setPosition(p);
setPosition(p);
if (!path.on_destination) { // Comprobar si ha terminado el recorrido
++path.counter; if (!path.on_destination) {
if (path.counter >= static_cast<int>(path.spots.size())) { ++path.counter;
path.on_destination = true; if (path.counter >= static_cast<int>(path.spots.size())) {
path.counter = static_cast<int>(path.spots.size()) - 1; path.on_destination = true;
} path.counter = static_cast<int>(path.spots.size()) - 1;
} }
}
if (path.on_destination) { // Comprobar si ha terminado la espera
path.waiting_elapsed += delta_time; if (path.on_destination) {
if (path.waiting_elapsed >= path.waiting_time_ms) { if (path.waiting_counter == 0) {
path.finished = true; path.finished = true;
}
}
} else {
// Lógica para paths generados en tiempo real
if (!path.on_destination) {
path.elapsed_time += delta_time;
// Calcular progreso (0.0 a 1.0)
float progress = path.elapsed_time / path.duration_ms;
if (progress >= 1.0f) {
progress = 1.0f;
path.on_destination = true;
}
// Aplicar función de easing
double eased_progress = path.easing_function(progress);
// Calcular posición actual
float current_pos = path.start_pos + (path.end_pos - path.start_pos) * static_cast<float>(eased_progress);
// Establecer posición según el tipo
SDL_FPoint position;
if (path.type == PathType::HORIZONTAL) {
position = {current_pos, path.fixed_pos};
} else {
position = {path.fixed_pos, current_pos};
}
setPosition(position);
} else { } else {
// Esperar en destino --path.waiting_counter;
path.waiting_elapsed += delta_time;
if (path.waiting_elapsed >= path.waiting_time_ms) {
path.finished = true;
}
} }
} }
} }

View File

@@ -24,31 +24,17 @@ enum class PathCentered { // Centrado del recorrido
}; };
// --- Estructuras --- // --- Estructuras ---
struct Path { // Define un recorrido para el sprite struct Path { // Define un recorrido para el sprite
float start_pos; // Posición inicial std::vector<SDL_FPoint> spots; // Puntos por los que se desplazará el sprite
float end_pos; // Posición final int waiting_counter; // Tiempo de espera una vez en el destino
PathType type; // Tipo de movimiento (horizontal/vertical) bool on_destination = false; // Indica si ha llegado al destino
float fixed_pos; // Posición fija en el eje contrario bool finished = false; // Indica si ha terminado de esperarse
float duration_ms; // Duración de la animación en milisegundos int counter = 0; // Contador interno
float waiting_time_ms; // Tiempo de espera una vez en el destino
std::function<double(double)> easing_function; // Función de easing
float elapsed_time = 0.0f; // Tiempo transcurrido
float waiting_elapsed = 0.0f; // Tiempo de espera transcurrido
bool on_destination = false; // Indica si ha llegado al destino
bool finished = false; // Indica si ha terminado de esperarse
// Constructor para paths generados // Constructor
Path(float start, float end, PathType path_type, float fixed, float duration, float waiting, std::function<double(double)> easing) Path(const std::vector<SDL_FPoint> &spots_init, int waiting_counter_init)
: start_pos(start), end_pos(end), type(path_type), fixed_pos(fixed), : spots(spots_init),
duration_ms(duration), waiting_time_ms(waiting), easing_function(std::move(easing)) {} waiting_counter(waiting_counter_init) {}
// Constructor para paths por puntos (mantenemos compatibilidad)
Path(const std::vector<SDL_FPoint> &spots_init, int waiting_counter_init);
// Variables para paths por puntos
std::vector<SDL_FPoint> spots; // Solo para paths por puntos
int counter = 0; // Solo para paths por puntos
bool is_point_path = false; // Indica si es un path por puntos
}; };
// --- Funciones --- // --- Funciones ---
@@ -63,8 +49,7 @@ class PathSprite : public Sprite {
~PathSprite() override = default; ~PathSprite() override = default;
// --- Métodos principales --- // --- Métodos principales ---
void update(); // Actualiza la posición del sprite según el recorrido (compatibilidad) void update(); // Actualiza la posición del sprite según el recorrido
void update(float delta_time); // Actualiza la posición del sprite según el recorrido
void render() override; // Muestra el sprite por pantalla void render() override; // Muestra el sprite por pantalla
// --- Gestión de recorridos --- // --- Gestión de recorridos ---
@@ -87,6 +72,6 @@ class PathSprite : public Sprite {
std::vector<Path> paths_; // Caminos a recorrer por el sprite std::vector<Path> paths_; // Caminos a recorrer por el sprite
// --- Métodos internos --- // --- Métodos internos ---
void moveThroughCurrentPath(float delta_time); // Coloca el sprite en los diferentes puntos del recorrido void moveThroughCurrentPath(); // Coloca el sprite en los diferentes puntos del recorrido
void goToNextPathOrDie(); // Cambia de recorrido o finaliza void goToNextPathOrDie(); // Cambia de recorrido o finaliza
}; };

View File

@@ -3,7 +3,6 @@
#include <SDL3/SDL.h> // Para SDL_GetTicks, SDL_FlipMode #include <SDL3/SDL.h> // Para SDL_GetTicks, SDL_FlipMode
#include <algorithm> // Para clamp, max, min #include <algorithm> // Para clamp, max, min
#include <cmath> // Para fmod
#include <cstdlib> // Para rand #include <cstdlib> // Para rand
#include "animated_sprite.h" // Para AnimatedSprite #include "animated_sprite.h" // Para AnimatedSprite
@@ -60,14 +59,17 @@ void Player::init() {
power_up_counter_ = POWERUP_COUNTER; power_up_counter_ = POWERUP_COUNTER;
extra_hit_ = false; extra_hit_ = false;
coffees_ = 0; coffees_ = 0;
continue_ticks_ = 0;
continue_counter_ = 10; continue_counter_ = 10;
name_entry_idle_time_accumulator_ = 0.0f; name_entry_ticks_ = 0;
name_entry_total_time_accumulator_ = 0.0f; name_entry_idle_counter_ = 0;
name_entry_total_counter_ = 0;
shiftColliders(); shiftColliders();
vel_x_ = 0; vel_x_ = 0;
vel_y_ = 0; vel_y_ = 0;
score_ = 0; score_ = 0;
score_multiplier_ = 1.0F; score_multiplier_ = 1.0F;
cant_fire_counter_ = 10;
enter_name_->init(last_enter_name_); enter_name_->init(last_enter_name_);
// Establece la posición del sprite // Establece la posición del sprite
@@ -146,7 +148,7 @@ void Player::setInputEnteringName(Input::Action action) {
default: default:
break; break;
} }
name_entry_idle_time_accumulator_ = 0.0f; name_entry_idle_counter_ = 0;
} }
// Mueve el jugador a la posición y animación que le corresponde // Mueve el jugador a la posición y animación que le corresponde
@@ -184,41 +186,6 @@ void Player::move() {
} }
} }
// Fase 1: Sistema de movimiento time-based
void Player::move(float deltaTime) {
switch (playing_state_) {
case State::PLAYING:
handlePlayingMovement(deltaTime);
break;
case State::ROLLING:
handleRollingMovement(); // Usa MovingSprite que ya soporta deltaTime
break;
case State::TITLE_ANIMATION:
handleTitleAnimation(); // Sin cambios - usa solo animaciones
break;
case State::CONTINUE_TIME_OUT:
handleContinueTimeOut(); // Sin cambios - usa MovingSprite que ya soporta deltaTime
break;
case State::LEAVING_SCREEN:
handleLeavingScreen(); // Sin cambios - usa MovingSprite que ya soporta deltaTime
break;
case State::ENTERING_SCREEN:
handleEnteringScreen(); // Sin cambios - usa MovingSprite que ya soporta deltaTime
break;
case State::CREDITS:
handleCreditsMovement(deltaTime); // Fase 4: Sistema de créditos time-based
break;
case State::WAITING:
handleWaitingMovement(deltaTime); // Fase 4: Sistema de waiting time-based
break;
case State::RECOVER:
handleRecoverMovement(); // Sin cambios - usa AnimatedSprite que ya soporta deltaTime
break;
default:
break;
}
}
void Player::handlePlayingMovement() { void Player::handlePlayingMovement() {
// Mueve el jugador a derecha o izquierda // Mueve el jugador a derecha o izquierda
pos_x_ += vel_x_; pos_x_ += vel_x_;
@@ -231,19 +198,6 @@ void Player::handlePlayingMovement() {
shiftSprite(); shiftSprite();
} }
// Fase 1: Movimiento time-based durante el juego
void Player::handlePlayingMovement(float deltaTime) {
// Mueve el jugador a derecha o izquierda (time-based)
pos_x_ += vel_x_ * deltaTime / (1000.0f / 60.0f);
// Si el jugador abandona el area de juego por los laterales, restaura su posición
const float MIN_X = play_area_.x - 5;
const float MAX_X = play_area_.w + 5 - WIDTH;
pos_x_ = std::clamp(pos_x_, MIN_X, MAX_X);
shiftSprite();
}
void Player::handleRecoverMovement() { void Player::handleRecoverMovement() {
if (player_sprite_->getCurrentAnimationFrame() == 10) { playSound("voice_recover.wav"); } if (player_sprite_->getCurrentAnimationFrame() == 10) { playSound("voice_recover.wav"); }
if (player_sprite_->animationIsCompleted()) { setPlayingState(State::RESPAWNING); } if (player_sprite_->animationIsCompleted()) { setPlayingState(State::RESPAWNING); }
@@ -383,20 +337,6 @@ void Player::handleCreditsMovement() {
shiftSprite(); shiftSprite();
} }
// Fase 4: Movimiento general en la pantalla de créditos (time-based)
void Player::handleCreditsMovement(float deltaTime) {
pos_x_ += (vel_x_ / 2.0F) * deltaTime / (1000.0f / 60.0f); // Convert frame-based movement to time-based
if (vel_x_ > 0) {
handleCreditsRightMovement();
} else {
handleCreditsLeftMovement();
}
updateWalkingStateForCredits();
shiftSprite();
}
void Player::handleCreditsRightMovement() { void Player::handleCreditsRightMovement() {
if (pos_x_ > param.game.game_area.rect.w - WIDTH) { if (pos_x_ > param.game.game_area.rect.w - WIDTH) {
pos_x_ = param.game.game_area.rect.w - WIDTH; pos_x_ = param.game.game_area.rect.w - WIDTH;
@@ -419,16 +359,6 @@ void Player::handleWaitingMovement() {
} }
} }
// Fase 4: Controla la animación del jugador saludando (time-based)
void Player::handleWaitingMovement(float deltaTime) {
waiting_time_accumulator_ += deltaTime;
float waiting_duration = static_cast<float>(WAITING_COUNTER) * (1000.0f / 60.0f); // Convert frames to milliseconds
if (waiting_time_accumulator_ >= waiting_duration) {
waiting_time_accumulator_ = 0.0f;
player_sprite_->resetAnimation();
}
}
void Player::updateWalkingStateForCredits() { void Player::updateWalkingStateForCredits() {
if (pos_x_ > param.game.game_area.center_x - WIDTH / 2) { if (pos_x_ > param.game.game_area.center_x - WIDTH / 2) {
setWalkingState(State::WALKING_LEFT); setWalkingState(State::WALKING_LEFT);
@@ -457,35 +387,11 @@ void Player::updateStepCounter() {
} }
} }
// Fase 4: Incrementa o ajusta el contador de pasos (time-based)
void Player::updateStepCounter(float deltaTime) {
step_time_accumulator_ += deltaTime;
float step_interval = 10.0f / 60.0f; // 10 frames converted to seconds
if (step_time_accumulator_ >= step_interval) {
step_time_accumulator_ = 0.0f;
playSound("walk.wav");
}
}
// Pinta el jugador en pantalla // Pinta el jugador en pantalla
void Player::render() { void Player::render() {
if (power_up_ && isPlaying()) { if (power_up_ && isPlaying()) {
// Convertir lógica de parpadeo a deltaTime if (power_up_counter_ > (POWERUP_COUNTER / 4) || power_up_counter_ % 20 > 4) {
float total_powerup_time_ms = static_cast<float>(POWERUP_COUNTER) / 60.0f * 1000.0f; // Total time in ms
float quarter_time_ms = total_powerup_time_ms / 4.0f; // 25% del tiempo total
if (power_up_time_accumulator_ > quarter_time_ms) {
// En los primeros 75% del tiempo, siempre visible
power_sprite_->render(); power_sprite_->render();
} else {
// En el último 25%, parpadea cada 20 frames (333ms aprox)
constexpr float blink_period_ms = 20.0f / 60.0f * 1000.0f; // 20 frames in ms
constexpr float visible_proportion = 4.0f / 20.0f; // 4 frames visible de 20 total
float cycle_position = fmod(power_up_time_accumulator_, blink_period_ms) / blink_period_ms;
if (cycle_position >= visible_proportion) {
power_sprite_->render();
}
} }
} }
@@ -548,7 +454,7 @@ auto Player::computeAnimation() const -> std::pair<std::string, SDL_FlipMode> {
return {anim_name, flip_mode}; return {anim_name, flip_mode};
} }
// Establece la animación correspondiente al estado (frame-based) // Establece la animación correspondiente al estado
void Player::setAnimation() { void Player::setAnimation() {
switch (playing_state_) { switch (playing_state_) {
case State::PLAYING: case State::PLAYING:
@@ -589,60 +495,104 @@ void Player::setAnimation() {
power_sprite_->update(); power_sprite_->update();
} }
// Fase 1: Establece la animación correspondiente al estado (time-based) // Actualiza el valor de la variable
void Player::setAnimation(float deltaTime) { void Player::updateCooldown() {
switch (playing_state_) { if (playing_state_ != State::PLAYING) {
case State::PLAYING: return;
case State::ENTERING_NAME_GAME_COMPLETED: }
case State::ENTERING_SCREEN:
case State::LEAVING_SCREEN: if (cant_fire_counter_ > 0) {
case State::TITLE_ANIMATION: handleFiringCooldown();
case State::CREDITS: { } else {
auto [animName, flipMode] = computeAnimation(); handleRecoilAndCooling();
player_sprite_->setCurrentAnimation(animName, false); }
player_sprite_->setFlip(flipMode); }
break;
void Player::handleFiringCooldown() {
cooling_state_counter_ = COOLING_DURATION;
// Transition to recoiling state at halfway point
if (cant_fire_counter_ == recoiling_state_duration_ / 2) {
transitionToRecoiling();
}
--cant_fire_counter_;
if (cant_fire_counter_ == 0) {
recoiling_state_counter_ = recoiling_state_duration_;
}
}
void Player::handleRecoilAndCooling() {
if (recoiling_state_counter_ > 0) {
--recoiling_state_counter_;
return;
}
handleCoolingState();
}
void Player::handleCoolingState() {
if (cooling_state_counter_ > COOLING_COMPLETE) {
if (cooling_state_counter_ == COOLING_DURATION) {
transitionToCooling();
} }
case State::RECOVER: --cooling_state_counter_;
player_sprite_->setCurrentAnimation("recover"); }
if (cooling_state_counter_ == COOLING_COMPLETE) {
completeCooling();
}
}
void Player::transitionToRecoiling() {
switch (firing_state_) {
case State::FIRING_LEFT:
setFiringState(State::RECOILING_LEFT);
break; break;
case State::WAITING: case State::FIRING_RIGHT:
case State::GAME_OVER: setFiringState(State::RECOILING_RIGHT);
player_sprite_->setCurrentAnimation("hello");
break; break;
case State::ROLLING: case State::FIRING_UP:
case State::CONTINUE_TIME_OUT: setFiringState(State::RECOILING_UP);
player_sprite_->setCurrentAnimation("rolling");
break;
case State::LYING_ON_THE_FLOOR_FOREVER:
case State::ENTERING_NAME:
case State::CONTINUE:
player_sprite_->setCurrentAnimation("dizzy");
break;
case State::CELEBRATING:
player_sprite_->setCurrentAnimation("celebration");
break; break;
default: default:
break; break;
} }
// La diferencia clave: usa deltaTime para las animaciones
player_sprite_->update(deltaTime);
power_sprite_->update(deltaTime);
} }
// Actualiza al jugador con deltaTime (time-based) void Player::transitionToCooling() {
void Player::update(float deltaTime) { switch (firing_state_) {
move(deltaTime); // Sistema de movimiento time-based case State::RECOILING_LEFT:
setAnimation(deltaTime); // Animaciones time-based setFiringState(State::COOLING_LEFT);
shiftColliders(); // Sin cambios (posicional) break;
updateFireSystem(deltaTime); // Sistema de disparo de dos líneas case State::RECOILING_RIGHT:
updatePowerUp(deltaTime); // Sistema de power-up time-based setFiringState(State::COOLING_RIGHT);
updateInvulnerable(deltaTime); // Sistema de invulnerabilidad time-based break;
updateScoreboard(); // Sin cambios (no temporal) case State::RECOILING_UP:
updateContinueCounter(deltaTime); // Sistema de continue time-based setFiringState(State::COOLING_UP);
updateEnterNameCounter(deltaTime); // Sistema de name entry time-based break;
updateShowingName(deltaTime); // Sistema de showing name time-based default:
break;
}
}
void Player::completeCooling() {
setFiringState(State::FIRING_NONE);
cooling_state_counter_ = -1;
}
// Actualiza al jugador a su posicion, animación y controla los contadores
void Player::update() {
move();
setAnimation();
shiftColliders();
updateCooldown();
updatePowerUp();
updateInvulnerable();
updateScoreboard();
updateContinueCounter();
updateEnterNameCounter();
updateShowingName();
} }
void Player::passShowingName() { void Player::passShowingName() {
@@ -712,8 +662,8 @@ void Player::setPlayingState(State state) {
} }
case State::CONTINUE: { case State::CONTINUE: {
// Inicializa el contador de continuar // Inicializa el contador de continuar
continue_ticks_ = SDL_GetTicks();
continue_counter_ = 9; continue_counter_ = 9;
continue_time_accumulator_ = 0.0f; // Initialize time accumulator
playSound("continue_clock.wav"); playSound("continue_clock.wav");
setScoreboardMode(Scoreboard::Mode::CONTINUE); setScoreboardMode(Scoreboard::Mode::CONTINUE);
break; break;
@@ -732,7 +682,6 @@ void Player::setPlayingState(State state) {
} }
pos_y_ = default_pos_y_; pos_y_ = default_pos_y_;
waiting_counter_ = 0; waiting_counter_ = 0;
waiting_time_accumulator_ = 0.0f; // Initialize time accumulator
shiftSprite(); shiftSprite();
player_sprite_->setCurrentAnimation("hello"); player_sprite_->setCurrentAnimation("hello");
player_sprite_->animtionPause(); player_sprite_->animtionPause();
@@ -744,7 +693,7 @@ void Player::setPlayingState(State state) {
break; break;
} }
case State::SHOWING_NAME: { case State::SHOWING_NAME: {
showing_name_time_accumulator_ = 0.0f; // Inicializar acumulador time-based showing_name_ticks_ = SDL_GetTicks();
setScoreboardMode(Scoreboard::Mode::SHOW_NAME); setScoreboardMode(Scoreboard::Mode::SHOW_NAME);
Scoreboard::get()->setRecordName(scoreboard_panel_, last_enter_name_); Scoreboard::get()->setRecordName(scoreboard_panel_, last_enter_name_);
addScoreToScoreBoard(); addScoreToScoreBoard();
@@ -800,14 +749,12 @@ void Player::setPlayingState(State state) {
} }
case State::LEAVING_SCREEN: { case State::LEAVING_SCREEN: {
step_counter_ = 0; step_counter_ = 0;
step_time_accumulator_ = 0.0f; // Initialize time accumulator
setScoreboardMode(Scoreboard::Mode::GAME_COMPLETED); setScoreboardMode(Scoreboard::Mode::GAME_COMPLETED);
break; break;
} }
case State::ENTERING_SCREEN: { case State::ENTERING_SCREEN: {
init(); init();
step_counter_ = 0; step_counter_ = 0;
step_time_accumulator_ = 0.0f; // Initialize time accumulator
setScoreboardMode(Scoreboard::Mode::SCORE); setScoreboardMode(Scoreboard::Mode::SCORE);
switch (id_) { switch (id_) {
case Id::PLAYER1: case Id::PLAYER1:
@@ -848,26 +795,24 @@ void Player::decScoreMultiplier() {
void Player::setInvulnerable(bool value) { void Player::setInvulnerable(bool value) {
invulnerable_ = value; invulnerable_ = value;
invulnerable_counter_ = invulnerable_ ? INVULNERABLE_COUNTER : 0; invulnerable_counter_ = invulnerable_ ? INVULNERABLE_COUNTER : 0;
invulnerable_time_accumulator_ = invulnerable_ ? static_cast<float>(INVULNERABLE_COUNTER) / 60.0f * 1000.0f : 0.0f; // Convert frames to milliseconds
} }
// Monitoriza el estado (time-based) // Monitoriza el estado
void Player::updateInvulnerable(float deltaTime) { void Player::updateInvulnerable() {
if (playing_state_ == State::PLAYING && invulnerable_) { if (playing_state_ == State::PLAYING && invulnerable_) {
if (invulnerable_time_accumulator_ > 0) { if (invulnerable_counter_ > 0) {
invulnerable_time_accumulator_ -= deltaTime; --invulnerable_counter_;
// Frecuencia fija de parpadeo adaptada a deltaTime (en milisegundos) // Frecuencia fija de parpadeo (como el original)
constexpr float blink_period_ms = 8.0f / 60.0f * 1000.0f; // 8 frames convertidos a ms constexpr int blink_speed = 8;
// Calcula proporción decreciente basada en tiempo restante // Calcula proporción decreciente: menos textura blanca hacia el final
float total_invulnerable_time_ms = static_cast<float>(INVULNERABLE_COUNTER) / 60.0f * 1000.0f; // Al inicio: 50-50, hacia el final: 70-30 (menos blanco)
float progress = 1.0f - (invulnerable_time_accumulator_ / total_invulnerable_time_ms); float progress = 1.0f - (static_cast<float>(invulnerable_counter_) / INVULNERABLE_COUNTER);
float white_proportion = 0.5f - progress * 0.2f; // Menos blanco hacia el final int white_frames = static_cast<int>((0.5f - progress * 0.2f) * blink_speed);
// Calcula si debe mostrar textura de invulnerabilidad basado en el ciclo temporal // Alterna entre texturas con proporción variable
float cycle_position = fmod(invulnerable_time_accumulator_, blink_period_ms) / blink_period_ms; bool should_show_invulnerable = (invulnerable_counter_ % blink_speed) < white_frames;
bool should_show_invulnerable = cycle_position < white_proportion;
size_t target_texture = should_show_invulnerable ? INVULNERABLE_TEXTURE : coffees_; size_t target_texture = should_show_invulnerable ? INVULNERABLE_TEXTURE : coffees_;
// Solo cambia textura si es diferente (optimización) // Solo cambia textura si es diferente (optimización)
@@ -876,7 +821,6 @@ void Player::updateInvulnerable(float deltaTime) {
} }
} else { } else {
// Fin de invulnerabilidad // Fin de invulnerabilidad
invulnerable_time_accumulator_ = 0;
setInvulnerable(false); setInvulnerable(false);
player_sprite_->setActiveTexture(coffees_); player_sprite_->setActiveTexture(coffees_);
} }
@@ -887,18 +831,14 @@ void Player::updateInvulnerable(float deltaTime) {
void Player::setPowerUp() { void Player::setPowerUp() {
power_up_ = true; power_up_ = true;
power_up_counter_ = POWERUP_COUNTER; power_up_counter_ = POWERUP_COUNTER;
power_up_time_accumulator_ = static_cast<float>(POWERUP_COUNTER) / 60.0f * 1000.0f; // Convert frames to milliseconds
} }
// Actualiza el valor de la variable (time-based) // Actualiza el valor de la variable
void Player::updatePowerUp(float deltaTime) { void Player::updatePowerUp() {
if (playing_state_ == State::PLAYING) { if (playing_state_ == State::PLAYING) {
if (power_up_) { if (power_up_) {
power_up_time_accumulator_ -= deltaTime; --power_up_counter_;
power_up_ = power_up_time_accumulator_ > 0; power_up_ = power_up_counter_ > 0;
if (!power_up_) {
power_up_time_accumulator_ = 0;
}
} }
} }
} }
@@ -935,36 +875,31 @@ void Player::setPlayerTextures(const std::vector<std::shared_ptr<Texture>> &text
power_sprite_->setTexture(texture[1]); power_sprite_->setTexture(texture[1]);
} }
// Actualiza el contador de continue (time-based) // Actualiza el contador de continue
void Player::updateContinueCounter(float deltaTime) { void Player::updateContinueCounter() {
if (playing_state_ == State::CONTINUE) { if (playing_state_ == State::CONTINUE) {
continue_time_accumulator_ += deltaTime; constexpr int TICKS_SPEED = 1000;
constexpr float CONTINUE_INTERVAL = 1000.0f; // 1 segundo en milisegundos if (SDL_GetTicks() - continue_ticks_ > TICKS_SPEED) {
if (continue_time_accumulator_ >= CONTINUE_INTERVAL) {
continue_time_accumulator_ -= CONTINUE_INTERVAL;
decContinueCounter(); decContinueCounter();
} }
} }
} }
// Actualiza el contador de entrar nombre (time-based) // Actualiza el contador de entrar nombre
void Player::updateEnterNameCounter(float deltaTime) { void Player::updateEnterNameCounter() {
if (playing_state_ == State::ENTERING_NAME || playing_state_ == State::ENTERING_NAME_GAME_COMPLETED) { if (playing_state_ == State::ENTERING_NAME || playing_state_ == State::ENTERING_NAME_GAME_COMPLETED) {
name_entry_time_accumulator_ += deltaTime; constexpr int TICKS_SPEED = 1000;
constexpr float NAME_ENTRY_INTERVAL = 1000.0f; // 1 segundo en milisegundos if (SDL_GetTicks() - name_entry_ticks_ > TICKS_SPEED) {
if (name_entry_time_accumulator_ >= NAME_ENTRY_INTERVAL) {
name_entry_time_accumulator_ -= NAME_ENTRY_INTERVAL;
decNameEntryCounter(); decNameEntryCounter();
} }
} }
} }
// Actualiza el estado de SHOWING_NAME (time-based) // Actualiza el estado de SHOWING_NAME
void Player::updateShowingName(float deltaTime) { void Player::updateShowingName() {
if (playing_state_ == State::SHOWING_NAME) { if (playing_state_ == State::SHOWING_NAME) {
showing_name_time_accumulator_ += deltaTime; constexpr int TICKS_SPEED = 5000;
constexpr float SHOWING_NAME_DURATION = 5000.0f; // 5 segundos en milisegundos if (SDL_GetTicks() - name_entry_ticks_ > TICKS_SPEED) {
if (showing_name_time_accumulator_ >= SHOWING_NAME_DURATION) {
game_completed_ ? setPlayingState(State::LEAVING_SCREEN) : setPlayingState(State::CONTINUE); game_completed_ ? setPlayingState(State::LEAVING_SCREEN) : setPlayingState(State::CONTINUE);
} }
} }
@@ -972,7 +907,7 @@ void Player::updateShowingName(float deltaTime) {
// Decrementa el contador de continuar // Decrementa el contador de continuar
void Player::decContinueCounter() { void Player::decContinueCounter() {
continue_time_accumulator_ = 0.0f; // Reset time accumulator continue_ticks_ = SDL_GetTicks();
--continue_counter_; --continue_counter_;
if (continue_counter_ < 0) { if (continue_counter_ < 0) {
setPlayingState(State::CONTINUE_TIME_OUT); setPlayingState(State::CONTINUE_TIME_OUT);
@@ -983,17 +918,17 @@ void Player::decContinueCounter() {
// Decrementa el contador de entrar nombre // Decrementa el contador de entrar nombre
void Player::decNameEntryCounter() { void Player::decNameEntryCounter() {
name_entry_time_accumulator_ = 0.0f; // Reset time accumulator name_entry_ticks_ = SDL_GetTicks();
// Incrementa acumuladores de tiempo (1 segundo = 1000ms) // Actualiza contadores
name_entry_idle_time_accumulator_ += 1000.0f; ++name_entry_idle_counter_;
name_entry_total_time_accumulator_ += 1000.0f; ++name_entry_total_counter_;
// Comprueba los acumuladores directamente contra los límites en milisegundos // Comprueba los contadores
if ((name_entry_total_time_accumulator_ >= param.game.name_entry_total_time) || if ((name_entry_total_counter_ >= param.game.name_entry_total_time) ||
(name_entry_idle_time_accumulator_ >= param.game.name_entry_idle_time)) { (name_entry_idle_counter_ >= param.game.name_entry_idle_time)) {
name_entry_total_time_accumulator_ = 0.0f; name_entry_total_counter_ = 0;
name_entry_idle_time_accumulator_ = 0.0f; name_entry_idle_counter_ = 0;
if (playing_state_ == State::ENTERING_NAME) { if (playing_state_ == State::ENTERING_NAME) {
last_enter_name_ = getRecordName(); last_enter_name_ = getRecordName();
setPlayingState(State::SHOWING_NAME); setPlayingState(State::SHOWING_NAME);
@@ -1054,144 +989,3 @@ void Player::addCredit() {
++credits_used_; ++credits_used_;
playSound("credit.wav"); playSound("credit.wav");
} }
// ========================================
// SISTEMA DE DISPARO DE DOS LÍNEAS
// ========================================
// Método principal del sistema de disparo
void Player::updateFireSystem(float deltaTime) {
updateFunctionalLine(deltaTime); // Línea 1: CanFire
updateVisualLine(deltaTime); // Línea 2: Animaciones
}
// LÍNEA 1: Sistema Funcional (CanFire)
void Player::updateFunctionalLine(float deltaTime) {
if (fire_cooldown_timer_ > 0) {
fire_cooldown_timer_ -= deltaTime;
can_fire_new_system_ = false;
} else {
fire_cooldown_timer_ = 0; // Evitar valores negativos
can_fire_new_system_ = true;
}
}
// LÍNEA 2: Sistema Visual (Animaciones)
void Player::updateVisualLine(float deltaTime) {
if (visual_fire_state_ == VisualFireState::NORMAL) {
return; // No hay temporizador activo en estado NORMAL
}
visual_state_timer_ -= deltaTime;
switch (visual_fire_state_) {
case VisualFireState::AIMING:
if (visual_state_timer_ <= 0) {
transitionToRecoilingNew();
}
break;
case VisualFireState::RECOILING:
if (visual_state_timer_ <= 0) {
transitionToThreatPose();
}
break;
case VisualFireState::THREAT_POSE:
if (visual_state_timer_ <= 0) {
transitionToNormalNew();
}
break;
case VisualFireState::NORMAL:
// Ya manejado arriba
break;
}
}
// Inicia un disparo en ambas líneas
void Player::startFiringSystem(int cooldown_frames) {
// LÍNEA 1: Inicia cooldown funcional
fire_cooldown_timer_ = static_cast<float>(cooldown_frames) / 60.0f * 1000.0f; // Convertir frames a ms
can_fire_new_system_ = false;
// LÍNEA 2: Resetea completamente el estado visual
aiming_duration_ = fire_cooldown_timer_ * AIMING_DURATION_FACTOR; // 50% del cooldown
recoiling_duration_ = aiming_duration_ * RECOILING_DURATION_MULTIPLIER; // 4 veces la duración de aiming
visual_fire_state_ = VisualFireState::AIMING;
visual_state_timer_ = aiming_duration_;
updateFiringStateFromVisual(); // Sincroniza firing_state_ para animaciones
}
// Sincroniza firing_state_ con visual_fire_state_
void Player::updateFiringStateFromVisual() {
// Mantener la dirección actual del disparo
State base_state = State::FIRING_NONE;
if (firing_state_ == State::FIRING_LEFT || firing_state_ == State::RECOILING_LEFT || firing_state_ == State::COOLING_LEFT) {
base_state = State::FIRING_LEFT;
} else if (firing_state_ == State::FIRING_RIGHT || firing_state_ == State::RECOILING_RIGHT || firing_state_ == State::COOLING_RIGHT) {
base_state = State::FIRING_RIGHT;
} else if (firing_state_ == State::FIRING_UP || firing_state_ == State::RECOILING_UP || firing_state_ == State::COOLING_UP) {
base_state = State::FIRING_UP;
}
switch (visual_fire_state_) {
case VisualFireState::NORMAL:
firing_state_ = State::FIRING_NONE;
break;
case VisualFireState::AIMING:
firing_state_ = base_state; // FIRING_LEFT/RIGHT/UP
break;
case VisualFireState::RECOILING:
switch (base_state) {
case State::FIRING_LEFT: firing_state_ = State::RECOILING_LEFT; break;
case State::FIRING_RIGHT: firing_state_ = State::RECOILING_RIGHT; break;
case State::FIRING_UP: firing_state_ = State::RECOILING_UP; break;
default: firing_state_ = State::RECOILING_UP; break;
}
break;
case VisualFireState::THREAT_POSE:
switch (base_state) {
case State::FIRING_LEFT: firing_state_ = State::COOLING_LEFT; break;
case State::FIRING_RIGHT: firing_state_ = State::COOLING_RIGHT; break;
case State::FIRING_UP: firing_state_ = State::COOLING_UP; break;
default: firing_state_ = State::COOLING_UP; break;
}
break;
}
}
// Transiciones del sistema visual
void Player::transitionToRecoilingNew() {
visual_fire_state_ = VisualFireState::RECOILING;
visual_state_timer_ = recoiling_duration_;
updateFiringStateFromVisual();
}
void Player::transitionToThreatPose() {
visual_fire_state_ = VisualFireState::THREAT_POSE;
// Calcular threat_pose_duration ajustada:
// Duración original (833ms) menos el tiempo extra que ahora dura recoiling
float original_recoiling_duration = fire_cooldown_timer_; // Era 100% del cooldown
float new_recoiling_duration = aiming_duration_ * RECOILING_DURATION_MULTIPLIER; // Ahora es más del cooldown
float extra_recoiling_time = new_recoiling_duration - original_recoiling_duration;
float adjusted_threat_duration = THREAT_POSE_DURATION - extra_recoiling_time;
// Asegurar que no sea negativo
visual_state_timer_ = std::max(adjusted_threat_duration, MIN_THREAT_POSE_DURATION);
updateFiringStateFromVisual();
}
void Player::transitionToNormalNew() {
visual_fire_state_ = VisualFireState::NORMAL;
visual_state_timer_ = 0;
updateFiringStateFromVisual();
}

View File

@@ -17,21 +17,7 @@
class Texture; class Texture;
// --- Clase Player: jugador principal del juego --- // --- Clase Player ---
//
// 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 { class Player {
public: public:
// --- Constantes --- // --- Constantes ---
@@ -108,9 +94,9 @@ class Player {
~Player() = default; ~Player() = default;
// --- Inicialización y ciclo de vida --- // --- Inicialización y ciclo de vida ---
void init(); // Inicializa el jugador void init(); // Inicializa el jugador
void update(float deltaTime); // Actualiza estado, animación y contadores (time-based) void update(); // Actualiza estado, animación y contadores
void render(); // Dibuja el jugador en pantalla void render(); // Dibuja el jugador en pantalla
// --- Entrada y control --- // --- Entrada y control ---
void setInput(Input::Action action); // Procesa entrada general void setInput(Input::Action action); // Procesa entrada general
@@ -118,15 +104,14 @@ class Player {
void setInputEnteringName(Input::Action action); // Procesa entrada al introducir nombre void setInputEnteringName(Input::Action action); // Procesa entrada al introducir nombre
// --- Movimiento y animación --- // --- Movimiento y animación ---
void move(); // Mueve el jugador (frame-based) void move(); // Mueve el jugador
void move(float deltaTime); // Mueve el jugador (time-based) void setAnimation(); // Establece la animación según el estado
void setAnimation(); // Establece la animación según el estado (frame-based)
void setAnimation(float deltaTime); // Establece la animación según el estado (time-based)
// --- Texturas y animaciones --- // --- Texturas y animaciones ---
void setPlayerTextures(const std::vector<std::shared_ptr<Texture>> &texture); // Cambia las texturas del jugador void setPlayerTextures(const std::vector<std::shared_ptr<Texture>> &texture); // Cambia las texturas del jugador
// --- Estados y contadores --- // --- Estados y contadores ---
void updateCooldown(); // Actualiza el cooldown de disparo
// --- Puntuación y marcador --- // --- Puntuación y marcador ---
void addScore(int score, int lowest_hi_score_entry); // Añade puntos void addScore(int score, int lowest_hi_score_entry); // Añade puntos
@@ -137,7 +122,7 @@ class Player {
void setPlayingState(State state); // Cambia el estado de juego void setPlayingState(State state); // Cambia el estado de juego
void setInvulnerable(bool value); // Establece el valor del estado de invulnerabilidad void setInvulnerable(bool value); // Establece el valor del estado de invulnerabilidad
void setPowerUp(); // Activa el modo PowerUp void setPowerUp(); // Activa el modo PowerUp
void updatePowerUp(float deltaTime); // Actualiza el valor de PowerUp (time-based) void updatePowerUp(); // Actualiza el valor de PowerUp
void giveExtraHit(); // Concede un toque extra al jugador void giveExtraHit(); // Concede un toque extra al jugador
void removeExtraHit(); // Quita el toque extra al jugador void removeExtraHit(); // Quita el toque extra al jugador
void decContinueCounter(); // Decrementa el contador de continuar void decContinueCounter(); // Decrementa el contador de continuar
@@ -160,7 +145,7 @@ class Player {
[[nodiscard]] auto isTitleHidden() const -> bool { return playing_state_ == State::TITLE_HIDDEN; } [[nodiscard]] auto isTitleHidden() const -> bool { return playing_state_ == State::TITLE_HIDDEN; }
// Getters // Getters
[[nodiscard]] auto canFire() const -> bool { return can_fire_new_system_; } // Usa nuevo sistema [[nodiscard]] auto canFire() const -> bool { return cant_fire_counter_ <= 0; }
[[nodiscard]] auto hasExtraHit() const -> bool { return extra_hit_; } [[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 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 isRecoiling() const -> bool { return firing_state_ == State::RECOILING_LEFT || firing_state_ == State::RECOILING_UP || firing_state_ == State::RECOILING_RIGHT; }
@@ -190,7 +175,7 @@ class Player {
// Setters inline // Setters inline
void setController(int index) { controller_index_ = index; } void setController(int index) { controller_index_ = index; }
void startFiringSystem(int cooldown_frames); // Método público para iniciar disparo void setCantFireCounter(int counter) { recoiling_state_duration_ = cant_fire_counter_ = counter; }
void setFiringState(State state) { firing_state_ = state; } void setFiringState(State state) { firing_state_ = state; }
void setInvulnerableCounter(int value) { invulnerable_counter_ = value; } void setInvulnerableCounter(int value) { invulnerable_counter_ = value; }
void setName(const std::string &name) { name_ = name; } void setName(const std::string &name) { name_ = name; }
@@ -208,27 +193,15 @@ class Player {
[[nodiscard]] auto getUsesKeyboard() const -> bool { return uses_keyboard_; } [[nodiscard]] auto getUsesKeyboard() const -> bool { return uses_keyboard_; }
private: private:
// --- Constantes de física y movimiento --- // --- Constantes ---
static constexpr float BASE_SPEED = 1.5F; // Velocidad base del jugador static constexpr int POWERUP_COUNTER = 1500; // Duración del estado PowerUp
static constexpr int INVULNERABLE_COUNTER = 200; // Duración del estado invulnerable
// --- 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 static constexpr size_t INVULNERABLE_TEXTURE = 3; // Textura usada durante invulnerabilidad
static constexpr float BASE_SPEED = 1.5F; // Velocidad base del jugador
// --- 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_DURATION = 50; // Duración del enfriamiento tras disparar
static constexpr int COOLING_COMPLETE = 0; // Valor que indica enfriamiento completado 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 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 = 833.33f; // 50 frames = ~833ms (duración base)
static constexpr float MIN_THREAT_POSE_DURATION = 100.0f; // Duración mínima para threat pose
// --- Objetos y punteros --- // --- Objetos y punteros ---
std::unique_ptr<AnimatedSprite> player_sprite_; // Sprite para dibujar el jugador 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<AnimatedSprite> power_sprite_; // Sprite para dibujar el aura del jugador con el poder a tope
@@ -249,6 +222,9 @@ class Player {
State firing_state_ = State::FIRING_NONE; // Estado del jugador al disparar State firing_state_ = State::FIRING_NONE; // Estado del jugador al disparar
State playing_state_ = State::WAITING; // Estado del jugador en el juego State playing_state_ = State::WAITING; // Estado del jugador en el juego
Uint32 continue_ticks_ = 0; // Variable para poder cambiar el contador de continue en función del tiempo
Uint32 name_entry_ticks_ = 0; // Variable para poder cambiar el contador de poner nombre en función del tiempo
Uint32 showing_name_ticks_ = 0; // Tiempo en el que se entra al estado SHOWING_NAME
float pos_x_ = 0.0F; // Posición en el eje X float pos_x_ = 0.0F; // Posición en el eje X
float default_pos_x_; // Posición inicial para el jugador 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 vel_x_ = 0.0F; // Cantidad de píxeles a desplazarse en el eje X
@@ -256,36 +232,10 @@ class Player {
int pos_y_ = 0; // Posición en el eje Y int pos_y_ = 0; // Posición en el eje Y
int default_pos_y_; // Posición inicial para el jugador int default_pos_y_; // Posición inicial para el jugador
int vel_y_ = 0; // Cantidad de píxeles a desplazarse en el eje Y 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) int cant_fire_counter_ = 0; // Contador durante el cual no puede disparar
float power_up_time_accumulator_ = 0.0f; // Acumulador de tiempo para power-up (time-based) int recoiling_state_counter_ = 0; // Contador para la animación del estado de retroceso
float continue_time_accumulator_ = 0.0f; // Acumulador de tiempo para continue counter (time-based) int recoiling_state_duration_ = 0; // Número de frames que dura el estado de retroceso
float name_entry_time_accumulator_ = 0.0f; // Acumulador de tiempo para name entry counter (time-based) int cooling_state_counter_ = 0; // Contador para la animación del estado cooling
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 invulnerable_counter_ = INVULNERABLE_COUNTER; // Contador para la invulnerabilidad
int score_ = 0; // Puntos del jugador int score_ = 0; // Puntos del jugador
int coffees_ = 0; // Indica cuántos cafés lleva acumulados int coffees_ = 0; // Indica cuántos cafés lleva acumulados
@@ -293,8 +243,8 @@ class Player {
int power_up_x_offset_ = 0; // Desplazamiento del sprite de PowerUp respecto al sprite del jugador 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 continue_counter_ = 10; // Contador para poder continuar
int controller_index_ = 0; // Índice del array de mandos que utilizará para moverse int controller_index_ = 0; // Índice del array de mandos que utilizará para moverse
float name_entry_idle_time_accumulator_ = 0.0f; // Tiempo idle acumulado para poner nombre (milisegundos) int name_entry_idle_counter_ = 0; // Contador para poner nombre
float name_entry_total_time_accumulator_ = 0.0f; // Tiempo total acumulado poniendo nombre (milisegundos) int name_entry_total_counter_ = 0; // Segundos totales que lleva acumulados poniendo nombre
int step_counter_ = 0; // Cuenta los pasos para los estados en los que camina automáticamente 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 credits_used_ = 0; // Indica el número de veces que ha continuado
int waiting_counter_ = 0; // Contador para el estado de espera int waiting_counter_ = 0; // Contador para el estado de espera
@@ -309,39 +259,23 @@ class Player {
// --- Métodos internos --- // --- Métodos internos ---
void shiftColliders(); // Actualiza el círculo de colisión a la posición del jugador void shiftColliders(); // Actualiza el círculo de colisión a la posición del jugador
void shiftSprite(); // Recoloca el sprite void shiftSprite(); // Recoloca el sprite
void updateInvulnerable(float deltaTime); // Monitoriza el estado de invulnerabilidad (time-based) void updateInvulnerable(); // Monitoriza el estado de invulnerabilidad
void updateContinueCounter(float deltaTime); // Actualiza el contador de continue (time-based) void updateContinueCounter(); // Actualiza el contador de continue
void updateEnterNameCounter(float deltaTime); // Actualiza el contador de entrar nombre (time-based) void updateEnterNameCounter(); // Actualiza el contador de entrar nombre
void updateShowingName(float deltaTime); // Actualiza el estado SHOWING_NAME (time-based) void updateShowingName(); // Actualiza el estado SHOWING_NAME
void decNameEntryCounter(); // Decrementa el contador de entrar nombre void decNameEntryCounter(); // Decrementa el contador de entrar nombre
void updateScoreboard(); // Actualiza el panel del marcador void updateScoreboard(); // Actualiza el panel del marcador
void setScoreboardMode(Scoreboard::Mode mode) const; // Cambia el modo del marcador void setScoreboardMode(Scoreboard::Mode mode) const; // Cambia el modo del marcador
void playSound(const std::string &name) const; // Hace sonar un sonido void playSound(const std::string &name) const; // Hace sonar un sonido
[[nodiscard]] auto isRenderable() const -> bool; // Indica si se puede dibujar el objeto [[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 void addScoreToScoreBoard() const; // Añade una puntuación a la tabla de records
void handleFiringCooldown(); // Gestiona el tiempo de espera después de disparar antes de permitir otro disparo
// --- Métodos del sistema de disparo de dos líneas --- void handleRecoilAndCooling(); // Procesa simultáneamente el retroceso del arma y la transición al estado de enfriamiento si aplica
void updateFireSystem(float deltaTime); // Método principal del nuevo sistema de disparo void handleCoolingState(); // Actualiza la lógica interna mientras el sistema está en estado de enfriamiento
void updateFunctionalLine(float deltaTime); // Actualiza la línea funcional (CanFire) void transitionToRecoiling(); // Cambia el estado actual al de retroceso después de disparar
void updateVisualLine(float deltaTime); // Actualiza la línea visual (Animaciones) void transitionToCooling(); // Cambia el estado actual al de enfriamiento (por ejemplo, tras una ráfaga o sobrecalentamiento)
void startFiring(int cooldown_frames); // Inicia un nuevo disparo en ambas líneas void completeCooling(); // Finaliza el proceso de enfriamiento y restablece el estado listo para disparar
void updateFiringStateFromVisual(); // Sincroniza firing_state_ con visual_fire_state_ void handlePlayingMovement(); // Gestiona el movimiento del personaje u objeto durante el estado de juego activo
void transitionToRecoilingNew(); // Transición AIMING → RECOILING
void transitionToThreatPose(); // Transición RECOILING → THREAT_POSE
void transitionToNormalNew(); // Transición THREAT_POSE → NORMAL
// --- Métodos del sistema de disparo obsoleto ---
void handleFiringCooldown(); // Gestiona el tiempo de espera después de disparar (frame-based)
void handleFiringCooldown(float deltaTime); // Gestiona el tiempo de espera después de disparar (time-based)
void handleRecoilAndCooling(); // Procesa retroceso y enfriamiento (frame-based)
void handleRecoilAndCooling(float deltaTime); // Procesa retroceso y enfriamiento (time-based)
void handleCoolingState(); // Actualiza estado de enfriamiento (frame-based)
void handleCoolingState(float deltaTime); // Actualiza estado de enfriamiento (time-based)
void transitionToRecoiling(); // Transición a retroceso (sistema obsoleto)
void transitionToCooling(); // Transición a enfriamiento (sistema obsoleto)
void completeCooling(); // Finaliza enfriamiento (sistema obsoleto)
void handlePlayingMovement(); // Gestiona el movimiento del personaje u objeto durante el estado de juego activo (frame-based)
void handlePlayingMovement(float deltaTime); // Gestiona el movimiento del personaje u objeto durante el estado de juego activo (time-based)
void handleRecoverMovement(); // Comprueba si ha acabado la animación void handleRecoverMovement(); // Comprueba si ha acabado la animación
void handleRollingMovement(); // Actualiza la lógica de movimiento de "rodar" (posiblemente tras impacto o acción especial) void handleRollingMovement(); // Actualiza la lógica de movimiento de "rodar" (posiblemente tras impacto o acción especial)
void handleRollingBoundaryCollision(); // Detecta y maneja colisiones del objeto rodante con los límites de la pantalla void handleRollingBoundaryCollision(); // Detecta y maneja colisiones del objeto rodante con los límites de la pantalla
@@ -354,15 +288,12 @@ class Player {
void handleEnteringScreen(); // Lógica para entrar en una nueva pantalla, posiblemente con animación o retraso void handleEnteringScreen(); // Lógica para entrar en una nueva pantalla, posiblemente con animación o retraso
void handlePlayer1Entering(); // Controla la animación o posición de entrada del Jugador 1 en pantalla void handlePlayer1Entering(); // Controla la animación o posición de entrada del Jugador 1 en pantalla
void handlePlayer2Entering(); // Controla la animación o posición de entrada del Jugador 2 en pantalla void handlePlayer2Entering(); // Controla la animación o posición de entrada del Jugador 2 en pantalla
void handleCreditsMovement(); // Movimiento general en la pantalla de créditos (frame-based) void handleCreditsMovement(); // Movimiento general en la pantalla de créditos (desplazamiento vertical u horizontal)
void handleCreditsMovement(float deltaTime); // Movimiento general en la pantalla de créditos (time-based)
void handleCreditsRightMovement(); // Lógica específica para mover los créditos hacia la derecha void handleCreditsRightMovement(); // Lógica específica para mover los créditos hacia la derecha
void handleCreditsLeftMovement(); // Lógica específica para mover los créditos hacia la izquierda void handleCreditsLeftMovement(); // Lógica específica para mover los créditos hacia la izquierda
void handleWaitingMovement(); // Controla la animación del jugador saludando (frame-based) void handleWaitingMovement(); // Controla la animación del jugador saludando
void handleWaitingMovement(float deltaTime); // Controla la animación del jugador saludando (time-based)
void updateWalkingStateForCredits(); // Actualiza el estado de caminata de algún personaje u elemento animado en los créditos void updateWalkingStateForCredits(); // Actualiza el estado de caminata de algún personaje u elemento animado en los créditos
void setInputBasedOnPlayerId(); // Asocia las entradas de control en función del identificador del jugador (teclas, mando, etc.) void setInputBasedOnPlayerId(); // Asocia las entradas de control en función del identificador del jugador (teclas, mando, etc.)
void updateStepCounter(); // Incrementa o ajusta el contador de pasos (frame-based) void updateStepCounter(); // Incrementa o ajusta el contador de pasos para animaciones o mecánicas relacionadas con movimiento
void updateStepCounter(float deltaTime); // Incrementa o ajusta el contador de pasos (time-based)
[[nodiscard]] auto computeAnimation() const -> std::pair<std::string, SDL_FlipMode>; // Calcula la animacion de moverse y disparar del jugador [[nodiscard]] auto computeAnimation() const -> std::pair<std::string, SDL_FlipMode>; // Calcula la animacion de moverse y disparar del jugador
}; };

View File

@@ -44,8 +44,21 @@ Screen::Screen()
initSDLVideo(); initSDLVideo();
// Crea la textura de destino // Crea la textura de destino
#ifdef __APPLE__
const auto render_name = SDL_GetRendererName(renderer_);
if (render_name && !strncmp(render_name, "metal", 5)) {
// Usar nuestra propia Metal texture como render target
game_canvas_ = shader::metal::createMetalRenderTarget(renderer_, param.game.width, param.game.height);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Using custom Metal render target for game_canvas_");
} else {
// Fallback para otros renderers
game_canvas_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height);
SDL_SetTextureScaleMode(game_canvas_, SDL_SCALEMODE_NEAREST);
}
#else
game_canvas_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height); game_canvas_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height);
SDL_SetTextureScaleMode(game_canvas_, SDL_SCALEMODE_NEAREST); SDL_SetTextureScaleMode(game_canvas_, SDL_SCALEMODE_NEAREST);
#endif
// Crea el objeto de texto // Crea el objeto de texto
createText(); createText();
@@ -99,7 +112,18 @@ void Screen::renderPresent() {
clean(); clean();
if (Options::video.shaders) { if (Options::video.shaders) {
#ifdef __APPLE__
const auto render_name = SDL_GetRendererName(renderer_);
if (render_name && !strncmp(render_name, "metal", 5)) {
// Use Metal post-processing with our custom render target
shader::metal::renderWithPostProcessing(renderer_, game_canvas_);
} else {
// Fallback to standard shader system for non-Metal renderers
shader::render();
}
#else
shader::render(); shader::render();
#endif
} else { } else {
SDL_RenderTexture(renderer_, game_canvas_, nullptr, nullptr); SDL_RenderTexture(renderer_, game_canvas_, nullptr, nullptr);
SDL_RenderPresent(renderer_); SDL_RenderPresent(renderer_);
@@ -296,17 +320,29 @@ auto Screen::initSDLVideo() -> bool {
// Obtener información de la pantalla // Obtener información de la pantalla
getDisplayInfo(); getDisplayInfo();
#ifdef __APPLE__
// Configurar hint para Metal
if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal")) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Warning: Failed to set Metal hint!");
}
// Configurar flags para la creación de la ventana
SDL_WindowFlags window_flags = SDL_WINDOW_METAL;
#else // NOT APPLE
// Configurar hint para OpenGL // Configurar hint para OpenGL
if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl")) { if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl")) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Warning: Failed to set OpenGL hint!"); "Warning: Failed to set OpenGL hint!");
} }
// Configurar flags para la creación de la ventana
// Crear ventana
SDL_WindowFlags window_flags = SDL_WINDOW_OPENGL; SDL_WindowFlags window_flags = SDL_WINDOW_OPENGL;
#endif
// Configurar flags para la creación de la ventana
if (Options::video.fullscreen) { if (Options::video.fullscreen) {
window_flags |= SDL_WINDOW_FULLSCREEN; window_flags |= SDL_WINDOW_FULLSCREEN;
} }
// Crear ventana
window_ = SDL_CreateWindow( window_ = SDL_CreateWindow(
Options::window.caption.c_str(), Options::window.caption.c_str(),
param.game.width * Options::window.zoom, param.game.width * Options::window.zoom,

View File

@@ -78,46 +78,36 @@ Credits::~Credits() {
Options::gamepad_manager.clearPlayers(); Options::gamepad_manager.clearPlayers();
} }
// Calcula el deltatime
auto Credits::calculateDeltaTime() -> float {
const Uint64 current_time = SDL_GetTicks();
const float delta_time = static_cast<float>(current_time - last_time_);
last_time_ = current_time;
return delta_time;
}
// Bucle principal // Bucle principal
void Credits::run() { void Credits::run() {
last_time_ = SDL_GetTicks();
while (Section::name == Section::Name::CREDITS) { while (Section::name == Section::Name::CREDITS) {
checkInput(); checkInput();
const float delta_time = calculateDeltaTime(); update();
update(delta_time);
checkEvents(); // Tiene que ir antes del render checkEvents(); // Tiene que ir antes del render
render(); render();
} }
} }
// Actualiza las variables (time-based) // Actualiza las variables
void Credits::update(float deltaTime) { void Credits::update() {
const float multiplier = want_to_pass_ ? 4.0f : 1.0f; if (SDL_GetTicks() - ticks_ > param.game.speed) {
const float adjusted_delta_time = deltaTime * multiplier; ticks_ = SDL_GetTicks();
const int REPEAT = want_to_pass_ ? 4 : 1;
for (int i = 0; i < REPEAT; ++i) {
tiled_bg_->update();
cycleColors();
balloon_manager_->update();
updateTextureDstRects();
throwBalloons();
updatePlayers();
updateAllFades();
++counter_;
}
tiled_bg_->update(adjusted_delta_time); Screen::get()->update();
cycleColors();
balloon_manager_->update(adjusted_delta_time);
updateTextureDstRects(adjusted_delta_time);
throwBalloons(adjusted_delta_time);
updatePlayers(adjusted_delta_time);
updateAllFades(adjusted_delta_time);
// Convertir deltaTime a factor de frame (asumiendo 60fps) fillCanvas();
const float frameFactor = adjusted_delta_time / (1000.0f / 60.0f); }
counter_ += frameFactor;
Screen::get()->update();
fillCanvas();
Audio::update(); Audio::update();
} }
@@ -287,9 +277,9 @@ void Credits::fillCanvas() {
SDL_SetRenderTarget(Screen::get()->getRenderer(), temp); SDL_SetRenderTarget(Screen::get()->getRenderer(), temp);
} }
// Actualiza el destino de los rectangulos de las texturas (frame-based) // Actualiza el destino de los rectangulos de las texturas
void Credits::updateTextureDstRects() { void Credits::updateTextureDstRects() {
if (static_cast<int>(counter_) % 10 == 0) { if (counter_ % 10 == 0) {
// Comprueba la posición de la textura con los titulos de credito // Comprueba la posición de la textura con los titulos de credito
if (credits_rect_dst_.y + credits_rect_dst_.h > play_area_.y) { if (credits_rect_dst_.y + credits_rect_dst_.h > play_area_.y) {
--credits_rect_dst_.y; --credits_rect_dst_.y;
@@ -316,42 +306,7 @@ void Credits::updateTextureDstRects() {
} }
} }
// Actualiza el destino de los rectangulos de las texturas (time-based) // Tira globos al escenario
void Credits::updateTextureDstRects(float deltaTime) {
constexpr float TEXTURE_UPDATE_INTERVAL = 10 * (1000.0f / 60.0f); // 166.67ms (cada 10 frames)
static float texture_accumulator = 0.0f;
texture_accumulator += deltaTime;
if (texture_accumulator >= TEXTURE_UPDATE_INTERVAL) {
texture_accumulator -= TEXTURE_UPDATE_INTERVAL;
// Comprueba la posición de la textura con los titulos de credito
if (credits_rect_dst_.y + credits_rect_dst_.h > play_area_.y) {
--credits_rect_dst_.y;
}
// Comprueba la posición de la textura con el mini_logo
if (mini_logo_rect_dst_.y == mini_logo_final_pos_) {
mini_logo_on_position_ = true;
// Si el jugador quiere pasar los titulos de credito, el fade se inicia solo
if (want_to_pass_) {
fading_ = true;
}
// Se activa el contador para evitar que la sección sea infinita
if (counter_prevent_endless_ == 1000) {
fading_ = true;
} else {
++counter_prevent_endless_;
}
} else {
--mini_logo_rect_dst_.y;
}
}
}
// Tira globos al escenario (frame-based)
void Credits::throwBalloons() { void Credits::throwBalloons() {
constexpr int SPEED = 200; constexpr int SPEED = 200;
const std::vector<int> SETS = {0, 63, 25, 67, 17, 75, 13, 50}; const std::vector<int> SETS = {0, 63, 25, 67, 17, 75, 13, 50};
@@ -360,41 +315,12 @@ void Credits::throwBalloons() {
return; return;
} }
if (static_cast<int>(counter_) % SPEED == 0) { if (counter_ % SPEED == 0) {
const int INDEX = (static_cast<int>(counter_) / SPEED) % SETS.size(); const int INDEX = (counter_ / SPEED) % SETS.size();
balloon_manager_->deployFormation(SETS.at(INDEX), -60); balloon_manager_->deployFormation(SETS.at(INDEX), -60);
} }
if (static_cast<int>(counter_) % (SPEED * 4) == 0 && counter_ > 0) { if (counter_ % (SPEED * 4) == 0 && counter_ > 0) {
balloon_manager_->createPowerBall();
}
}
// Tira globos al escenario (time-based)
void Credits::throwBalloons(float deltaTime) {
constexpr int SPEED = 200;
const std::vector<int> SETS = {0, 63, 25, 67, 17, 75, 13, 50};
constexpr float BALLOON_INTERVAL = SPEED * (1000.0f / 60.0f); // 3333.33ms (cada 200 frames)
constexpr float POWERBALL_INTERVAL = (SPEED * 4) * (1000.0f / 60.0f); // 13333.33ms (cada 800 frames)
if (counter_ > ((SETS.size() - 1) * SPEED) * 3) {
return;
}
static float balloon_accumulator = 0.0f;
static float powerball_accumulator = 0.0f;
balloon_accumulator += deltaTime;
powerball_accumulator += deltaTime;
if (balloon_accumulator >= BALLOON_INTERVAL) {
balloon_accumulator -= BALLOON_INTERVAL;
const int INDEX = (static_cast<int>(counter_ / SPEED)) % SETS.size();
balloon_manager_->deployFormation(SETS.at(INDEX), -60);
}
if (powerball_accumulator >= POWERBALL_INTERVAL && counter_ > 0) {
powerball_accumulator -= POWERBALL_INTERVAL;
balloon_manager_->createPowerBall(); balloon_manager_->createPowerBall();
} }
} }
@@ -466,12 +392,12 @@ void Credits::initPlayers() {
} }
} }
// Actualiza los rectangulos negros (frame-based) // Actualiza los rectangulos negros
void Credits::updateBlackRects() { void Credits::updateBlackRects() {
static int current_step_ = steps_; static int current_step_ = steps_;
if (top_black_rect_.h != param.game.game_area.center_y - 1 && bottom_black_rect_.y != param.game.game_area.center_y + 1) { if (top_black_rect_.h != param.game.game_area.center_y - 1 && bottom_black_rect_.y != param.game.game_area.center_y + 1) {
// Si los rectangulos superior e inferior no han llegado al centro // Si los rectangulos superior e inferior no han llegado al centro
if (static_cast<int>(counter_) % 4 == 0) { if (counter_ % 4 == 0) {
// Incrementa la altura del rectangulo superior // Incrementa la altura del rectangulo superior
top_black_rect_.h = std::min(top_black_rect_.h + 1, param.game.game_area.center_y - 1); top_black_rect_.h = std::min(top_black_rect_.h + 1, param.game.game_area.center_y - 1);
@@ -509,57 +435,6 @@ void Credits::updateBlackRects() {
} }
} }
// Actualiza los rectangulos negros (time-based)
void Credits::updateBlackRects(float deltaTime) {
static float current_step_ = static_cast<float>(steps_);
constexpr float BLACK_RECT_INTERVAL = 4 * (1000.0f / 60.0f); // 66.67ms (cada 4 frames)
static float black_rect_accumulator = 0.0f;
if (top_black_rect_.h != param.game.game_area.center_y - 1 && bottom_black_rect_.y != param.game.game_area.center_y + 1) {
// Si los rectangulos superior e inferior no han llegado al centro
black_rect_accumulator += deltaTime;
if (black_rect_accumulator >= BLACK_RECT_INTERVAL) {
black_rect_accumulator -= BLACK_RECT_INTERVAL;
// Incrementa la altura del rectangulo superior
top_black_rect_.h = std::min(top_black_rect_.h + 1, param.game.game_area.center_y - 1);
// Incrementa la altura y modifica la posición del rectangulo inferior
++bottom_black_rect_.h;
bottom_black_rect_.y = std::max(bottom_black_rect_.y - 1, param.game.game_area.center_y + 1);
--current_step_;
setVolume(static_cast<int>(initial_volume_ * current_step_ / steps_));
}
} else {
// Si los rectangulos superior e inferior han llegado al centro
if (left_black_rect_.w != param.game.game_area.center_x && right_black_rect_.x != param.game.game_area.center_x) {
constexpr int SPEED = 2;
// Si los rectangulos izquierdo y derecho no han llegado al centro
// Incrementa la anchura del rectangulo situado a la izquierda
left_black_rect_.w = std::min(left_black_rect_.w + SPEED, param.game.game_area.center_x);
// Incrementa la anchura y modifica la posición del rectangulo situado a la derecha
right_black_rect_.w += SPEED;
right_black_rect_.x = std::max(right_black_rect_.x - SPEED, param.game.game_area.center_x);
--current_step_;
setVolume(static_cast<int>(initial_volume_ * current_step_ / steps_));
} else {
// Si los rectangulos izquierdo y derecho han llegado al centro
setVolume(0);
Audio::get()->stopMusic();
if (counter_pre_fade_ == 400) {
fade_out_->activate();
} else {
// Convertir deltaTime a factor de frame
const float frameFactor = deltaTime / (1000.0f / 60.0f);
counter_pre_fade_ += frameFactor;
}
}
}
}
// Actualiza el rectangulo rojo // Actualiza el rectangulo rojo
void Credits::updateRedRect() { void Credits::updateRedRect() {
border_rect_.x = left_black_rect_.x + left_black_rect_.w; border_rect_.x = left_black_rect_.x + left_black_rect_.w;
@@ -568,7 +443,7 @@ void Credits::updateRedRect() {
border_rect_.h = bottom_black_rect_.y - border_rect_.y + 1; border_rect_.h = bottom_black_rect_.y - border_rect_.y + 1;
} }
// Actualiza el estado de fade (frame-based) // Actualiza el estado de fade
void Credits::updateAllFades() { void Credits::updateAllFades() {
if (fading_) { if (fading_) {
updateBlackRects(); updateBlackRects();
@@ -586,24 +461,6 @@ void Credits::updateAllFades() {
} }
} }
// Actualiza el estado de fade (time-based)
void Credits::updateAllFades(float deltaTime) {
if (fading_) {
updateBlackRects(deltaTime);
updateRedRect();
}
fade_in_->update(); // Fade ya usa tiempo interno
if (fade_in_->hasEnded()) {
Audio::get()->playMusic("credits.ogg");
}
fade_out_->update(); // Fade ya usa tiempo interno
if (fade_out_->hasEnded()) {
Section::name = Section::Name::HI_SCORE_TABLE;
}
}
// Establece el nivel de volumen // Establece el nivel de volumen
void Credits::setVolume(int amount) { void Credits::setVolume(int amount) {
Options::audio.music.volume = std::clamp(amount, 0, 100); Options::audio.music.volume = std::clamp(amount, 0, 100);
@@ -651,10 +508,10 @@ void Credits::cycleColors() {
tiled_bg_->setColor(color_); tiled_bg_->setColor(color_);
} }
// Actualza los jugadores (time-based) // Actualza los jugadores
void Credits::updatePlayers(float deltaTime) { void Credits::updatePlayers() {
for (auto &player : players_) { for (auto &player : players_) {
player->update(deltaTime); player->update();
} }
} }

View File

@@ -25,11 +25,6 @@ class Credits {
// --- Bucle principal --- // --- Bucle principal ---
void run(); void run();
private:
// --- Métodos del bucle principal ---
void update(float deltaTime); // Actualización principal de la lógica (time-based)
auto calculateDeltaTime() -> float; // Calcula el deltatime
private: private:
// --- Constantes de clase --- // --- Constantes de clase ---
static constexpr int PLAY_AREA_HEIGHT = 200; static constexpr int PLAY_AREA_HEIGHT = 200;
@@ -46,10 +41,10 @@ class Credits {
SDL_Texture *canvas_; // Textura donde se dibuja todo SDL_Texture *canvas_; // Textura donde se dibuja todo
// --- Temporización y contadores --- // --- Temporización y contadores ---
Uint64 last_time_ = 0; // Último tiempo registrado para deltaTime Uint64 ticks_ = 0; // Control de velocidad del programa
float counter_ = 0; // Contador principal de lógica Uint32 counter_ = 0; // Contador principal de lógica
float counter_pre_fade_ = 0; // Activación del fundido final Uint32 counter_pre_fade_ = 0; // Activación del fundido final
float counter_prevent_endless_ = 0; // Prevención de bucle infinito Uint32 counter_prevent_endless_ = 0; // Prevención de bucle infinito
// --- Variables de estado --- // --- Variables de estado ---
bool fading_ = false; // Estado del fade final bool fading_ = false; // Estado del fade final
@@ -106,6 +101,8 @@ class Credits {
// Borde para la ventana // Borde para la ventana
SDL_FRect border_rect_ = play_area_; // Delimitador de ventana SDL_FRect border_rect_ = play_area_; // Delimitador de ventana
// --- Métodos del bucle principal ---
void update(); // Actualización principal de la lógica
void render(); // Renderizado de la escena void render(); // Renderizado de la escena
static void checkEvents(); // Manejo de eventos static void checkEvents(); // Manejo de eventos
void checkInput(); // Procesamiento de entrada void checkInput(); // Procesamiento de entrada
@@ -113,23 +110,19 @@ class Credits {
// --- Métodos de renderizado --- // --- Métodos de renderizado ---
void fillTextTexture(); // Crear textura de texto de créditos void fillTextTexture(); // Crear textura de texto de créditos
void fillCanvas(); // Renderizar todos los sprites y fondos void fillCanvas(); // Renderizar todos los sprites y fondos
void updateTextureDstRects(); // Actualizar destinos de texturas
void renderPlayers(); // Renderiza los jugadores void renderPlayers(); // Renderiza los jugadores
// --- Métodos de lógica del juego --- // --- Métodos de lógica del juego ---
void throwBalloons(); // Lanzar globos al escenario (frame-based) void throwBalloons(); // Lanzar globos al escenario
void throwBalloons(float deltaTime); // Lanzar globos al escenario (time-based) void initPlayers(); // Inicializar jugadores
void initPlayers(); // Inicializar jugadores void updateAllFades(); // Actualizar estados de fade
void updateAllFades(); // Actualizar estados de fade (frame-based) void cycleColors(); // Cambiar colores de fondo
void updateAllFades(float deltaTime); // Actualizar estados de fade (time-based) void updatePlayers(); // Actualza los jugadores
void cycleColors(); // Cambiar colores de fondo
void updatePlayers(float deltaTime); // Actualza los jugadores (time-based)
// --- Métodos de interfaz --- // --- Métodos de interfaz ---
void updateBlackRects(); // Actualizar rectángulos negros (letterbox) (frame-based) void updateBlackRects(); // Actualizar rectángulos negros (letterbox)
void updateBlackRects(float deltaTime); // Actualizar rectángulos negros (letterbox) (time-based) void updateRedRect(); // Actualizar rectángulo rojo (borde)
void updateRedRect(); // Actualizar rectángulo rojo (borde)
void updateTextureDstRects(); // Actualizar destinos de texturas (frame-based)
void updateTextureDstRects(float deltaTime); // Actualizar destinos de texturas (time-based)
// --- Métodos de audio --- // --- Métodos de audio ---
static void setVolume(int amount); // Establecer volumen static void setVolume(int amount); // Establecer volumen

View File

@@ -79,7 +79,7 @@ Game::Game(Player::Id player_id, int current_stage, bool demo)
scoreboard_ = Scoreboard::get(); scoreboard_ = Scoreboard::get();
fade_in_->setColor(param.fade.color); fade_in_->setColor(param.fade.color);
fade_in_->setPreDuration(demo_.enabled ? DEMO_FADE_PRE_DURATION_MS : 0); fade_in_->setPreDuration(demo_.enabled ? 500 : 0);
fade_in_->setPostDuration(0); fade_in_->setPostDuration(0);
fade_in_->setType(Fade::Type::RANDOM_SQUARE2); fade_in_->setType(Fade::Type::RANDOM_SQUARE2);
fade_in_->setMode(Fade::Mode::IN); fade_in_->setMode(Fade::Mode::IN);
@@ -211,9 +211,9 @@ void Game::updateHiScore() {
} }
// Actualiza las variables del jugador // Actualiza las variables del jugador
void Game::updatePlayers(float deltaTime) { void Game::updatePlayers() {
for (auto &player : players_) { for (auto &player : players_) {
player->update(deltaTime); player->update();
if (player->isPlaying()) { if (player->isPlaying()) {
// Comprueba la colisión entre el jugador y los globos // Comprueba la colisión entre el jugador y los globos
@@ -222,7 +222,7 @@ void Game::updatePlayers(float deltaTime) {
// Si hay colisión // Si hay colisión
if (balloon) { if (balloon) {
// Si el globo está parado y el temporizador activo, lo explota // Si el globo está parado y el temporizador activo, lo explota
if (balloon->isStopped() && time_stopped_timer_ > 0) { if (balloon->isStopped() && time_stopped_counter_ > 0) {
balloon_manager_->popBalloon(balloon); balloon_manager_->popBalloon(balloon);
} }
// En caso contrario, el jugador ha sido golpeado por un globo activo // En caso contrario, el jugador ha sido golpeado por un globo activo
@@ -312,9 +312,9 @@ void Game::updateStage() {
} }
// Actualiza el estado de fin de la partida // Actualiza el estado de fin de la partida
void Game::updateGameStateGameOver(float deltaTime) { void Game::updateGameStateGameOver() {
fade_out_->update(); fade_out_->update();
updatePlayers(deltaTime); updatePlayers();
updateScoreboard(); updateScoreboard();
updateBackground(); updateBackground();
balloon_manager_->update(); balloon_manager_->update();
@@ -323,17 +323,20 @@ void Game::updateGameStateGameOver(float deltaTime) {
updateItems(); updateItems();
updateSmartSprites(); updateSmartSprites();
updatePathSprites(); updatePathSprites();
updateTimeStopped(deltaTime); updateTimeStopped();
checkBulletCollision(); checkBulletCollision();
cleanVectors(); cleanVectors();
if (game_over_timer_ < GAME_OVER_DURATION_MS) { if (game_over_counter_ > 0) {
handleGameOverEvents(); // Maneja eventos al inicio if (game_over_counter_ == GAME_OVER_COUNTER) {
createMessage({paths_.at(2), paths_.at(3)}, Resource::get()->getTexture("game_text_game_over"));
Audio::get()->fadeOutMusic(1000);
balloon_manager_->setBouncingSounds(true);
}
game_over_timer_ += deltaTime; // Incremento time-based game_over_counter_--;
constexpr float FADE_TRIGGER_MS = GAME_OVER_DURATION_MS - (150.0f * (1000.0f / 60.0f)); // 2500ms antes del final if (game_over_counter_ == 150) {
if (game_over_timer_ >= FADE_TRIGGER_MS && !fade_out_->isEnabled()) {
fade_out_->activate(); fade_out_->activate();
} }
} }
@@ -346,7 +349,7 @@ void Game::updateGameStateGameOver(float deltaTime) {
} }
if (fade_out_->hasEnded()) { if (fade_out_->hasEnded()) {
if (game_completed_timer_ > 0) { if (game_completed_counter_ > 0) {
Section::name = Section::Name::CREDITS; // Los jugadores han completado el juego Section::name = Section::Name::CREDITS; // Los jugadores han completado el juego
} else { } else {
Section::name = Section::Name::HI_SCORE_TABLE; // La partida ha terminado con la derrota de los jugadores Section::name = Section::Name::HI_SCORE_TABLE; // La partida ha terminado con la derrota de los jugadores
@@ -360,8 +363,11 @@ void Game::updateGameStateGameOver(float deltaTime) {
} }
// Gestiona eventos para el estado del final del juego // Gestiona eventos para el estado del final del juego
void Game::updateGameStateCompleted(float deltaTime) { void Game::updateGameStateCompleted() {
updatePlayers(deltaTime); constexpr int START_CELEBRATIONS = 400;
constexpr int END_CELEBRATIONS = START_CELEBRATIONS + 300;
updatePlayers();
updateScoreboard(); updateScoreboard();
updateBackground(); updateBackground();
balloon_manager_->update(); balloon_manager_->update();
@@ -372,16 +378,40 @@ void Game::updateGameStateCompleted(float deltaTime) {
updatePathSprites(); updatePathSprites();
cleanVectors(); cleanVectors();
// Maneja eventos del juego completado // Comienza las celebraciones
handleGameCompletedEvents(); // Muestra el mensaje de felicitación y da los puntos a los jugadores
if (game_completed_counter_ == START_CELEBRATIONS) {
createMessage({paths_.at(4), paths_.at(5)}, Resource::get()->getTexture("game_text_congratulations"));
createMessage({paths_.at(6), paths_.at(7)}, Resource::get()->getTexture("game_text_1000000_points"));
for (auto &player : players_) {
if (player->isPlaying()) {
player->addScore(1000000, Options::settings.hi_score_table.back().score);
player->setPlayingState(Player::State::CELEBRATING);
} else {
player->setPlayingState(Player::State::GAME_OVER);
}
}
updateHiScore();
}
// Termina las celebraciones
if (game_completed_counter_ == END_CELEBRATIONS) {
for (auto &player : players_) {
if (player->isCelebrating()) {
player->setPlayingState(player->qualifiesForHighScore() ? Player::State::ENTERING_NAME_GAME_COMPLETED : Player::State::LEAVING_SCREEN);
}
}
}
// Si los jugadores ya no estan y no quedan mensajes en pantalla // Si los jugadores ya no estan y no quedan mensajes en pantalla
if (allPlayersAreGameOver() && path_sprites_.empty()) { if (allPlayersAreGameOver() && path_sprites_.empty()) {
setState(State::GAME_OVER); setState(State::GAME_OVER);
} }
// Incrementa el acumulador al final // Incrementa el contador al final
game_completed_timer_ += deltaTime; ++game_completed_counter_;
} }
// Comprueba el estado del juego // Comprueba el estado del juego
@@ -857,35 +887,21 @@ void Game::handlePlayerCollision(std::shared_ptr<Player> &player, std::shared_pt
} }
} }
// Actualiza el estado del tiempo detenido // Actualiza y comprueba el valor de la variable
void Game::updateTimeStopped(float deltaTime) { void Game::updateTimeStopped() {
static constexpr float WARNING_THRESHOLD_MS = 2000.0f; // 120 frames a 60fps if (time_stopped_counter_ > 0) {
static constexpr float CLOCK_SOUND_INTERVAL_MS = 500.0f; // 30 frames a 60fps time_stopped_counter_--;
static constexpr float COLOR_FLASH_INTERVAL_MS = 250.0f; // 15 frames a 60fps if (time_stopped_counter_ > 120) {
if (time_stopped_counter_ % 30 == 0) {
if (time_stopped_timer_ > 0) {
time_stopped_timer_ -= deltaTime;
// Fase de advertencia (últimos 2 segundos)
if (time_stopped_timer_ <= WARNING_THRESHOLD_MS) {
static float last_sound_time = 0.0f;
last_sound_time += deltaTime;
if (last_sound_time >= CLOCK_SOUND_INTERVAL_MS) {
balloon_manager_->normalColorsToAllBalloons();
playSound("clock.wav");
last_sound_time = 0.0f;
} else if (last_sound_time >= COLOR_FLASH_INTERVAL_MS) {
balloon_manager_->reverseColorsToAllBalloons();
playSound("clock.wav"); playSound("clock.wav");
} }
} else { } else {
// Fase normal - solo sonido ocasional if (time_stopped_counter_ % 30 == 0) {
static float sound_timer = 0.0f; balloon_manager_->normalColorsToAllBalloons();
sound_timer += deltaTime; playSound("clock.wav");
if (sound_timer >= CLOCK_SOUND_INTERVAL_MS) { } else if (time_stopped_counter_ % 30 == 15) {
balloon_manager_->reverseColorsToAllBalloons();
playSound("clock.wav"); playSound("clock.wav");
sound_timer = 0.0f;
} }
} }
} else { } else {
@@ -893,16 +909,18 @@ void Game::updateTimeStopped(float deltaTime) {
} }
} }
// Actualiza toda la lógica del juego void Game::update() {
void Game::update(float deltaTime) { if (SDL_GetTicks() - ticks_ > param.game.speed) {
screen_->update(); ticks_ = SDL_GetTicks();
screen_->update();
updateDemo(); updateDemo();
#ifdef RECORDING #ifdef RECORDING
updateRecording(); updateRecording();
#endif #endif
updateGameStates(deltaTime); updateGameStates();
fillCanvas(); fillCanvas();
}
Audio::update(); Audio::update();
} }
@@ -920,26 +938,26 @@ void Game::render() {
} }
// Actualiza los estados del juego // Actualiza los estados del juego
void Game::updateGameStates(float deltaTime) { void Game::updateGameStates() {
if (!pause_manager_->isPaused()) { if (!pause_manager_->isPaused()) {
switch (state_) { switch (state_) {
case State::FADE_IN: case State::FADE_IN:
updateGameStateFadeIn(); updateGameStateFadeIn();
break; break;
case State::ENTERING_PLAYER: case State::ENTERING_PLAYER:
updateGameStateEnteringPlayer(deltaTime); updateGameStateEnteringPlayer();
break; break;
case State::SHOWING_GET_READY_MESSAGE: case State::SHOWING_GET_READY_MESSAGE:
updateGameStateShowingGetReadyMessage(deltaTime); updateGameStateShowingGetReadyMessage();
break; break;
case State::PLAYING: case State::PLAYING:
updateGameStatePlaying(deltaTime); updateGameStatePlaying();
break; break;
case State::COMPLETED: case State::COMPLETED:
updateGameStateCompleted(deltaTime); updateGameStateCompleted();
break; break;
case State::GAME_OVER: case State::GAME_OVER:
updateGameStateGameOver(deltaTime); updateGameStateGameOver();
break; break;
default: default:
break; break;
@@ -978,34 +996,23 @@ void Game::fillCanvas() {
void Game::enableTimeStopItem() { void Game::enableTimeStopItem() {
balloon_manager_->stopAllBalloons(); balloon_manager_->stopAllBalloons();
balloon_manager_->reverseColorsToAllBalloons(); balloon_manager_->reverseColorsToAllBalloons();
time_stopped_timer_ = TIME_STOPPED_DURATION_MS; time_stopped_counter_ = TIME_STOPPED_COUNTER;
} }
// Deshabilita el efecto del item de detener el tiempo // Deshabilita el efecto del item de detener el tiempo
void Game::disableTimeStopItem() { void Game::disableTimeStopItem() {
time_stopped_timer_ = 0; time_stopped_counter_ = 0;
balloon_manager_->startAllBalloons(); balloon_manager_->startAllBalloons();
balloon_manager_->normalColorsToAllBalloons(); balloon_manager_->normalColorsToAllBalloons();
} }
// Calcula el deltatime
auto Game::calculateDeltaTime() -> float {
const Uint64 current_time = SDL_GetTicks();
const float delta_time = static_cast<float>(current_time - last_time_);
last_time_ = current_time;
return delta_time;
}
// Bucle para el juego // Bucle para el juego
void Game::run() { void Game::run() {
last_time_ = SDL_GetTicks();
while (Section::name == Section::Name::GAME) { while (Section::name == Section::Name::GAME) {
#ifndef RECORDING #ifndef RECORDING
checkInput(); checkInput();
#endif #endif
const float delta_time = calculateDeltaTime(); update();
update(delta_time);
handleEvents(); // Tiene que ir antes del render handleEvents(); // Tiene que ir antes del render
render(); render();
} }
@@ -1322,7 +1329,7 @@ void Game::handleFireInput(const std::shared_ptr<Player> &player, BulletType bul
cant_fire_counter = NORMAL_COOLDOWN; cant_fire_counter = NORMAL_COOLDOWN;
} }
player->startFiringSystem(cant_fire_counter); // Sistema de disparo de dos líneas player->setCantFireCounter(cant_fire_counter);
} }
} }
@@ -1711,9 +1718,9 @@ void Game::updateGameStateFadeIn() {
} }
// Actualiza las variables durante dicho estado // Actualiza las variables durante dicho estado
void Game::updateGameStateEnteringPlayer(float deltaTime) { void Game::updateGameStateEnteringPlayer() {
balloon_manager_->update(); balloon_manager_->update();
updatePlayers(deltaTime); updatePlayers();
updateScoreboard(); updateScoreboard();
updateBackground(); updateBackground();
for (const auto &player : players_) { for (const auto &player : players_) {
@@ -1726,8 +1733,8 @@ void Game::updateGameStateEnteringPlayer(float deltaTime) {
} }
// Actualiza las variables durante dicho estado // Actualiza las variables durante dicho estado
void Game::updateGameStateShowingGetReadyMessage(float deltaTime) { void Game::updateGameStateShowingGetReadyMessage() {
updateGameStatePlaying(deltaTime); updateGameStatePlaying();
if (path_sprites_.empty()) { if (path_sprites_.empty()) {
setState(State::PLAYING); setState(State::PLAYING);
} }
@@ -1738,13 +1745,13 @@ void Game::updateGameStateShowingGetReadyMessage(float deltaTime) {
} }
// Actualiza las variables durante el transcurso normal del juego // Actualiza las variables durante el transcurso normal del juego
void Game::updateGameStatePlaying(float deltaTime) { void Game::updateGameStatePlaying() {
#ifdef _DEBUG #ifdef _DEBUG
if (auto_pop_balloons_) { if (auto_pop_balloons_) {
stage_manager_->addPower(5); stage_manager_->addPower(5);
} }
#endif #endif
updatePlayers(deltaTime); updatePlayers();
checkPlayersStatusPlaying(); checkPlayersStatusPlaying();
updateScoreboard(); updateScoreboard();
updateBackground(); updateBackground();
@@ -1755,7 +1762,7 @@ void Game::updateGameStatePlaying(float deltaTime) {
updateStage(); updateStage();
updateSmartSprites(); updateSmartSprites();
updatePathSprites(); updatePathSprites();
updateTimeStopped(deltaTime); updateTimeStopped();
updateHelper(); updateHelper();
checkBulletCollision(); checkBulletCollision();
updateMenace(); updateMenace();
@@ -1893,55 +1900,6 @@ void Game::onPauseStateChanged(bool is_paused) {
tabe_->pauseTimer(is_paused); tabe_->pauseTimer(is_paused);
} }
// Maneja eventos del juego completado usando flags para triggers únicos
void Game::handleGameCompletedEvents() {
constexpr float START_CELEBRATIONS_MS = 6667.0f; // 400 frames a 60fps
constexpr float END_CELEBRATIONS_MS = 11667.0f; // 700 frames a 60fps
// Inicio de celebraciones
static bool start_celebrations_triggered = false;
if (!start_celebrations_triggered && game_completed_timer_ >= START_CELEBRATIONS_MS) {
createMessage({paths_.at(4), paths_.at(5)}, Resource::get()->getTexture("game_text_congratulations"));
createMessage({paths_.at(6), paths_.at(7)}, Resource::get()->getTexture("game_text_1000000_points"));
for (auto &player : players_) {
if (player->isPlaying()) {
player->addScore(1000000, Options::settings.hi_score_table.back().score);
player->setPlayingState(Player::State::CELEBRATING);
} else {
player->setPlayingState(Player::State::GAME_OVER);
}
}
updateHiScore();
start_celebrations_triggered = true;
}
// Fin de celebraciones
static bool end_celebrations_triggered = false;
if (!end_celebrations_triggered && game_completed_timer_ >= END_CELEBRATIONS_MS) {
for (auto &player : players_) {
if (player->isCelebrating()) {
player->setPlayingState(player->qualifiesForHighScore() ? Player::State::ENTERING_NAME_GAME_COMPLETED : Player::State::LEAVING_SCREEN);
}
}
fade_out_->activate();
end_celebrations_triggered = true;
}
}
// Maneja eventos de game over usando flag para trigger único
void Game::handleGameOverEvents() {
static bool game_over_triggered = false;
if (!game_over_triggered && game_over_timer_ == 0.0f) {
createMessage({paths_.at(2), paths_.at(3)}, Resource::get()->getTexture("game_text_game_over"));
Audio::get()->fadeOutMusic(1000);
balloon_manager_->setBouncingSounds(true);
game_over_triggered = true;
}
}
#ifdef _DEBUG #ifdef _DEBUG
// Comprueba los eventos en el modo DEBUG // Comprueba los eventos en el modo DEBUG
void Game::handleDebugEvents(const SDL_Event &event) { void Game::handleDebugEvents(const SDL_Event &event) {

View File

@@ -33,22 +33,7 @@ namespace Difficulty {
enum class Code; enum class Code;
} // namespace Difficulty } // namespace Difficulty
// --- Clase Game: núcleo principal del gameplay --- // --- Clase Game: gestor principal del juego ---
//
// Esta clase gestiona toda la lógica del juego durante las partidas activas,
// incluyendo mecánicas de juego, estados, objetos y sistemas de puntuación.
//
// Funcionalidades principales:
// • Gestión de jugadores: soporte para 1 o 2 jugadores simultáneos
// • Sistema de estados: fade-in, entrada, jugando, completado, game-over
// • Mecánicas de juego: globos, balas, ítems, power-ups y efectos especiales
// • Sistema de puntuación: scoreboard y tabla de récords
// • Efectos temporales: tiempo detenido, ayudas automáticas
// • Modo demo: reproducción automática para attract mode
// • Gestión de fases: progresión entre niveles y dificultad
//
// Utiliza un sistema de tiempo basado en milisegundos para garantizar
// comportamiento consistente independientemente del framerate.
class Game { class Game {
public: public:
// --- Constantes --- // --- Constantes ---
@@ -73,13 +58,12 @@ class Game {
GAME_OVER, // Fin del juego GAME_OVER, // Fin del juego
}; };
// --- Constantes de tiempo (en milisegundos) --- // --- Constantes internas ---
static constexpr float HELP_COUNTER_MS = 16667.0f; // Contador de ayuda (1000 frames a 60fps) static constexpr int HELP_COUNTER = 1000;
static constexpr float GAME_COMPLETED_START_FADE_MS = 8333.0f; // Inicio del fade al completar (500 frames) static constexpr int GAME_COMPLETED_START_FADE = 500;
static constexpr float GAME_COMPLETED_END_MS = 11667.0f; // Fin del juego completado (700 frames) static constexpr int GAME_COMPLETED_END = 700;
static constexpr float GAME_OVER_DURATION_MS = 5833.0f; // Duración game over (350 frames) static constexpr int GAME_OVER_COUNTER = 350;
static constexpr float TIME_STOPPED_DURATION_MS = 6000.0f; // Duración del tiempo detenido (360 frames) static constexpr int TIME_STOPPED_COUNTER = 360;
static constexpr int DEMO_FADE_PRE_DURATION_MS = 500; // Pre-duración del fade en modo demo
static constexpr int ITEM_POINTS_1_DISK_ODDS = 10; static constexpr int ITEM_POINTS_1_DISK_ODDS = 10;
static constexpr int ITEM_POINTS_2_GAVINA_ODDS = 6; static constexpr int ITEM_POINTS_2_GAVINA_ODDS = 6;
static constexpr int ITEM_POINTS_3_PACMAR_ODDS = 3; static constexpr int ITEM_POINTS_3_PACMAR_ODDS = 3;
@@ -93,7 +77,7 @@ class Game {
bool need_coffee{false}; // Indica si se necesitan cafes bool need_coffee{false}; // Indica si se necesitan cafes
bool need_coffee_machine{false}; // Indica si se necesita PowerUp bool need_coffee_machine{false}; // Indica si se necesita PowerUp
bool need_power_ball{false}; // Indica si se necesita una PowerBall bool need_power_ball{false}; // Indica si se necesita una PowerBall
float counter; // Contador para no dar ayudas consecutivas int counter; // Contador para no dar ayudas consecutivas
int item_disk_odds; // Probabilidad de aparición del objeto int item_disk_odds; // Probabilidad de aparición del objeto
int item_gavina_odds; // Probabilidad de aparición del objeto int item_gavina_odds; // Probabilidad de aparición del objeto
int item_pacmar_odds; // Probabilidad de aparición del objeto int item_pacmar_odds; // Probabilidad de aparición del objeto
@@ -102,7 +86,7 @@ class Game {
int item_coffee_machine_odds; // Probabilidad de aparición del objeto int item_coffee_machine_odds; // Probabilidad de aparición del objeto
Helper() Helper()
: counter(HELP_COUNTER_MS), : counter(HELP_COUNTER),
item_disk_odds(ITEM_POINTS_1_DISK_ODDS), item_disk_odds(ITEM_POINTS_1_DISK_ODDS),
item_gavina_odds(ITEM_POINTS_2_GAVINA_ODDS), item_gavina_odds(ITEM_POINTS_2_GAVINA_ODDS),
item_pacmar_odds(ITEM_POINTS_3_PACMAR_ODDS), item_pacmar_odds(ITEM_POINTS_3_PACMAR_ODDS),
@@ -150,14 +134,14 @@ class Game {
Demo demo_; // Variable con todas las variables relacionadas con el modo demo Demo demo_; // Variable con todas las variables relacionadas con el modo demo
Difficulty::Code difficulty_ = Options::settings.difficulty; // Dificultad del juego Difficulty::Code difficulty_ = Options::settings.difficulty; // Dificultad del juego
Helper helper_; // Variable para gestionar las ayudas Helper helper_; // Variable para gestionar las ayudas
Uint64 last_time_ = 0; // Último tiempo registrado para deltaTime Uint64 ticks_ = 0; // Contador de ticks para ajustar la velocidad del programa
bool coffee_machine_enabled_ = false; // Indica si hay una máquina de café en el terreno de juego bool coffee_machine_enabled_ = false; // Indica si hay una máquina de café en el terreno de juego
bool hi_score_achieved_ = false; // Indica si se ha superado la puntuación máxima bool hi_score_achieved_ = false; // Indica si se ha superado la puntuación máxima
float difficulty_score_multiplier_ = 1.0f; // Multiplicador de puntos en función de la dificultad float difficulty_score_multiplier_; // Multiplicador de puntos en función de la dificultad
float counter_ = 0.0f; // Contador para el juego int counter_ = 0; // Contador para el juego
float game_completed_timer_ = 0.0f; // Acumulador de tiempo para el tramo final (milisegundos) int game_completed_counter_ = 0; // Contador para el tramo final, cuando se ha completado la partida y ya no aparecen más globos
float game_over_timer_ = 0.0f; // Timer para el estado de fin de partida (milisegundos) int game_over_counter_ = GAME_OVER_COUNTER; // Contador para el estado de fin de partida
float time_stopped_timer_ = 0.0f; // Temporizador para llevar la cuenta del tiempo detenido int time_stopped_counter_ = 0; // Temporizador para llevar la cuenta del tiempo detenido
int menace_ = 0; // Nivel de amenaza actual int menace_ = 0; // Nivel de amenaza actual
int menace_threshold_ = 0; // Umbral del nivel de amenaza. Si el nivel de amenaza cae por debajo del umbral, se generan más globos. Si el umbral aumenta, aumenta el número de globos int menace_threshold_ = 0; // Umbral del nivel de amenaza. Si el nivel de amenaza cae por debajo del umbral, se generan más globos. Si el umbral aumenta, aumenta el número de globos
State state_ = State::FADE_IN; // Estado State state_ = State::FADE_IN; // Estado
@@ -170,26 +154,25 @@ class Game {
#endif #endif
// --- Ciclo principal del juego --- // --- Ciclo principal del juego ---
void update(float deltaTime); // Actualiza la lógica principal del juego void update(); // Actualiza la lógica principal del juego
auto calculateDeltaTime() -> float; // Calcula el deltatime void render(); // Renderiza todos los elementos del juego
void render(); // Renderiza todos los elementos del juego void handleEvents(); // Procesa los eventos del sistema en cola
void handleEvents(); // Procesa los eventos del sistema en cola void checkState(); // Verifica y actualiza el estado actual del juego
void checkState(); // Verifica y actualiza el estado actual del juego void setState(State state); // Cambia el estado del juego
void setState(State state); // Cambia el estado del juego void cleanVectors(); // Limpia vectores de elementos deshabilitados
void cleanVectors(); // Limpia vectores de elementos deshabilitados
// --- Gestión de estados del juego --- // --- Gestión de estados del juego ---
void updateGameStates(float deltaTime); // Actualiza todos los estados del juego void updateGameStates(); // Actualiza todos los estados del juego
void updateGameStateFadeIn(); // Gestiona el estado de transición de entrada void updateGameStateFadeIn(); // Gestiona el estado de transición de entrada
void updateGameStateEnteringPlayer(float deltaTime); // Gestiona el estado de entrada de jugador void updateGameStateEnteringPlayer(); // Gestiona el estado de entrada de jugador
void updateGameStateShowingGetReadyMessage(float deltaTime); // Gestiona el estado de mensaje "preparado" void updateGameStateShowingGetReadyMessage(); // Gestiona el estado de mensaje "preparado"
void updateGameStatePlaying(float deltaTime); // Gestiona el estado de juego activo void updateGameStatePlaying(); // Gestiona el estado de juego activo
void updateGameStateCompleted(float deltaTime); // Gestiona el estado de juego completado void updateGameStateCompleted(); // Gestiona el estado de juego completado
void updateGameStateGameOver(float deltaTime); // Gestiona el estado de fin de partida void updateGameStateGameOver(); // Gestiona el estado de fin de partida
// --- Gestión de jugadores --- // --- Gestión de jugadores ---
void initPlayers(Player::Id player_id); // Inicializa los datos de los jugadores void initPlayers(Player::Id player_id); // Inicializa los datos de los jugadores
void updatePlayers(float deltaTime); // Actualiza las variables y estados de los jugadores void updatePlayers(); // Actualiza las variables y estados de los jugadores
void renderPlayers(); // Renderiza todos los jugadores en pantalla void renderPlayers(); // Renderiza todos los jugadores en pantalla
void sortPlayersByZOrder(); // Reorganiza el orden de dibujado de jugadores void sortPlayersByZOrder(); // Reorganiza el orden de dibujado de jugadores
auto getPlayer(Player::Id id) -> std::shared_ptr<Player>; // Obtiene un jugador por su identificador auto getPlayer(Player::Id id) -> std::shared_ptr<Player>; // Obtiene un jugador por su identificador
@@ -246,12 +229,10 @@ class Game {
void destroyAllItems(); // Elimina todos los ítems activos de la pantalla void destroyAllItems(); // Elimina todos los ítems activos de la pantalla
// --- ítems especiales --- // --- ítems especiales ---
void enableTimeStopItem(); // Activa el efecto de detener el tiempo void enableTimeStopItem(); // Activa el efecto de detener el tiempo
void disableTimeStopItem(); // Desactiva el efecto de detener el tiempo void disableTimeStopItem(); // Desactiva el efecto de detener el tiempo
void updateTimeStopped(float deltaTime); // Actualiza el estado del tiempo detenido void updateTimeStopped(); // Actualiza el estado del tiempo detenido
void handleGameCompletedEvents(); // Maneja eventos del juego completado void throwCoffee(int x, int y); // Crea efecto de café arrojado al ser golpeado
void handleGameOverEvents(); // Maneja eventos de game over
void throwCoffee(int x, int y); // Crea efecto de café arrojado al ser golpeado
// --- Gestión de caída de ítems --- // --- Gestión de caída de ítems ---
void handleItemDrop(const std::shared_ptr<Balloon> &balloon, const std::shared_ptr<Player> &player); // Gestiona caída de ítem desde globo void handleItemDrop(const std::shared_ptr<Balloon> &balloon, const std::shared_ptr<Player> &player); // Gestiona caída de ítem desde globo

View File

@@ -33,7 +33,7 @@ HiScoreTable::HiScoreTable()
backbuffer_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height)), backbuffer_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height)),
fade_(std::make_unique<Fade>()), fade_(std::make_unique<Fade>()),
background_(std::make_unique<Background>()), background_(std::make_unique<Background>()),
last_time_(0), ticks_(0),
view_area_(SDL_FRect{0, 0, param.game.width, param.game.height}), view_area_(SDL_FRect{0, 0, param.game.width, param.game.height}),
fade_mode_(Fade::Mode::IN), fade_mode_(Fade::Mode::IN),
background_fade_color_(Color(0, 0, 0)) { background_fade_color_(Color(0, 0, 0)) {
@@ -53,14 +53,17 @@ HiScoreTable::~HiScoreTable() {
} }
// Actualiza las variables // Actualiza las variables
void HiScoreTable::update(float delta_time) { void HiScoreTable::update() {
Screen::get()->update(); // Actualiza el objeto screen if (SDL_GetTicks() - ticks_ > param.game.speed) {
ticks_ = SDL_GetTicks(); // Actualiza el contador de ticks
Screen::get()->update(); // Actualiza el objeto screen
updateSprites(delta_time); // Actualiza las posiciones de los sprites de texto updateSprites(); // Actualiza las posiciones de los sprites de texto
background_->update(delta_time); // Actualiza el fondo background_->update(); // Actualiza el fondo
updateFade(delta_time); // Gestiona el fade updateFade(); // Gestiona el fade
updateCounter(); // Gestiona el contador y sus eventos updateCounter(); // Gestiona el contador y sus eventos
fillTexture(); // Dibuja los sprites en la textura fillTexture(); // Dibuja los sprites en la textura
}
Audio::update(); Audio::update();
} }
@@ -114,32 +117,20 @@ void HiScoreTable::checkInput() {
GlobalInputs::check(); GlobalInputs::check();
} }
// Calcula el tiempo transcurrido desde el último frame
float HiScoreTable::calculateDeltaTime() {
const Uint64 current_time = SDL_GetTicks();
const float delta_time = static_cast<float>(current_time - last_time_);
last_time_ = current_time;
return delta_time;
}
// Bucle para la pantalla de instrucciones // Bucle para la pantalla de instrucciones
void HiScoreTable::run() { void HiScoreTable::run() {
last_time_ = SDL_GetTicks();
Audio::get()->playMusic("title.ogg"); Audio::get()->playMusic("title.ogg");
while (Section::name == Section::Name::HI_SCORE_TABLE) { while (Section::name == Section::Name::HI_SCORE_TABLE) {
const float delta_time = calculateDeltaTime();
checkInput(); checkInput();
update(delta_time); update();
checkEvents(); // Tiene que ir antes del render checkEvents(); // Tiene que ir antes del render
render(); render();
} }
} }
// Gestiona el fade // Gestiona el fade
void HiScoreTable::updateFade(float delta_time) { void HiScoreTable::updateFade() {
fade_->update(delta_time); fade_->update();
if (fade_->hasEnded() && fade_mode_ == Fade::Mode::IN) { if (fade_->hasEnded() && fade_mode_ == Fade::Mode::IN) {
fade_->reset(); fade_->reset();
@@ -258,7 +249,7 @@ void HiScoreTable::createSprites() {
} }
// Actualiza las posiciones de los sprites de texto // Actualiza las posiciones de los sprites de texto
void HiScoreTable::updateSprites(float delta_time) { void HiScoreTable::updateSprites() {
constexpr int INIT_COUNTER = 190; constexpr int INIT_COUNTER = 190;
const int COUNTER_BETWEEN_ENTRIES = 16; const int COUNTER_BETWEEN_ENTRIES = 16;
if (counter_ >= INIT_COUNTER) { if (counter_ >= INIT_COUNTER) {
@@ -271,7 +262,7 @@ void HiScoreTable::updateSprites(float delta_time) {
} }
} }
for (auto const &entry : entry_names_) { for (auto const &entry : entry_names_) {
entry->update(delta_time); entry->update();
} }
glowEntryNames(); glowEntryNames();

View File

@@ -45,27 +45,26 @@ class HiScoreTable {
// --- Variables --- // --- Variables ---
Uint16 counter_ = 0; // Contador Uint16 counter_ = 0; // Contador
Uint64 last_time_ = 0; // Último timestamp para calcular delta-time Uint64 ticks_; // Contador de ticks para ajustar la velocidad del programa
SDL_FRect view_area_; // Parte de la textura que se muestra en pantalla SDL_FRect view_area_; // Parte de la textura que se muestra en pantalla
Fade::Mode fade_mode_; // Modo de fade a utilizar Fade::Mode fade_mode_; // Modo de fade a utilizar
Color background_fade_color_; // Color de atenuación del fondo Color background_fade_color_; // Color de atenuación del fondo
std::vector<Color> entry_colors_; // Colores para destacar las entradas en la tabla std::vector<Color> entry_colors_; // Colores para destacar las entradas en la tabla
// --- Métodos internos --- // --- Métodos internos ---
void update(float delta_time); // Actualiza las variables void update(); // Actualiza las variables
void render(); // Pinta en pantalla void render(); // Pinta en pantalla
static void checkEvents(); // Comprueba los eventos static void checkEvents(); // Comprueba los eventos
static void checkInput(); // Comprueba las entradas static void checkInput(); // Comprueba las entradas
static auto format(int number) -> std::string; // Convierte un entero a un string con separadores de miles static auto format(int number) -> std::string; // Convierte un entero a un string con separadores de miles
void fillTexture(); // Dibuja los sprites en la textura void fillTexture(); // Dibuja los sprites en la textura
void updateFade(float delta_time); // Gestiona el fade void updateFade(); // Gestiona el fade
void createSprites(); // Crea los sprites con los textos void createSprites(); // Crea los sprites con los textos
void updateSprites(float delta_time); // Actualiza las posiciones de los sprites de texto void updateSprites(); // Actualiza las posiciones de los sprites de texto
void initFade(); // Inicializa el fade void initFade(); // Inicializa el fade
void initBackground(); // Inicializa el fondo void initBackground(); // Inicializa el fondo
auto getEntryColor(int counter) -> Color; // Obtiene un color del vector de colores de entradas auto getEntryColor(int counter) -> Color; // Obtiene un color del vector de colores de entradas
void iniEntryColors(); // Inicializa los colores de las entradas void iniEntryColors(); // Inicializa los colores de las entradas
void glowEntryNames(); // Hace brillar los nombres de la tabla de records void glowEntryNames(); // Hace brillar los nombres de la tabla de records
void updateCounter(); // Gestiona el contador void updateCounter(); // Gestiona el contador
float calculateDeltaTime(); // Calcula el tiempo transcurrido desde el último frame
}; };

View File

@@ -204,15 +204,18 @@ void Instructions::fillBackbuffer() {
} }
// Actualiza las variables // Actualiza las variables
void Instructions::update(float delta_time) { void Instructions::update() {
Screen::get()->update(); // Actualiza el objeto screen if (SDL_GetTicks() - ticks_ > param.game.speed) {
ticks_ = SDL_GetTicks(); // Actualiza el contador de ticks
Screen::get()->update(); // Actualiza el objeto screen
counter_++; // Incrementa el contador counter_++; // Incrementa el contador
updateSprites(); // Actualiza los sprites updateSprites(); // Actualiza los sprites
updateBackbuffer(); // Gestiona la textura con los graficos updateBackbuffer(); // Gestiona la textura con los graficos
tiled_bg_->update(delta_time); // Actualiza el mosaico de fondo tiled_bg_->update(); // Actualiza el mosaico de fondo
fade_->update(delta_time); // Actualiza el objeto "fade" fade_->update(); // Actualiza el objeto "fade"
fillBackbuffer(); // Rellena el backbuffer fillBackbuffer(); // Rellena el backbuffer
}
Audio::update(); Audio::update();
} }
@@ -252,24 +255,12 @@ void Instructions::checkInput() {
GlobalInputs::check(); GlobalInputs::check();
} }
// Calcula el tiempo transcurrido desde el último frame
float Instructions::calculateDeltaTime() {
const Uint64 current_time = SDL_GetTicks();
const float delta_time = static_cast<float>(current_time - last_time_);
last_time_ = current_time;
return delta_time;
}
// Bucle para la pantalla de instrucciones // Bucle para la pantalla de instrucciones
void Instructions::run() { void Instructions::run() {
last_time_ = SDL_GetTicks();
Audio::get()->playMusic("title.ogg"); Audio::get()->playMusic("title.ogg");
while (Section::name == Section::Name::INSTRUCTIONS) { while (Section::name == Section::Name::INSTRUCTIONS) {
const float delta_time = calculateDeltaTime();
checkInput(); checkInput();
update(delta_time); update();
checkEvents(); // Tiene que ir antes del render checkEvents(); // Tiene que ir antes del render
render(); render();
} }

View File

@@ -63,7 +63,7 @@ class Instructions {
// --- Variables --- // --- Variables ---
int counter_ = 0; // Contador para manejar el progreso en la pantalla de instrucciones int counter_ = 0; // Contador para manejar el progreso en la pantalla de instrucciones
Uint64 last_time_ = 0; // Último timestamp para calcular delta-time Uint64 ticks_ = 0; // Contador de ticks para ajustar la velocidad del programa
SDL_FRect view_; // Vista del backbuffer que se va a mostrar por pantalla SDL_FRect view_; // Vista del backbuffer que se va a mostrar por pantalla
SDL_FPoint sprite_pos_ = {0, 0}; // Posición del primer sprite en la lista SDL_FPoint sprite_pos_ = {0, 0}; // Posición del primer sprite en la lista
float item_space_ = 2.0; // Espacio entre los items en pantalla float item_space_ = 2.0; // Espacio entre los items en pantalla
@@ -73,7 +73,7 @@ class Instructions {
bool start_delay_triggered_ = false; // Bandera para determinar si el retraso ha comenzado bool start_delay_triggered_ = false; // Bandera para determinar si el retraso ha comenzado
// --- Métodos internos --- // --- Métodos internos ---
void update(float delta_time); // Actualiza las variables void update(); // Actualiza las variables
void render(); // Pinta en pantalla void render(); // Pinta en pantalla
static void checkEvents(); // Comprueba los eventos static void checkEvents(); // Comprueba los eventos
static void checkInput(); // Comprueba las entradas static void checkInput(); // Comprueba las entradas
@@ -82,8 +82,7 @@ class Instructions {
void iniSprites(); // Inicializa los sprites de los items void iniSprites(); // Inicializa los sprites de los items
void updateSprites(); // Actualiza los sprites void updateSprites(); // Actualiza los sprites
static auto initializeLines(int height) -> std::vector<Line>; // Inicializa las líneas animadas static auto initializeLines(int height) -> std::vector<Line>; // Inicializa las líneas animadas
static auto moveLines(std::vector<Line> &lines, int width, float duration, Uint32 start_delay) -> bool; // Mueve las líneas (ya usa tiempo real) static auto moveLines(std::vector<Line> &lines, int width, float duration, Uint32 start_delay) -> bool; // Mueve las líneas
static void renderLines(SDL_Renderer *renderer, SDL_Texture *texture, const std::vector<Line> &lines); // Renderiza las líneas static void renderLines(SDL_Renderer *renderer, SDL_Texture *texture, const std::vector<Line> &lines); // Renderiza las líneas
void updateBackbuffer(); // Gestiona la textura con los gráficos void updateBackbuffer(); // Gestiona la textura con los gráficos
float calculateDeltaTime(); // Calcula el tiempo transcurrido desde el último frame
}; };

View File

@@ -207,21 +207,24 @@ void Intro::switchText(int from_index, int to_index) {
} }
// Actualiza las variables del objeto // Actualiza las variables del objeto
void Intro::update(float delta_time) { void Intro::update() {
Screen::get()->update(); // Actualiza el objeto screen if (SDL_GetTicks() - ticks_ > param.game.speed) {
ticks_ = SDL_GetTicks(); // Actualiza el contador de ticks
Screen::get()->update(); // Actualiza el objeto screen
tiled_bg_->update(delta_time); // Actualiza el fondo tiled_bg_->update(); // Actualiza el fondo
switch (state_) { switch (state_) {
case State::SCENES: case State::SCENES:
updateSprites(delta_time); updateSprites();
updateTexts(delta_time); updateTexts();
updateScenes(); updateScenes();
break; break;
case State::POST: case State::POST:
updatePostState(); updatePostState();
break; break;
}
} }
Audio::update(); Audio::update();
@@ -250,24 +253,12 @@ void Intro::render() {
SCREEN->render(); // Vuelca el contenido del renderizador en pantalla SCREEN->render(); // Vuelca el contenido del renderizador en pantalla
} }
// Calcula el tiempo transcurrido desde el último frame
float Intro::calculateDeltaTime() {
const Uint64 current_time = SDL_GetTicks();
const float delta_time = static_cast<float>(current_time - last_time_);
last_time_ = current_time;
return delta_time;
}
// Bucle principal // Bucle principal
void Intro::run() { void Intro::run() {
last_time_ = SDL_GetTicks();
Audio::get()->playMusic("intro.ogg", 0); Audio::get()->playMusic("intro.ogg", 0);
while (Section::name == Section::Name::INTRO) { while (Section::name == Section::Name::INTRO) {
const float delta_time = calculateDeltaTime();
checkInput(); checkInput();
update(delta_time); update();
checkEvents(); // Tiene que ir antes del render checkEvents(); // Tiene que ir antes del render
render(); render();
} }
@@ -286,7 +277,7 @@ void Intro::initSprites() {
// Constantes // Constantes
constexpr int TOTAL_SPRITES = TEXTURE_LIST.size(); constexpr int TOTAL_SPRITES = TEXTURE_LIST.size();
const float BORDER = CARD_BORDER_SIZE; const float BORDER = 2.0F;
auto texture = Resource::get()->getTexture(TEXTURE_LIST.front()); auto texture = Resource::get()->getTexture(TEXTURE_LIST.front());
const float CARD_WIDTH = texture->getWidth() + (BORDER * 2); const float CARD_WIDTH = texture->getWidth() + (BORDER * 2);
@@ -338,16 +329,16 @@ void Intro::initSprites() {
const float X_DEST = param.game.game_area.center_x - (CARD_WIDTH / 2); const float X_DEST = param.game.game_area.center_x - (CARD_WIDTH / 2);
const float Y_DEST = param.game.game_area.first_quarter_y - (CARD_HEIGHT / 4); const float Y_DEST = param.game.game_area.first_quarter_y - (CARD_HEIGHT / 4);
card_sprites_.at(0)->addPath(-CARD_WIDTH - CARD_OFFSET_MARGIN, X_DEST, PathType::HORIZONTAL, Y_DEST, CARD_ANIM_DURATION_NORMAL, easeInOutExpo, 0); card_sprites_.at(0)->addPath(-CARD_WIDTH - 10, X_DEST, PathType::HORIZONTAL, Y_DEST, 100, easeInOutExpo, 0);
card_sprites_.at(1)->addPath(param.game.width, X_DEST, PathType::HORIZONTAL, Y_DEST, CARD_ANIM_DURATION_NORMAL, easeOutBounce, 0); card_sprites_.at(1)->addPath(param.game.width, X_DEST, PathType::HORIZONTAL, Y_DEST, 100, easeOutBounce, 0);
card_sprites_.at(2)->addPath(-CARD_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, CARD_ANIM_DURATION_FAST, easeOutQuint, 0); card_sprites_.at(2)->addPath(-CARD_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, 40, easeOutQuint, 0);
card_sprites_.at(3)->addPath(param.game.height, Y_DEST, PathType::VERTICAL, X_DEST, CARD_ANIM_DURATION_VERY_SLOW, easeInOutExpo, 0); card_sprites_.at(3)->addPath(param.game.height, Y_DEST, PathType::VERTICAL, X_DEST, 300, easeInOutExpo, 0);
card_sprites_.at(4)->addPath(-CARD_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, CARD_ANIM_DURATION_MEDIUM, easeOutElastic, 0); card_sprites_.at(4)->addPath(-CARD_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, 70, easeOutElastic, 0);
card_sprites_.at(5)->addPath(-CARD_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, CARD_ANIM_DURATION_SLOW, easeOutQuad, CARD_ANIM_DELAY_LONG); card_sprites_.at(5)->addPath(-CARD_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, 250, easeOutQuad, 450);
card_sprites_.at(5)->addPath(X_DEST, -CARD_WIDTH, PathType::HORIZONTAL, Y_DEST, CARD_ANIM_DURATION_SHORT, easeInElastic, 0); card_sprites_.at(5)->addPath(X_DEST, -CARD_WIDTH, PathType::HORIZONTAL, Y_DEST, 80, easeInElastic, 0);
// Constantes // Constantes
const float DESP = SHADOW_OFFSET; const float DESP = 8;
const float SHADOW_SPRITE_WIDTH = CARD_WIDTH; const float SHADOW_SPRITE_WIDTH = CARD_WIDTH;
const float SHADOW_SPRITE_HEIGHT = CARD_HEIGHT; const float SHADOW_SPRITE_HEIGHT = CARD_HEIGHT;
@@ -389,13 +380,13 @@ void Intro::initSprites() {
const float S_X_DEST = X_DEST + DESP; const float S_X_DEST = X_DEST + DESP;
const float S_Y_DEST = Y_DEST + DESP; const float S_Y_DEST = Y_DEST + DESP;
shadow_sprites_.at(0)->addPath(param.game.height + CARD_OFFSET_MARGIN, S_Y_DEST, PathType::VERTICAL, S_X_DEST, CARD_ANIM_DURATION_NORMAL, easeInOutExpo, 0); shadow_sprites_.at(0)->addPath(param.game.height + 10, S_Y_DEST, PathType::VERTICAL, S_X_DEST, 100, easeInOutExpo, 0);
shadow_sprites_.at(1)->addPath(-SHADOW_SPRITE_HEIGHT, S_Y_DEST, PathType::VERTICAL, S_X_DEST, CARD_ANIM_DURATION_NORMAL, easeOutBounce, 0); shadow_sprites_.at(1)->addPath(-SHADOW_SPRITE_HEIGHT, S_Y_DEST, PathType::VERTICAL, S_X_DEST, 100, easeOutBounce, 0);
shadow_sprites_.at(2)->addPath(-SHADOW_SPRITE_WIDTH, S_X_DEST, PathType::HORIZONTAL, S_Y_DEST, CARD_ANIM_DURATION_FAST, easeOutQuint, 0); shadow_sprites_.at(2)->addPath(-SHADOW_SPRITE_WIDTH, S_X_DEST, PathType::HORIZONTAL, S_Y_DEST, 40, easeOutQuint, 0);
shadow_sprites_.at(3)->addPath(-SHADOW_SPRITE_HEIGHT, S_Y_DEST, PathType::VERTICAL, S_X_DEST, CARD_ANIM_DURATION_VERY_SLOW, easeInOutExpo, 0); shadow_sprites_.at(3)->addPath(-SHADOW_SPRITE_HEIGHT, S_Y_DEST, PathType::VERTICAL, S_X_DEST, 300, easeInOutExpo, 0);
shadow_sprites_.at(4)->addPath(param.game.height, S_Y_DEST, PathType::VERTICAL, S_X_DEST, CARD_ANIM_DURATION_MEDIUM, easeOutElastic, 0); shadow_sprites_.at(4)->addPath(param.game.height, S_Y_DEST, PathType::VERTICAL, S_X_DEST, 70, easeOutElastic, 0);
shadow_sprites_.at(5)->addPath(param.game.width, S_X_DEST, PathType::HORIZONTAL, S_Y_DEST, CARD_ANIM_DURATION_SLOW, easeOutQuad, CARD_ANIM_DELAY_LONG); shadow_sprites_.at(5)->addPath(param.game.width, S_X_DEST, PathType::HORIZONTAL, S_Y_DEST, 250, easeOutQuad, 450);
shadow_sprites_.at(5)->addPath(S_X_DEST, param.game.width, PathType::HORIZONTAL, S_Y_DEST, CARD_ANIM_DURATION_SHORT, easeInElastic, 0); shadow_sprites_.at(5)->addPath(S_X_DEST, param.game.width, PathType::HORIZONTAL, S_Y_DEST, 80, easeInElastic, 0);
} }
// Inicializa los textos // Inicializa los textos
@@ -405,47 +396,47 @@ void Intro::initTexts() {
auto writer = std::make_unique<Writer>(Resource::get()->getText("04b_25_metal")); auto writer = std::make_unique<Writer>(Resource::get()->getText("04b_25_metal"));
writer->setPosX(0); writer->setPosX(0);
writer->setPosY(param.game.height - param.intro.text_distance_from_bottom); writer->setPosY(param.game.height - param.intro.text_distance_from_bottom);
writer->setKerning(TEXT_KERNING); writer->setKerning(-2);
writer->setEnabled(false); writer->setEnabled(false);
writer->setFinishedTimerMs(TEXT_DISPLAY_DURATION_MS); writer->setFinishedCounter(180);
texts_.push_back(std::move(writer)); texts_.push_back(std::move(writer));
} }
// Un dia qualsevol de l'any 2000 // Un dia qualsevol de l'any 2000
texts_.at(0)->setCaption(Lang::getText("[INTRO] 1")); texts_.at(0)->setCaption(Lang::getText("[INTRO] 1"));
texts_.at(0)->setSpeed(TEXT_SPEED_NORMAL); texts_.at(0)->setSpeed(8);
// Tot esta tranquil a la UPV // Tot esta tranquil a la UPV
texts_.at(1)->setCaption(Lang::getText("[INTRO] 2")); texts_.at(1)->setCaption(Lang::getText("[INTRO] 2"));
texts_.at(1)->setSpeed(TEXT_SPEED_NORMAL); texts_.at(1)->setSpeed(8);
// Fins que un desaprensiu... // Fins que un desaprensiu...
texts_.at(2)->setCaption(Lang::getText("[INTRO] 3")); texts_.at(2)->setCaption(Lang::getText("[INTRO] 3"));
texts_.at(2)->setSpeed(TEXT_SPEED_FAST); texts_.at(2)->setSpeed(12);
// HEY! ME ANE A FERME UN CORTAET... // HEY! ME ANE A FERME UN CORTAET...
texts_.at(3)->setCaption(Lang::getText("[INTRO] 4")); texts_.at(3)->setCaption(Lang::getText("[INTRO] 4"));
texts_.at(3)->setSpeed(TEXT_SPEED_NORMAL); texts_.at(3)->setSpeed(8);
// UAAAAAAAAAAAAA!!! // UAAAAAAAAAAAAA!!!
texts_.at(4)->setCaption(Lang::getText("[INTRO] 5")); texts_.at(4)->setCaption(Lang::getText("[INTRO] 5"));
texts_.at(4)->setSpeed(TEXT_SPEED_VERY_SLOW); texts_.at(4)->setSpeed(1);
// Espera un moment... // Espera un moment...
texts_.at(5)->setCaption(Lang::getText("[INTRO] 6")); texts_.at(5)->setCaption(Lang::getText("[INTRO] 6"));
texts_.at(5)->setSpeed(TEXT_SPEED_VERY_FAST); texts_.at(5)->setSpeed(16);
// Si resulta que no tinc solt! // Si resulta que no tinc solt!
texts_.at(6)->setCaption(Lang::getText("[INTRO] 7")); texts_.at(6)->setCaption(Lang::getText("[INTRO] 7"));
texts_.at(6)->setSpeed(TEXT_SPEED_SLOW); texts_.at(6)->setSpeed(2);
// MERDA DE MAQUINA! // MERDA DE MAQUINA!
texts_.at(7)->setCaption(Lang::getText("[INTRO] 8")); texts_.at(7)->setCaption(Lang::getText("[INTRO] 8"));
texts_.at(7)->setSpeed(TEXT_SPEED_MEDIUM_SLOW); texts_.at(7)->setSpeed(3);
// Blop... blop... blop... // Blop... blop... blop...
texts_.at(8)->setCaption(Lang::getText("[INTRO] 9")); texts_.at(8)->setCaption(Lang::getText("[INTRO] 9"));
texts_.at(8)->setSpeed(TEXT_SPEED_ULTRA_FAST); texts_.at(8)->setSpeed(20);
for (auto &text : texts_) { for (auto &text : texts_) {
text->center(param.game.game_area.center_x); text->center(param.game.game_area.center_x);
@@ -453,20 +444,20 @@ void Intro::initTexts() {
} }
// Actualiza los sprites // Actualiza los sprites
void Intro::updateSprites(float delta_time) { void Intro::updateSprites() {
for (auto &sprite : card_sprites_) { for (auto &sprite : card_sprites_) {
sprite->update(delta_time); sprite->update();
} }
for (auto &sprite : shadow_sprites_) { for (auto &sprite : shadow_sprites_) {
sprite->update(delta_time); sprite->update();
} }
} }
// Actualiza los textos // Actualiza los textos
void Intro::updateTexts(float delta_time) { void Intro::updateTexts() {
for (auto &text : texts_) { for (auto &text : texts_) {
text->update(delta_time); text->update();
} }
} }
@@ -489,8 +480,8 @@ void Intro::updatePostState() {
switch (post_state_) { switch (post_state_) {
case PostState::STOP_BG: case PostState::STOP_BG:
// EVENTO: Detiene el fondo después del tiempo especificado // EVENTO: Detiene el fondo después de 1 segundo
if (ELAPSED_TIME >= POST_BG_STOP_DELAY_MS) { if (ELAPSED_TIME >= 1000) {
tiled_bg_->stopGracefully(); tiled_bg_->stopGracefully();
if (!bg_color_.IS_EQUAL_TO(param.title.bg_color)) { if (!bg_color_.IS_EQUAL_TO(param.title.bg_color)) {
@@ -508,8 +499,8 @@ void Intro::updatePostState() {
break; break;
case PostState::END: case PostState::END:
// Finaliza la intro después del tiempo especificado // Finaliza la intro después de 1 segundo
if (ELAPSED_TIME >= POST_END_DELAY_MS) { if (ELAPSED_TIME >= 1000) {
Audio::get()->stopMusic(); Audio::get()->stopMusic();
Section::name = Section::Name::TITLE; Section::name = Section::Name::TITLE;
Section::options = Section::Options::TITLE_1; Section::options = Section::Options::TITLE_1;

View File

@@ -11,22 +11,9 @@
#include "tiled_bg.h" // Para TiledBG #include "tiled_bg.h" // Para TiledBG
#include "writer.h" // Para Writer #include "writer.h" // Para Writer
// --- Clase Intro: secuencia cinemática de introducción del juego --- // --- Clase Intro: muestra la secuencia de introducción ---
// // Esta clase gestiona un estado del programa. Se encarga de mostrar la secuencia
// Esta clase gestiona la secuencia de introducción narrativa del juego, mostrando // de introducción.
// una serie de escenas con imágenes, texto y efectos visuales sincronizados.
//
// Funcionalidades principales:
// • Sistema de escenas secuencial: 6 escenas con transiciones automáticas
// • Animaciones de tarjetas: efectos de entrada con diferentes tipos de easing
// • Texto narrativo: velocidades de escritura configurables por escena
// • Efectos visuales: sombras, bordes y transiciones de color
// • Audio sincronizado: música de fondo durante toda la secuencia
// • Estado POST: transición suave hacia el menú principal
//
// Todas las duraciones y velocidades están configuradas mediante constantes
// para facilitar el ajuste fino de la experiencia cinematográfica.
class Intro { class Intro {
public: public:
// --- Constructor y destructor --- // --- Constructor y destructor ---
@@ -37,35 +24,6 @@ class Intro {
void run(); void run();
private: private:
// --- Constantes de tiempo (en milisegundos) ---
static constexpr float TEXT_DISPLAY_DURATION_MS = 3000.0f; // Duración de visualización de texto (180 frames a 60fps)
static constexpr Uint32 POST_BG_STOP_DELAY_MS = 1000; // Retraso antes de detener el fondo
static constexpr Uint32 POST_END_DELAY_MS = 1000; // Retraso antes de finalizar intro
// --- Constantes de layout ---
static constexpr float CARD_BORDER_SIZE = 2.0f; // Tamaño del borde de tarjetas
static constexpr float SHADOW_OFFSET = 8.0f; // Desplazamiento de sombra
static constexpr int TEXT_KERNING = -2; // Espaciado entre caracteres
// --- Constantes de velocidades de texto ---
static constexpr int TEXT_SPEED_NORMAL = 8; // Velocidad normal de escritura
static constexpr int TEXT_SPEED_FAST = 12; // Velocidad rápida
static constexpr int TEXT_SPEED_VERY_SLOW = 1; // Velocidad muy lenta (grito)
static constexpr int TEXT_SPEED_VERY_FAST = 16; // Velocidad muy rápida
static constexpr int TEXT_SPEED_SLOW = 2; // Velocidad lenta
static constexpr int TEXT_SPEED_MEDIUM_SLOW = 3; // Velocidad medio-lenta
static constexpr int TEXT_SPEED_ULTRA_FAST = 20; // Velocidad ultra rápida
// --- Constantes de animaciones de tarjetas (duraciones en ms) ---
static constexpr int CARD_ANIM_DURATION_NORMAL = 100; // Duración estándar (100ms)
static constexpr int CARD_ANIM_DURATION_FAST = 40; // Duración rápida (40ms)
static constexpr int CARD_ANIM_DURATION_MEDIUM = 70; // Duración media (70ms)
static constexpr int CARD_ANIM_DURATION_SHORT = 80; // Duración corta (80ms)
static constexpr int CARD_ANIM_DURATION_SLOW = 250; // Duración lenta (250ms)
static constexpr int CARD_ANIM_DURATION_VERY_SLOW = 300; // Duración muy lenta (300ms)
static constexpr int CARD_ANIM_DELAY_LONG = 450; // Retraso largo antes de animación
static constexpr float CARD_OFFSET_MARGIN = 10.0f; // Margen fuera de pantalla
// --- Estados internos --- // --- Estados internos ---
enum class State { enum class State {
SCENES, SCENES,
@@ -84,7 +42,7 @@ class Intro {
std::unique_ptr<TiledBG> tiled_bg_; // Fondo en mosaico std::unique_ptr<TiledBG> tiled_bg_; // Fondo en mosaico
// --- Variables --- // --- Variables ---
Uint64 last_time_ = 0; // Último timestamp para calcular delta-time Uint64 ticks_ = 0; // Contador de ticks para ajustar la velocidad del programa
int scene_ = 0; // Indica qué escena está activa int scene_ = 0; // Indica qué escena está activa
State state_ = State::SCENES; // Estado principal de la intro State state_ = State::SCENES; // Estado principal de la intro
PostState post_state_ = PostState::STOP_BG; // Estado POST PostState post_state_ = PostState::STOP_BG; // Estado POST
@@ -92,20 +50,19 @@ class Intro {
Color bg_color_ = param.intro.bg_color; // Color de fondo Color bg_color_ = param.intro.bg_color; // Color de fondo
// --- Métodos internos --- // --- Métodos internos ---
void update(float delta_time); // Actualiza las variables del objeto void update(); // Actualiza las variables del objeto
void render(); // Dibuja el objeto en pantalla void render(); // Dibuja el objeto en pantalla
static void checkInput(); // Comprueba las entradas static void checkInput(); // Comprueba las entradas
static void checkEvents(); // Comprueba los eventos static void checkEvents(); // Comprueba los eventos
void updateScenes(); // Actualiza las escenas de la intro void updateScenes(); // Actualiza las escenas de la intro
void initSprites(); // Inicializa las imágenes void initSprites(); // Inicializa las imágenes
void initTexts(); // Inicializa los textos void initTexts(); // Inicializa los textos
void updateSprites(float delta_time); // Actualiza los sprites void updateSprites(); // Actualiza los sprites
void updateTexts(float delta_time); // Actualiza los textos void updateTexts(); // Actualiza los textos
void renderSprites(); // Dibuja los sprites void renderSprites(); // Dibuja los sprites
void renderTexts(); // Dibuja los textos void renderTexts(); // Dibuja los textos
static void renderTextRect(); // Dibuja el rectangulo de fondo del texto; static void renderTextRect(); // Dibuja el rectangulo de fondo del texto;
void updatePostState(); // Actualiza el estado POST void updatePostState(); // Actualiza el estado POST
float calculateDeltaTime(); // Calcula el tiempo transcurrido desde el último frame
// --- Métodos para manejar cada escena individualmente --- // --- Métodos para manejar cada escena individualmente ---
void updateScene0(); void updateScene0();

View File

@@ -28,38 +28,38 @@ Logo::Logo()
dest_.y = param.game.game_area.center_y - jail_texture_->getHeight() / 2; dest_.y = param.game.game_area.center_y - jail_texture_->getHeight() / 2;
since_sprite_->setPosition(SDL_FRect{ since_sprite_->setPosition(SDL_FRect{
static_cast<float>((param.game.width - since_texture_->getWidth()) / 2), static_cast<float>((param.game.width - since_texture_->getWidth()) / 2),
static_cast<float>(SINCE_SPRITE_Y_OFFSET + jail_texture_->getHeight() + LOGO_SPACING), static_cast<float>(83 + jail_texture_->getHeight() + 5),
static_cast<float>(since_texture_->getWidth()), static_cast<float>(since_texture_->getWidth()),
static_cast<float>(since_texture_->getHeight())}); static_cast<float>(since_texture_->getHeight())});
since_sprite_->setY(dest_.y + jail_texture_->getHeight() + LOGO_SPACING); since_sprite_->setY(dest_.y + jail_texture_->getHeight() + 5);
since_sprite_->setSpriteClip(0, 0, since_texture_->getWidth(), since_texture_->getHeight()); since_sprite_->setSpriteClip(0, 0, since_texture_->getWidth(), since_texture_->getHeight());
since_texture_->setColor(SPECTRUM_BLACK.r, SPECTRUM_BLACK.g, SPECTRUM_BLACK.b); since_texture_->setColor(0x00, 0x00, 0x00);
// Crea los sprites de cada linea // Crea los sprites de cada linea
for (int i = 0; i < jail_texture_->getHeight(); ++i) { for (int i = 0; i < jail_texture_->getHeight(); ++i) {
auto temp = std::make_unique<Sprite>(jail_texture_, 0, i, jail_texture_->getWidth(), SPRITE_LINE_HEIGHT); auto temp = std::make_unique<Sprite>(jail_texture_, 0, i, jail_texture_->getWidth(), 1);
temp->setSpriteClip(0, i, jail_texture_->getWidth(), SPRITE_LINE_HEIGHT); temp->setSpriteClip(0, i, jail_texture_->getWidth(), 1);
const int POS_X = (i % 2 == 0) ? param.game.width + (i * LINE_OFFSET_FACTOR) : -jail_texture_->getWidth() - (i * LINE_OFFSET_FACTOR); const int POS_X = (i % 2 == 0) ? param.game.width + (i * 3) : -jail_texture_->getWidth() - (i * 3);
temp->setX(POS_X); temp->setX(POS_X);
temp->setY(dest_.y + i); temp->setY(dest_.y + i);
jail_sprite_.push_back(std::move(temp)); jail_sprite_.push_back(std::move(temp));
} }
// Inicializa el vector de colores con la paleta ZX Spectrum // Inicializa el vector de colores
color_.emplace_back(SPECTRUM_BLACK); color_.emplace_back(0x00, 0x00, 0x00); // Black
color_.emplace_back(SPECTRUM_BLUE); color_.emplace_back(0x00, 0x00, 0xd8); // Blue
color_.emplace_back(SPECTRUM_RED); color_.emplace_back(0xd8, 0x00, 0x00); // Red
color_.emplace_back(SPECTRUM_MAGENTA); color_.emplace_back(0xd8, 0x00, 0xd8); // Magenta
color_.emplace_back(SPECTRUM_GREEN); color_.emplace_back(0x00, 0xd8, 0x00); // Green
color_.emplace_back(SPECTRUM_CYAN); color_.emplace_back(0x00, 0xd8, 0xd8); // Cyan
color_.emplace_back(SPECTRUM_YELLOW); color_.emplace_back(0xd8, 0xd8, 0x00); // Yellow
color_.emplace_back(SPECTRUM_WHITE); color_.emplace_back(0xFF, 0xFF, 0xFF); // Bright white
} }
// Destructor // Destructor
Logo::~Logo() { Logo::~Logo() {
jail_texture_->setColor(RESET_COLOR.r, RESET_COLOR.g, RESET_COLOR.b); jail_texture_->setColor(255, 255, 255);
since_texture_->setColor(RESET_COLOR.r, RESET_COLOR.g, RESET_COLOR.b); since_texture_->setColor(255, 255, 255);
Audio::get()->stopAllSounds(); Audio::get()->stopAllSounds();
Audio::get()->stopMusic(); Audio::get()->stopMusic();
} }
@@ -78,30 +78,22 @@ void Logo::checkInput() {
GlobalInputs::check(); GlobalInputs::check();
} }
// Maneja la reproducción del sonido del logo
void Logo::handleSound() {
static bool sound_triggered = false;
if (!sound_triggered && elapsed_time_ms_ >= SOUND_TRIGGER_TIME_MS) {
Audio::get()->playSound("logo.wav");
sound_triggered = true;
}
}
// Gestiona el logo de JAILGAMES // Gestiona el logo de JAILGAMES
void Logo::updateJAILGAMES(float delta_time) { void Logo::updateJAILGAMES() {
if (elapsed_time_ms_ > SOUND_TRIGGER_TIME_MS) { if (counter_ == 30) {
const float PIXELS_TO_MOVE = LOGO_SPEED_PX_PER_MS * delta_time; Audio::get()->playSound("logo.wav");
}
for (size_t i = 0; i < jail_sprite_.size(); ++i) { if (counter_ > 30) {
for (int i = 0; i < (int)jail_sprite_.size(); ++i) {
if (jail_sprite_[i]->getX() != dest_.x) { if (jail_sprite_[i]->getX() != dest_.x) {
if (i % 2 == 0) { if (i % 2 == 0) {
jail_sprite_[i]->incX(-PIXELS_TO_MOVE); jail_sprite_[i]->incX(-SPEED);
if (jail_sprite_[i]->getX() < dest_.x) { if (jail_sprite_[i]->getX() < dest_.x) {
jail_sprite_[i]->setX(dest_.x); jail_sprite_[i]->setX(dest_.x);
} }
} else { } else {
jail_sprite_[i]->incX(PIXELS_TO_MOVE); jail_sprite_[i]->incX(SPEED);
if (jail_sprite_[i]->getX() > dest_.x) { if (jail_sprite_[i]->getX() > dest_.x) {
jail_sprite_[i]->setX(dest_.x); jail_sprite_[i]->setX(dest_.x);
} }
@@ -111,41 +103,43 @@ void Logo::updateJAILGAMES(float delta_time) {
} }
// Comprueba si ha terminado el logo // Comprueba si ha terminado el logo
if (elapsed_time_ms_ >= END_LOGO_TIME_MS + POST_LOGO_DURATION_MS) { if (counter_ == END_LOGO_COUNTER_MARK + POST_LOGO_DURATION) {
Section::name = Section::Name::INTRO; Section::name = Section::Name::INTRO;
} }
} }
// Gestiona el color de las texturas // Gestiona el color de las texturas
void Logo::updateTextureColors(float delta_time) { void Logo::updateTextureColors() {
constexpr int INC = 4;
// Manejo de 'sinceTexture' // Manejo de 'sinceTexture'
for (int i = 0; i <= MAX_SINCE_COLOR_INDEX; ++i) { for (int i = 0; i <= 7; ++i) {
const float target_time = SHOW_SINCE_SPRITE_TIME_MS + COLOR_CHANGE_INTERVAL_MS * i; if (counter_ == SHOW_SINCE_SPRITE_COUNTER_MARK + INC * i) {
if (elapsed_time_ms_ >= target_time && elapsed_time_ms_ - delta_time < target_time) {
since_texture_->setColor(color_[i].r, color_[i].g, color_[i].b); since_texture_->setColor(color_[i].r, color_[i].g, color_[i].b);
} }
} }
// Manejo de 'jailTexture' y 'sinceTexture' en el fade // Manejo de 'jailTexture' y 'sinceTexture' en el fade
for (int i = 0; i <= MAX_FADE_COLOR_INDEX; ++i) { for (int i = 0; i <= 6; ++i) {
const float target_time = INIT_FADE_TIME_MS + COLOR_CHANGE_INTERVAL_MS * i; if (counter_ == INIT_FADE_COUNTER_MARK + INC * i) {
if (elapsed_time_ms_ >= target_time && elapsed_time_ms_ - delta_time < target_time) { jail_texture_->setColor(color_[6 - i].r, color_[6 - i].g, color_[6 - i].b);
jail_texture_->setColor(color_[MAX_FADE_COLOR_INDEX - i].r, color_[MAX_FADE_COLOR_INDEX - i].g, color_[MAX_FADE_COLOR_INDEX - i].b); since_texture_->setColor(color_[6 - i].r, color_[6 - i].g, color_[6 - i].b);
since_texture_->setColor(color_[MAX_FADE_COLOR_INDEX - i].r, color_[MAX_FADE_COLOR_INDEX - i].g, color_[MAX_FADE_COLOR_INDEX - i].b);
} }
} }
} }
// Actualiza las variables // Actualiza las variables
void Logo::update(float delta_time) { void Logo::update() {
elapsed_time_ms_ += delta_time; // Acumula el tiempo transcurrido if (SDL_GetTicks() - ticks_ > param.game.speed) {
ticks_ = SDL_GetTicks(); // Actualiza el contador de ticks
Screen::get()->update(); // Actualiza el objeto screen
Screen::get()->update(); // Actualiza el objeto screen updateJAILGAMES(); // Actualiza el logo de JAILGAMES
Audio::update(); // Actualiza el objeto audio updateTextureColors(); // Actualiza los colores de las texturas
++counter_; // Gestiona el contador
}
handleSound(); // Maneja la reproducción del sonido Audio::update();
updateTextureColors(delta_time); // Actualiza los colores de las texturas
updateJAILGAMES(delta_time); // Actualiza el logo de JAILGAMES
} }
// Dibuja en pantalla // Dibuja en pantalla
@@ -160,23 +154,11 @@ void Logo::render() {
SCREEN->render(); SCREEN->render();
} }
// Calcula el tiempo transcurrido desde el último frame
float Logo::calculateDeltaTime() {
const Uint64 current_time = SDL_GetTicks();
const float delta_time = static_cast<float>(current_time - last_time_);
last_time_ = current_time;
return delta_time;
}
// Bucle para el logo del juego // Bucle para el logo del juego
void Logo::run() { void Logo::run() {
last_time_ = SDL_GetTicks();
while (Section::name == Section::Name::LOGO) { while (Section::name == Section::Name::LOGO) {
const float delta_time = calculateDeltaTime();
checkInput(); checkInput();
update(delta_time); update();
checkEvents(); // Tiene que ir antes del render checkEvents(); // Tiene que ir antes del render
render(); render();
} }
@@ -189,7 +171,7 @@ void Logo::renderJAILGAMES() {
sprite->render(); sprite->render();
} }
if (elapsed_time_ms_ >= SHOW_SINCE_SPRITE_TIME_MS) { if (counter_ >= SHOW_SINCE_SPRITE_COUNTER_MARK) {
since_sprite_->render(); since_sprite_->render();
} }
} }

View File

@@ -10,21 +10,12 @@
class Texture; class Texture;
// --- Clase Logo: pantalla de presentación de JAILGAMES con efectos retro --- // --- Clase Logo: dibuja el logo de JAILGAMES con efectos visuales ---
// // Esta clase gestiona un estado del programa. Se encarga de dibujar por pantalla el
// Esta clase gestiona el estado inicial del programa, mostrando el logo corporativo // logo de "JAILGAMES" utilizando un sencillo efecto consistente en generar un sprite por
// de JAILGAMES con efectos visuales inspirados en el ZX Spectrum. // cada línea del bitmap que forma la palabra "JAILGAMES". Posteriormente realiza una
// // modulación de color sobre la textura para simular un fade to black al estilo
// Funcionalidades principales: // ZX Spectrum.
// • Animación de convergencia: cada línea del logo entra desde los laterales
// • Efectos de color: transiciones automáticas usando la paleta ZX Spectrum
// • Audio sincronizado: reproduce sonido del logo en momento específico
// • Transición temporal: duración controlada con paso automático al siguiente estado
// • Sistema delta-time: animaciones suaves independientes del framerate
//
// La clase utiliza un sistema de tiempo basado en milisegundos para garantizar
// consistencia visual en diferentes velocidades de procesamiento.
class Logo { class Logo {
public: public:
// --- Constructor y destructor --- // --- Constructor y destructor ---
@@ -35,35 +26,12 @@ class Logo {
void run(); void run();
private: private:
// --- Constantes de tiempo (en milisegundos) --- // --- Constantes ---
static constexpr float SOUND_TRIGGER_TIME_MS = 500.0f; // Tiempo para activar el sonido del logo static constexpr int SHOW_SINCE_SPRITE_COUNTER_MARK = 70; // Tiempo del contador en el que empieza a verse el sprite de "SINCE 1998"
static constexpr float SHOW_SINCE_SPRITE_TIME_MS = 1167.0f; // Tiempo para mostrar el sprite "SINCE 1998" static constexpr int INIT_FADE_COUNTER_MARK = 300; // Tiempo del contador cuando inicia el fade a negro
static constexpr float INIT_FADE_TIME_MS = 5000.0f; // Tiempo de inicio del fade a negro static constexpr int END_LOGO_COUNTER_MARK = 400; // Tiempo del contador para terminar el logo
static constexpr float END_LOGO_TIME_MS = 6668.0f; // Tiempo de finalización del logo static constexpr int POST_LOGO_DURATION = 20; // Tiempo que dura el logo con el fade al máximo
static constexpr float POST_LOGO_DURATION_MS = 333.0f; // Duración adicional después del fade static constexpr int SPEED = 8; // Velocidad de desplazamiento de cada línea
static constexpr float LOGO_SPEED_PX_PER_MS = 8.0f / 16.67f; // Velocidad de desplazamiento (píxeles por ms)
static constexpr float COLOR_CHANGE_INTERVAL_MS = 66.7f; // Intervalo entre cambios de color (~4 frames a 60fps)
// --- Constantes de layout ---
static constexpr int SINCE_SPRITE_Y_OFFSET = 83; // Posición Y base del sprite "Since 1998"
static constexpr int LOGO_SPACING = 5; // Espaciado entre elementos del logo
static constexpr int LINE_OFFSET_FACTOR = 3; // Factor de desplazamiento inicial por línea
static constexpr int SPRITE_LINE_HEIGHT = 1; // Altura de cada línea sprite
// --- Constantes de colores ---
static constexpr int MAX_SINCE_COLOR_INDEX = 7; // Índice máximo para colores del sprite "Since"
static constexpr int MAX_FADE_COLOR_INDEX = 6; // Índice máximo para colores del fade
// --- Paleta ZX Spectrum para efectos de logo ---
static constexpr Color SPECTRUM_BLACK = Color(0x00, 0x00, 0x00); // Negro
static constexpr Color SPECTRUM_BLUE = Color(0x00, 0x00, 0xd8); // Azul
static constexpr Color SPECTRUM_RED = Color(0xd8, 0x00, 0x00); // Rojo
static constexpr Color SPECTRUM_MAGENTA = Color(0xd8, 0x00, 0xd8); // Magenta
static constexpr Color SPECTRUM_GREEN = Color(0x00, 0xd8, 0x00); // Verde
static constexpr Color SPECTRUM_CYAN = Color(0x00, 0xd8, 0xd8); // Cian
static constexpr Color SPECTRUM_YELLOW = Color(0xd8, 0xd8, 0x00); // Amarillo
static constexpr Color SPECTRUM_WHITE = Color(0xFF, 0xFF, 0xFF); // Blanco brillante
static constexpr Color RESET_COLOR = Color(255, 255, 255); // Color de reset
// --- Objetos y punteros --- // --- Objetos y punteros ---
std::shared_ptr<Texture> since_texture_; // Textura con los gráficos "Since 1998" std::shared_ptr<Texture> since_texture_; // Textura con los gráficos "Since 1998"
@@ -72,19 +40,17 @@ class Logo {
std::vector<std::unique_ptr<Sprite>> jail_sprite_; // Vector con los sprites de cada línea que forman el bitmap JAILGAMES std::vector<std::unique_ptr<Sprite>> jail_sprite_; // Vector con los sprites de cada línea que forman el bitmap JAILGAMES
// --- Variables --- // --- Variables ---
std::vector<Color> color_; // Vector con los colores para el fade std::vector<Color> color_; // Vector con los colores para el fade
float elapsed_time_ms_ = 0.0f; // Tiempo transcurrido en milisegundos int counter_ = 0; // Contador
Uint64 last_time_ = 0; // Último timestamp para calcular delta-time Uint64 ticks_ = 0; // Contador de ticks para ajustar la velocidad del programa
SDL_FPoint dest_; // Posición donde dibujar el logo SDL_FPoint dest_; // Posición donde dibujar el logo
// --- Métodos internos --- // --- Métodos internos ---
void update(float delta_time); // Actualiza las variables void update(); // Actualiza las variables
void render(); // Dibuja en pantalla void render(); // Dibuja en pantalla
static void checkEvents(); // Comprueba el manejador de eventos static void checkEvents(); // Comprueba el manejador de eventos
static void checkInput(); // Comprueba las entradas static void checkInput(); // Comprueba las entradas
void updateJAILGAMES(float delta_time); // Gestiona el logo de JAILGAMES void updateJAILGAMES(); // Gestiona el logo de JAILGAMES
void renderJAILGAMES(); // Renderiza el logo de JAILGAMES void renderJAILGAMES(); // Renderiza el logo de JAILGAMES
void updateTextureColors(float delta_time); // Gestiona el color de las texturas void updateTextureColors(); // Gestiona el color de las texturas
void handleSound(); // Maneja la reproducción del sonido del logo
float calculateDeltaTime(); // Calcula el tiempo transcurrido desde el último frame
}; };

View File

@@ -42,7 +42,7 @@ Title::Title()
tiled_bg_(std::make_unique<TiledBG>(param.game.game_area.rect, TiledBGMode::RANDOM)), tiled_bg_(std::make_unique<TiledBG>(param.game.game_area.rect, TiledBGMode::RANDOM)),
game_logo_(std::make_unique<GameLogo>(param.game.game_area.center_x, param.title.title_c_c_position)), game_logo_(std::make_unique<GameLogo>(param.game.game_area.center_x, param.title.title_c_c_position)),
mini_logo_sprite_(std::make_unique<Sprite>(Resource::get()->getTexture("logo_jailgames_mini.png"))), mini_logo_sprite_(std::make_unique<Sprite>(Resource::get()->getTexture("logo_jailgames_mini.png"))),
state_(State::LOGO_ANIMATING), state_(TitleState::LOGO_ANIMATING),
num_controllers_(Input::get()->getNumGamepads()) { num_controllers_(Input::get()->getNumGamepads()) {
// Configura objetos // Configura objetos
tiled_bg_->setColor(param.title.bg_color); tiled_bg_->setColor(param.title.bg_color);
@@ -60,16 +60,16 @@ Title::Title()
Section::attract_mode = IS_TITLE_TO_DEMO ? Section::AttractMode::TITLE_TO_LOGO : Section::AttractMode::TITLE_TO_DEMO; Section::attract_mode = IS_TITLE_TO_DEMO ? Section::AttractMode::TITLE_TO_LOGO : Section::AttractMode::TITLE_TO_DEMO;
// Define los anclajes de los elementos // Define los anclajes de los elementos
anchor_.mini_logo = (param.game.height / MINI_LOGO_Y_DIVISOR * MINI_LOGO_Y_FACTOR) + BLOCK; anchor_.mini_logo = (param.game.height / 5 * 4) + BLOCK;
mini_logo_sprite_->setY(anchor_.mini_logo); mini_logo_sprite_->setY(anchor_.mini_logo);
anchor_.copyright_text = anchor_.mini_logo + mini_logo_sprite_->getHeight() + COPYRIGHT_TEXT_SPACING; anchor_.copyright_text = anchor_.mini_logo + mini_logo_sprite_->getHeight() + 3;
} }
// Destructor // Destructor
Title::~Title() { Title::~Title() {
Audio::get()->stopAllSounds(); Audio::get()->stopAllSounds();
if (Section::name == Section::Name::LOGO) { if (Section::name == Section::Name::LOGO) {
Audio::get()->fadeOutMusic(MUSIC_FADE_OUT_SHORT_MS); Audio::get()->fadeOutMusic(300);
} }
// Desregistra los jugadores de Options // Desregistra los jugadores de Options
@@ -78,27 +78,20 @@ Title::~Title() {
} }
// Actualiza las variables del objeto // Actualiza las variables del objeto
void Title::update(float deltaTime) { void Title::update() {
Screen::get()->update(); if (SDL_GetTicks() - ticks_ > param.game.speed) {
updateFade(); ticks_ = SDL_GetTicks();
updateState(deltaTime); Screen::get()->update();
updateStartPrompt();
for (auto& player : players_) { updateFade();
player->update(deltaTime); updateState();
updateStartPrompt();
updatePlayers();
} }
Audio::update(); Audio::update();
} }
// Calcula el tiempo transcurrido desde el último frame
float Title::calculateDeltaTime() {
const Uint64 current_time = SDL_GetTicks();
const float delta_time = static_cast<float>(current_time - last_time_);
last_time_ = current_time;
return delta_time;
}
// Dibuja el objeto en pantalla // Dibuja el objeto en pantalla
void Title::render() { void Title::render() {
static auto* const SCREEN = Screen::get(); static auto* const SCREEN = Screen::get();
@@ -143,7 +136,7 @@ void Title::handleDebugColorKeys(SDL_Keycode key) {
adjustColorComponent(key, color_); adjustColorComponent(key, color_);
counter_time_ = 0.0f; counter_ = 0;
tiled_bg_->setColor(color_); tiled_bg_->setColor(color_);
printColorValue(color_); printColorValue(color_);
} }
@@ -277,7 +270,7 @@ void Title::handleStartButtonPress(const Options::Gamepad* controller) {
} }
auto Title::canProcessStartButton() const -> bool { auto Title::canProcessStartButton() const -> bool {
return (state_ != State::LOGO_ANIMATING || ALLOW_TITLE_ANIMATION_SKIP); return (state_ != TitleState::LOGO_ANIMATING || ALLOW_TITLE_ANIMATION_SKIP);
} }
void Title::processPlayer1Start() { void Title::processPlayer1Start() {
@@ -296,26 +289,22 @@ void Title::processPlayer2Start() {
void Title::activatePlayerAndSetState(Player::Id player_id) { void Title::activatePlayerAndSetState(Player::Id player_id) {
getPlayer(player_id)->setPlayingState(Player::State::TITLE_ANIMATION); getPlayer(player_id)->setPlayingState(Player::State::TITLE_ANIMATION);
setState(State::START_HAS_BEEN_PRESSED); setState(TitleState::START_HAS_BEEN_PRESSED);
counter_time_ = 0.0f; counter_ = 0;
} }
// Bucle para el titulo del juego // Bucle para el titulo del juego
void Title::run() { void Title::run() {
last_time_ = SDL_GetTicks();
while (Section::name == Section::Name::TITLE) { while (Section::name == Section::Name::TITLE) {
const float delta_time = calculateDeltaTime();
checkInput(); checkInput();
update(delta_time); update();
checkEvents(); // Tiene que ir antes del render checkEvents(); // Tiene que ir antes del render
render(); render();
} }
} }
// Reinicia el contador interno // Reinicia el contador interno
void Title::resetCounter() { counter_time_ = 0.0f; } void Title::resetCounter() { counter_ = 0; }
// Intercambia la asignación de mandos a los jugadores // Intercambia la asignación de mandos a los jugadores
void Title::swapControllers() { void Title::swapControllers() {
@@ -377,22 +366,22 @@ void Title::updateFade() {
} }
// Actualiza el estado // Actualiza el estado
void Title::updateState(float deltaTime) { void Title::updateState() {
game_logo_->update(deltaTime);
tiled_bg_->update(deltaTime);
// Establece la lógica según el estado // Establece la lógica según el estado
switch (state_) { switch (state_) {
case State::LOGO_ANIMATING: { case TitleState::LOGO_ANIMATING: {
game_logo_->update();
if (game_logo_->hasFinished()) { if (game_logo_->hasFinished()) {
setState(State::LOGO_FINISHED); setState(TitleState::LOGO_FINISHED);
} }
break; break;
} }
case State::LOGO_FINISHED: { case TitleState::LOGO_FINISHED: {
counter_time_ += deltaTime; ++counter_; // Incrementa el contador
game_logo_->update(); // Actualiza el logo con el título del juego
tiled_bg_->update(); // Actualiza el mosaico de fondo
if (counter_time_ >= param.title.title_duration) { if (counter_ == param.title.title_duration) {
// El menu ha hecho time out // El menu ha hecho time out
fade_->setPostDuration(0); fade_->setPostDuration(0);
fade_->activate(); fade_->activate();
@@ -400,10 +389,12 @@ void Title::updateState(float deltaTime) {
} }
break; break;
} }
case State::START_HAS_BEEN_PRESSED: { case TitleState::START_HAS_BEEN_PRESSED: {
counter_time_ += deltaTime; ++counter_; // Incrementa el contador
game_logo_->update(); // Actualiza el logo con el título del juego
tiled_bg_->update(); // Actualiza el mosaico de fondo
if (counter_time_ >= START_PRESSED_DELAY_MS) { if (counter_ == 100) {
fade_->activate(); fade_->activate();
} }
break; break;
@@ -415,16 +406,22 @@ void Title::updateState(float deltaTime) {
} }
void Title::updateStartPrompt() { void Title::updateStartPrompt() {
constexpr Uint32 LOGO_BLINK_PERIOD = 833; // milisegundos
constexpr Uint32 LOGO_BLINK_ON_TIME = 583; // 833 - 250
constexpr Uint32 START_BLINK_PERIOD = 167;
constexpr Uint32 START_BLINK_ON_TIME = 83; // 167 - 83
Uint32 time_ms = SDL_GetTicks(); Uint32 time_ms = SDL_GetTicks();
bool condition_met = false; bool condition_met = false;
switch (state_) { switch (state_) {
case State::LOGO_FINISHED: case TitleState::LOGO_FINISHED:
condition_met = (time_ms % LOGO_BLINK_PERIOD_MS) >= (LOGO_BLINK_PERIOD_MS - LOGO_BLINK_ON_TIME_MS); condition_met = (time_ms % LOGO_BLINK_PERIOD) >= (LOGO_BLINK_PERIOD - LOGO_BLINK_ON_TIME);
break; break;
case State::START_HAS_BEEN_PRESSED: case TitleState::START_HAS_BEEN_PRESSED:
condition_met = (time_ms % START_BLINK_PERIOD_MS) >= (START_BLINK_PERIOD_MS - START_BLINK_ON_TIME_MS); condition_met = (time_ms % START_BLINK_PERIOD) >= (START_BLINK_PERIOD - START_BLINK_ON_TIME);
break; break;
default: default:
@@ -448,7 +445,7 @@ void Title::renderStartPrompt() {
} }
void Title::renderCopyright() { void Title::renderCopyright() {
if (state_ != State::LOGO_ANIMATING) { if (state_ != TitleState::LOGO_ANIMATING) {
// Mini logo // Mini logo
mini_logo_sprite_->render(); mini_logo_sprite_->render();
@@ -465,20 +462,20 @@ void Title::renderCopyright() {
} }
// Cambia el estado // Cambia el estado
void Title::setState(State state) { void Title::setState(TitleState state) {
if (state_ == state) { if (state_ == state) {
return; return;
} }
state_ = state; state_ = state;
switch (state_) { switch (state_) {
case State::LOGO_ANIMATING: case TitleState::LOGO_ANIMATING:
break; break;
case State::LOGO_FINISHED: case TitleState::LOGO_FINISHED:
Audio::get()->playMusic("title.ogg"); Audio::get()->playMusic("title.ogg");
break; break;
case State::START_HAS_BEEN_PRESSED: case TitleState::START_HAS_BEEN_PRESSED:
Audio::get()->fadeOutMusic(MUSIC_FADE_OUT_LONG_MS); Audio::get()->fadeOutMusic(1500);
break; break;
} }
} }
@@ -547,6 +544,13 @@ void Title::initPlayers() {
} }
} }
// Actualza los jugadores
void Title::updatePlayers() {
for (auto& player : players_) {
player->update();
}
}
// Renderiza los jugadores // Renderiza los jugadores
void Title::renderPlayers() { void Title::renderPlayers() {
for (auto const& player : players_) { for (auto const& player : players_) {

View File

@@ -19,22 +19,11 @@ namespace Options {
struct Gamepad; struct Gamepad;
} // namespace Options } // namespace Options
// --- Clase Title: pantalla de título y menú principal del juego --- // --- Constantes ---
// constexpr std::string_view TEXT_COPYRIGHT = "@2020,2025 JailDesigner"; // Texto de copyright
// Esta clase gestiona la pantalla de título del juego, incluyendo el menú principal constexpr bool ALLOW_TITLE_ANIMATION_SKIP = false; // Permite saltar la animación del título
// y la transición entre diferentes modos de juego.
// // --- Clase Title: gestiona el estado de título/menú principal del juego ---
// Funcionalidades principales:
// • Logo animado: muestra y anima el logotipo principal del juego
// • Selección de jugadores: permite iniciar partidas de 1 o 2 jugadores
// • Modo attract: cicla automáticamente entre título y demo
// • Efectos visuales: parpadeos, transiciones y efectos de fondo
// • Gestión de controles: soporte para teclado y múltiples gamepads
// • Timeouts automáticos: transición automática si no hay interacción
// • Debug de colores: herramientas de depuración para ajustes visuales
//
// La clase utiliza un sistema de tiempo basado en milisegundos para garantizar
// comportamiento consistente independientemente del framerate.
class Title { class Title {
public: public:
// --- Constructor y destructor --- // --- Constructor y destructor ---
@@ -45,28 +34,8 @@ class Title {
void run(); void run();
private: private:
// --- Constantes de tiempo (en milisegundos) ---
static constexpr float START_PRESSED_DELAY_MS = 1666.67f; // Tiempo antes de fade tras pulsar start (100 frames a 60fps)
static constexpr int MUSIC_FADE_OUT_LONG_MS = 1500; // Fade out largo de música
static constexpr int MUSIC_FADE_OUT_SHORT_MS = 300; // Fade out corto de música
// --- Constantes de parpadeo ---
static constexpr Uint32 LOGO_BLINK_PERIOD_MS = 833; // Período de parpadeo del logo
static constexpr Uint32 LOGO_BLINK_ON_TIME_MS = 583; // Tiempo encendido del logo (833-250)
static constexpr Uint32 START_BLINK_PERIOD_MS = 167; // Período de parpadeo del start
static constexpr Uint32 START_BLINK_ON_TIME_MS = 83; // Tiempo encendido del start (167-83)
// --- Constantes de layout ---
static constexpr int MINI_LOGO_Y_DIVISOR = 5; // Divisor para posición Y del mini logo
static constexpr int MINI_LOGO_Y_FACTOR = 4; // Factor para posición Y del mini logo
static constexpr int COPYRIGHT_TEXT_SPACING = 3; // Espaciado del texto de copyright
// --- Constantes de texto y configuración ---
static constexpr std::string_view TEXT_COPYRIGHT = "@2020,2025 JailDesigner"; // Texto de copyright
static constexpr bool ALLOW_TITLE_ANIMATION_SKIP = false; // Permite saltar la animación del título
// --- Enums --- // --- Enums ---
enum class State { enum class TitleState {
LOGO_ANIMATING, // El logo está animándose LOGO_ANIMATING, // El logo está animándose
LOGO_FINISHED, // El logo ha terminado de animarse LOGO_FINISHED, // El logo ha terminado de animarse
START_HAS_BEEN_PRESSED, // Se ha pulsado el botón de start START_HAS_BEEN_PRESSED, // Se ha pulsado el botón de start
@@ -90,20 +59,19 @@ class Title {
Anchor anchor_; // Anclas para definir la posición de los elementos del título Anchor anchor_; // Anclas para definir la posición de los elementos del título
Section::Name next_section_; // Siguiente sección a cargar Section::Name next_section_; // Siguiente sección a cargar
Section::Options selection_ = Section::Options::TITLE_TIME_OUT; // Opción elegida en el título Section::Options selection_ = Section::Options::TITLE_TIME_OUT; // Opción elegida en el título
State state_; // Estado actual de la sección TitleState state_; // Estado actual de la sección
Uint64 last_time_ = 0; // Último timestamp para calcular delta-time Uint64 ticks_ = 0; // Contador de ticks para ajustar la velocidad
float counter_time_ = 0.0f; // Temporizador para la pantalla de título (en milisegundos) int counter_ = 0; // Temporizador para la pantalla de título
int num_controllers_; // Número de mandos conectados int num_controllers_; // Número de mandos conectados
bool should_render_start_prompt_ = false; // Indica si se muestra el texto de PRESS START BUTTON TO PLAY bool should_render_start_prompt_ = false; // Indica si se muestra el texto de PRESS START BUTTON TO PLAY
bool player1_start_pressed_ = false; // Indica si se ha pulsado el botón de empezar para el jugador 1 bool player1_start_pressed_ = false; // Indica si se ha pulsado el botón de empezar para el jugador 1
bool player2_start_pressed_ = false; // Indica si se ha pulsado el botón de empezar para el jugador 2 bool player2_start_pressed_ = false; // Indica si se ha pulsado el botón de empezar para el jugador 2
// --- Ciclo de vida del título --- // --- Ciclo de vida del título ---
void update(float deltaTime); // Actualiza las variables del objeto void update(); // Actualiza las variables del objeto
float calculateDeltaTime(); // Calcula el tiempo transcurrido desde el último frame void updateState(); // Actualiza el estado actual del título
void updateState(float deltaTime); // Actualiza el estado actual del título void setState(TitleState state); // Cambia el estado del título
void setState(State state); // Cambia el estado del título void resetCounter(); // Reinicia el contador interno
void resetCounter(); // Reinicia el contador interno
// --- Entrada de usuario --- // --- Entrada de usuario ---
void checkEvents(); // Comprueba los eventos void checkEvents(); // Comprueba los eventos

View File

@@ -2,7 +2,7 @@
#include "moving_sprite.h" // Para MovingSprite #include "moving_sprite.h" // Para MovingSprite
// Actualiza la posición y comprueba si ha llegado a su destino (frame-based) // Actualiza la posición y comprueba si ha llegado a su destino
void SmartSprite::update() { void SmartSprite::update() {
if (enabled_) { if (enabled_) {
MovingSprite::update(); MovingSprite::update();
@@ -11,15 +11,6 @@ void SmartSprite::update() {
} }
} }
// Actualiza la posición y comprueba si ha llegado a su destino (time-based)
void SmartSprite::update(float deltaTime) {
if (enabled_) {
MovingSprite::update(deltaTime);
checkMove();
checkFinished();
}
}
// Dibuja el sprite // Dibuja el sprite
void SmartSprite::render() { void SmartSprite::render() {
if (enabled_) { if (enabled_) {

View File

@@ -16,9 +16,8 @@ class SmartSprite : public AnimatedSprite {
~SmartSprite() override = default; ~SmartSprite() override = default;
// --- Métodos principales --- // --- Métodos principales ---
void update() override; // Actualiza la posición y comprueba si ha llegado a su destino (frame-based) void update() override; // Actualiza la posición y comprueba si ha llegado a su destino
void update(float deltaTime) override; // Actualiza la posición y comprueba si ha llegado a su destino (time-based) void render() override; // Dibuja el sprite
void render() override; // Dibuja el sprite
// --- Getters --- // --- Getters ---
auto getDestX() const -> int { return dest_x_; } // Obtiene la posición de destino en X auto getDestX() const -> int { return dest_x_; } // Obtiene la posición de destino en X

View File

@@ -17,7 +17,7 @@ Tabe::Tabe()
: sprite_(std::make_unique<AnimatedSprite>(Resource::get()->getTexture("tabe.png"), Resource::get()->getAnimation("tabe.ani"))), : 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)) {} timer_(Timer(param.tabe.min_spawn_time, param.tabe.max_spawn_time)) {}
// Actualiza la lógica (frame-based) // Actualiza la lógica
void Tabe::update() { void Tabe::update() {
if (enabled_ && !timer_.is_paused) { if (enabled_ && !timer_.is_paused) {
sprite_->update(); sprite_->update();
@@ -31,20 +31,6 @@ void Tabe::update() {
} }
} }
// Actualiza la lógica (time-based)
void Tabe::update(float deltaTime) {
if (enabled_ && !timer_.is_paused) {
sprite_->update(deltaTime);
move(deltaTime);
updateState(deltaTime);
}
timer_.update();
if (timer_.shouldSpawn()) {
enable();
}
}
// Dibuja el objeto // Dibuja el objeto
void Tabe::render() { void Tabe::render() {
if (enabled_) { if (enabled_) {
@@ -52,7 +38,7 @@ void Tabe::render() {
} }
} }
// Mueve el objeto (frame-based) // Mueve el objeto
void Tabe::move() { void Tabe::move() {
const int X = static_cast<int>(x_); const int X = static_cast<int>(x_);
speed_ += accel_; speed_ += accel_;
@@ -117,75 +103,6 @@ void Tabe::move() {
shiftSprite(); shiftSprite();
} }
// Mueve el objeto (time-based)
void Tabe::move(float deltaTime) {
// Convertir deltaTime (milisegundos) a factor de frame (asumiendo 60fps)
float frameFactor = deltaTime / (1000.0f / 60.0f);
const int X = static_cast<int>(x_);
speed_ += accel_ * frameFactor;
x_ += speed_ * frameFactor;
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_ -= frameFactor;
if (waiting_counter_ < 0) 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 // Habilita el objeto
void Tabe::enable() { void Tabe::enable() {
if (!enabled_) { if (!enabled_) {
@@ -258,23 +175,11 @@ void Tabe::setState(State state) {
} }
} }
// Actualiza el estado (frame-based) // Actualiza el estado
void Tabe::updateState() { void Tabe::updateState() {
if (state_ == State::HIT) { if (state_ == State::HIT) {
--hit_counter_; --hit_counter_;
if (hit_counter_ <= 0) { if (hit_counter_ == 0) {
setState(State::FLY);
}
}
}
// Actualiza el estado (time-based)
void Tabe::updateState(float deltaTime) {
if (state_ == State::HIT) {
// Convertir deltaTime (milisegundos) a factor de frame (asumiendo 60fps)
float frameFactor = deltaTime / (1000.0f / 60.0f);
hit_counter_ -= frameFactor;
if (hit_counter_ <= 0) {
setState(State::FLY); setState(State::FLY);
} }
} }

View File

@@ -26,9 +26,8 @@ class Tabe {
~Tabe() = default; ~Tabe() = default;
// --- Métodos principales --- // --- Métodos principales ---
void update(); // Actualiza la lógica (frame-based) void update(); // Actualiza la lógica
void update(float deltaTime); // Actualiza la lógica (time-based) void render(); // Dibuja el objeto
void render(); // Dibuja el objeto
void enable(); // Habilita el objeto void enable(); // Habilita el objeto
void setState(State state); // Establece el estado void setState(State state); // Establece el estado
auto tryToGetBonus() -> bool; // Intenta obtener el bonus auto tryToGetBonus() -> bool; // Intenta obtener el bonus
@@ -131,23 +130,21 @@ class Tabe {
float speed_ = 0.0F; // Velocidad de movimiento float speed_ = 0.0F; // Velocidad de movimiento
float accel_ = 0.0F; // Aceleración float accel_ = 0.0F; // Aceleración
int fly_distance_ = 0; // Distancia de vuelo int fly_distance_ = 0; // Distancia de vuelo
float waiting_counter_ = 0; // Tiempo que pasa quieto int waiting_counter_ = 0; // Tiempo que pasa quieto
bool enabled_ = false; // Indica si el objeto está activo bool enabled_ = false; // Indica si el objeto está activo
Direction direction_ = Direction::TO_THE_LEFT; // Dirección actual Direction direction_ = Direction::TO_THE_LEFT; // Dirección actual
Direction destiny_ = Direction::TO_THE_LEFT; // Destino Direction destiny_ = Direction::TO_THE_LEFT; // Destino
State state_ = State::FLY; // Estado actual State state_ = State::FLY; // Estado actual
float hit_counter_ = 0; // Contador para el estado HIT int hit_counter_ = 0; // Contador para el estado HIT
int number_of_hits_ = 0; // Cantidad de disparos recibidos int number_of_hits_ = 0; // Cantidad de disparos recibidos
bool has_bonus_ = true; // Indica si aún tiene el bonus para soltar bool has_bonus_ = true; // Indica si aún tiene el bonus para soltar
Timer timer_; // Temporizador para gestionar la aparición Timer timer_; // Temporizador para gestionar la aparición
// --- Métodos internos --- // --- Métodos internos ---
void move(); // Mueve el objeto (frame-based) void move(); // Mueve el objeto
void move(float deltaTime); // Mueve el objeto (time-based)
void shiftSprite() { sprite_->setPos(x_, y_); } // Actualiza la posición del sprite void shiftSprite() { sprite_->setPos(x_, y_); } // Actualiza la posición del sprite
void setRandomFlyPath(Direction direction, int length); // Establece un vuelo aleatorio void setRandomFlyPath(Direction direction, int length); // Establece un vuelo aleatorio
void updateState(); // Actualiza el estado (frame-based) void updateState(); // Actualiza el estado
void updateState(float deltaTime); // Actualiza el estado (time-based)
void updateTimer(); // Actualiza el temporizador void updateTimer(); // Actualiza el temporizador
void disable(); // Deshabilita el objeto void disable(); // Deshabilita el objeto
}; };

View File

@@ -3,7 +3,7 @@
#include <SDL3/SDL.h> // Para SDL_SetRenderTarget, SDL_CreateTexture, SDL_DestroyTexture, SDL_FRect, SDL_GetRenderTarget, SDL_RenderTexture, SDL_PixelFormat, SDL_TextureAccess #include <SDL3/SDL.h> // Para SDL_SetRenderTarget, SDL_CreateTexture, SDL_DestroyTexture, SDL_FRect, SDL_GetRenderTarget, SDL_RenderTexture, SDL_PixelFormat, SDL_TextureAccess
#include <algorithm> #include <algorithm>
#include <cmath> // Para sin, pow #include <cmath> // Para sin
#include <cstdlib> // Para rand #include <cstdlib> // Para rand
#include <memory> // Para allocator, unique_ptr, make_unique #include <memory> // Para allocator, unique_ptr, make_unique
#include <numbers> // Para pi #include <numbers> // Para pi
@@ -81,7 +81,7 @@ void TiledBG::render() {
SDL_RenderTexture(renderer_, canvas_, &window_, &pos_); SDL_RenderTexture(renderer_, canvas_, &window_, &pos_);
} }
// Actualiza la lógica de la clase (frame-based) // Actualiza la lógica de la clase
void TiledBG::update() { void TiledBG::update() {
updateDesp(); updateDesp();
updateStop(); updateStop();
@@ -107,33 +107,7 @@ void TiledBG::update() {
} }
} }
// Actualiza la lógica de la clase (time-based) // Detiene el desplazamiento de forma ordenada
void TiledBG::update(float delta_time) {
updateDesp(delta_time);
updateStop(delta_time);
switch (mode_) {
case TiledBGMode::DIAGONAL: {
// El tileado de fondo se desplaza en diagonal
window_.x = static_cast<int>(desp_) % TILE_WIDTH;
window_.y = static_cast<int>(desp_) % TILE_HEIGHT;
break;
}
case TiledBGMode::CIRCLE: {
// El tileado de fondo se desplaza en circulo
const int INDEX = static_cast<int>(desp_) % 360;
window_.x = 128 + (static_cast<int>(sin_[(INDEX + 270) % 360] * 128));
window_.y = 128 + (static_cast<int>(sin_[(360 - INDEX) % 360] * 96));
break;
}
default:
break;
}
}
// Detiene el desplazamiento de forma ordenada (frame-based)
void TiledBG::updateStop() { void TiledBG::updateStop() {
if (stopping_) { if (stopping_) {
const int UMBRAL = 20 * speed_; // Ajusta este valor según la precisión deseada const int UMBRAL = 20 * speed_; // Ajusta este valor según la precisión deseada
@@ -153,26 +127,3 @@ void TiledBG::updateStop() {
} }
} }
} }
// Detiene el desplazamiento de forma ordenada (time-based)
void TiledBG::updateStop(float delta_time) {
if (stopping_) {
const int UMBRAL = 20 * speed_; // Ajusta este valor según la precisión deseada
// Desacelerar si estamos cerca de completar el ciclo (ventana a punto de regresar a 0)
if (window_.x >= TILE_WIDTH - UMBRAL) {
// Convertir 1.05F por frame a por milisegundo: (1.05^(60*delta_time/1000))
float deceleration_factor = std::pow(1.05F, 60.0F * delta_time / 1000.0F);
speed_ /= deceleration_factor;
// Asegura que no baje demasiado
speed_ = std::max(speed_, 0.1F);
}
// Si estamos en 0, detener
if (window_.x == 0) {
speed_ = 0.0F;
stopping_ = false; // Desactivamos el estado de "stopping"
}
}
}

View File

@@ -24,9 +24,8 @@ class TiledBG {
~TiledBG(); ~TiledBG();
// --- Métodos principales --- // --- Métodos principales ---
void render(); // Pinta la clase en pantalla void render(); // Pinta la clase en pantalla
void update(); // Actualiza la lógica de la clase (compatibilidad) void update(); // Actualiza la lógica de la clase
void update(float delta_time); // Actualiza la lógica de la clase
// --- Configuración --- // --- Configuración ---
void setSpeed(float speed) { speed_ = speed; } // Establece la velocidad void setSpeed(float speed) { speed_ = speed; } // Establece la velocidad
@@ -55,9 +54,7 @@ class TiledBG {
bool stopping_ = false; // Indica si se está deteniendo bool stopping_ = false; // Indica si se está deteniendo
// --- Métodos internos --- // --- Métodos internos ---
void fillTexture(); // Rellena la textura con el contenido void fillTexture(); // Rellena la textura con el contenido
void updateDesp() { desp_ += speed_; } // Actualiza el desplazamiento (frame-based) void updateDesp() { desp_ += speed_; } // Actualiza el desplazamiento
void updateDesp(float delta_time) { desp_ += speed_ * delta_time / (1000.0f / 60.0f); } // Actualiza el desplazamiento (time-based) void updateStop(); // Detiene el desplazamiento de forma ordenada
void updateStop(); // Detiene el desplazamiento de forma ordenada (frame-based)
void updateStop(float delta_time); // Detiene el desplazamiento de forma ordenada (time-based)
}; };

View File

@@ -3,14 +3,15 @@
#include "text.h" // Para Text #include "text.h" // Para Text
// Actualiza el objeto // Actualiza el objeto
void Writer::update(float delta_time) { void Writer::update() {
if (enabled_) { if (enabled_) {
if (!completed_) { if (!completed_) {
// No completado // No completado
writing_timer_ += delta_time; if (writing_counter_ > 0) {
if (writing_timer_ >= speed_ms_) { writing_counter_--;
} else {
index_++; index_++;
writing_timer_ = 0.0f; writing_counter_ = speed_;
} }
if (index_ == length_) { if (index_ == length_) {
@@ -18,8 +19,10 @@ void Writer::update(float delta_time) {
} }
} else { } else {
// Completado // Completado
enabled_timer_ += delta_time; finished_ = enabled_counter_ <= 0;
finished_ = enabled_timer_ >= enabled_timer_target_; if (!finished_) {
enabled_counter_--;
}
} }
} }
} }
@@ -54,10 +57,8 @@ void Writer::setCaption(const std::string &text) {
// Establece el valor de la variable // Establece el valor de la variable
void Writer::setSpeed(int value) { void Writer::setSpeed(int value) {
// Convierte frames a milisegundos (frames * 16.67ms) speed_ = value;
constexpr float FRAME_TIME_MS = 1000.0f / 60.0f; writing_counter_ = value;
speed_ms_ = static_cast<float>(value) * FRAME_TIME_MS;
writing_timer_ = 0.0f;
} }
// Establece el valor de la variable // Establece el valor de la variable
@@ -70,10 +71,9 @@ auto Writer::isEnabled() const -> bool {
return enabled_; return enabled_;
} }
// Establece el temporizador para deshabilitar el objeto (en milisegundos) // Establece el valor de la variable
void Writer::setFinishedTimerMs(float time_ms) { void Writer::setFinishedCounter(int time) {
enabled_timer_target_ = time_ms; enabled_counter_ = time;
enabled_timer_ = 0.0f;
} }
// Centra la cadena de texto a un punto X // Centra la cadena de texto a un punto X

View File

@@ -15,7 +15,7 @@ class Writer {
~Writer() = default; ~Writer() = default;
// --- Métodos principales --- // --- Métodos principales ---
void update(float delta_time); // Actualiza el objeto void update(); // Actualiza el objeto
void render() const; // Dibuja el objeto en pantalla void render() const; // Dibuja el objeto en pantalla
// --- Setters --- // --- Setters ---
@@ -25,7 +25,7 @@ class Writer {
void setCaption(const std::string &text); // Establece el texto a escribir void setCaption(const std::string &text); // Establece el texto a escribir
void setSpeed(int value); // Establece la velocidad de escritura void setSpeed(int value); // Establece la velocidad de escritura
void setEnabled(bool value); // Habilita o deshabilita el objeto void setEnabled(bool value); // Habilita o deshabilita el objeto
void setFinishedTimerMs(float time_ms); // Establece el temporizador para deshabilitar el objeto (en ms) void setFinishedCounter(int time); // Establece el temporizador para deshabilitar el objeto
void center(int x); // Centra la cadena de texto a un punto X void center(int x); // Centra la cadena de texto a un punto X
@@ -38,17 +38,16 @@ class Writer {
std::shared_ptr<Text> text_; // Objeto encargado de escribir el texto std::shared_ptr<Text> text_; // Objeto encargado de escribir el texto
// --- Variables de estado --- // --- Variables de estado ---
std::string caption_; // El texto para escribir std::string caption_; // El texto para escribir
int pos_x_ = 0; // Posición en el eje X donde empezar a escribir el texto int pos_x_ = 0; // Posición en el eje X donde empezar a escribir el texto
int pos_y_ = 0; // Posición en el eje Y donde empezar a escribir el texto int pos_y_ = 0; // Posición en el eje Y donde empezar a escribir el texto
int kerning_ = 0; // Kerning del texto, es decir, espaciado entre caracteres int kerning_ = 0; // Kerning del texto, es decir, espaciado entre caracteres
float speed_ms_ = 0.0f; // Velocidad de escritura en milisegundos int speed_ = 0; // Velocidad de escritura
float writing_timer_ = 0.0f; // Temporizador de escritura para cada caracter int writing_counter_ = 0; // Temporizador de escritura para cada caracter
int index_ = 0; // Posición del texto que se está escribiendo int index_ = 0; // Posición del texto que se está escribiendo
int length_ = 0; // Longitud de la cadena a escribir int length_ = 0; // Longitud de la cadena a escribir
float enabled_timer_ = 0.0f; // Temporizador para deshabilitar el objeto int enabled_counter_ = 0; // Temporizador para deshabilitar el objeto
float enabled_timer_target_ = 0.0f; // Tiempo objetivo para deshabilitar el objeto bool completed_ = false; // Indica si se ha escrito todo el texto
bool completed_ = false; // Indica si se ha escrito todo el texto bool enabled_ = false; // Indica si el objeto está habilitado
bool enabled_ = false; // Indica si el objeto está habilitado bool finished_ = false; // Indica si ya ha terminado
bool finished_ = false; // Indica si ya ha terminado
}; };