reestructuració

This commit is contained in:
2026-04-14 13:26:22 +02:00
parent 4ac34b8583
commit 4429cd92c1
143 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,742 @@
// IWYU pragma: no_include <bits/std_abs.h>
#include "credits.hpp"
#include <SDL3/SDL.h> // Para SDL_RenderFillRect, SDL_RenderTexture, SDL_SetRenderTarget, SDL_SetRenderDrawColor, SDL_CreateTexture, SDL_DestroyTexture, SDL_GetTicks, SDL_GetRenderTarget, SDL_PixelFormat, SDL_PollEvent, SDL_RenderClear, SDL_RenderRect, SDL_SetTextureBlendMode, SDL_TextureAccess, SDL_BLENDMODE_BLEND, SDL_Event, Uint64
#include <algorithm> // Para max, min, clamp
#include <array> // Para array
#include <cmath> // Para abs
#include <stdexcept> // Para runtime_error
#include <string> // Para basic_string, string
#include <string_view> // Para string_view
#include <vector> // Para vector
#include "audio.hpp" // Para Audio
#include "balloon_manager.hpp" // Para BalloonManager
#include "color.hpp" // Para Color, SHADOW_TEXT, NO_COLOR_MOD
#include "fade.hpp" // Para Fade
#include "global_events.hpp" // Para handle
#include "global_inputs.hpp" // Para check
#include "input.hpp" // Para Input
#include "lang.hpp" // Para getText
#include "param.hpp" // Para Param, param, ParamGame, ParamFade
#include "player.hpp" // Para Player
#include "resource.hpp" // Para Resource
#include "screen.hpp" // Para Screen
#include "section.hpp" // Para Name, name
#include "sprite.hpp" // Para Sprite
#include "text.hpp" // Para Text
#include "texture.hpp" // Para Texture
#include "tiled_bg.hpp" // Para TiledBG, TiledBGMode
#include "ui/service_menu.hpp" // Para ServiceMenu
#include "utils.hpp" // Para Zone
// Textos
constexpr std::string_view TEXT_COPYRIGHT = "@2020,2025 JailDesigner";
// Constructor
Credits::Credits()
: balloon_manager_(std::make_unique<BalloonManager>(nullptr)),
tiled_bg_(std::make_unique<TiledBG>(param.game.game_area.rect, TiledBGMode::DIAGONAL)),
fade_in_(std::make_unique<Fade>()),
fade_out_(std::make_unique<Fade>()),
text_texture_(SDL_CreateTexture(Screen::get()->getRenderer(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, static_cast<int>(param.game.width), static_cast<int>(param.game.height))),
canvas_(SDL_CreateTexture(Screen::get()->getRenderer(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, static_cast<int>(param.game.width), static_cast<int>(param.game.height))) {
if (text_texture_ == nullptr) {
throw std::runtime_error("Failed to create SDL texture for text.");
}
initVars();
startCredits();
// Inicializa el timer de delta time para el primer frame del callback
last_time_ = SDL_GetTicks();
}
// Destructor
Credits::~Credits() {
SDL_DestroyTexture(text_texture_);
SDL_DestroyTexture(canvas_);
resetVolume();
Audio::get()->stopMusic();
// Desregistra los jugadores de Options
Options::keyboard.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_) / 1000.0F; // Convertir ms a segundos
last_time_ = CURRENT_TIME;
return DELTA_TIME;
}
// Avanza un frame (llamado desde Director::iterate)
void Credits::iterate() {
checkInput();
const float DELTA_TIME = calculateDeltaTime();
update(DELTA_TIME);
render();
}
// Procesa un evento (llamado desde Director::handleEvent)
void Credits::handleEvent(const SDL_Event& /*event*/) {
// Eventos globales ya gestionados por Director::handleEvent
}
// Bucle principal legacy (fallback)
void Credits::run() {
last_time_ = SDL_GetTicks();
while (Section::name == Section::Name::CREDITS) {
checkInput();
const float DELTA_TIME = calculateDeltaTime();
update(DELTA_TIME);
checkEvents(); // Tiene que ir antes del render
render();
}
}
// Actualiza las variables (time-based puro - sin conversión frame-based)
void Credits::update(float delta_time) {
const float MULTIPLIER = want_to_pass_ ? FAST_FORWARD_MULTIPLIER : 1.0F;
const float ADJUSTED_DELTA_TIME = delta_time * MULTIPLIER;
static auto* const SCREEN = Screen::get();
SCREEN->update(delta_time); // Actualiza el objeto screen
Audio::update(); // Actualiza el objeto audio
tiled_bg_->update(ADJUSTED_DELTA_TIME);
cycleColors(ADJUSTED_DELTA_TIME);
balloon_manager_->update(ADJUSTED_DELTA_TIME);
updateTextureDstRects(ADJUSTED_DELTA_TIME);
throwBalloons(ADJUSTED_DELTA_TIME);
updatePlayers(ADJUSTED_DELTA_TIME);
updateAllFades(ADJUSTED_DELTA_TIME);
fillCanvas();
}
// Dibuja Credits::en patalla
void Credits::render() {
static auto* const SCREEN = Screen::get();
SCREEN->start(); // Prepara para empezar a dibujar en la textura de juego
SDL_RenderTexture(SCREEN->getRenderer(), canvas_, nullptr, nullptr); // Copia la textura con la zona de juego a la pantalla
SCREEN->render(); // Vuelca el contenido del renderizador en pantalla
}
// Comprueba el manejador de eventos
void Credits::checkEvents() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
GlobalEvents::handle(event);
}
}
// Comprueba las entradas
void Credits::checkInput() {
Input::get()->update();
if (!ServiceMenu::get()->isEnabled()) {
// Comprueba si se ha pulsado cualquier botón (de los usados para jugar)
if (Input::get()->checkAnyButton(Input::ALLOW_REPEAT)) {
want_to_pass_ = true;
fading_ = mini_logo_on_position_;
} else {
want_to_pass_ = false;
}
}
// Comprueba los inputs que se pueden introducir en cualquier sección del juego
GlobalInputs::check();
}
// Crea la textura con el texto
void Credits::fillTextTexture() {
auto text = Resource::get()->getText("smb2");
auto text_grad = Resource::get()->getText("smb2_grad");
SDL_SetRenderTarget(Screen::get()->getRenderer(), text_texture_);
SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 0, 0, 0);
SDL_RenderClear(Screen::get()->getRenderer());
const std::array<std::string, 11> TEXTS = {
Lang::getText("[CREDITS] PROGRAMMED_AND_DESIGNED_BY"),
Lang::getText("[CREDITS] PIXELART_DRAWN_BY"),
Lang::getText("[CREDITS] MUSIC_COMPOSED_BY"),
Lang::getText("[CREDITS] SOUND_EFFECTS"),
"JAILDESIGNER",
"JAILDOCTOR",
"ERIC MATYAS (SOUNDIMAGE.ORG)",
"WWW.THEMOTIONMONKEY.CO.UK",
"WWW.KENNEY.NL",
"JAILDOCTOR",
"JAILDESIGNER"};
const int SPACE_POST_TITLE = 3 + text->getCharacterSize();
const int SPACE_PRE_TITLE = text->getCharacterSize() * 4;
const int TEXTS_HEIGHT = (1 * text->getCharacterSize()) + (8 * SPACE_POST_TITLE) + (3 * SPACE_PRE_TITLE);
const int POS_X = static_cast<int>(param.game.game_area.center_x);
credits_rect_dst_.h = credits_rect_src_.h = static_cast<float>(TEXTS_HEIGHT);
auto text_style = Text::Style(Text::CENTER | Text::SHADOW, Colors::NO_COLOR_MOD, Colors::SHADOW_TEXT);
// PROGRAMMED_AND_DESIGNED_BY
int y = 0;
text_grad->writeStyle(POS_X, y, TEXTS.at(0), text_style);
y += SPACE_POST_TITLE;
text->writeStyle(POS_X, y, TEXTS.at(4), text_style);
// PIXELART_DRAWN_BY
y += SPACE_PRE_TITLE;
text_grad->writeStyle(POS_X, y, TEXTS.at(1), text_style);
y += SPACE_POST_TITLE;
text->writeStyle(POS_X, y, TEXTS.at(4), text_style);
// MUSIC_COMPOSED_BY
y += SPACE_PRE_TITLE;
text_grad->writeStyle(POS_X, y, TEXTS.at(2), text_style);
y += SPACE_POST_TITLE;
text->writeStyle(POS_X, y, TEXTS.at(5), text_style);
y += SPACE_POST_TITLE;
text->writeStyle(POS_X, y, TEXTS.at(6), text_style);
// SOUND_EFFECTS
y += SPACE_PRE_TITLE;
text_grad->writeStyle(POS_X, y, TEXTS.at(3), text_style);
y += SPACE_POST_TITLE;
text->writeStyle(POS_X, y, TEXTS.at(7), text_style);
y += SPACE_POST_TITLE;
text->writeStyle(POS_X, y, TEXTS.at(8), text_style);
y += SPACE_POST_TITLE;
text->writeStyle(POS_X, y, TEXTS.at(9), text_style);
y += SPACE_POST_TITLE;
text->writeStyle(POS_X, y, TEXTS.at(10), text_style);
// Mini logo
y += SPACE_PRE_TITLE;
mini_logo_rect_src_.y = static_cast<float>(y);
auto mini_logo_sprite = std::make_unique<Sprite>(Resource::get()->getTexture("logo_jailgames_mini.png"));
mini_logo_sprite->setPosition(1 + POS_X - (mini_logo_sprite->getWidth() / 2), 1 + y);
Resource::get()->getTexture("logo_jailgames_mini.png")->setColor(Colors::SHADOW_TEXT.r, Colors::SHADOW_TEXT.g, Colors::SHADOW_TEXT.b);
mini_logo_sprite->render();
mini_logo_sprite->setPosition(POS_X - (mini_logo_sprite->getWidth() / 2), y);
Resource::get()->getTexture("logo_jailgames_mini.png")->setColor(255, 255, 255);
mini_logo_sprite->render();
// Texto con el copyright
y += mini_logo_sprite->getHeight() + 3;
text->writeDX(Text::CENTER | Text::SHADOW, POS_X, y, std::string(TEXT_COPYRIGHT), 1, Colors::NO_COLOR_MOD, 1, Colors::SHADOW_TEXT);
// Resetea el renderizador
SDL_SetRenderTarget(Screen::get()->getRenderer(), nullptr);
// Actualiza las variables
mini_logo_rect_dst_.h = mini_logo_rect_src_.h = mini_logo_sprite->getHeight() + 3 + text->getCharacterSize();
credits_rect_dst_.y = param.game.game_area.rect.h;
mini_logo_rect_dst_.y = credits_rect_dst_.y + credits_rect_dst_.h + 30;
mini_logo_final_pos_ = param.game.game_area.center_y - (mini_logo_rect_src_.h / 2);
}
// Dibuja todos los sprites en la textura
void Credits::fillCanvas() {
// Cambia el destino del renderizador
auto* temp = SDL_GetRenderTarget(Screen::get()->getRenderer());
SDL_SetRenderTarget(Screen::get()->getRenderer(), canvas_);
// Dibuja el fondo, los globos y los jugadores
tiled_bg_->render();
balloon_manager_->render();
renderPlayers();
// Dibuja los titulos de credito
SDL_RenderTexture(Screen::get()->getRenderer(), text_texture_, &credits_rect_src_, &credits_rect_dst_);
// Dibuja el mini_logo
SDL_RenderTexture(Screen::get()->getRenderer(), text_texture_, &mini_logo_rect_src_, &mini_logo_rect_dst_);
// Dibuja los rectangulos negros
SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 0, 0, 0xFF);
SDL_RenderFillRect(Screen::get()->getRenderer(), &top_black_rect_);
SDL_RenderFillRect(Screen::get()->getRenderer(), &bottom_black_rect_);
SDL_RenderFillRect(Screen::get()->getRenderer(), &left_black_rect_);
SDL_RenderFillRect(Screen::get()->getRenderer(), &right_black_rect_);
// Dibuja el rectangulo rojo
drawBorderRect();
// Si el mini_logo está en su destino, lo dibuja encima de lo anterior
if (mini_logo_on_position_) {
SDL_RenderTexture(Screen::get()->getRenderer(), text_texture_, &mini_logo_rect_src_, &mini_logo_rect_dst_);
}
// Dibuja el fade sobre el resto de elementos
fade_in_->render();
fade_out_->render();
// Deja el renderizador apuntando donde estaba
SDL_SetRenderTarget(Screen::get()->getRenderer(), temp);
}
// Actualiza el destino de los rectangulos de las texturas (time-based puro)
void Credits::updateTextureDstRects(float delta_time) {
constexpr float TEXTURE_UPDATE_INTERVAL_S = 10.0F / 60.0F; // ~0.167s (cada 10 frames)
credits_state_.texture_accumulator += delta_time;
if (credits_state_.texture_accumulator >= TEXTURE_UPDATE_INTERVAL_S) {
credits_state_.texture_accumulator -= TEXTURE_UPDATE_INTERVAL_S;
// 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 <= static_cast<float>(mini_logo_final_pos_)) {
// Forzar posición exacta para evitar problemas de comparación float
mini_logo_rect_dst_.y = static_cast<float>(mini_logo_final_pos_);
mini_logo_on_position_ = true;
} else {
--mini_logo_rect_dst_.y;
}
}
// Acumular tiempo desde que el logo llegó a su posición (fuera del if para que se ejecute cada frame)
if (mini_logo_on_position_) {
time_since_logo_positioned_ += delta_time;
// Timeout para evitar que la sección sea infinita
if (time_since_logo_positioned_ >= MAX_TIME_AFTER_LOGO_S) {
fading_ = true;
}
// Si el jugador quiere pasar los titulos de credito, el fade se inicia solo
if (want_to_pass_) {
fading_ = true;
}
}
}
// Tira globos al escenario (time-based puro)
void Credits::throwBalloons(float delta_time) {
constexpr int SPEED = 200;
constexpr size_t NUM_SETS = 8; // Tamaño del vector SETS
const std::vector<int> SETS = {0, 63, 25, 67, 17, 75, 13, 50};
constexpr float BALLOON_INTERVAL_S = SPEED / 60.0F; // ~3.33s (cada 200 frames)
constexpr float POWERBALL_INTERVAL_S = (SPEED * 4) / 60.0F; // ~13.33s (cada 800 frames)
constexpr float MAX_BALLOON_TIME_S = ((NUM_SETS - 1) * SPEED * 3) / 60.0F; // Tiempo máximo para lanzar globos
// Acumular tiempo total de globos
elapsed_time_balloons_ += delta_time;
// Detener lanzamiento después del tiempo límite
if (elapsed_time_balloons_ > MAX_BALLOON_TIME_S) {
return;
}
credits_state_.balloon_accumulator += delta_time;
credits_state_.powerball_accumulator += delta_time;
if (credits_state_.balloon_accumulator >= BALLOON_INTERVAL_S) {
credits_state_.balloon_accumulator -= BALLOON_INTERVAL_S;
const int INDEX = (static_cast<int>(elapsed_time_balloons_ * 60.0F / SPEED)) % SETS.size();
balloon_manager_->deployFormation(SETS.at(INDEX), -60);
}
if (credits_state_.powerball_accumulator >= POWERBALL_INTERVAL_S && elapsed_time_balloons_ > 0.0F) {
credits_state_.powerball_accumulator -= POWERBALL_INTERVAL_S;
balloon_manager_->createPowerBall();
}
}
// Inicializa los jugadores
void Credits::initPlayers() {
std::vector<std::vector<std::shared_ptr<Texture>>> player_textures; // Vector con todas las texturas de los jugadores;
std::vector<std::vector<std::string>> player1_animations; // Vector con las animaciones del jugador 1
std::vector<std::vector<std::string>> player2_animations; // Vector con las animaciones del jugador 2
// Texturas - Player1
std::vector<std::shared_ptr<Texture>> player1_textures;
player1_textures.emplace_back(Resource::get()->getTexture("player1_pal0"));
player1_textures.emplace_back(Resource::get()->getTexture("player1_pal1"));
player1_textures.emplace_back(Resource::get()->getTexture("player1_pal2"));
player1_textures.emplace_back(Resource::get()->getTexture("player1_pal3"));
player1_textures.emplace_back(Resource::get()->getTexture("player1_power.png"));
player_textures.push_back(player1_textures);
// Texturas - Player2
std::vector<std::shared_ptr<Texture>> player2_textures;
player2_textures.emplace_back(Resource::get()->getTexture("player2_pal0"));
player2_textures.emplace_back(Resource::get()->getTexture("player2_pal1"));
player2_textures.emplace_back(Resource::get()->getTexture("player2_pal2"));
player2_textures.emplace_back(Resource::get()->getTexture("player2_pal3"));
player2_textures.emplace_back(Resource::get()->getTexture("player2_power.png"));
player_textures.push_back(player2_textures);
// Animaciones -- Jugador
player1_animations.emplace_back(Resource::get()->getAnimation("player1.ani"));
player1_animations.emplace_back(Resource::get()->getAnimation("player_power.ani"));
player2_animations.emplace_back(Resource::get()->getAnimation("player2.ani"));
player2_animations.emplace_back(Resource::get()->getAnimation("player_power.ani"));
// Crea los dos jugadores
const int Y = play_area_.y + play_area_.h - Player::WIDTH;
constexpr bool DEMO = false;
constexpr int AWAY_DISTANCE = 700;
Player::Config config_player1;
config_player1.id = Player::Id::PLAYER1;
config_player1.x = play_area_.x - AWAY_DISTANCE - Player::WIDTH;
config_player1.y = Y;
config_player1.demo = DEMO;
config_player1.play_area = &play_area_;
config_player1.texture = player_textures.at(0);
config_player1.animations = player1_animations;
config_player1.hi_score_table = &Options::settings.hi_score_table;
config_player1.glowing_entry = &Options::settings.glowing_entries.at(static_cast<int>(Player::Id::PLAYER1) - 1);
players_.emplace_back(std::make_unique<Player>(config_player1));
players_.back()->setWalkingState(Player::State::WALKING_RIGHT);
players_.back()->setPlayingState(Player::State::CREDITS);
Player::Config config_player2;
config_player2.id = Player::Id::PLAYER2;
config_player2.x = play_area_.x + play_area_.w + AWAY_DISTANCE;
config_player2.y = Y;
config_player2.demo = DEMO;
config_player2.play_area = &play_area_;
config_player2.texture = player_textures.at(1);
config_player2.animations = player2_animations;
config_player2.hi_score_table = &Options::settings.hi_score_table;
config_player2.glowing_entry = &Options::settings.glowing_entries.at(static_cast<int>(Player::Id::PLAYER2) - 1);
players_.emplace_back(std::make_unique<Player>(config_player2));
players_.back()->setWalkingState(Player::State::WALKING_LEFT);
players_.back()->setPlayingState(Player::State::CREDITS);
// Registra los jugadores en Options
for (const auto& player : players_) {
Options::keyboard.addPlayer(player);
Options::gamepad_manager.addPlayer(player);
}
}
// Actualiza los rectangulos negros (time-based)
void Credits::updateBlackRects(float delta_time) {
if (!initialized_) { return; }
delta_time = std::max(delta_time, 0.0F);
// Fase vertical: hasta que ambos rects verticales estén exactos en su target
if (!vertical_done_) {
credits_state_.black_rect_accumulator += delta_time;
if (credits_state_.black_rect_accumulator >= BLACK_RECT_INTERVAL_S) {
credits_state_.black_rect_accumulator -= BLACK_RECT_INTERVAL_S;
// top
int prev_top_h = static_cast<int>(top_black_rect_.h);
top_black_rect_.h = std::min(top_black_rect_.h + 1.0F,
static_cast<float>(param.game.game_area.center_y - 1));
int top_delta = static_cast<int>(top_black_rect_.h) - prev_top_h;
// bottom
int prev_bottom_h = static_cast<int>(bottom_black_rect_.h);
int prev_bottom_y = static_cast<int>(bottom_black_rect_.y);
bottom_black_rect_.h = bottom_black_rect_.h + 1.0F;
bottom_black_rect_.y = std::max(bottom_black_rect_.y - 1.0F,
static_cast<float>(param.game.game_area.center_y + 1));
int bottom_steps_by_h = static_cast<int>(bottom_black_rect_.h) - prev_bottom_h;
int bottom_steps_by_y = prev_bottom_y - static_cast<int>(bottom_black_rect_.y);
int bottom_steps = std::max({0, bottom_steps_by_h, bottom_steps_by_y});
int steps_done = top_delta + bottom_steps;
if (steps_done > 0) {
current_step_ = std::max(0.0F, current_step_ - static_cast<float>(steps_done));
float vol_f = initial_volume_ * (current_step_ / static_cast<float>(total_steps_));
int vol_i = static_cast<int>(std::clamp(vol_f, 0.0F, static_cast<float>(initial_volume_)));
Audio::get()->setMusicVolume(vol_i); // usa tu API de audio aquí
}
// Si han alcanzado los objetivos, fijarlos exactamente y marcar done
bool top_at_target = static_cast<int>(top_black_rect_.h) == param.game.game_area.center_y - 1.0F;
bool bottom_at_target = static_cast<int>(bottom_black_rect_.y) == param.game.game_area.center_y + 1.0F;
if (top_at_target && bottom_at_target) {
top_black_rect_.h = param.game.game_area.center_y - 1.0F;
bottom_black_rect_.y = param.game.game_area.center_y + 1.0F;
vertical_done_ = true;
}
}
// actualizar border_rect cada frame aunque todavía en fase vertical
updateBorderRect();
return;
}
// Fase horizontal
if (!horizontal_done_) {
int prev_left_w = static_cast<int>(left_black_rect_.w);
left_black_rect_.w = std::min(left_black_rect_.w + static_cast<float>(HORIZONTAL_SPEED),
param.game.game_area.center_x);
int left_gain = static_cast<int>(left_black_rect_.w) - prev_left_w;
int prev_right_x = static_cast<int>(right_black_rect_.x);
right_black_rect_.w = right_black_rect_.w + static_cast<float>(HORIZONTAL_SPEED);
right_black_rect_.x = std::max(right_black_rect_.x - static_cast<float>(HORIZONTAL_SPEED),
param.game.game_area.center_x);
int right_move = prev_right_x - static_cast<int>(right_black_rect_.x);
int steps_done = left_gain + right_move;
if (steps_done > 0) {
current_step_ = std::max(0.0F, current_step_ - static_cast<float>(steps_done));
float vol_f = initial_volume_ * (current_step_ / static_cast<float>(total_steps_));
int vol_i = static_cast<int>(std::clamp(vol_f, 0.0F, static_cast<float>(initial_volume_)));
Audio::get()->setMusicVolume(vol_i); // usa tu API de audio aquí
}
bool left_at_target = static_cast<int>(left_black_rect_.w) == param.game.game_area.center_x;
bool right_at_target = static_cast<int>(right_black_rect_.x) == param.game.game_area.center_x;
if (left_at_target && right_at_target) {
left_black_rect_.w = param.game.game_area.center_x;
right_black_rect_.x = param.game.game_area.center_x;
horizontal_done_ = true;
}
updateBorderRect();
return;
}
// Fase final: ya completado el movimiento de rects
Audio::get()->setMusicVolume(0);
// Audio::get()->stopMusic(); // opcional, si quieres parar la reproducción
// Usar segundos puros en lugar de frames equivalentes
if (counter_pre_fade_ >= PRE_FADE_DELAY_S) {
if (fade_out_) {
fade_out_->activate();
}
} else {
counter_pre_fade_ += delta_time;
}
}
// Actualiza el rectangulo del borde
void Credits::updateBorderRect() {
border_rect_.x = left_black_rect_.x + left_black_rect_.w;
border_rect_.y = top_black_rect_.y + top_black_rect_.h - 1.0F;
float raw_w = right_black_rect_.x - border_rect_.x;
float raw_h = bottom_black_rect_.y - border_rect_.y + 1.0F;
border_rect_.w = std::max(0.0F, raw_w);
border_rect_.h = std::max(0.0F, raw_h);
}
// Actualiza el estado de fade (time-based)
void Credits::updateAllFades(float delta_time) {
if (fading_) {
updateBlackRects(delta_time);
updateBorderRect();
}
fade_in_->update();
if (fade_in_->hasEnded() && Audio::get()->getMusicState() != Audio::MusicState::PLAYING) {
Audio::get()->playMusic("credits.ogg");
}
fade_out_->update();
if (fade_out_->hasEnded()) {
Section::name = Section::Name::HI_SCORE_TABLE;
}
}
// Establece el nivel de volumen
void Credits::setVolume(int amount) {
Options::audio.music.volume = std::clamp(amount, 0, 100);
Audio::get()->setMusicVolume(Options::audio.music.volume);
}
// Reestablece el nivel de volumen
void Credits::resetVolume() const {
Options::audio.music.volume = initial_volume_;
Audio::get()->setMusicVolume(Options::audio.music.volume);
}
// Cambia el color del fondo (time-based)
void Credits::cycleColors(float delta_time) {
constexpr int UPPER_LIMIT = 140; // Límite superior
constexpr int LOWER_LIMIT = 30; // Límite inferior
// Factor para escalar los valores de incremento.
// Asumimos que los valores originales estaban balanceados para 60 FPS.
const float FRAME_ADJUSTMENT = delta_time * 60.0F;
// Inicializar valores RGB si es la primera vez
if (credits_state_.r == 255.0F && credits_state_.g == 0.0F && credits_state_.b == 0.0F && credits_state_.step_r == -0.5F) {
credits_state_.r = static_cast<float>(UPPER_LIMIT);
credits_state_.g = static_cast<float>(LOWER_LIMIT);
credits_state_.b = static_cast<float>(LOWER_LIMIT);
}
// Ajustar valores de R
credits_state_.r += credits_state_.step_r * FRAME_ADJUSTMENT;
if (credits_state_.r >= UPPER_LIMIT) {
credits_state_.r = UPPER_LIMIT; // Clamp para evitar que se pase
credits_state_.step_r = -credits_state_.step_r; // Cambia de dirección al alcanzar los límites
} else if (credits_state_.r <= LOWER_LIMIT) {
credits_state_.r = LOWER_LIMIT; // Clamp para evitar que se pase
credits_state_.step_r = -credits_state_.step_r;
}
// Ajustar valores de G
credits_state_.g += credits_state_.step_g * FRAME_ADJUSTMENT;
if (credits_state_.g >= UPPER_LIMIT) {
credits_state_.g = UPPER_LIMIT;
credits_state_.step_g = -credits_state_.step_g; // Cambia de dirección al alcanzar los límites
} else if (credits_state_.g <= LOWER_LIMIT) {
credits_state_.g = LOWER_LIMIT;
credits_state_.step_g = -credits_state_.step_g;
}
// Ajustar valores de B
credits_state_.b += credits_state_.step_b * FRAME_ADJUSTMENT;
if (credits_state_.b >= UPPER_LIMIT) {
credits_state_.b = UPPER_LIMIT;
credits_state_.step_b = -credits_state_.step_b; // Cambia de dirección al alcanzar los límites
} else if (credits_state_.b <= LOWER_LIMIT) {
credits_state_.b = LOWER_LIMIT;
credits_state_.step_b = -credits_state_.step_b;
}
// Aplicar el color, redondeando a enteros antes de usar
color_ = Color(static_cast<int>(credits_state_.r), static_cast<int>(credits_state_.g), static_cast<int>(credits_state_.b));
tiled_bg_->setColor(color_);
}
// Actualza los jugadores (time-based)
void Credits::updatePlayers(float delta_time) {
for (auto& player : players_) {
player->update(delta_time);
}
}
// Renderiza los jugadores
void Credits::renderPlayers() {
for (auto const& player : players_) {
player->render();
}
}
// Inicializa variables
void Credits::initVars() {
// Inicialización segura de rects tal y como los mostraste
top_black_rect_ = {
.x = play_area_.x,
.y = param.game.game_area.rect.y,
.w = play_area_.w,
.h = black_bars_size_};
bottom_black_rect_ = {
.x = play_area_.x,
.y = param.game.game_area.rect.h - black_bars_size_,
.w = play_area_.w,
.h = black_bars_size_};
left_black_rect_ = {
.x = play_area_.x,
.y = param.game.game_area.center_y - 1.0F,
.w = 0.0F,
.h = 2.0F};
right_black_rect_ = {
.x = play_area_.x + play_area_.w,
.y = param.game.game_area.center_y - 1.0F,
.w = 0.0F,
.h = 2.0F};
initialized_ = false;
Section::name = Section::Name::CREDITS;
balloon_manager_->setPlayArea(play_area_);
fade_in_->setColor(param.fade.color);
fade_in_->setType(Fade::Type::FULLSCREEN);
fade_in_->setPostDuration(800);
fade_in_->setMode(Fade::Mode::IN);
fade_in_->activate();
fade_out_->setColor(0, 0, 0);
fade_out_->setType(Fade::Type::FULLSCREEN);
fade_out_->setPostDuration(7000);
updateBorderRect();
tiled_bg_->setColor(Color(255, 96, 96));
tiled_bg_->setSpeed(60.0F);
initPlayers();
SDL_SetTextureBlendMode(text_texture_, SDL_BLENDMODE_BLEND);
fillTextTexture();
steps_ = static_cast<int>(std::abs((top_black_rect_.h - param.game.game_area.center_y - 1) + ((left_black_rect_.w - param.game.game_area.center_x) / 4)));
}
void Credits::startCredits() {
// Guardar iniciales (enteros para contar "pasos" por píxel)
init_top_h_ = static_cast<int>(top_black_rect_.h);
init_bottom_y_ = static_cast<int>(bottom_black_rect_.y);
init_left_w_ = static_cast<int>(left_black_rect_.w);
init_right_x_ = static_cast<int>(right_black_rect_.x);
// Objetivos
int top_target_h = param.game.game_area.center_y - 1;
int bottom_target_y = param.game.game_area.center_y + 1;
int left_target_w = param.game.game_area.center_x;
int right_target_x = param.game.game_area.center_x;
// Pasos verticales
int pasos_top = std::max(0, top_target_h - init_top_h_);
int pasos_bottom = std::max(0, init_bottom_y_ - bottom_target_y);
// Pasos horizontales. right se mueve a velocidad HORIZONTAL_SPEED, contamos pasos como unidades de movimiento equivalentes
int pasos_left = std::max(0, left_target_w - init_left_w_);
int dx_right = std::max(0, init_right_x_ - right_target_x);
int pasos_right = (dx_right + (HORIZONTAL_SPEED - 1)) / HORIZONTAL_SPEED; // ceil
total_steps_ = pasos_top + pasos_bottom + pasos_left + pasos_right;
if (total_steps_ <= 0) {
total_steps_ = 1;
}
current_step_ = static_cast<float>(total_steps_);
// Reiniciar contadores y estado
credits_state_.black_rect_accumulator = 0.0F;
counter_pre_fade_ = 0.0F;
initialized_ = true;
// Asegurar volumen inicial consistente
if (steps_ <= 0) {
steps_ = 1;
}
float vol_f = initial_volume_ * (current_step_ / static_cast<float>(total_steps_));
setVolume(static_cast<int>(std::clamp(vol_f, 0.0F, static_cast<float>(initial_volume_))));
}
// Dibuja el rectángulo del borde si es visible
void Credits::drawBorderRect() {
// Umbral: cualquier valor menor que 1 píxel no se considera visible
constexpr float VISIBLE_THRESHOLD = 1.0F;
if (border_rect_.w < VISIBLE_THRESHOLD || border_rect_.h < VISIBLE_THRESHOLD) {
return; // no dibujar
}
const Color COLOR = color_.LIGHTEN();
SDL_Renderer* rdr = Screen::get()->getRenderer();
SDL_SetRenderDrawColor(rdr, COLOR.r, COLOR.g, COLOR.b, 0xFF);
// Convertir a enteros de forma conservadora para evitar líneas de 1px por redondeo extraño
SDL_Rect r;
r.x = static_cast<int>(std::floor(border_rect_.x + 0.5F));
r.y = static_cast<int>(std::floor(border_rect_.y + 0.5F));
r.w = static_cast<int>(std::max(0.0F, std::floor(border_rect_.w + 0.5F)));
r.h = static_cast<int>(std::max(0.0F, std::floor(border_rect_.h + 0.5F)));
if (r.w > 0 && r.h > 0) {
SDL_RenderRect(Screen::get()->getRenderer(), &border_rect_);
}
}

View File

@@ -0,0 +1,171 @@
#pragma once
#include <SDL3/SDL.h> // Para SDL_FRect, Uint32, SDL_Texture, Uint64
#include <memory> // Para unique_ptr, shared_ptr
#include <vector> // Para vector
#include "color.hpp" // Para Zone, Color
#include "options.hpp" // Para AudioOptions, MusicOptions, audio
#include "param.hpp" // Para Param, ParamGame, param
#include "utils.hpp"
// Declaraciones adelantadas
class BalloonManager;
class Fade;
class Player;
class TiledBG;
class Credits {
public:
// --- Constructor y destructor ---
Credits();
~Credits();
// --- Callbacks para el bucle SDL_MAIN_USE_CALLBACKS ---
void iterate(); // Ejecuta un frame
void handleEvent(const SDL_Event& event); // Procesa un evento
// --- Bucle principal legacy (fallback) ---
void run();
private:
// --- Métodos del bucle principal ---
void update(float delta_time); // Actualización principal de la lógica (time-based)
auto calculateDeltaTime() -> float; // Calcula el deltatime
void initVars(); // Inicializa variables
void startCredits(); // Inicializa mas variables
// --- Constantes de clase (time-based) ---
static constexpr int PLAY_AREA_HEIGHT = 200;
static constexpr float FAST_FORWARD_MULTIPLIER = 6.0F;
static constexpr float BLACK_RECT_INTERVAL_S = 4.0F / 60.0F; // ~0.0667s (cada 4 frames a 60fps)
static constexpr int HORIZONTAL_SPEED = 2;
static constexpr float MAX_TIME_AFTER_LOGO_S = 20.0F;
static constexpr float PRE_FADE_DELAY_S = 8.0F;
// --- Objetos principales ---
std::unique_ptr<BalloonManager> balloon_manager_; // Gestión de globos
std::unique_ptr<TiledBG> tiled_bg_; // Mosaico animado de fondo
std::unique_ptr<Fade> fade_in_; // Fundido de entrada
std::unique_ptr<Fade> fade_out_; // Fundido de salida
std::vector<std::shared_ptr<Player>> players_; // Vector de jugadores
// --- Gestión de texturas ---
SDL_Texture* text_texture_; // Textura con el texto de créditos
SDL_Texture* canvas_; // Textura donde se dibuja todo
// --- Temporización (time-based puro) ---
Uint64 last_time_ = 0; // Último tiempo registrado para deltaTime
float elapsed_time_balloons_ = 0.0F; // Tiempo acumulado para lanzamiento de globos (segundos)
float counter_pre_fade_ = 0.0F; // Tiempo antes de activar fundido final (segundos)
float time_since_logo_positioned_ = 0.0F; // Tiempo desde que el logo llegó a su posición (segundos)
float current_step_ = 0.0F;
int total_steps_ = 1;
bool initialized_ = false;
// --- Guardar estados iniciales para cálculo de pasos ---
int init_top_h_ = 0;
int init_bottom_y_ = 0;
int init_left_w_ = 0;
int init_right_x_ = 0;
// --- Variables de estado ---
bool fading_ = false; // Estado del fade final
bool want_to_pass_ = false; // Jugador quiere saltarse créditos
bool mini_logo_on_position_ = false; // Minilogo en posición final
bool vertical_done_ = false;
bool horizontal_done_ = false;
// --- Diseño y posicionamiento ---
float black_bars_size_ = (param.game.game_area.rect.h - PLAY_AREA_HEIGHT) / 2; // Tamaño de las barras negras
int mini_logo_final_pos_ = 0; // Posición final del minilogo
Color color_; // Color usado para los efectos
// --- Control de audio ---
int initial_volume_ = Options::audio.music.volume; // Volumen inicial
int steps_ = 0; // Pasos para reducir audio
// --- Estado de acumuladores para animaciones ---
struct CreditsState {
float texture_accumulator = 0.0F;
float balloon_accumulator = 0.0F;
float powerball_accumulator = 0.0F;
float black_rect_accumulator = 0.0F;
float r = 255.0F; // UPPER_LIMIT
float g = 0.0F; // LOWER_LIMIT
float b = 0.0F; // LOWER_LIMIT
float step_r = -0.5F;
float step_g = 0.3F;
float step_b = 0.1F;
} credits_state_;
// --- Rectángulos de renderizado ---
// Texto de créditos
SDL_FRect credits_rect_src_ = param.game.game_area.rect;
SDL_FRect credits_rect_dst_ = param.game.game_area.rect;
// Mini logo
SDL_FRect mini_logo_rect_src_ = param.game.game_area.rect;
SDL_FRect mini_logo_rect_dst_ = param.game.game_area.rect;
// Definición del área de juego
SDL_FRect play_area_ = {
.x = param.game.game_area.rect.x,
.y = param.game.game_area.rect.y + black_bars_size_,
.w = param.game.game_area.rect.w,
.h = PLAY_AREA_HEIGHT};
// Barras negras para efecto letterbox
SDL_FRect top_black_rect_ = {
.x = play_area_.x,
.y = param.game.game_area.rect.y,
.w = play_area_.w,
.h = black_bars_size_};
SDL_FRect bottom_black_rect_ = {
.x = play_area_.x,
.y = param.game.game_area.rect.h - black_bars_size_,
.w = play_area_.w,
.h = black_bars_size_};
SDL_FRect left_black_rect_ = {
.x = play_area_.x,
.y = param.game.game_area.center_y - 1,
.w = 0,
.h = 2};
SDL_FRect right_black_rect_ = {
.x = play_area_.x + play_area_.w,
.y = param.game.game_area.center_y - 1,
.w = 0,
.h = 2};
// Borde para la ventana
SDL_FRect border_rect_ = play_area_; // Delimitador de ventana
void render(); // Renderizado de la escena
static void checkEvents(); // Manejo de eventos
void checkInput(); // Procesamiento de entrada
// --- Métodos de renderizado ---
void fillTextTexture(); // Crear textura de texto de créditos
void fillCanvas(); // Renderizar todos los sprites y fondos
void renderPlayers(); // Renderiza los jugadores
void drawBorderRect(); // Renderiza el rectangulo del borde
// --- Métodos de lógica del juego ---
void throwBalloons(float delta_time); // Lanzar globos al escenario (time-based)
void initPlayers(); // Inicializar jugadores
void updateAllFades(float delta_time); // Actualizar estados de fade (time-based)
void cycleColors(float delta_time); // Cambiar colores de fondo
void updatePlayers(float delta_time); // Actualza los jugadores (time-based)
// --- Métodos de interfaz ---
void updateBlackRects(); // Actualizar rectángulos negros (letterbox) (frame-based)
void updateBlackRects(float delta_time); // Actualizar rectángulos negros (letterbox) (time-based)
void updateBorderRect(); // Actualizar rectángulo rojo (borde)
void updateTextureDstRects(); // Actualizar destinos de texturas (frame-based)
void updateTextureDstRects(float delta_time); // Actualizar destinos de texturas (time-based)
// --- Métodos de audio ---
static void setVolume(int amount); // Establecer volumen
void resetVolume() const; // Restablecer volumen
};

2152
source/game/scenes/game.cpp Normal file

File diff suppressed because it is too large Load Diff

353
source/game/scenes/game.hpp Normal file
View File

@@ -0,0 +1,353 @@
#pragma once
#include <SDL3/SDL.h> // Para SDL_Event, SDL_Renderer, SDL_Texture, Uint64
#include <list> // Para list
#include <memory> // Para shared_ptr, unique_ptr
#include <string> // Para string
#include <vector> // Para vector
#include "bullet.hpp" // for Bullet
#include "demo.hpp" // for Demo
#include "item.hpp" // for Item (ptr only), ItemType
#include "manage_hiscore_table.hpp" // for HiScoreEntry
#include "options.hpp" // for Settings, settings
#include "player.hpp" // for Player
class Background;
class Balloon;
class BalloonManager;
class BulletManager;
class Fade;
class Input;
class PathSprite;
class PauseManager;
class Scoreboard;
class Screen;
class SmartSprite;
class StageManager;
class Tabe;
class Texture;
struct Path;
namespace Difficulty {
enum class Code;
} // namespace Difficulty
// --- Clase Game: núcleo principal del gameplay ---
//
// 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 {
public:
// --- Constantes ---
static constexpr bool DEMO_OFF = false; // Modo demo desactivado
static constexpr bool DEMO_ON = true; // Modo demo activado
// --- Constructor y destructor ---
Game(Player::Id player_id, int current_stage, bool demo_enabled); // Constructor principal
~Game(); // Destructor
// --- Callbacks para el bucle SDL_MAIN_USE_CALLBACKS ---
void iterate(); // Ejecuta un frame
void handleEvent(const SDL_Event& event); // Procesa un evento
// --- Bucle principal legacy (fallback) ---
void run(); // Ejecuta el bucle principal del juego
private:
using Players = std::vector<std::shared_ptr<Player>>;
// --- Enums ---
enum class State {
FADE_IN, // Transición de entrada
ENTERING_PLAYER, // Jugador entrando
SHOWING_GET_READY_MESSAGE, // Mostrando mensaje de preparado
PLAYING, // Jugando
COMPLETED, // Juego completado
GAME_OVER, // Fin del juego
};
// --- Constantes de tiempo (en segundos) ---
static constexpr float HELP_COUNTER_S = 16.667F; // Contador de ayuda (1000 frames a 60fps → segundos)
static constexpr float GAME_COMPLETED_START_FADE_S = 8.333F; // Inicio del fade al completar (500 frames → segundos)
static constexpr float GAME_COMPLETED_END_S = 11.667F; // Fin del juego completado (700 frames → segundos)
static constexpr float GAME_OVER_DURATION_S = 8.5F;
static constexpr float TIME_STOPPED_DURATION_S = 6.0F;
static constexpr float DEMO_FADE_PRE_DURATION_S = 0.5F;
static constexpr int ITEM_POINTS_1_DISK_ODDS = 10;
static constexpr int ITEM_POINTS_2_GAVINA_ODDS = 6;
static constexpr int ITEM_POINTS_3_PACMAR_ODDS = 3;
static constexpr int ITEM_CLOCK_ODDS = 5;
static constexpr int ITEM_COFFEE_ODDS = 5;
static constexpr int ITEM_POWER_BALL_ODDS = 0;
static constexpr int ITEM_COFFEE_MACHINE_ODDS = 4;
// --- Estructuras ---
struct Helper {
bool need_coffee{false}; // Indica si se necesitan cafes
bool need_coffee_machine{false}; // Indica si se necesita PowerUp
bool need_power_ball{false}; // Indica si se necesita una PowerBall
float counter{HELP_COUNTER_S * 1000}; // Contador para no dar ayudas consecutivas
int item_disk_odds{ITEM_POINTS_1_DISK_ODDS}; // Probabilidad de aparición del objeto
int item_gavina_odds{ITEM_POINTS_2_GAVINA_ODDS}; // Probabilidad de aparición del objeto
int item_pacmar_odds{ITEM_POINTS_3_PACMAR_ODDS}; // Probabilidad de aparición del objeto
int item_clock_odds{ITEM_CLOCK_ODDS}; // Probabilidad de aparición del objeto
int item_coffee_odds{ITEM_COFFEE_ODDS}; // Probabilidad de aparición del objeto
int item_coffee_machine_odds{ITEM_COFFEE_MACHINE_ODDS}; // Probabilidad de aparición del objeto
};
// --- Objetos y punteros ---
SDL_Renderer* renderer_; // El renderizador de la ventana
Screen* screen_; // Objeto encargado de dibujar en pantalla
Input* input_; // Manejador de entrada
Scoreboard* scoreboard_; // Objeto para dibujar el marcador
SDL_Texture* canvas_; // Textura para dibujar la zona de juego
Players players_; // Vector con los jugadores
Players players_draw_list_; // Vector con los jugadores ordenados para ser renderizados
std::list<std::unique_ptr<Item>> items_; // Vector con los items
std::list<std::unique_ptr<SmartSprite>> smart_sprites_; // Vector con los smartsprites
std::list<std::unique_ptr<PathSprite>> path_sprites_; // Vector con los pathsprites
std::vector<std::shared_ptr<Texture>> item_textures_; // Vector con las texturas de los items
std::vector<std::vector<std::shared_ptr<Texture>>> player_textures_; // Vector con todas las texturas de los jugadores
std::vector<std::shared_ptr<Texture>> game_text_textures_; // Vector con las texturas para los sprites con textos
std::vector<std::vector<std::string>> item_animations_; // Vector con las animaciones de los items
std::vector<std::vector<std::string>> player1_animations_; // Vector con las animaciones del jugador 1
std::vector<std::vector<std::string>> player2_animations_; // Vector con las animaciones del jugador 2
std::unique_ptr<PauseManager> pause_manager_; // Objeto para gestionar la pausa
std::unique_ptr<StageManager> stage_manager_; // Objeto para gestionar las fases
std::unique_ptr<BalloonManager> balloon_manager_; // Objeto para gestionar los globos
std::unique_ptr<BulletManager> bullet_manager_; // Objeto para gestionar las balas
std::unique_ptr<Background> background_; // Objeto para dibujar el fondo del juego
std::unique_ptr<Fade> fade_in_; // Objeto para renderizar fades
std::unique_ptr<Fade> fade_out_; // Objeto para renderizar fades
std::unique_ptr<Tabe> tabe_; // Objeto para gestionar el Tabe Volaor
std::vector<Path> paths_; // Vector con los recorridos precalculados almacenados
// --- Variables de estado ---
HiScoreEntry hi_score_ = HiScoreEntry(
Options::settings.hi_score_table[0].name,
Options::settings.hi_score_table[0].score); // Máxima puntuación y nombre de quien la ostenta
Demo demo_; // Variable con todas las variables relacionadas con el modo demo
Difficulty::Code difficulty_ = Options::settings.difficulty; // Dificultad del juego
Helper helper_; // Variable para gestionar las ayudas
Uint64 last_time_ = 0; // Último tiempo registrado para deltaTime
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
float difficulty_score_multiplier_ = 1.0F; // Multiplicador de puntos en función de la dificultad
float counter_ = 0.0F; // Contador para el juego
float game_completed_timer_ = 0.0F; // Acumulador de tiempo para el tramo final (milisegundos)
float game_over_timer_ = 0.0F; // Timer para el estado de fin de partida (milisegundos)
float time_stopped_timer_ = 0.0F; // Temporizador para llevar la cuenta del tiempo detenido
float time_stopped_sound_timer_ = 0.0F; // Temporizador para controlar el sonido del tiempo detenido
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
State state_ = State::FADE_IN; // Estado
// Estructuras para gestionar flags de eventos basados en tiempo
struct GameOverFlags {
bool music_fade_triggered = false;
bool message_triggered = false;
bool fade_out_triggered = false;
void reset() {
music_fade_triggered = false;
message_triggered = false;
fade_out_triggered = false;
}
} game_over_flags_;
struct GameCompletedFlags {
bool start_celebrations_triggered = false;
bool end_celebrations_triggered = false;
void reset() {
start_celebrations_triggered = false;
end_celebrations_triggered = false;
}
} game_completed_flags_;
struct TimeStoppedFlags {
bool color_flash_sound_played = false;
bool warning_phase_started = false;
void reset() {
color_flash_sound_played = false;
warning_phase_started = false;
}
} time_stopped_flags_;
#ifdef _DEBUG
bool auto_pop_balloons_ = false; // Si es true, incrementa automaticamente los globos explotados
#endif
// --- Ciclo principal del juego ---
void update(float delta_time); // Actualiza la lógica principal del juego
auto calculateDeltaTime() -> float; // Calcula el deltatime
void render(); // Renderiza todos los elementos del juego
void handleEvents(); // Procesa los eventos del sistema en cola
void checkState(); // Verifica y actualiza el estado actual del juego
void setState(State state); // Cambia el estado del juego
void cleanLists(); // Limpia vectores de elementos deshabilitados
// --- Gestión de estados del juego ---
void updateGameStates(float delta_time); // Actualiza todos los estados del juego
void updateGameStateFadeIn(float delta_time); // Gestiona el estado de transición de entrada (time-based)
void updateGameStateEnteringPlayer(float delta_time); // Gestiona el estado de entrada de jugador
void updateGameStateShowingGetReadyMessage(float delta_time); // Gestiona el estado de mensaje "preparado"
void updateGameStatePlaying(float delta_time); // Gestiona el estado de juego activo
void updateGameStateCompleted(float delta_time); // Gestiona el estado de juego completado
void updateGameStateGameOver(float delta_time); // Gestiona las actualizaciones continuas del estado de fin de partida
// --- Gestión de jugadores ---
void initPlayers(Player::Id player_id); // Inicializa los datos de los jugadores
void updatePlayers(float delta_time); // Actualiza las variables y estados de los jugadores
void renderPlayers(); // Renderiza todos los jugadores en pantalla
auto getPlayer(Player::Id id) -> std::shared_ptr<Player>; // Obtiene un jugador por su identificador
static auto getController(Player::Id player_id) -> int; // Obtiene el controlador asignado a un jugador
// --- Estado de jugadores ---
void checkAndUpdatePlayerStatus(int active_player_index, int inactive_player_index); // Actualiza estado entre jugadores
void checkPlayersStatusPlaying(); // Verifica el estado de juego de todos los jugadores
auto allPlayersAreWaitingOrGameOver() -> bool; // Verifica si todos esperan o han perdido
auto allPlayersAreGameOver() -> bool; // Verifica si todos los jugadores han perdido
auto allPlayersAreNotPlaying() -> bool; // Verifica si ningún jugador está activo
// --- Colisiones de jugadores ---
void handlePlayerCollision(std::shared_ptr<Player>& player, std::shared_ptr<Balloon>& balloon); // Procesa colisión de jugador con globo
auto checkPlayerBalloonCollision(std::shared_ptr<Player>& player) -> std::shared_ptr<Balloon>; // Detecta colisión jugador-globo
void checkPlayerItemCollision(std::shared_ptr<Player>& player); // Detecta colisión jugador-ítem
// --- Sistema de entrada (input) ---
void checkInput(); // Gestiona toda la entrada durante el juego
void checkPauseInput(); // Verifica solicitudes de pausa de controladores
// --- Entrada de jugadores normales ---
void handlePlayersInput(); // Gestiona entrada de todos los jugadores
void handleNormalPlayerInput(const std::shared_ptr<Player>& player); // Procesa entrada de un jugador específico
void handleFireInput(const std::shared_ptr<Player>& player, Bullet::Type type); // Gestiona disparo de jugador
void handleFireInputs(const std::shared_ptr<Player>& player, bool autofire); // Procesa disparos automáticos
void handlePlayerContinueInput(const std::shared_ptr<Player>& player); // Permite continuar al jugador
void handlePlayerWaitingInput(const std::shared_ptr<Player>& player); // Permite (re)entrar al jugador
void handleNameInput(const std::shared_ptr<Player>& player); // Gestiona entrada de nombre del jugador
// --- Entrada en modo demo ---
void demoHandleInput(); // Gestiona entrada durante el modo demostración
void demoHandlePassInput(); // Permite saltar la demostración
void demoHandlePlayerInput(const std::shared_ptr<Player>& player, int index); // Procesa entrada de jugador en demo
// --- Colisiones específicas de balas ---
auto checkBulletTabeCollision(const std::shared_ptr<Bullet>& bullet) -> bool; // Detecta colisión bala-Tabe
auto checkBulletBalloonCollision(const std::shared_ptr<Bullet>& bullet) -> bool; // Detecta colisión bala-globo
void processBalloonHit(const std::shared_ptr<Bullet>& bullet, const std::shared_ptr<Balloon>& balloon); // Procesa impacto en globo
// --- Sistema de ítems y power-ups ---
void updateItems(float delta_time); // Actualiza posición y estado de todos los ítems
void renderItems(); // Renderiza todos los ítems activos
auto dropItem() -> ItemType; // Determina aleatoriamente qué ítem soltar
void createItem(ItemType type, float x, float y); // Crea un nuevo ítem en posición específica
void freeItems(); // Libera memoria del vector de ítems
void destroyAllItems(); // Elimina todos los ítems activos de la pantalla
// --- ítems especiales ---
void enableTimeStopItem(); // Activa el efecto de detener el tiempo
void disableTimeStopItem(); // Desactiva el efecto de detener el tiempo
void updateTimeStopped(float delta_time); // Actualiza el estado del tiempo detenido
void handleGameCompletedEvents(); // Maneja eventos del juego completado
void handleGameOverEvents(); // Maneja eventos discretos basados en tiempo durante game over
void throwCoffee(int x, int y); // Crea efecto de café arrojado al ser golpeado
// --- 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
// --- Sprites inteligentes (smartsprites) ---
void updateSmartSprites(float delta_time); // Actualiza todos los sprites con lógica propia (time-based)
void renderSmartSprites(); // Renderiza todos los sprites inteligentes
void freeSmartSprites(); // Libera memoria de sprites inteligentes
// --- Sprites por ruta (pathsprites) ---
void updatePathSprites(float delta_time); // Actualiza sprites que siguen rutas predefinidas
void renderPathSprites(); // Renderiza sprites animados por ruta
void freePathSprites(); // Libera memoria de sprites por ruta
void initPaths(); // Inicializa rutas predefinidas para animaciones
// --- Creación de sprites especiales ---
void createItemText(int x, const std::shared_ptr<Texture>& texture); // Crea texto animado para ítems
void createMessage(const std::vector<Path>& paths, const std::shared_ptr<Texture>& texture); // Crea mensaje con animación por ruta
// --- Sistema de globos y enemigos ---
void handleBalloonDestruction(const std::shared_ptr<Balloon>& balloon, const std::shared_ptr<Player>& player); // Procesa destrucción de globo
void handleTabeHitEffects(); // Gestiona efectos al golpear a Tabe
void checkAndUpdateBalloonSpeed(); // Ajusta velocidad de globos según progreso
// --- Gestión de fases y progresión ---
void updateStage(); // Verifica y actualiza cambio de fase
void initDifficultyVars(); // Inicializa variables de dificultad
// --- Sistema de amenaza ---
void updateMenace(); // Gestiona el nivel de amenaza del juego
void setMenace(); // Calcula y establece amenaza según globos activos
// --- Puntuación y marcador ---
void updateHiScore(); // Actualiza el récord máximo si es necesario
void updateScoreboard(float delta_time); // Actualiza la visualización del marcador
void updateHiScoreName(); // Pone en el marcador el nombre del primer jugador de la tabla
void initScoreboard(); // Inicializa el sistema de puntuación
// --- Modo demostración ---
void initDemo(Player::Id player_id); // Inicializa variables para el modo demostración
void updateDemo(float delta_time); // Actualiza lógica específica del modo demo
// --- Recursos y renderizado ---
void setResources(); // Asigna texturas y animaciones a los objetos
void updateBackground(float delta_time); // Actualiza elementos del fondo (time-based)
void fillCanvas(); // Renderiza elementos del área de juego en su textura
void updateHelper(); // Actualiza variables auxiliares de renderizado
// --- Sistema de audio ---
static void playMusic(const std::string& music_file, int loop = -1); // Reproduce la música de fondo
void stopMusic() const; // Detiene la reproducción de música
static void pauseMusic(); // Pausa la música
static void resumeMusic(); // Retoma la música que eestaba pausada
void playSound(const std::string& name) const; // Reproduce un efecto de sonido específico
// --- Gestion y dibujado de jugadores en z-order ---
static void buildPlayerDrawList(const Players& elements, Players& draw_list); // Construye el draw_list a partir del vector principal
static void updatePlayerDrawList(const Players& elements, Players& draw_list); // Actualiza draw_list tras cambios en los z_order
static void renderPlayerDrawList(const Players& draw_list); // Dibuja en el orden definido
static auto findPlayerIndex(const Players& elems, const std::shared_ptr<Player>& who) -> size_t;
static void sendPlayerToBack(Players& elements, const std::shared_ptr<Player>& who, Players& draw_list); // Envia al jugador al fondo de la pantalla
static void bringPlayerToFront(Players& elements, const std::shared_ptr<Player>& who, Players& draw_list); // Envia al jugador al frente de la pantalla
// --- Varios ---
void onPauseStateChanged(bool is_paused);
// SISTEMA DE GRABACIÓN (CONDICIONAL)
#ifdef RECORDING
void updateRecording(float deltaTime); // Actualiza variables durante modo de grabación
#endif
// --- Depuración (solo en modo DEBUG) ---
#ifdef _DEBUG
void handleDebugEvents(const SDL_Event& event); // Comprueba los eventos en el modo DEBUG
#endif
};

View File

@@ -0,0 +1,399 @@
#include "hiscore_table.hpp"
#include <SDL3/SDL.h> // Para SDL_GetTicks, SDL_SetRenderTarget
#include <algorithm> // Para max
#include <cstdlib> // Para rand, size_t
#include <functional> // Para function
#include <utility> // Para std::cmp_less
#include <vector> // Para vector
#include "audio.hpp" // Para Audio
#include "background.hpp" // Para Background
#include "color.hpp" // Para Color, easeOutQuint, Colors::NO_COLOR_MOD
#include "fade.hpp" // Para Fade, FadeMode, FadeType
#include "global_events.hpp" // Para check
#include "global_inputs.hpp" // Para check
#include "input.hpp" // Para Input
#include "lang.hpp" // Para getText
#include "manage_hiscore_table.hpp" // Para HiScoreEntry
#include "options.hpp" // Para SettingsOptions, settings
#include "param.hpp" // Para Param, param, ParamGame, ParamFade
#include "path_sprite.hpp" // Para PathSprite, Path, PathType
#include "resource.hpp" // Para Resource
#include "screen.hpp" // Para Screen
#include "section.hpp" // Para Name, name, Options, options
#include "sprite.hpp" // Para Sprite
#include "text.hpp" // Para Text, Text::SHADOW, Text::COLOR
#include "texture.hpp" // Para Texture
#include "utils.hpp"
// Constructor
HiScoreTable::HiScoreTable()
: renderer_(Screen::get()->getRenderer()),
backbuffer_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height)),
fade_(std::make_unique<Fade>()),
background_(std::make_unique<Background>()),
view_area_(SDL_FRect{.x = 0, .y = 0, .w = param.game.width, .h = param.game.height}),
fade_mode_(Fade::Mode::IN),
background_fade_color_(Color(0, 0, 0)) {
// Inicializa el resto
Section::name = Section::Name::HI_SCORE_TABLE;
SDL_SetTextureBlendMode(backbuffer_, SDL_BLENDMODE_BLEND);
initFade();
initBackground();
iniEntryColors();
createSprites();
// Inicializa el timer de delta time y arranca la música
last_time_ = SDL_GetTicks();
Audio::get()->playMusic("title.ogg");
}
// Destructor
HiScoreTable::~HiScoreTable() {
SDL_DestroyTexture(backbuffer_);
Options::settings.clearLastHiScoreEntries();
}
// Actualiza las variables
void HiScoreTable::update(float delta_time) {
elapsed_time_ += delta_time; // Incrementa el tiempo transcurrido
static auto* const SCREEN = Screen::get();
SCREEN->update(delta_time); // Actualiza el objeto screen
Audio::update(); // Actualiza el objeto audio
updateSprites(delta_time); // Actualiza las posiciones de los sprites de texto
background_->update(delta_time); // Actualiza el fondo
updateFade(delta_time); // Gestiona el fade
updateCounter(); // Gestiona el contador y sus eventos
fillTexture(); // Dibuja los sprites en la textura
}
// Pinta en pantalla
void HiScoreTable::render() {
static auto* const SCREEN = Screen::get();
SCREEN->start(); // Prepara para empezar a dibujar en la textura de juego
SCREEN->clean(); // Limpia la pantalla
background_->render(); // Pinta el fondo
float scroll_offset = elapsed_time_ * SCROLL_SPEED_PPS; // Calcula el desplazamiento del scroll usando velocidad en pixels/segundo
view_area_.y = std::round(std::max(0.0F, (param.game.height + 100.0F) - scroll_offset)); // Establece la ventana del backbuffer (redondeado para evitar deformaciones)
SDL_RenderTexture(renderer_, backbuffer_, nullptr, &view_area_); // Copia el backbuffer al renderizador
fade_->render(); // Renderiza el fade
SCREEN->render(); // Vuelca el contenido del renderizador en pantalla
}
// Dibuja los sprites en la textura
void HiScoreTable::fillTexture() {
// Pinta en el backbuffer el texto y los sprites
auto* temp = SDL_GetRenderTarget(renderer_);
SDL_SetRenderTarget(renderer_, backbuffer_);
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
SDL_RenderClear(renderer_);
// Escribe el texto: Mejores puntuaciones
header_->render();
// Escribe los nombres de la tabla de puntuaciones
for (auto const& entry : entry_names_) {
entry->render();
}
// Cambia el destino de renderizado
SDL_SetRenderTarget(renderer_, temp);
}
// Comprueba los eventos
void HiScoreTable::checkEvents() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
GlobalEvents::handle(event);
}
}
// Comprueba las entradas
void HiScoreTable::checkInput() {
Input::get()->update();
GlobalInputs::check();
}
// Calcula el tiempo transcurrido desde el último frame
auto HiScoreTable::calculateDeltaTime() -> float {
const Uint64 CURRENT_TIME = SDL_GetTicks();
const float DELTA_TIME = static_cast<float>(CURRENT_TIME - last_time_) / 1000.0F; // Convertir ms a segundos
last_time_ = CURRENT_TIME;
return DELTA_TIME;
}
// Avanza un frame (llamado desde Director::iterate)
void HiScoreTable::iterate() {
const float DELTA_TIME = calculateDeltaTime();
checkInput();
update(DELTA_TIME);
render();
}
// Procesa un evento (llamado desde Director::handleEvent)
void HiScoreTable::handleEvent(const SDL_Event& /*event*/) {
// Eventos globales ya gestionados por Director::handleEvent
}
// Bucle para la pantalla de puntuaciones (fallback legacy)
void HiScoreTable::run() {
last_time_ = SDL_GetTicks();
Audio::get()->playMusic("title.ogg");
while (Section::name == Section::Name::HI_SCORE_TABLE) {
const float DELTA_TIME = calculateDeltaTime();
checkInput();
update(DELTA_TIME);
checkEvents(); // Tiene que ir antes del render
render();
}
}
// Gestiona el fade
void HiScoreTable::updateFade(float delta_time) {
fade_->update(delta_time);
if (fade_->hasEnded() && fade_mode_ == Fade::Mode::IN) {
(*fade_).reset();
fade_mode_ = Fade::Mode::OUT;
fade_->setMode(fade_mode_);
}
if (fade_->hasEnded() && fade_mode_ == Fade::Mode::OUT) {
Section::name = (Section::options == Section::Options::HI_SCORE_AFTER_PLAYING)
? Section::Name::TITLE
: Section::Name::INSTRUCTIONS;
Section::options = Section::Options::NONE;
}
}
// Convierte un entero a un string con separadores de miles
auto HiScoreTable::format(int number) -> std::string {
const std::string SEPARATOR = ".";
const std::string SCORE = std::to_string(number);
auto index = static_cast<int>(SCORE.size()) - 1;
std::string result;
auto i = 0;
while (index >= 0) {
result = SCORE.at(index) + result;
index--;
i++;
if (i == 3) {
i = 0;
result = SEPARATOR + result;
}
}
return result;
}
// Crea los sprites con los textos
void HiScoreTable::createSprites() {
auto header_text = Resource::get()->getText("04b_25_grey");
auto entry_text = Resource::get()->getText("smb2");
// Obtiene el tamaño de la textura
float backbuffer_width;
float backbuffer_height;
SDL_GetTextureSize(backbuffer_, &backbuffer_width, &backbuffer_height);
constexpr int ENTRY_LENGTH = 22;
constexpr int MAX_NAMES = 10;
const int SPACE_BETWEEN_HEADER = entry_text->getCharacterSize() * 4;
const int SPACE_BETWEEN_LINES = entry_text->getCharacterSize() * 2;
const int SIZE = SPACE_BETWEEN_HEADER + (SPACE_BETWEEN_LINES * (MAX_NAMES - 1)) + entry_text->getCharacterSize();
const int FIRST_LINE = (param.game.height - SIZE) / 2;
// Crea el sprite para el texto de cabecera
header_ = std::make_unique<Sprite>(header_text->writeDXToTexture(Text::COLOR, Lang::getText("[HIGHSCORE_TABLE] CAPTION"), -2, background_fade_color_.INVERSE().LIGHTEN(25)));
header_->setPosition(param.game.game_area.center_x - (header_->getWidth() / 2), FIRST_LINE);
// Crea los sprites para las entradas en la tabla de puntuaciones
const int ANIMATION = rand() % 4;
const std::string SAMPLE_LINE(ENTRY_LENGTH + 3, ' ');
auto sample_entry = std::make_unique<Sprite>(entry_text->writeDXToTexture(Text::SHADOW, SAMPLE_LINE, 1, Colors::NO_COLOR_MOD, 1, Colors::SHADOW_TEXT));
const auto ENTRY_WIDTH = sample_entry->getWidth();
for (int i = 0; i < MAX_NAMES; ++i) {
const auto TABLE_POSITION = format(i + 1) + ". ";
const auto SCORE = format(Options::settings.hi_score_table.at(i).score);
const auto NUM_DOTS = ENTRY_LENGTH - Options::settings.hi_score_table.at(i).name.size() - SCORE.size();
const auto* const ONE_CC = Options::settings.hi_score_table.at(i).one_credit_complete ? " }" : "";
std::string dots;
for (int j = 0; std::cmp_less(j, NUM_DOTS); ++j) {
dots = dots + ".";
}
const auto LINE = TABLE_POSITION + Options::settings.hi_score_table.at(i).name + dots + SCORE + ONE_CC;
entry_names_.emplace_back(std::make_shared<PathSprite>(entry_text->writeDXToTexture(Text::SHADOW, LINE, 1, Colors::NO_COLOR_MOD, 1, Colors::SHADOW_TEXT)));
const int DEFAULT_POS_X = (backbuffer_width - ENTRY_WIDTH) / 2;
const int POS_X = (i < 9) ? DEFAULT_POS_X : DEFAULT_POS_X - entry_text->getCharacterSize();
const int POS_Y = (i * SPACE_BETWEEN_LINES) + FIRST_LINE + SPACE_BETWEEN_HEADER;
switch (ANIMATION) {
case 0: // Ambos lados alternativamente
{
if (i % 2 == 0) {
entry_names_.back()->addPath(-entry_names_.back()->getWidth(), POS_X, PathType::HORIZONTAL, POS_Y, ANIM_DURATION_S, easeOutQuint);
entry_names_.back()->setPosition(-entry_names_.back()->getWidth(), 0);
} else {
entry_names_.back()->addPath(backbuffer_width, POS_X, PathType::HORIZONTAL, POS_Y, ANIM_DURATION_S, easeOutQuint);
entry_names_.back()->setPosition(backbuffer_width, 0);
}
break;
}
case 1: // Entran por la izquierda
{
entry_names_.back()->addPath(-entry_names_.back()->getWidth(), POS_X, PathType::HORIZONTAL, POS_Y, ANIM_DURATION_S, easeOutQuint);
entry_names_.back()->setPosition(-entry_names_.back()->getWidth(), 0);
break;
}
case 2: // Entran por la derecha
{
entry_names_.back()->addPath(backbuffer_width, POS_X, PathType::HORIZONTAL, POS_Y, ANIM_DURATION_S, easeOutQuint);
entry_names_.back()->setPosition(backbuffer_width, 0);
break;
}
case 3: // Entran desde la parte inferior
{
entry_names_.back()->addPath(backbuffer_height, POS_Y, PathType::VERTICAL, POS_X, ANIM_DURATION_S, easeOutQuint);
entry_names_.back()->setPosition(0, backbuffer_height);
}
default:
break;
}
}
}
// Actualiza las posiciones de los sprites de texto
void HiScoreTable::updateSprites(float delta_time) {
if (elapsed_time_ >= INIT_DELAY_S) {
const float ELAPSED_SINCE_INIT = elapsed_time_ - INIT_DELAY_S;
int index = static_cast<int>(ELAPSED_SINCE_INIT / ENTRY_DELAY_S);
if (std::cmp_less(index, entry_names_.size()) && index >= 0) {
// Verificar si este índice debe activarse ahora
float expected_time = index * ENTRY_DELAY_S;
if (ELAPSED_SINCE_INIT >= expected_time && ELAPSED_SINCE_INIT < expected_time + delta_time) {
entry_names_.at(index)->enable();
}
}
}
for (auto const& entry : entry_names_) {
entry->update(delta_time);
}
glowEntryNames();
}
// Inicializa el fade
void HiScoreTable::initFade() {
fade_->setColor(param.fade.color);
fade_->setType(Fade::Type::RANDOM_SQUARE2);
fade_->setPostDuration(param.fade.post_duration_ms);
fade_->setMode(fade_mode_);
fade_->activate();
}
// Inicializa el fondo
void HiScoreTable::initBackground() {
background_->setManualMode(true);
background_->setPos(param.game.game_area.rect);
background_->setCloudsSpeed(CLOUDS_SPEED);
const int LUCKY = rand() % 3;
switch (LUCKY) {
case 0: // Fondo verde
{
background_->setGradientNumber(2);
background_->setTransition(0.0F);
background_->setSunProgression(1.0F);
background_->setMoonProgression(0.0F);
background_fade_color_ = Colors::GREEN_SKY;
break;
}
case 1: // Fondo naranja
{
background_->setGradientNumber(1);
background_->setTransition(0.0F);
background_->setSunProgression(0.65F);
background_->setMoonProgression(0.0F);
background_fade_color_ = Colors::PINK_SKY;
break;
}
case 2: // Fondo azul
{
background_->setGradientNumber(0);
background_->setTransition(0.0F);
background_->setSunProgression(0.0F);
background_->setMoonProgression(0.0F);
background_fade_color_ = Colors::BLUE_SKY;
break;
}
default:
break;
}
}
// Obtiene un color del vector de colores de entradas
auto HiScoreTable::getEntryColor(int counter) -> Color {
int cycle_length = (entry_colors_.size() * 2) - 2;
size_t n = counter % cycle_length;
size_t index;
if (n < entry_colors_.size()) {
index = n; // Avanza: 0,1,2,3
} else {
index = (2 * (entry_colors_.size() - 1)) - n; // Retrocede: 2,1
}
return entry_colors_[index];
}
// Inicializa los colores de las entradas
void HiScoreTable::iniEntryColors() {
entry_colors_.clear();
entry_colors_.emplace_back(background_fade_color_.INVERSE().LIGHTEN(75));
entry_colors_.emplace_back(background_fade_color_.INVERSE().LIGHTEN(50));
entry_colors_.emplace_back(background_fade_color_.INVERSE().LIGHTEN(25));
entry_colors_.emplace_back(background_fade_color_.INVERSE());
}
// Hace brillar los nombres de la tabla de records
void HiScoreTable::glowEntryNames() {
int color_counter = static_cast<int>(elapsed_time_ * 60.0F / 5.0F); // Convertir tiempo a equivalente frame
const Color ENTRY_COLOR = getEntryColor(color_counter);
for (const auto& entry_index : Options::settings.glowing_entries) {
if (entry_index != -1) {
entry_names_.at(entry_index)->getTexture()->setColor(ENTRY_COLOR);
}
}
}
// Gestiona el contador
void HiScoreTable::updateCounter() {
if (elapsed_time_ >= BACKGROUND_CHANGE_S && !hiscore_flags_.background_changed) {
background_->setColor(background_fade_color_.DARKEN());
background_->setAlpha(96);
hiscore_flags_.background_changed = true;
}
if (elapsed_time_ >= COUNTER_END_S && !hiscore_flags_.fade_activated) {
fade_->activate();
hiscore_flags_.fade_activated = true;
}
}

View File

@@ -0,0 +1,93 @@
#pragma once
#include <SDL3/SDL.h> // Para SDL_FRect, SDL_Renderer, SDL_Texture, Uint64
#include <memory> // Para unique_ptr, shared_ptr
#include <string> // Para string
#include <vector> // Para vector
#include "color.hpp" // for Color
#include "fade.hpp" // for Fade
class Background;
class PathSprite;
class Sprite;
struct Path;
// --- Clase HiScoreTable: muestra la tabla de puntuaciones más altas ---
// Esta clase gestiona un estado del programa. Se encarga de mostrar la tabla con las puntuaciones
// más altas. Para ello utiliza un objeto que se encarga de pintar el fondo y una textura
// sobre la que escribe las puntuaciones. Esta textura se recorre modificando la ventana de vista
// para dar el efecto de que la textura se mueve sobre la pantalla.
// Para mejorar la legibilidad de los textos, el objeto que dibuja el fondo es capaz de modificar
// su atenuación.
class HiScoreTable {
public:
// --- Constructor y destructor ---
HiScoreTable();
~HiScoreTable();
// --- Callbacks para el bucle SDL_MAIN_USE_CALLBACKS ---
void iterate(); // Ejecuta un frame
void handleEvent(const SDL_Event& event); // Procesa un evento
// --- Bucle principal legacy (fallback) ---
void run();
private:
// --- Constantes (en segundos) ---
static constexpr float COUNTER_END_S = 800.0F / 60.0F; // Tiempo final (≈13.33s)
static constexpr float INIT_DELAY_S = 190.0F / 60.0F; // Retraso inicial (≈3.17s)
static constexpr float ENTRY_DELAY_S = 16.0F / 60.0F; // Retraso entre entradas (≈0.27s)
static constexpr float BACKGROUND_CHANGE_S = 150.0F / 60.0F; // Tiempo cambio fondo (≈2.5s)
static constexpr float ANIM_DURATION_S = 80.0F / 60.0F; // Duración animación (≈1.33s)
static constexpr float CLOUDS_SPEED = -6.0F; // Velocidad nubes (pixels/s)
static constexpr float SCROLL_SPEED_PPS = 60.0F; // Velocidad de scroll (60 pixels por segundo)
// --- Objetos y punteros ---
SDL_Renderer* renderer_; // El renderizador de la ventana
SDL_Texture* backbuffer_; // Textura para usar como backbuffer
std::unique_ptr<Fade> fade_; // Objeto para renderizar fades
std::unique_ptr<Background> background_; // Objeto para dibujar el fondo del juego
std::unique_ptr<Sprite> header_; // Sprite con la cabecera del texto
std::vector<std::shared_ptr<PathSprite>> entry_names_; // Lista con los sprites de cada uno de los nombres de la tabla de records
std::vector<Path> paths_; // Vector con los recorridos precalculados
// --- Variables ---
float elapsed_time_ = 0.0F; // Tiempo transcurrido (segundos)
Uint64 last_time_ = 0; // Último timestamp para calcular delta-time
SDL_FRect view_area_; // Parte de la textura que se muestra en pantalla
Fade::Mode fade_mode_; // Modo de fade a utilizar
Color background_fade_color_; // Color de atenuación del fondo
std::vector<Color> entry_colors_; // Colores para destacar las entradas en la tabla
// --- Flags para eventos basados en tiempo ---
struct HiScoreFlags {
bool background_changed = false;
bool fade_activated = false;
void reset() {
background_changed = false;
fade_activated = false;
}
} hiscore_flags_;
// --- Métodos internos ---
void update(float delta_time); // Actualiza las variables
void render(); // Pinta en pantalla
static void checkEvents(); // Comprueba los eventos
static void checkInput(); // Comprueba las entradas
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 updateFade(float delta_time); // Gestiona el fade
void createSprites(); // Crea los sprites con los textos
void updateSprites(float delta_time); // Actualiza las posiciones de los sprites de texto
void initFade(); // Inicializa el fade
void initBackground(); // Inicializa el fondo
auto getEntryColor(int counter) -> Color; // Obtiene un color del vector de colores de entradas
void iniEntryColors(); // Inicializa los colores de las entradas
void glowEntryNames(); // Hace brillar los nombres de la tabla de records
void updateCounter(); // Gestiona el contador
auto calculateDeltaTime() -> float; // Calcula el tiempo transcurrido desde el último frame
};

View File

@@ -0,0 +1,387 @@
#include "instructions.hpp"
#include <SDL3/SDL.h> // Para SDL_GetTicks, SDL_SetRenderTarget, SDL_Re...
#include <algorithm> // Para max
#include <array> // Para array
#include <string> // Para basic_string, string
#include <utility> // Para move
#include <vector> // Para vector
#include "audio.hpp" // Para Audio
#include "color.hpp" // Para Color, Colors::SHADOW_TEXT, Zone, NO_TEXT_C...
#include "fade.hpp" // Para Fade, FadeMode, FadeType
#include "global_events.hpp" // Para check
#include "global_inputs.hpp" // Para check
#include "input.hpp" // Para Input
#include "item.hpp" // Para Item
#include "lang.hpp" // Para getText
#include "param.hpp" // Para Param, param, ParamGame, ParamFade, Param...
#include "resource.hpp" // Para Resource
#include "screen.hpp" // Para Screen
#include "section.hpp" // Para Name, name, Options, options
#include "sprite.hpp" // Para Sprite
#include "text.hpp" // Para Text, Text::CENTER, Text::COLOR, Text::SHADOW
#include "tiled_bg.hpp" // Para TiledBG, TiledBGMode
#include "utils.hpp"
// Constructor
Instructions::Instructions()
: renderer_(Screen::get()->getRenderer()),
texture_(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)),
text_(Resource::get()->getText("smb2")),
tiled_bg_(std::make_unique<TiledBG>(param.game.game_area.rect, TiledBGMode::STATIC)),
fade_(std::make_unique<Fade>()) {
// Configura las texturas
SDL_SetTextureBlendMode(backbuffer_, SDL_BLENDMODE_BLEND);
SDL_SetTextureBlendMode(texture_, SDL_BLENDMODE_BLEND);
// Inicializa variables
Section::name = Section::Name::INSTRUCTIONS;
view_ = param.game.game_area.rect;
// Inicializa objetos
tiled_bg_->setColor(param.title.bg_color);
fade_->setColor(param.fade.color);
fade_->setType(Fade::Type::FULLSCREEN);
fade_->setPostDuration(param.fade.post_duration_ms);
fade_->setMode(Fade::Mode::IN);
fade_->activate();
// Inicializa las líneas con un retraso progresivo
lines_ = initializeLines(256, LINE_START_DELAY_S);
// Rellena la textura de texto
fillTexture();
// Inicializa los sprites de los items
iniSprites();
// Inicializa el timer de delta time y arranca la música
last_time_ = SDL_GetTicks();
Audio::get()->playMusic("title.ogg");
}
// Destructor
Instructions::~Instructions() {
item_textures_.clear();
sprites_.clear();
SDL_DestroyTexture(backbuffer_);
SDL_DestroyTexture(texture_);
}
// Inicializa los sprites de los items
void Instructions::iniSprites() {
// Inicializa las texturas
item_textures_.emplace_back(Resource::get()->getTexture("item_points1_disk.png"));
item_textures_.emplace_back(Resource::get()->getTexture("item_points2_gavina.png"));
item_textures_.emplace_back(Resource::get()->getTexture("item_points3_pacmar.png"));
item_textures_.emplace_back(Resource::get()->getTexture("item_clock.png"));
item_textures_.emplace_back(Resource::get()->getTexture("item_coffee.png"));
// Inicializa los sprites
for (int i = 0; std::cmp_less(i, item_textures_.size()); ++i) {
auto sprite = std::make_unique<Sprite>(item_textures_[i], 0, 0, Item::WIDTH, Item::HEIGHT);
sprite->setPosition((SDL_FPoint){.x = sprite_pos_.x, .y = sprite_pos_.y + ((Item::HEIGHT + item_space_) * i)});
sprites_.push_back(std::move(sprite));
}
}
// Actualiza los sprites
void Instructions::updateSprites() {
SDL_FRect src_rect = {.x = 0, .y = 0, .w = Item::WIDTH, .h = Item::HEIGHT};
// Disquito (desplazamiento 12/60 = 0.2s)
src_rect.y = Item::HEIGHT * (static_cast<int>((elapsed_time_ + 0.2F) / SPRITE_ANIMATION_CYCLE_S) % 2);
sprites_[0]->setSpriteClip(src_rect);
// Gavina (desplazamiento 9/60 = 0.15s)
src_rect.y = Item::HEIGHT * (static_cast<int>((elapsed_time_ + 0.15F) / SPRITE_ANIMATION_CYCLE_S) % 2);
sprites_[1]->setSpriteClip(src_rect);
// Pacmar (desplazamiento 6/60 = 0.1s)
src_rect.y = Item::HEIGHT * (static_cast<int>((elapsed_time_ + 0.1F) / SPRITE_ANIMATION_CYCLE_S) % 2);
sprites_[2]->setSpriteClip(src_rect);
// Time Stopper (desplazamiento 3/60 = 0.05s)
src_rect.y = Item::HEIGHT * (static_cast<int>((elapsed_time_ + 0.05F) / SPRITE_ANIMATION_CYCLE_S) % 2);
sprites_[3]->setSpriteClip(src_rect);
// Coffee (sin desplazamiento)
src_rect.y = Item::HEIGHT * (static_cast<int>(elapsed_time_ / SPRITE_ANIMATION_CYCLE_S) % 2);
sprites_[4]->setSpriteClip(src_rect);
}
// Rellena la textura de texto
void Instructions::fillTexture() {
const int X_OFFSET = Item::WIDTH + 8;
// Modifica el renderizador para pintar en la textura
auto* temp = SDL_GetRenderTarget(renderer_);
SDL_SetRenderTarget(renderer_, texture_);
// Limpia la textura
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
SDL_RenderClear(renderer_);
// Constantes
constexpr int NUM_LINES = 4;
constexpr int NUM_ITEM_LINES = 4;
constexpr int NUM_POST_HEADERS = 2;
constexpr int NUM_PRE_HEADERS = 1;
constexpr int SPACE_POST_HEADER = 20;
constexpr int SPACE_PRE_HEADER = 28;
const int SPACE_BETWEEN_LINES = text_->getCharacterSize() * 1.5F;
const int SPACE_BETWEEN_ITEM_LINES = Item::HEIGHT + item_space_;
const int SPACE_NEW_PARAGRAPH = SPACE_BETWEEN_LINES * 0.5F;
const int SIZE = (NUM_LINES * SPACE_BETWEEN_LINES) + (NUM_ITEM_LINES * SPACE_BETWEEN_ITEM_LINES) + (NUM_POST_HEADERS * SPACE_POST_HEADER) + (NUM_PRE_HEADERS * SPACE_PRE_HEADER) + SPACE_NEW_PARAGRAPH;
const int FIRST_LINE = (param.game.height - SIZE) / 2;
// Calcula cual es el texto más largo de las descripciones de los items
int length = 0;
const std::array<std::string, 5> ITEM_DESCRIPTIONS = {
Lang::getText("[INSTRUCTIONS] 07"),
Lang::getText("[INSTRUCTIONS] 08"),
Lang::getText("[INSTRUCTIONS] 09"),
Lang::getText("[INSTRUCTIONS] 10"),
Lang::getText("[INSTRUCTIONS] 11")};
for (const auto& desc : ITEM_DESCRIPTIONS) {
const int L = text_->length(desc);
length = L > length ? L : length;
}
const int ANCHOR_ITEM = (param.game.width - (length + X_OFFSET)) / 2;
auto caption_style = Text::Style(Text::CENTER | Text::COLOR | Text::SHADOW, Colors::ORANGE_TEXT, Colors::SHADOW_TEXT);
auto text_style = Text::Style(Text::CENTER | Text::COLOR | Text::SHADOW, Colors::NO_COLOR_MOD, Colors::SHADOW_TEXT);
// Escribe el texto de las instrucciones
text_->writeStyle(param.game.game_area.center_x, FIRST_LINE, Lang::getText("[INSTRUCTIONS] 01"), caption_style);
const int ANCHOR1 = FIRST_LINE + SPACE_POST_HEADER;
text_->writeStyle(param.game.game_area.center_x, ANCHOR1 + (SPACE_BETWEEN_LINES * 0), Lang::getText("[INSTRUCTIONS] 02"), text_style);
text_->writeStyle(param.game.game_area.center_x, ANCHOR1 + (SPACE_BETWEEN_LINES * 1), Lang::getText("[INSTRUCTIONS] 03"), text_style);
text_->writeStyle(param.game.game_area.center_x, ANCHOR1 + SPACE_NEW_PARAGRAPH + (SPACE_BETWEEN_LINES * 2), Lang::getText("[INSTRUCTIONS] 04"), text_style);
text_->writeStyle(param.game.game_area.center_x, ANCHOR1 + SPACE_NEW_PARAGRAPH + (SPACE_BETWEEN_LINES * 3), Lang::getText("[INSTRUCTIONS] 05"), text_style);
// Escribe el texto de los objetos y sus puntos
const int ANCHOR2 = ANCHOR1 + SPACE_PRE_HEADER + SPACE_NEW_PARAGRAPH + (SPACE_BETWEEN_LINES * 3);
text_->writeStyle(param.game.game_area.center_x, ANCHOR2, Lang::getText("[INSTRUCTIONS] 06"), caption_style);
const int ANCHOR3 = ANCHOR2 + SPACE_POST_HEADER;
text_->writeShadowed(ANCHOR_ITEM + X_OFFSET, ANCHOR3 + (SPACE_BETWEEN_ITEM_LINES * 0), Lang::getText("[INSTRUCTIONS] 07"), Colors::SHADOW_TEXT);
text_->writeShadowed(ANCHOR_ITEM + X_OFFSET, ANCHOR3 + (SPACE_BETWEEN_ITEM_LINES * 1), Lang::getText("[INSTRUCTIONS] 08"), Colors::SHADOW_TEXT);
text_->writeShadowed(ANCHOR_ITEM + X_OFFSET, ANCHOR3 + (SPACE_BETWEEN_ITEM_LINES * 2), Lang::getText("[INSTRUCTIONS] 09"), Colors::SHADOW_TEXT);
text_->writeShadowed(ANCHOR_ITEM + X_OFFSET, ANCHOR3 + (SPACE_BETWEEN_ITEM_LINES * 3), Lang::getText("[INSTRUCTIONS] 10"), Colors::SHADOW_TEXT);
text_->writeShadowed(ANCHOR_ITEM + X_OFFSET, ANCHOR3 + (SPACE_BETWEEN_ITEM_LINES * 4), Lang::getText("[INSTRUCTIONS] 11"), Colors::SHADOW_TEXT);
// Deja el renderizador como estaba
SDL_SetRenderTarget(renderer_, temp);
// Da valor a la variable
sprite_pos_.x = ANCHOR_ITEM;
sprite_pos_.y = ANCHOR3 - ((Item::HEIGHT - text_->getCharacterSize()) / 2);
}
// Rellena el backbuffer
void Instructions::fillBackbuffer() {
// Modifica el renderizador para pintar en la textura
auto* temp = SDL_GetRenderTarget(renderer_);
SDL_SetRenderTarget(renderer_, backbuffer_);
// Limpia la textura
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
SDL_RenderClear(renderer_);
// Coloca el texto de fondo
SDL_RenderTexture(renderer_, texture_, nullptr, nullptr);
// Dibuja los sprites
for (auto& sprite : sprites_) {
sprite->render();
}
// Deja el renderizador como estaba
SDL_SetRenderTarget(renderer_, temp);
}
// Actualiza las variables
void Instructions::update(float delta_time) {
elapsed_time_ += delta_time; // Incrementa el tiempo transcurrido
static auto* const SCREEN = Screen::get();
SCREEN->update(delta_time); // Actualiza el objeto screen
Audio::update(); // Actualiza el objeto audio
updateSprites(); // Actualiza los sprites
updateBackbuffer(delta_time); // Gestiona la textura con los graficos
tiled_bg_->update(delta_time); // Actualiza el mosaico de fondo
fade_->update(delta_time); // Actualiza el objeto "fade"
fillBackbuffer(); // Rellena el backbuffer
}
// Pinta en pantalla
void Instructions::render() {
static auto* const SCREEN = Screen::get();
SCREEN->start(); // Prepara para empezar a dibujar en la textura de juego
SCREEN->clean(); // Limpia la pantalla
tiled_bg_->render(); // Dibuja el mosacico de fondo
// Copia la textura y el backbuffer al renderizador
if (view_.y == 0) {
renderLines(renderer_, backbuffer_, lines_);
} else {
SDL_RenderTexture(renderer_, backbuffer_, nullptr, &view_);
}
fade_->render(); // Renderiza el fundido
SCREEN->render(); // Vuelca el contenido del renderizador en pantalla
}
// Comprueba los eventos
void Instructions::checkEvents() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
GlobalEvents::handle(event);
}
}
// Comprueba las entradas
void Instructions::checkInput() {
Input::get()->update();
GlobalInputs::check();
}
// Calcula el tiempo transcurrido desde el último frame
auto Instructions::calculateDeltaTime() -> float {
const Uint64 CURRENT_TIME = SDL_GetTicks();
const float DELTA_TIME = static_cast<float>(CURRENT_TIME - last_time_) / 1000.0F; // Convertir ms a segundos
last_time_ = CURRENT_TIME;
return DELTA_TIME;
}
// Avanza un frame (llamado desde Director::iterate)
void Instructions::iterate() {
const float DELTA_TIME = calculateDeltaTime();
checkInput();
update(DELTA_TIME);
render();
}
// Procesa un evento (llamado desde Director::handleEvent)
void Instructions::handleEvent(const SDL_Event& /*event*/) {
// Eventos globales ya gestionados por Director::handleEvent
}
// Bucle para la pantalla de instrucciones (fallback legacy)
void Instructions::run() {
last_time_ = SDL_GetTicks();
Audio::get()->playMusic("title.ogg");
while (Section::name == Section::Name::INSTRUCTIONS) {
const float DELTA_TIME = calculateDeltaTime();
checkInput();
update(DELTA_TIME);
checkEvents(); // Tiene que ir antes del render
render();
}
}
// Método para inicializar las líneas
auto Instructions::initializeLines(int height, float line_delay) -> std::vector<Line> {
std::vector<Line> lines;
for (int y = 0; y < height; y++) {
int direction = (y % 2 == 0) ? -1 : 1; // Pares a la izquierda, impares a la derecha
float delay = y * line_delay; // Retraso progresivo basado en la línea
lines.emplace_back(y, 0.0F, direction, delay);
}
return lines;
}
// Método para mover las líneas con suavizado (usando delta_time puro)
auto Instructions::moveLines(std::vector<Line>& lines, int width, float duration, float delta_time) -> bool {
bool all_lines_off_screen = true;
for (auto& line : lines) {
// Verificar si la línea ha superado su tiempo de retraso
if (!line.started) {
line.delay_time -= delta_time;
if (line.delay_time <= 0.0F) {
line.started = true;
line.accumulated_time = 0.0F;
} else {
all_lines_off_screen = false; // Aún hay líneas esperando para empezar
continue;
}
}
// Si la línea ya ha completado su movimiento, saltarla
if (line.accumulated_time >= duration) {
continue; // Esta línea ya terminó
}
// Acumular tiempo y calcular posición
line.accumulated_time += delta_time;
if (line.accumulated_time >= duration) {
// La línea ha completado su movimiento
line.accumulated_time = duration;
} else {
// La línea aún se está moviendo
all_lines_off_screen = false;
}
// Calcular posición con suavizado
float t = line.accumulated_time / duration;
float smooth_factor = easeInOutQuint(t);
line.x = line.direction * smooth_factor * width;
}
return all_lines_off_screen;
}
// Método para renderizar las líneas
void Instructions::renderLines(SDL_Renderer* renderer, SDL_Texture* texture, const std::vector<Line>& lines) {
for (const auto& line : lines) {
SDL_FRect src_rect = {.x = 0, .y = static_cast<float>(line.y), .w = 320, .h = 1};
SDL_FRect dst_rect = {.x = static_cast<float>(line.x), .y = static_cast<float>(line.y), .w = 320, .h = 1};
SDL_RenderTexture(renderer, texture, &src_rect, &dst_rect);
}
}
// Gestiona la textura con los graficos
void Instructions::updateBackbuffer(float delta_time) {
// Establece la ventana del backbuffer usando velocidad en pixels por segundo
// El scroll comienza desde (param.game.height + 100) y desciende a 0
// IMPORTANTE: Se redondea a entero para evitar deformaciones de textura causadas por sub-pixel rendering
float scroll_offset = elapsed_time_ * SCROLL_SPEED_PPS;
view_.y = std::round(std::max(0.0F, (param.game.height + 100.0F) - scroll_offset));
// Verifica si view_.y == 0 y gestiona el temporizador
if (view_.y == 0.0F) {
if (!start_delay_triggered_) {
// Activa el temporizador si no ha sido activado
start_delay_triggered_ = true;
start_delay_timer_ = 0.0F;
} else {
start_delay_timer_ += delta_time;
if (start_delay_timer_ >= START_DELAY_S) {
// Han pasado los segundos de retraso, mover líneas
all_lines_off_screen_ = moveLines(lines_, 320, LINE_MOVE_DURATION_S, delta_time);
}
}
}
// Comprueba si todas las líneas han terminado
if (all_lines_off_screen_) {
Section::name = Section::Name::TITLE;
Section::options = Section::Options::TITLE_1;
}
}

View File

@@ -0,0 +1,102 @@
#pragma once
#include <SDL3/SDL.h> // Para SDL_Texture, SDL_Renderer, Uint32, SDL_FPoint, SDL_FRect, Uint64
#include <memory> // Para unique_ptr, shared_ptr
#include <vector> // Para vector
class Fade;
class Sprite;
class Text;
class Texture;
class TiledBG;
/*
Esta clase gestiona un estado del programa. Se encarga de poner en pantalla
un texto explicativo para entender cómo se juega.
Además muestra algunos items y explica para qué sirven.
Utiliza dos texturas de apoyo, una con el texto ya escrito y otra donde se combina
tanto el texto de la primera textura como los sprites de los items.
Finalmente, una ventana recorre la textura para dar el efecto de que todo se desplaza
por la pantalla sobre el mosaico de fondo (gestionado por el correspondiente objeto).
*/
// --- Estructuras ---
struct Line { // Almacena información de línea animada
int y; // Coordenada Y de la línea
float x; // Coordenada X inicial (usamos float para mayor precisión en el suavizado)
int direction; // Dirección de movimiento: -1 para izquierda, 1 para derecha
float accumulated_time{0}; // Tiempo acumulado desde que empezó la animación (segundos)
float delay_time{0}; // Tiempo de retraso antes de comenzar la animación (segundos)
bool started{false}; // Indica si la línea ha comenzado a moverse
// Constructor de Line
Line(int y, float x, int direction, float delay)
: y(y),
x(x),
direction(direction),
delay_time(delay) {}
};
// Clase Instructions
class Instructions {
public:
// --- Constructor y destructor ---
Instructions();
~Instructions();
// --- Callbacks para el bucle SDL_MAIN_USE_CALLBACKS ---
void iterate(); // Ejecuta un frame
void handleEvent(const SDL_Event& event); // Procesa un evento
// --- Bucle principal legacy (fallback) ---
void run();
private:
// --- Constantes de tiempo (en segundos) ---
static constexpr float SPRITE_ANIMATION_CYCLE_S = 36.0F / 60.0F; // Ciclo de animación sprites (≈0.6s)
static constexpr float START_DELAY_S = 4.0F; // Retraso antes de mover líneas (4s)
static constexpr float LINE_MOVE_DURATION_S = 1.0F; // Duración movimiento líneas (1s)
static constexpr float LINE_START_DELAY_S = 0.005F; // Retraso entre líneas (5ms = 0.005s)
static constexpr float SCROLL_SPEED_PPS = 60.0F; // Velocidad de scroll (60 pixels por segundo)
// --- Objetos y punteros ---
SDL_Renderer* renderer_; // El renderizador de la ventana
SDL_Texture* texture_; // Textura fija con el texto
SDL_Texture* backbuffer_; // Textura para usar como backbuffer
std::vector<std::shared_ptr<Texture>> item_textures_; // Vector con las texturas de los items
std::vector<std::unique_ptr<Sprite>> sprites_; // Vector con los sprites de los items
std::shared_ptr<Text> text_; // Objeto para escribir texto
std::unique_ptr<TiledBG> tiled_bg_; // Objeto para dibujar el mosaico animado de fondo
std::unique_ptr<Fade> fade_; // Objeto para renderizar fades
// --- Variables ---
float elapsed_time_ = 0.0F; // Tiempo transcurrido (segundos)
Uint64 last_time_ = 0; // Último timestamp para calcular delta-time
SDL_FRect view_; // Vista del backbuffer que se va a mostrar por pantalla
SDL_FPoint sprite_pos_ = {.x = 0, .y = 0}; // Posición del primer sprite en la lista
float item_space_ = 2.0; // Espacio entre los items en pantalla
std::vector<Line> lines_; // Vector que contiene las líneas animadas en la pantalla
bool all_lines_off_screen_ = false; // Indica si todas las líneas han salido de la pantalla
float start_delay_timer_ = 0.0F; // Timer para retraso antes de mover líneas (segundos)
bool start_delay_triggered_ = false; // Bandera para determinar si el retraso ha comenzado
// --- Métodos internos ---
void update(float delta_time); // Actualiza las variables
void render(); // Pinta en pantalla
static void checkEvents(); // Comprueba los eventos
static void checkInput(); // Comprueba las entradas
void fillTexture(); // Rellena la textura de texto
void fillBackbuffer(); // Rellena el backbuffer
void iniSprites(); // Inicializa los sprites de los items
void updateSprites(); // Actualiza los sprites
static auto initializeLines(int height, float line_delay) -> std::vector<Line>; // Inicializa las líneas animadas
static auto moveLines(std::vector<Line>& lines, int width, float duration, float delta_time) -> bool; // Mueve las líneas usando delta_time puro
static void renderLines(SDL_Renderer* renderer, SDL_Texture* texture, const std::vector<Line>& lines); // Renderiza las líneas
void updateBackbuffer(float delta_time); // Gestiona la textura con los gráficos
auto calculateDeltaTime() -> float; // Calcula el tiempo transcurrido desde el último frame
};

View File

@@ -0,0 +1,570 @@
#include "intro.hpp"
#include <SDL3/SDL.h> // Para SDL_GetTicks, SDL_SetRenderDrawColor, SDL_FRect, SDL_RenderFillRect, SDL_GetRenderTarget, SDL_RenderClear, SDL_RenderRect, SDL_SetRenderTarget, SDL_BLENDMODE_BLEND, SDL_PixelFormat, SDL_PollEvent, SDL_RenderTexture, SDL_TextureAccess, SDL_Event, Uint64
#include <array> // Para array
#include <string> // Para basic_string, string
#include <utility> // Para move
#include "audio.hpp" // Para Audio
#include "card_sprite.hpp" // Para CardSprite
#include "color.hpp" // Para Color
#include "global_events.hpp" // Para handle
#include "global_inputs.hpp" // Para check
#include "input.hpp" // Para Input
#include "lang.hpp" // Para getText
#include "param.hpp" // Para Param, param, ParamGame, ParamIntro, ParamTitle
#include "resource.hpp" // Para Resource
#include "screen.hpp" // Para Screen
#include "section.hpp" // Para Name, name, Options, options
#include "text.hpp" // Para Text
#include "texture.hpp" // Para Texture
#include "tiled_bg.hpp" // Para TiledBG, TiledBGMode
#include "utils.hpp" // Para easeOutBounce
#include "writer.hpp" // Para Writer
// Constructor
Intro::Intro()
: tiled_bg_(std::make_unique<TiledBG>(param.game.game_area.rect, TiledBGMode::DIAGONAL)) {
// Inicializa variables
Section::name = Section::Name::INTRO;
Section::options = Section::Options::NONE;
// Inicializa las tarjetas
initSprites();
// Inicializa los textos
initTexts();
// Configura el fondo
tiled_bg_->setSpeed(TILED_BG_SPEED);
tiled_bg_->setColor(bg_color_);
// Inicializa el timer de delta time y arranca la música
last_time_ = SDL_GetTicks();
Audio::get()->playMusic("intro.ogg", 0);
}
// Comprueba los eventos
void Intro::checkEvents() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
GlobalEvents::handle(event);
}
}
// Comprueba las entradas
void Intro::checkInput() {
Input::get()->update();
GlobalInputs::check();
}
// Actualiza las escenas de la intro
void Intro::updateScenes() {
// Sonido al lanzar la tarjeta (enable() devuelve true solo la primera vez)
if (card_sprites_.at(scene_)->enable()) {
Audio::get()->playSound(SFX_CARD_THROW);
}
// Cuando la tarjeta actual toca la mesa por primera vez: shake + sonido + la anterior sale despedida
if (!shake_done_ && card_sprites_.at(scene_)->hasFirstTouch()) {
Screen::get()->shake();
Audio::get()->playSound(SFX_CARD_IMPACT);
shake_done_ = true;
if (scene_ > 0) {
card_sprites_.at(scene_ - 1)->startExit();
}
}
switch (scene_) {
case 0:
updateScene0();
break;
case 1:
updateScene1();
break;
case 2:
updateScene2();
break;
case 3:
updateScene3();
break;
case 4:
updateScene4();
break;
case 5:
updateScene5();
break;
default:
break;
}
}
void Intro::updateScene0() {
// Primer texto cuando aterriza
if (card_sprites_.at(0)->hasLanded() && !texts_.at(0)->hasFinished()) {
texts_.at(0)->setEnabled(true);
}
// Segundo texto
if (texts_.at(0)->hasFinished() && !texts_.at(1)->hasFinished()) {
switchText(0, 1);
}
// Tercer texto
if (texts_.at(1)->hasFinished() && !texts_.at(2)->hasFinished()) {
switchText(1, 2);
}
// Fin de la primera escena: la tarjeta sale despedida
if (texts_.at(2)->hasFinished()) {
texts_.at(2)->setEnabled(false);
scene_++;
shake_done_ = false;
}
}
void Intro::updateScene1() {
// Texto cuando aterriza
if (card_sprites_.at(1)->hasLanded() && !texts_.at(3)->hasFinished()) {
texts_.at(3)->setEnabled(true);
}
// Fin de la segunda escena
if (texts_.at(3)->hasFinished()) {
texts_.at(3)->setEnabled(false);
scene_++;
shake_done_ = false;
}
}
void Intro::updateScene2() {
// Tercera imagen - GRITO: tarjeta y texto a la vez
if (!texts_.at(4)->hasFinished()) {
texts_.at(4)->setEnabled(true);
}
// Fin de la tercera escena
if (card_sprites_.at(2)->hasLanded() && texts_.at(4)->hasFinished()) {
texts_.at(4)->setEnabled(false);
scene_++;
shake_done_ = false;
}
}
void Intro::updateScene3() {
// Cuarta imagen - Reflexión
if (!texts_.at(5)->hasFinished()) {
texts_.at(5)->setEnabled(true);
}
// Segundo texto
if (texts_.at(5)->hasFinished() && !texts_.at(6)->hasFinished()) {
switchText(5, 6);
}
// Fin de la cuarta escena
if (card_sprites_.at(3)->hasLanded() && texts_.at(6)->hasFinished()) {
texts_.at(6)->setEnabled(false);
scene_++;
shake_done_ = false;
}
}
void Intro::updateScene4() {
// Quinta imagen - Patada
if (!texts_.at(7)->hasFinished()) {
texts_.at(7)->setEnabled(true);
}
// Fin de la quinta escena
if (card_sprites_.at(4)->hasLanded() && texts_.at(7)->hasFinished()) {
texts_.at(7)->setEnabled(false);
scene_++;
shake_done_ = false;
}
}
void Intro::updateScene5() {
// Sexta imagen - Globos de café
if (!texts_.at(8)->hasFinished()) {
texts_.at(8)->setEnabled(true);
}
// Acaba el último texto
if (texts_.at(8)->hasFinished()) {
texts_.at(8)->setEnabled(false);
}
// Última tarjeta: sale "como si se la llevara el viento" y transición a POST
if (card_sprites_.at(5)->hasLanded() && texts_.at(8)->hasFinished()) {
card_sprites_.at(5)->startExit();
state_ = State::POST;
state_start_time_ = SDL_GetTicks() / 1000.0F;
}
}
void Intro::switchText(int from_index, int to_index) {
texts_.at(from_index)->setEnabled(false);
texts_.at(to_index)->setEnabled(true);
}
// Actualiza las variables del objeto
void Intro::update(float delta_time) {
static auto* const SCREEN = Screen::get();
SCREEN->update(delta_time); // Actualiza el objeto screen
Audio::update(); // Actualiza el objeto Audio
tiled_bg_->update(delta_time); // Actualiza el fondo
switch (state_) {
case State::SCENES:
// Pausa inicial antes de empezar
if (initial_elapsed_ < INITIAL_DELAY_S) {
initial_elapsed_ += delta_time;
break;
}
updateSprites(delta_time);
updateTexts(delta_time);
updateScenes();
break;
case State::POST:
updateSprites(delta_time); // La última tarjeta puede estar saliendo durante POST
updatePostState();
break;
}
}
// Dibuja el objeto en pantalla
void Intro::render() {
static auto* const SCREEN = Screen::get();
SCREEN->start(); // Prepara para empezar a dibujar en la textura de juego
SCREEN->clean(); // Limpia la pantalla
tiled_bg_->render(); // Dibuja el fondo
switch (state_) {
case State::SCENES: {
renderTextRect();
renderSprites();
renderTexts();
break;
}
case State::POST:
renderSprites(); // La última tarjeta puede estar saliendo
break;
}
SCREEN->render(); // Vuelca el contenido del renderizador en pantalla
}
// Calcula el tiempo transcurrido desde el último frame
auto Intro::calculateDeltaTime() -> float {
const Uint64 CURRENT_TIME = SDL_GetTicks();
const float DELTA_TIME = static_cast<float>(CURRENT_TIME - last_time_) / 1000.0F; // Convertir ms a segundos
last_time_ = CURRENT_TIME;
return DELTA_TIME;
}
// Avanza un frame (llamado desde Director::iterate)
void Intro::iterate() {
const float DELTA_TIME = calculateDeltaTime();
checkInput();
update(DELTA_TIME);
render();
}
// Procesa un evento (llamado desde Director::handleEvent)
void Intro::handleEvent(const SDL_Event& /*event*/) {
// Eventos globales ya gestionados por Director::handleEvent
}
// Bucle principal legacy (fallback)
void Intro::run() {
last_time_ = SDL_GetTicks();
Audio::get()->playMusic("intro.ogg", 0);
while (Section::name == Section::Name::INTRO) {
const float DELTA_TIME = calculateDeltaTime();
checkInput();
update(DELTA_TIME);
checkEvents(); // Tiene que ir antes del render
render();
}
}
// Inicializa las tarjetas
void Intro::initSprites() {
// Listado de imagenes a usar
const std::array<std::string, 6> TEXTURE_LIST = {
"intro1.png",
"intro2.png",
"intro3.png",
"intro4.png",
"intro5.png",
"intro6.png"};
// Constantes
constexpr int TOTAL_SPRITES = TEXTURE_LIST.size();
const float BORDER = CARD_BORDER_SIZE;
auto texture = Resource::get()->getTexture(TEXTURE_LIST.front());
const float CARD_WIDTH = texture->getWidth() + (BORDER * 2);
const float CARD_HEIGHT = texture->getHeight() + (BORDER * 2);
// Crea las texturas para las tarjetas (imagen con marco)
std::vector<std::shared_ptr<Texture>> card_textures;
for (int i = 0; i < TOTAL_SPRITES; ++i) {
auto card_texture = std::make_unique<Texture>(Screen::get()->getRenderer());
card_texture->createBlank(CARD_WIDTH, CARD_HEIGHT, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET);
card_texture->setBlendMode(SDL_BLENDMODE_BLEND);
auto* temp = SDL_GetRenderTarget(Screen::get()->getRenderer());
card_texture->setAsRenderTarget(Screen::get()->getRenderer());
SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 0, 0, 0);
SDL_RenderClear(Screen::get()->getRenderer());
// Marco de la tarjeta
auto color = param.intro.card_color;
SDL_SetRenderDrawColor(Screen::get()->getRenderer(), color.r, color.g, color.b, color.a);
SDL_FRect rect1 = {.x = 1, .y = 0, .w = CARD_WIDTH - 2, .h = CARD_HEIGHT};
SDL_FRect rect2 = {.x = 0, .y = 1, .w = CARD_WIDTH, .h = CARD_HEIGHT - 2};
SDL_RenderRect(Screen::get()->getRenderer(), &rect1);
SDL_RenderRect(Screen::get()->getRenderer(), &rect2);
// Imagen dentro del marco
SDL_FRect dest = {.x = BORDER, .y = BORDER, .w = CARD_WIDTH - (BORDER * 2), .h = CARD_HEIGHT - (BORDER * 2)};
SDL_RenderTexture(Screen::get()->getRenderer(), Resource::get()->getTexture(TEXTURE_LIST.at(i))->getSDLTexture(), nullptr, &dest);
SDL_SetRenderTarget(Screen::get()->getRenderer(), temp);
card_textures.push_back(std::move(card_texture));
}
// Crea la textura de sombra (compartida entre todas las tarjetas)
auto shadow_texture = std::make_shared<Texture>(Screen::get()->getRenderer());
shadow_texture->createBlank(CARD_WIDTH, CARD_HEIGHT, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET);
shadow_texture->setBlendMode(SDL_BLENDMODE_BLEND);
auto* temp = SDL_GetRenderTarget(Screen::get()->getRenderer());
shadow_texture->setAsRenderTarget(Screen::get()->getRenderer());
SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 0, 0, 0);
SDL_RenderClear(Screen::get()->getRenderer());
auto shadow_color = param.intro.shadow_color;
SDL_SetRenderDrawColor(Screen::get()->getRenderer(), shadow_color.r, shadow_color.g, shadow_color.b, Color::MAX_ALPHA_VALUE);
SDL_FRect shadow_rect1 = {.x = 1, .y = 0, .w = CARD_WIDTH - 2, .h = CARD_HEIGHT};
SDL_FRect shadow_rect2 = {.x = 0, .y = 1, .w = CARD_WIDTH, .h = CARD_HEIGHT - 2};
SDL_RenderFillRect(Screen::get()->getRenderer(), &shadow_rect1);
SDL_RenderFillRect(Screen::get()->getRenderer(), &shadow_rect2);
SDL_SetRenderTarget(Screen::get()->getRenderer(), temp);
shadow_texture->setAlpha(shadow_color.a);
// Posición de aterrizaje (centro de la zona de juego)
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);
// Configuración por tarjeta: posición de entrada, ángulo, salida
// Cada tarjeta viene de un borde diferente (gente alrededor de una mesa lanzando cartas al centro)
struct CardConfig {
float entry_x; // Posición inicial X
float entry_y; // Posición inicial Y
double entry_angle; // Ángulo de entrada
float exit_vx; // Velocidad de salida X
float exit_vy; // Velocidad de salida Y
float exit_ax; // Aceleración de salida X
float exit_ay; // Aceleración de salida Y
double exit_rotation; // Velocidad de rotación de salida
};
const float W = param.game.width;
const float H = param.game.height;
const float S = CARD_EXIT_SPEED;
const float A = CARD_EXIT_ACCEL;
const double R = CARD_EXIT_ROTATION;
const CardConfig CARD_CONFIGS[] = {
// 0: Entra desde la izquierda. La 1 entra desde la derecha → sale empujada hacia la izquierda
{-CARD_WIDTH, Y_DEST - 20.0F, CARD_ANGLE_0, -S, S * 0.1F, -A, 0.0F, -R},
// 1: Entra desde la derecha. La 2 entra desde arriba → sale empujada hacia abajo
{W + CARD_WIDTH, Y_DEST + 15.0F, CARD_ANGLE_1, S * 0.15F, S, 0.0F, A, R * 1.1},
// 2: Entra desde arriba. La 3 entra desde abajo → sale empujada hacia arriba
{X_DEST + 30.0F, -CARD_HEIGHT, CARD_ANGLE_2, -S * 0.15F, -S, 0.0F, -A, -R * 0.9},
// 3: Entra desde abajo. La 4 entra desde arriba-izquierda → sale empujada hacia abajo-derecha
{X_DEST - 25.0F, H + CARD_HEIGHT, CARD_ANGLE_3, S * 0.8F, S * 0.6F, A * 0.5F, A * 0.4F, R},
// 4: Entra desde arriba-izquierda. La 5 entra desde derecha-abajo → sale empujada hacia arriba-izquierda
{-CARD_WIDTH * 0.5F, -CARD_HEIGHT, CARD_ANGLE_4, -S * 0.7F, -S * 0.5F, -A * 0.5F, -A * 0.3F, -R * 1.2},
// 5: Entra desde la derecha-abajo. Última: sale hacia la izquierda suave (viento)
{W + CARD_WIDTH, H * 0.6F, CARD_ANGLE_5, -S * 0.6F, -S * 0.1F, -A * 0.5F, 0.0F, -R * 0.7},
};
// Inicializa los CardSprites
for (int i = 0; i < TOTAL_SPRITES; ++i) {
auto card = std::make_unique<CardSprite>(card_textures.at(i));
card->setWidth(CARD_WIDTH);
card->setHeight(CARD_HEIGHT);
card->setSpriteClip(0, 0, CARD_WIDTH, CARD_HEIGHT);
const auto& cfg = CARD_CONFIGS[i];
// Posición de aterrizaje (centro)
card->setLandingPosition(X_DEST, Y_DEST);
// Posición de entrada (borde de pantalla)
card->setEntryPosition(cfg.entry_x, cfg.entry_y);
// Parámetros de entrada: zoom, ángulo, duración, easing
card->setEntryParams(CARD_START_ZOOM, cfg.entry_angle, CARD_ENTRY_DURATION_S, easeOutBounce);
// Parámetros de salida
card->setExitParams(cfg.exit_vx, cfg.exit_vy, cfg.exit_ax, cfg.exit_ay, cfg.exit_rotation);
// Sombra
card->setShadowTexture(shadow_texture);
card->setShadowOffset(SHADOW_OFFSET, SHADOW_OFFSET);
// Límites de pantalla
card->setScreenBounds(param.game.width, param.game.height);
// Última tarjeta: gana algo de altura al salir (se la lleva el viento)
if (i == TOTAL_SPRITES - 1) {
card->setExitLift(1.2F, 0.15F); // Hasta zoom 1.2, a 0.15/s
}
card_sprites_.push_back(std::move(card));
}
}
// Inicializa los textos
void Intro::initTexts() {
constexpr int TOTAL_TEXTS = 9;
for (int i = 0; i < TOTAL_TEXTS; ++i) {
auto writer = std::make_unique<Writer>(Resource::get()->getText("04b_25_metal"));
writer->setPosX(0);
writer->setPosY(param.game.height - param.intro.text_distance_from_bottom);
writer->setKerning(TEXT_KERNING);
writer->setEnabled(false);
writer->setFinishedTimerS(TEXT_DISPLAY_DURATION_S);
texts_.push_back(std::move(writer));
}
// Un dia qualsevol de l'any 2000
texts_.at(0)->setCaption(Lang::getText("[INTRO] 1"));
texts_.at(0)->setSpeedS(TEXT_SPEED_NORMAL);
// Tot esta tranquil a la UPV
texts_.at(1)->setCaption(Lang::getText("[INTRO] 2"));
texts_.at(1)->setSpeedS(TEXT_SPEED_NORMAL);
// Fins que un desaprensiu...
texts_.at(2)->setCaption(Lang::getText("[INTRO] 3"));
texts_.at(2)->setSpeedS(TEXT_SPEED_SLOW);
// HEY! ME ANE A FERME UN CORTAET...
texts_.at(3)->setCaption(Lang::getText("[INTRO] 4"));
texts_.at(3)->setSpeedS(TEXT_SPEED_NORMAL);
// UAAAAAAAAAAAAA!!!
texts_.at(4)->setCaption(Lang::getText("[INTRO] 5"));
texts_.at(4)->setSpeedS(TEXT_SPEED_ULTRA_FAST);
// Espera un moment...
texts_.at(5)->setCaption(Lang::getText("[INTRO] 6"));
texts_.at(5)->setSpeedS(TEXT_SPEED_VERY_SLOW);
// Si resulta que no tinc solt!
texts_.at(6)->setCaption(Lang::getText("[INTRO] 7"));
texts_.at(6)->setSpeedS(TEXT_SPEED_VERY_FAST);
// MERDA DE MAQUINA!
texts_.at(7)->setCaption(Lang::getText("[INTRO] 8"));
texts_.at(7)->setSpeedS(TEXT_SPEED_FAST);
// Blop... blop... blop...
texts_.at(8)->setCaption(Lang::getText("[INTRO] 9"));
texts_.at(8)->setSpeedS(TEXT_SPEED_ULTRA_SLOW);
for (auto& text : texts_) {
text->center(param.game.game_area.center_x);
}
}
// Actualiza los sprites
void Intro::updateSprites(float delta_time) {
for (auto& sprite : card_sprites_) {
sprite->update(delta_time);
}
}
// Actualiza los textos
void Intro::updateTexts(float delta_time) {
for (auto& text : texts_) {
text->updateS(delta_time); // Usar updateS para delta_time en segundos
}
}
// Dibuja los sprites (todas las tarjetas activas, para que convivan la saliente y la entrante)
void Intro::renderSprites() {
for (auto& card : card_sprites_) {
card->render();
}
}
// Dibuja los textos
void Intro::renderTexts() {
for (const auto& text : texts_) {
text->render();
}
}
// Actualiza el estado POST
void Intro::updatePostState() {
const float ELAPSED_TIME = (SDL_GetTicks() / 1000.0F) - state_start_time_;
switch (post_state_) {
case PostState::STOP_BG:
// EVENTO: Detiene el fondo después del tiempo especificado
if (ELAPSED_TIME >= POST_BG_STOP_DELAY_S) {
tiled_bg_->stopGracefully();
if (!bg_color_.IS_EQUAL_TO(param.title.bg_color)) {
bg_color_ = bg_color_.APPROACH_TO(param.title.bg_color, 1);
}
tiled_bg_->setColor(bg_color_);
}
// Cambia de estado si el fondo se ha detenido y recuperado el color
if (tiled_bg_->isStopped() && bg_color_.IS_EQUAL_TO(param.title.bg_color)) {
post_state_ = PostState::END;
state_start_time_ = SDL_GetTicks() / 1000.0F;
}
break;
case PostState::END:
// Finaliza la intro después del tiempo especificado
if (ELAPSED_TIME >= POST_END_DELAY_S) {
Audio::get()->stopMusic();
Section::name = Section::Name::TITLE;
Section::options = Section::Options::TITLE_1;
}
break;
default:
break;
}
}
void Intro::renderTextRect() {
static const float HEIGHT = Resource::get()->getText("04b_25_metal")->getCharacterSize();
static SDL_FRect rect_ = {.x = 0.0F, .y = param.game.height - param.intro.text_distance_from_bottom - HEIGHT, .w = param.game.width, .h = HEIGHT * 3};
SDL_SetRenderDrawColor(Screen::get()->getRenderer(), param.intro.shadow_color.r, param.intro.shadow_color.g, param.intro.shadow_color.b, param.intro.shadow_color.a);
SDL_RenderFillRect(Screen::get()->getRenderer(), &rect_);
}

View File

@@ -0,0 +1,133 @@
#pragma once
#include <SDL3/SDL.h> // Para Uint32, Uint64
#include <memory> // Para unique_ptr
#include <vector> // Para vector
#include "card_sprite.hpp" // Para CardSprite
#include "color.hpp" // Para Color
#include "param.hpp" // Para Param, ParamIntro, param
#include "tiled_bg.hpp" // Para TiledBG
#include "writer.hpp" // Para Writer
// --- Clase Intro: secuencia cinemática de introducción del juego ---
//
// Esta clase gestiona la secuencia de introducción narrativa del juego, mostrando
// 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: efecto de lanzamiento sobre mesa con zoom, rotación y rebote
// • 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
class Intro {
public:
// --- Constructor y destructor ---
Intro();
~Intro() = default;
// --- Callbacks para el bucle SDL_MAIN_USE_CALLBACKS ---
void iterate(); // Ejecuta un frame
void handleEvent(const SDL_Event& event); // Procesa un evento
// --- Bucle principal legacy (fallback) ---
void run();
private:
// --- Constantes de tiempo (en segundos) ---
static constexpr float TEXT_DISPLAY_DURATION_S = 3.0F; // Duración de visualización de texto
static constexpr float POST_BG_STOP_DELAY_S = 1.0F; // Retraso antes de detener el fondo
static constexpr float POST_END_DELAY_S = 1.0F; // Retraso antes de finalizar intro
static constexpr float INITIAL_DELAY_S = 2.0F; // Pausa antes de empezar las escenas
// --- Constantes de sonido ---
static constexpr const char* SFX_CARD_THROW = "service_menu_select.wav"; // Sonido al lanzar una tarjeta
static constexpr const char* SFX_CARD_IMPACT = "player_collision.wav"; // Sonido al impactar en la mesa
// --- 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 float TILED_BG_SPEED = 18.0F; // Velocidad del fondo mosaico (pixels/segundo)
static constexpr int TEXT_KERNING = -2; // Espaciado entre caracteres
// --- Constantes de velocidades de texto (segundos entre caracteres, menor = más rápido) ---
static constexpr float TEXT_SPEED_ULTRA_FAST = 0.0167F; // Ultra rápida (1 frame a 60fps)
static constexpr float TEXT_SPEED_VERY_FAST = 0.033F; // Muy rápida (2 frames a 60fps)
static constexpr float TEXT_SPEED_FAST = 0.05F; // Rápida (3 frames a 60fps)
static constexpr float TEXT_SPEED_NORMAL = 0.133F; // Normal (8 frames a 60fps)
static constexpr float TEXT_SPEED_SLOW = 0.2F; // Lenta (12 frames a 60fps)
static constexpr float TEXT_SPEED_VERY_SLOW = 0.267F; // Muy lenta (16 frames a 60fps)
static constexpr float TEXT_SPEED_ULTRA_SLOW = 0.333F; // Ultra lenta (20 frames a 60fps)
// --- Constantes de animaciones de tarjetas ---
static constexpr float CARD_ENTRY_DURATION_S = 1.5F; // Duración de la animación de entrada
static constexpr float CARD_START_ZOOM = 1.8F; // Zoom inicial (como si estuviera cerca)
static constexpr float CARD_EXIT_SPEED = 400.0F; // Velocidad base de salida (pixels/s)
static constexpr float CARD_EXIT_ACCEL = 200.0F; // Aceleración de salida (pixels/s²)
static constexpr double CARD_EXIT_ROTATION = 450.0; // Velocidad de rotación en salida (grados/s)
// --- Ángulos iniciales de entrada por tarjeta (grados) ---
static constexpr double CARD_ANGLE_0 = 12.0;
static constexpr double CARD_ANGLE_1 = -15.0;
static constexpr double CARD_ANGLE_2 = 8.0;
static constexpr double CARD_ANGLE_3 = -10.0;
static constexpr double CARD_ANGLE_4 = 18.0;
static constexpr double CARD_ANGLE_5 = -7.0;
// --- Estados internos ---
enum class State {
SCENES,
POST,
};
enum class PostState {
STOP_BG,
END,
};
// --- Objetos ---
std::vector<std::unique_ptr<CardSprite>> card_sprites_; // Tarjetas animadas con sombra integrada
std::vector<std::unique_ptr<Writer>> texts_; // Textos de la intro
std::unique_ptr<TiledBG> tiled_bg_; // Fondo en mosaico
// --- Variables ---
Uint64 last_time_ = 0; // Último timestamp para calcular delta-time
int scene_ = 0; // Indica qué escena está activa
State state_ = State::SCENES; // Estado principal de la intro
PostState post_state_ = PostState::STOP_BG; // Estado POST
float state_start_time_ = 0.0F; // Tiempo de inicio del estado actual (segundos)
Color bg_color_ = param.intro.bg_color; // Color de fondo
bool shake_done_ = false; // Evita shake repetido en la misma escena
float initial_elapsed_ = 0.0F; // Tiempo acumulado antes de empezar
// --- Métodos internos ---
void update(float delta_time); // Actualiza las variables del objeto
void render(); // Dibuja el objeto en pantalla
static void checkInput(); // Comprueba las entradas
static void checkEvents(); // Comprueba los eventos
void updateScenes(); // Actualiza las escenas de la intro
void initSprites(); // Inicializa las tarjetas
void initTexts(); // Inicializa los textos
void updateSprites(float delta_time); // Actualiza los sprites
void updateTexts(float delta_time); // Actualiza los textos
void renderSprites(); // Dibuja los sprites
void renderTexts(); // Dibuja los textos
static void renderTextRect(); // Dibuja el rectángulo de fondo del texto
void updatePostState(); // Actualiza el estado POST
auto calculateDeltaTime() -> float; // Calcula el tiempo transcurrido desde el último frame
// --- Métodos para manejar cada escena individualmente ---
void updateScene0();
void updateScene1();
void updateScene2();
void updateScene3();
void updateScene4();
void updateScene5();
// --- Métodos auxiliares ---
void switchText(int from_index, int to_index);
};

212
source/game/scenes/logo.cpp Normal file
View File

@@ -0,0 +1,212 @@
#include "logo.hpp"
#include <SDL3/SDL.h> // Para SDL_GetTicks, SDL_PollEvent, SDL_Event, SDL_FRect, Uint64
#include <cstddef> // Para size_t
#include <string> // Para basic_string
#include <utility> // Para move
#include "audio.hpp" // Para Audio
#include "color.hpp" // Para Color
#include "global_events.hpp" // Para handle
#include "global_inputs.hpp" // Para check
#include "input.hpp" // Para Input
#include "param.hpp" // Para Param, ParamGame, param
#include "resource.hpp" // Para Resource
#include "screen.hpp" // Para Screen
#include "section.hpp" // Para Name, name
#include "sprite.hpp" // Para Sprite
#include "texture.hpp" // Para Texture
#include "utils.hpp" // Para Zone
// Constructor
Logo::Logo()
: since_texture_(Resource::get()->getTexture("logo_since_1998.png")),
since_sprite_(std::make_unique<Sprite>(since_texture_)),
jail_texture_(Resource::get()->getTexture("logo_jailgames.png")) {
// Inicializa variables
Section::name = Section::Name::LOGO;
dest_.x = param.game.game_area.center_x - (jail_texture_->getWidth() / 2);
dest_.y = param.game.game_area.center_y - (jail_texture_->getHeight() / 2);
since_sprite_->setPosition(SDL_FRect{
.x = static_cast<float>((param.game.width - since_texture_->getWidth()) / 2),
.y = static_cast<float>(SINCE_SPRITE_Y_OFFSET + jail_texture_->getHeight() + LOGO_SPACING),
.w = static_cast<float>(since_texture_->getWidth()),
.h = static_cast<float>(since_texture_->getHeight())});
since_sprite_->setY(dest_.y + jail_texture_->getHeight() + LOGO_SPACING);
since_sprite_->setSpriteClip(0, 0, since_texture_->getWidth(), since_texture_->getHeight());
since_texture_->setColor(SPECTRUM_BLACK.r, SPECTRUM_BLACK.g, SPECTRUM_BLACK.b);
// Crea los sprites de cada linea
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);
temp->setSpriteClip(0, i, jail_texture_->getWidth(), SPRITE_LINE_HEIGHT);
const int POS_X = (i % 2 == 0) ? param.game.width + (i * LINE_OFFSET_FACTOR) : -jail_texture_->getWidth() - (i * LINE_OFFSET_FACTOR);
temp->setX(POS_X);
temp->setY(dest_.y + i);
jail_sprite_.push_back(std::move(temp));
}
// Inicializa el timer de delta time para el primer frame del callback
last_time_ = SDL_GetTicks();
// Inicializa el vector de colores con la paleta ZX Spectrum
color_.emplace_back(SPECTRUM_BLACK);
color_.emplace_back(SPECTRUM_BLUE);
color_.emplace_back(SPECTRUM_RED);
color_.emplace_back(SPECTRUM_MAGENTA);
color_.emplace_back(SPECTRUM_GREEN);
color_.emplace_back(SPECTRUM_CYAN);
color_.emplace_back(SPECTRUM_YELLOW);
color_.emplace_back(SPECTRUM_WHITE);
}
// Destructor
Logo::~Logo() {
jail_texture_->setColor(RESET_COLOR.r, RESET_COLOR.g, RESET_COLOR.b);
since_texture_->setColor(RESET_COLOR.r, RESET_COLOR.g, RESET_COLOR.b);
Audio::get()->stopAllSounds();
Audio::get()->stopMusic();
}
// Comprueba el manejador de eventos
void Logo::checkEvents() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
GlobalEvents::handle(event);
}
}
// Comprueba las entradas
void Logo::checkInput() {
Input::get()->update();
GlobalInputs::check();
}
// Maneja la reproducción del sonido del logo
void Logo::handleSound() {
if (!sound_triggered_ && elapsed_time_s_ >= SOUND_TRIGGER_TIME_S) {
Audio::get()->playSound("logo.wav");
sound_triggered_ = true;
}
}
// Gestiona el logo de JAILGAMES
void Logo::updateJAILGAMES(float delta_time) {
if (elapsed_time_s_ > SOUND_TRIGGER_TIME_S) {
const float PIXELS_TO_MOVE = LOGO_SPEED_PX_PER_S * delta_time;
for (size_t i = 0; i < jail_sprite_.size(); ++i) {
if (jail_sprite_[i]->getX() != dest_.x) {
if (i % 2 == 0) {
jail_sprite_[i]->incX(-PIXELS_TO_MOVE);
if (jail_sprite_[i]->getX() < dest_.x) {
jail_sprite_[i]->setX(dest_.x);
}
} else {
jail_sprite_[i]->incX(PIXELS_TO_MOVE);
if (jail_sprite_[i]->getX() > dest_.x) {
jail_sprite_[i]->setX(dest_.x);
}
}
}
}
}
// Comprueba si ha terminado el logo
if (elapsed_time_s_ >= END_LOGO_TIME_S + POST_LOGO_DURATION_S) {
Section::name = Section::Name::INTRO;
}
}
// Gestiona el color de las texturas
void Logo::updateTextureColors(float delta_time) {
// Manejo de 'sinceTexture'
for (int i = 0; i <= MAX_SINCE_COLOR_INDEX; ++i) {
const float TARGET_TIME = SHOW_SINCE_SPRITE_TIME_S + (COLOR_CHANGE_INTERVAL_S * i);
if (elapsed_time_s_ >= TARGET_TIME && elapsed_time_s_ - delta_time < TARGET_TIME) {
since_texture_->setColor(color_[i].r, color_[i].g, color_[i].b);
}
}
// Manejo de 'jailTexture' y 'sinceTexture' en el fade
for (int i = 0; i <= MAX_FADE_COLOR_INDEX; ++i) {
const float TARGET_TIME = INIT_FADE_TIME_S + (COLOR_CHANGE_INTERVAL_S * i);
if (elapsed_time_s_ >= TARGET_TIME && elapsed_time_s_ - delta_time < TARGET_TIME) {
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_[MAX_FADE_COLOR_INDEX - i].r, color_[MAX_FADE_COLOR_INDEX - i].g, color_[MAX_FADE_COLOR_INDEX - i].b);
}
}
}
// Actualiza las variables
void Logo::update(float delta_time) {
elapsed_time_s_ += delta_time; // Acumula el tiempo transcurrido
static auto* const SCREEN = Screen::get();
SCREEN->update(delta_time); // Actualiza el objeto screen
Audio::update(); // Actualiza el objeto audio
handleSound(); // Maneja la reproducción del sonido
updateTextureColors(delta_time); // Actualiza los colores de las texturas
updateJAILGAMES(delta_time); // Actualiza el logo de JAILGAMES
}
// Dibuja en pantalla
void Logo::render() {
static auto* const SCREEN = Screen::get();
SCREEN->start();
SCREEN->clean();
renderJAILGAMES();
SCREEN->render();
}
// Calcula el tiempo transcurrido desde el último frame
auto Logo::calculateDeltaTime() -> float {
const Uint64 CURRENT_TIME = SDL_GetTicks();
const float DELTA_TIME = static_cast<float>(CURRENT_TIME - last_time_) / 1000.0F; // Convertir ms a segundos
last_time_ = CURRENT_TIME;
return DELTA_TIME;
}
// Avanza un frame del logo (llamado desde Director::iterate)
void Logo::iterate() {
const float DELTA_TIME = calculateDeltaTime();
checkInput();
update(DELTA_TIME);
render();
}
// Procesa un evento (llamado desde Director::handleEvent)
void Logo::handleEvent(const SDL_Event& /*event*/) {
// Eventos globales (QUIT, resize, hotplug) ya gestionados por Director::handleEvent
}
// Bucle para el logo del juego (fallback legacy)
void Logo::run() {
last_time_ = SDL_GetTicks();
while (Section::name == Section::Name::LOGO) {
const float DELTA_TIME = calculateDeltaTime();
checkInput();
update(DELTA_TIME);
checkEvents(); // Tiene que ir antes del render
render();
}
}
// Renderiza el logo de JAILGAMES
void Logo::renderJAILGAMES() {
// Dibuja los sprites
for (auto& sprite : jail_sprite_) {
sprite->render();
}
if (elapsed_time_s_ >= SHOW_SINCE_SPRITE_TIME_S) {
since_sprite_->render();
}
}

View File

@@ -0,0 +1,95 @@
#pragma once
#include <SDL3/SDL.h> // Para SDL_FPoint, Uint64
#include <memory> // Para shared_ptr, unique_ptr
#include <vector> // Para vector
#include "color.hpp" // for Color
class Sprite;
class Texture;
// --- Clase Logo: pantalla de presentación de JAILGAMES con efectos retro ---
//
// Esta clase gestiona el estado inicial del programa, mostrando el logo corporativo
// de JAILGAMES con efectos visuales inspirados en el ZX Spectrum.
//
// Funcionalidades principales:
// • 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 segundos para garantizar
// consistencia visual en diferentes velocidades de procesamiento.
class Logo {
public:
// --- Constructor y destructor ---
Logo();
~Logo();
// --- Callbacks para el bucle SDL_MAIN_USE_CALLBACKS ---
void iterate(); // Ejecuta un frame
void handleEvent(const SDL_Event& event); // Procesa un evento
// --- Bucle principal legacy (fallback) ---
void run();
private:
// --- Constantes de tiempo (en segundos) ---
static constexpr float SOUND_TRIGGER_TIME_S = 0.5F; // Tiempo para activar el sonido del logo
static constexpr float SHOW_SINCE_SPRITE_TIME_S = 1.167F; // Tiempo para mostrar el sprite "SINCE 1998"
static constexpr float INIT_FADE_TIME_S = 5.0F; // Tiempo de inicio del fade a negro
static constexpr float END_LOGO_TIME_S = 6.668F; // Tiempo de finalización del logo
static constexpr float POST_LOGO_DURATION_S = 0.333F; // Duración adicional después del fade
static constexpr float LOGO_SPEED_PX_PER_S = 480.0F; // Velocidad de desplazamiento (píxeles por segundo) - 8.0f/16.67f*1000
static constexpr float COLOR_CHANGE_INTERVAL_S = 0.0667F; // 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 ---
std::shared_ptr<Texture> since_texture_; // Textura con los gráficos "Since 1998"
std::unique_ptr<Sprite> since_sprite_; // Sprite para manejar la since_texture
std::shared_ptr<Texture> jail_texture_; // Textura con los gráficos "JAILGAMES"
std::vector<std::unique_ptr<Sprite>> jail_sprite_; // Vector con los sprites de cada línea que forman el bitmap JAILGAMES
// --- Variables ---
std::vector<Color> color_; // Vector con los colores para el fade
float elapsed_time_s_ = 0.0F; // Tiempo transcurrido en segundos
Uint64 last_time_ = 0; // Último timestamp para calcular delta-time
SDL_FPoint dest_; // Posición donde dibujar el logo
bool sound_triggered_ = false; // Indica si el sonido del logo ya se reprodujo
// --- Métodos internos ---
void update(float delta_time); // Actualiza las variables
void render(); // Dibuja en pantalla
static void checkEvents(); // Comprueba el manejador de eventos
static void checkInput(); // Comprueba las entradas
void updateJAILGAMES(float delta_time); // Gestiona el logo de JAILGAMES
void renderJAILGAMES(); // Renderiza el logo de JAILGAMES
void updateTextureColors(float delta_time); // Gestiona el color de las texturas
void handleSound(); // Maneja la reproducción del sonido del logo
auto calculateDeltaTime() -> float; // Calcula el tiempo transcurrido desde el último frame
};

View File

@@ -0,0 +1,524 @@
#include "title.hpp"
#include <SDL3/SDL.h> // Para SDL_GetTicks, SDL_Event, SDL_Keycode, SDL_PollEvent, SDLK_A, SDLK_C, SDLK_D, SDLK_F, SDLK_S, SDLK_V, SDLK_X, SDLK_Z, SDL_EventType, Uint64
#include <ranges> // Para __find_if_fn, find_if
#include <string> // Para basic_string, char_traits, operator+, to_string, string
#include <vector> // Para vector
#include "audio.hpp" // Para Audio
#include "color.hpp" // Para Color, NO_COLOR_MOD, TITLE_SHADOW_TEXT
#include "fade.hpp" // Para Fade
#include "game_logo.hpp" // Para GameLogo
#include "global_events.hpp" // Para handle
#include "global_inputs.hpp" // Para check
#include "input.hpp" // Para Input
#include "input_types.hpp" // Para InputAction
#include "lang.hpp" // Para getText
#include "options.hpp" // Para Gamepad, GamepadManager, gamepad_manager, Settings, settings, Keyboard, keyboard, getPlayerWhoUsesKeyboard, swapControllers, swapKeyboard
#include "param.hpp" // Para Param, param, ParamGame, ParamTitle, ParamFade
#include "player.hpp" // Para Player
#include "resource.hpp" // Para Resource
#include "screen.hpp" // Para Screen
#include "section.hpp" // Para Name, name, Options, options, AttractMode, attract_mode
#include "sprite.hpp" // Para Sprite
#include "text.hpp" // Para Text
#include "tiled_bg.hpp" // Para TiledBG, TiledBGMode
#include "ui/notifier.hpp" // Para Notifier
#include "ui/service_menu.hpp" // Para ServiceMenu
#include "utils.hpp" // Para Zone, BLOCK
class Texture;
// Constructor
Title::Title()
: text_(Resource::get()->getText("smb2_grad")),
fade_(std::make_unique<Fade>()),
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)),
mini_logo_sprite_(std::make_unique<Sprite>(Resource::get()->getTexture("logo_jailgames_mini.png"))),
state_(State::LOGO_ANIMATING),
num_controllers_(Input::get()->getNumGamepads()) {
// Configura objetos
tiled_bg_->setColor(param.title.bg_color);
tiled_bg_->setSpeed(0.0F);
game_logo_->enable();
mini_logo_sprite_->setX(param.game.game_area.center_x - (mini_logo_sprite_->getWidth() / 2));
fade_->setColor(param.fade.color);
fade_->setType(Fade::Type::RANDOM_SQUARE2);
fade_->setPostDuration(param.fade.post_duration_ms);
initPlayers();
// Asigna valores a otras variables
Section::options = Section::Options::TITLE_1;
const bool IS_TITLE_TO_DEMO = (Section::attract_mode == Section::AttractMode::TITLE_TO_DEMO);
next_section_ = IS_TITLE_TO_DEMO ? Section::Name::GAME_DEMO : Section::Name::LOGO;
Section::attract_mode = IS_TITLE_TO_DEMO ? Section::AttractMode::TITLE_TO_LOGO : Section::AttractMode::TITLE_TO_DEMO;
// Define los anclajes de los elementos
anchor_.mini_logo = (param.game.height / MINI_LOGO_Y_DIVISOR * MINI_LOGO_Y_FACTOR) + BLOCK;
mini_logo_sprite_->setY(anchor_.mini_logo);
anchor_.copyright_text = anchor_.mini_logo + mini_logo_sprite_->getHeight() + COPYRIGHT_TEXT_SPACING;
// Inicializa el timer de delta time para el primer frame del callback
last_time_ = SDL_GetTicks();
}
// Destructor
Title::~Title() {
Audio::get()->stopAllSounds();
if (Section::name == Section::Name::LOGO) {
Audio::get()->fadeOutMusic(MUSIC_FADE_OUT_SHORT_MS);
}
// Desregistra los jugadores de Options
Options::keyboard.clearPlayers();
Options::gamepad_manager.clearPlayers();
}
// Actualiza las variables del objeto
void Title::update(float delta_time) {
static auto* const SCREEN = Screen::get();
SCREEN->update(delta_time); // Actualiza el objeto screen
Audio::update(); // Actualiza el objeto audio
updateFade();
updateState(delta_time);
updateStartPrompt(delta_time);
updatePlayers(delta_time);
}
// Calcula el tiempo transcurrido desde el último frame
auto Title::calculateDeltaTime() -> float {
const Uint64 CURRENT_TIME = SDL_GetTicks();
const float DELTA_TIME = static_cast<float>(CURRENT_TIME - last_time_) / 1000.0F; // Convert ms to seconds
last_time_ = CURRENT_TIME;
return DELTA_TIME;
}
// Dibuja el objeto en pantalla
void Title::render() {
static auto* const SCREEN = Screen::get();
SCREEN->start();
SCREEN->clean();
tiled_bg_->render();
game_logo_->render();
renderPlayers();
renderStartPrompt();
renderCopyright();
fade_->render();
SCREEN->render();
}
// Comprueba los eventos
void Title::checkEvents() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_EVENT_KEY_DOWN) {
handleKeyDownEvent(event);
}
GlobalEvents::handle(event);
}
}
void Title::handleKeyDownEvent(const SDL_Event& /*event*/) {
}
// Comprueba las entradas
void Title::checkInput() {
Input::get()->update();
if (!ServiceMenu::get()->isEnabled()) {
processControllerInputs();
processKeyboardStart();
}
GlobalInputs::check();
}
void Title::processKeyboardStart() {
if (!canProcessStartButton()) {
return;
}
const bool START_PRESSED = Input::get()->checkAction(
Input::Action::START,
Input::DO_NOT_ALLOW_REPEAT,
Input::CHECK_KEYBOARD);
if (START_PRESSED) {
switch (Options::keyboard.player_id) {
case Player::Id::PLAYER1:
processPlayer1Start();
break;
case Player::Id::PLAYER2:
processPlayer2Start();
break;
default:
break;
}
}
}
void Title::processControllerInputs() {
for (const auto& controller : Options::gamepad_manager) {
if (isStartButtonPressed(&controller)) {
handleStartButtonPress(&controller);
}
}
}
auto Title::isStartButtonPressed(const Options::Gamepad* controller) -> bool {
return Input::get()->checkAction(
Input::Action::START,
Input::DO_NOT_ALLOW_REPEAT,
Input::DO_NOT_CHECK_KEYBOARD,
controller->instance);
}
void Title::handleStartButtonPress(const Options::Gamepad* controller) {
if (!canProcessStartButton()) {
return;
}
if (controller->player_id == Player::Id::PLAYER1) {
processPlayer1Start();
} else if (controller->player_id == Player::Id::PLAYER2) {
processPlayer2Start();
}
}
auto Title::canProcessStartButton() const -> bool {
return (state_ != State::LOGO_ANIMATING || ALLOW_TITLE_ANIMATION_SKIP);
}
void Title::processPlayer1Start() {
if (!player1_start_pressed_) {
player1_start_pressed_ = true;
activatePlayerAndSetState(Player::Id::PLAYER1);
}
}
void Title::processPlayer2Start() {
if (!player2_start_pressed_) {
player2_start_pressed_ = true;
activatePlayerAndSetState(Player::Id::PLAYER2);
}
}
void Title::activatePlayerAndSetState(Player::Id player_id) {
getPlayer(player_id)->setPlayingState(Player::State::TITLE_ANIMATION);
setState(State::START_HAS_BEEN_PRESSED);
counter_time_ = 0.0F;
}
// Avanza un frame (llamado desde Director::iterate)
void Title::iterate() {
const float DELTA_TIME = calculateDeltaTime();
checkInput();
update(DELTA_TIME);
render();
}
// Procesa un evento (llamado desde Director::handleEvent)
void Title::handleEvent(const SDL_Event& event) {
if (event.type == SDL_EVENT_KEY_DOWN) {
handleKeyDownEvent(event);
}
}
// Bucle para el titulo del juego (fallback legacy)
void Title::run() {
last_time_ = SDL_GetTicks();
while (Section::name == Section::Name::TITLE) {
const float DELTA_TIME = calculateDeltaTime();
checkInput();
update(DELTA_TIME);
checkEvents(); // Tiene que ir antes del render
render();
}
}
// Reinicia el contador interno
void Title::resetCounter() { counter_time_ = 0.0F; }
// Intercambia la asignación de mandos a los jugadores
void Title::swapControllers() {
if (Input::get()->getNumGamepads() == 0) {
return;
}
Options::swapControllers();
showControllers();
}
// Intercambia el teclado de jugador
void Title::swapKeyboard() {
Options::swapKeyboard();
std::string text = Lang::getText("[DEFINE_BUTTONS] PLAYER") + std::to_string(static_cast<int>(Options::getPlayerWhoUsesKeyboard())) + ": " + Lang::getText("[DEFINE_BUTTONS] KEYBOARD");
Notifier::get()->show({text});
}
// Muestra información sobre los controles y los jugadores
void Title::showControllers() {
// Crea los textos
std::string text1 = Lang::getText("[DEFINE_BUTTONS] PLAYER") + std::to_string(static_cast<int>(Player::Id::PLAYER1)) + ": " + Options::gamepad_manager.getGamepad(Player::Id::PLAYER1).name;
std::string text2 = Lang::getText("[DEFINE_BUTTONS] PLAYER") + std::to_string(static_cast<int>(Player::Id::PLAYER2)) + ": " + Options::gamepad_manager.getGamepad(Player::Id::PLAYER2).name;
// Muestra la notificación
Notifier::get()->show({text1, text2});
}
// Actualiza el fade
void Title::updateFade() {
fade_->update();
if (fade_->hasEnded()) {
const int COMBO = (player1_start_pressed_ ? 1 : 0) | (player2_start_pressed_ ? 2 : 0);
switch (COMBO) {
case 0: // Ningún jugador ha pulsado Start
Section::name = next_section_;
break;
case 1: // Solo el jugador 1 ha pulsado Start
Section::name = Section::Name::GAME;
Section::options = Section::Options::GAME_PLAY_1P;
Audio::get()->stopMusic();
break;
case 2: // Solo el jugador 2 ha pulsado Start
Section::name = Section::Name::GAME;
Section::options = Section::Options::GAME_PLAY_2P;
Audio::get()->stopMusic();
break;
case 3: // Ambos jugadores han pulsado Start
Section::name = Section::Name::GAME;
Section::options = Section::Options::GAME_PLAY_BOTH;
Audio::get()->stopMusic();
break;
}
}
}
// Actualiza el estado
void Title::updateState(float delta_time) {
game_logo_->update(delta_time);
tiled_bg_->update(delta_time);
// Establece la lógica según el estado
switch (state_) {
case State::LOGO_ANIMATING: {
if (game_logo_->hasFinished()) {
setState(State::LOGO_FINISHED);
}
break;
}
case State::LOGO_FINISHED: {
counter_time_ += delta_time;
if (counter_time_ >= param.title.title_duration) {
// El menu ha hecho time out
fade_->setPostDuration(0);
fade_->activate();
selection_ = Section::Options::TITLE_TIME_OUT;
}
break;
}
case State::START_HAS_BEEN_PRESSED: {
counter_time_ += delta_time;
if (counter_time_ >= START_PRESSED_DELAY_S) {
fade_->activate();
}
break;
}
default:
break;
}
}
void Title::updateStartPrompt(float delta_time) {
blink_accumulator_ += delta_time;
bool condition_met = false;
float period = 0.0F;
float on_time = 0.0F;
switch (state_) {
case State::LOGO_FINISHED:
period = LOGO_BLINK_PERIOD_S;
on_time = LOGO_BLINK_ON_TIME_S;
break;
case State::START_HAS_BEEN_PRESSED:
period = START_BLINK_PERIOD_S;
on_time = START_BLINK_ON_TIME_S;
break;
default:
break;
}
if (period > 0.0F) {
// Reset accumulator when it exceeds the period
if (blink_accumulator_ >= period) {
blink_accumulator_ -= period;
}
// Check if we're in the "on" time of the blink cycle
condition_met = blink_accumulator_ >= (period - on_time);
}
should_render_start_prompt_ = condition_met;
}
void Title::renderStartPrompt() {
if (should_render_start_prompt_) {
text_->writeDX(Text::CENTER | Text::SHADOW,
param.game.game_area.center_x,
param.title.press_start_position,
Lang::getText("[TITLE] PRESS_BUTTON_TO_PLAY"),
1,
Colors::NO_COLOR_MOD,
1,
Colors::TITLE_SHADOW_TEXT);
}
}
void Title::renderCopyright() {
if (state_ != State::LOGO_ANIMATING) {
// Mini logo
mini_logo_sprite_->render();
// Texto con el copyright
text_->writeDX(Text::CENTER | Text::SHADOW,
param.game.game_area.center_x,
anchor_.copyright_text,
std::string(TEXT_COPYRIGHT),
1,
Colors::NO_COLOR_MOD,
1,
Colors::TITLE_SHADOW_TEXT);
}
}
// Cambia el estado
void Title::setState(State state) {
if (state_ == state) {
return;
}
state_ = state;
switch (state_) {
case State::LOGO_ANIMATING:
break;
case State::LOGO_FINISHED:
Audio::get()->playMusic("title.ogg");
tiled_bg_->changeSpeedTo(60.0F, 0.5F);
blink_accumulator_ = 0.0F; // Resetea el timer para empezar el parpadeo desde el inicio
break;
case State::START_HAS_BEEN_PRESSED:
Audio::get()->fadeOutMusic(MUSIC_FADE_OUT_LONG_MS);
blink_accumulator_ = 0.0F; // Resetea el timer para empezar el parpadeo desde el inicio
break;
}
}
// Inicializa los jugadores
void Title::initPlayers() {
std::vector<std::vector<std::shared_ptr<Texture>>> player_textures; // Vector con todas las texturas de los jugadores;
std::vector<std::vector<std::string>> player1_animations; // Vector con las animaciones del jugador 1
std::vector<std::vector<std::string>> player2_animations; // Vector con las animaciones del jugador 2
// Texturas - Player1
std::vector<std::shared_ptr<Texture>> player1_textures;
player1_textures.emplace_back(Resource::get()->getTexture("player1_pal0"));
player1_textures.emplace_back(Resource::get()->getTexture("player1_pal1"));
player1_textures.emplace_back(Resource::get()->getTexture("player1_pal2"));
player1_textures.emplace_back(Resource::get()->getTexture("player1_pal3"));
player1_textures.emplace_back(Resource::get()->getTexture("player1_power.png"));
player_textures.push_back(player1_textures);
// Texturas - Player2
std::vector<std::shared_ptr<Texture>> player2_textures;
player2_textures.emplace_back(Resource::get()->getTexture("player2_pal0"));
player2_textures.emplace_back(Resource::get()->getTexture("player2_pal1"));
player2_textures.emplace_back(Resource::get()->getTexture("player2_pal2"));
player2_textures.emplace_back(Resource::get()->getTexture("player2_pal3"));
player2_textures.emplace_back(Resource::get()->getTexture("player2_power.png"));
player_textures.push_back(player2_textures);
// Animaciones -- Jugador
player1_animations.emplace_back(Resource::get()->getAnimation("player1.ani"));
player1_animations.emplace_back(Resource::get()->getAnimation("player_power.ani"));
player2_animations.emplace_back(Resource::get()->getAnimation("player2.ani"));
player2_animations.emplace_back(Resource::get()->getAnimation("player_power.ani"));
// Crea los dos jugadores
const int Y = param.title.press_start_position - (Player::HEIGHT / 2);
constexpr bool DEMO = false;
Player::Config config_player1;
config_player1.id = Player::Id::PLAYER1;
config_player1.x = param.game.game_area.center_x - (Player::WIDTH / 2);
config_player1.y = Y;
config_player1.demo = DEMO;
config_player1.play_area = &param.game.play_area.rect;
config_player1.texture = player_textures.at(0);
config_player1.animations = player1_animations;
config_player1.hi_score_table = &Options::settings.hi_score_table;
config_player1.glowing_entry = &Options::settings.glowing_entries.at(static_cast<int>(Player::Id::PLAYER1) - 1);
players_.emplace_back(std::make_unique<Player>(config_player1));
players_.back()->setPlayingState(Player::State::TITLE_HIDDEN);
Player::Config config_player2;
config_player2.id = Player::Id::PLAYER2;
config_player2.x = param.game.game_area.center_x - (Player::WIDTH / 2);
config_player2.y = Y;
config_player2.demo = DEMO;
config_player2.play_area = &param.game.play_area.rect;
config_player2.texture = player_textures.at(1);
config_player2.animations = player2_animations;
config_player2.hi_score_table = &Options::settings.hi_score_table;
config_player2.glowing_entry = &Options::settings.glowing_entries.at(static_cast<int>(Player::Id::PLAYER2) - 1);
players_.emplace_back(std::make_unique<Player>(config_player2));
players_.back()->setPlayingState(Player::State::TITLE_HIDDEN);
// Registra los jugadores en Options
for (const auto& player : players_) {
Options::keyboard.addPlayer(player);
Options::gamepad_manager.addPlayer(player);
}
}
// Actualiza los jugadores
void Title::updatePlayers(float delta_time) {
for (auto& player : players_) {
player->update(delta_time);
}
}
// Renderiza los jugadores
void Title::renderPlayers() {
for (auto const& player : players_) {
player->render();
}
}
// Obtiene un jugador a partir de su "id"
auto Title::getPlayer(Player::Id id) -> std::shared_ptr<Player> {
auto it = std::ranges::find_if(players_, [id](const auto& player) -> auto { return player->getId() == id; });
if (it != players_.end()) {
return *it;
}
return nullptr;
}

View File

@@ -0,0 +1,142 @@
#pragma once
#include <SDL3/SDL.h> // Para SDL_Keycode, SDL_Event, Uint64
#include <memory> // Para shared_ptr, unique_ptr
#include <string_view> // Para string_view
#include <vector> // Para vector
#include "player.hpp" // for Player
#include "section.hpp" // for Options, Name (ptr only)
class Fade;
class GameLogo;
class Sprite;
class Text;
class TiledBG;
namespace Options {
struct Gamepad;
} // namespace Options
// --- Clase Title: pantalla de título y menú principal del juego ---
//
// Esta clase gestiona la pantalla de título del juego, incluyendo el menú principal
// y la transición entre diferentes modos de 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
//
// La clase utiliza un sistema de tiempo basado en segundos para garantizar
// comportamiento consistente independientemente del framerate.
class Title {
public:
// --- Constructor y destructor ---
Title();
~Title();
// --- Callbacks para el bucle SDL_MAIN_USE_CALLBACKS ---
void iterate(); // Ejecuta un frame
void handleEvent(const SDL_Event& event); // Procesa un evento
// --- Bucle principal legacy (fallback) ---
void run();
private:
// --- Constantes de tiempo (en segundos) ---
static constexpr float START_PRESSED_DELAY_S = 1666.67F / 1000.0F; // 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 (en segundos) ---
static constexpr float LOGO_BLINK_PERIOD_S = 833.0F / 1000.0F; // Período de parpadeo del logo (833ms)
static constexpr float LOGO_BLINK_ON_TIME_S = 583.0F / 1000.0F; // Tiempo encendido del logo (583ms)
static constexpr float START_BLINK_PERIOD_S = 167.0F / 1000.0F; // Período de parpadeo del start (167ms)
static constexpr float START_BLINK_ON_TIME_S = 83.0F / 1000.0F; // Tiempo encendido del start (83ms)
// --- 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 ---
enum class State {
LOGO_ANIMATING, // El logo está animándose
LOGO_FINISHED, // El logo ha terminado de animarse
START_HAS_BEEN_PRESSED, // Se ha pulsado el botón de start
};
// --- Estructuras privadas ---
struct Anchor {
int mini_logo; // Ancla del logo mini
int copyright_text; // Ancla del texto de copyright
};
// --- Objetos y punteros ---
std::shared_ptr<Text> text_; // Objeto de texto para escribir en pantalla
std::unique_ptr<Fade> fade_; // Fundido en pantalla
std::unique_ptr<TiledBG> tiled_bg_; // Fondo animado de tiles
std::unique_ptr<GameLogo> game_logo_; // Logo del juego
std::unique_ptr<Sprite> mini_logo_sprite_; // Logo JailGames mini
std::vector<std::shared_ptr<Player>> players_; // Vector de jugadores
// --- Variables de estado ---
Anchor anchor_; // Anclas para definir la posición de los elementos del título
Section::Name next_section_; // Siguiente sección a cargar
Section::Options selection_ = Section::Options::TITLE_TIME_OUT; // Opción elegida en el título
State state_; // Estado actual de la sección
Uint64 last_time_ = 0; // Último timestamp para calcular delta-time
float counter_time_ = 0.0F; // Temporizador para la pantalla de título (en segundos)
float blink_accumulator_ = 0.0F; // Acumulador para el parpadeo (en segundos)
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 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
// --- Ciclo de vida del título ---
void update(float delta_time); // Actualiza las variables del objeto
auto calculateDeltaTime() -> float; // Calcula el tiempo transcurrido desde el último frame
void updateState(float delta_time); // Actualiza el estado actual del título
void setState(State state); // Cambia el estado del título
void resetCounter(); // Reinicia el contador interno
// --- Entrada de usuario ---
void checkEvents(); // Comprueba los eventos
void checkInput(); // Comprueba las entradas
void handleKeyDownEvent(const SDL_Event& event); // Maneja el evento de tecla presionada
void processKeyboardStart(); // Procesa las entradas del teclado
void processControllerInputs(); // Procesa las entradas de los mandos
[[nodiscard]] static auto isStartButtonPressed(const Options::Gamepad* controller) -> bool; // Comprueba si se ha pulsado el botón Start
void handleStartButtonPress(const Options::Gamepad* controller); // Maneja la pulsación del botón Start
[[nodiscard]] auto canProcessStartButton() const -> bool; // Verifica si se puede procesar la pulsación del botón Start
void processPlayer1Start(); // Procesa el inicio del jugador 1
void processPlayer2Start(); // Procesa el inicio del jugador 2
void activatePlayerAndSetState(Player::Id player_id); // Activa al jugador y cambia el estado del título
// --- Gestión de jugadores ---
void initPlayers(); // Inicializa los jugadores
void updatePlayers(float delta_time); // Actualiza los jugadores
void renderPlayers(); // Renderiza los jugadores
auto getPlayer(Player::Id id) -> std::shared_ptr<Player>; // Obtiene un jugador a partir de su "id"
// --- Visualización / Renderizado ---
void render(); // Dibuja el objeto en pantalla
void updateFade(); // Actualiza el efecto de fundido (fade in/out)
void updateStartPrompt(float delta_time); // Actualiza el mensaje de "Pulsa Start"
void renderStartPrompt(); // Dibuja el mensaje de "Pulsa Start" en pantalla
void renderCopyright(); // Dibuja el aviso de copyright
// --- Utilidades estáticas ---
static void swapControllers(); // Intercambia la asignación de mandos a los jugadores
static void swapKeyboard(); // Intercambia el teclado de jugador
static void showControllers(); // Muestra información sobre los controles y los jugadores
};