reestructuració
This commit is contained in:
742
source/game/scenes/credits.cpp
Normal file
742
source/game/scenes/credits.cpp
Normal 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_);
|
||||
}
|
||||
}
|
||||
171
source/game/scenes/credits.hpp
Normal file
171
source/game/scenes/credits.hpp
Normal 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
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
353
source/game/scenes/game.hpp
Normal 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
|
||||
};
|
||||
399
source/game/scenes/hiscore_table.cpp
Normal file
399
source/game/scenes/hiscore_table.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
93
source/game/scenes/hiscore_table.hpp
Normal file
93
source/game/scenes/hiscore_table.hpp
Normal 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
|
||||
};
|
||||
387
source/game/scenes/instructions.cpp
Normal file
387
source/game/scenes/instructions.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
102
source/game/scenes/instructions.hpp
Normal file
102
source/game/scenes/instructions.hpp
Normal 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
|
||||
};
|
||||
570
source/game/scenes/intro.cpp
Normal file
570
source/game/scenes/intro.cpp
Normal 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_);
|
||||
}
|
||||
133
source/game/scenes/intro.hpp
Normal file
133
source/game/scenes/intro.hpp
Normal 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
212
source/game/scenes/logo.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
95
source/game/scenes/logo.hpp
Normal file
95
source/game/scenes/logo.hpp
Normal 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
|
||||
};
|
||||
524
source/game/scenes/title.cpp
Normal file
524
source/game/scenes/title.cpp
Normal 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 = ¶m.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 = ¶m.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;
|
||||
}
|
||||
142
source/game/scenes/title.hpp
Normal file
142
source/game/scenes/title.hpp
Normal 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
|
||||
};
|
||||
Reference in New Issue
Block a user