#include "instructions.h" #include // Para SDL_GetTicks, SDL_SetRenderTarget, SDL_Re... #include // Para max #include // Para array #include // Para basic_string, string #include // Para move #include // Para vector #include "audio.h" // Para Audio #include "color.h" // Para Color, Colors::SHADOW_TEXT, Zone, NO_TEXT_C... #include "fade.h" // Para Fade, FadeMode, FadeType #include "global_events.h" // Para check #include "global_inputs.h" // Para check #include "input.h" // Para Input #include "lang.h" // Para getText #include "param.h" // Para Param, param, ParamGame, ParamFade, Param... #include "resource.h" // Para Resource #include "screen.h" // Para Screen #include "section.hpp" // Para Name, name, Options, options #include "sprite.h" // Para Sprite #include "text.h" // Para Text, Text::CENTER, Text::COLOR, Text::SHADOW #include "tiled_bg.h" // Para TiledBG, TiledBGMode #include "utils.h" // 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(param.game.game_area.rect, TiledBGMode::STATIC)), fade_(std::make_unique()) { // 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 de 50 ms lines_ = initializeLines(256); // Rellena la textura de texto fillTexture(); // Inicializa los sprites de los items iniSprites(); } // 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; i < (int)item_textures_.size(); ++i) { auto sprite = std::make_unique(item_textures_[i], 0, 0, param.game.item_size, param.game.item_size); sprite->setPosition((SDL_FPoint){sprite_pos_.x, sprite_pos_.y + ((param.game.item_size + item_space_) * i)}); sprites_.push_back(std::move(sprite)); } } // Actualiza los sprites void Instructions::updateSprites() { SDL_FRect src_rect = {0, 0, param.game.item_size, param.game.item_size}; // Disquito (desplazamiento 12/60 = 0.2s) src_rect.y = param.game.item_size * (static_cast((elapsed_time_ + 0.2f) / SPRITE_ANIMATION_CYCLE_S) % 2); sprites_[0]->setSpriteClip(src_rect); // Gavina (desplazamiento 9/60 = 0.15s) src_rect.y = param.game.item_size * (static_cast((elapsed_time_ + 0.15f) / SPRITE_ANIMATION_CYCLE_S) % 2); sprites_[1]->setSpriteClip(src_rect); // Pacmar (desplazamiento 6/60 = 0.1s) src_rect.y = param.game.item_size * (static_cast((elapsed_time_ + 0.1f) / SPRITE_ANIMATION_CYCLE_S) % 2); sprites_[2]->setSpriteClip(src_rect); // Time Stopper (desplazamiento 3/60 = 0.05s) src_rect.y = param.game.item_size * (static_cast((elapsed_time_ + 0.05f) / SPRITE_ANIMATION_CYCLE_S) % 2); sprites_[3]->setSpriteClip(src_rect); // Coffee (sin desplazamiento) src_rect.y = param.game.item_size * (static_cast(elapsed_time_ / SPRITE_ANIMATION_CYCLE_S) % 2); sprites_[4]->setSpriteClip(src_rect); } // Rellena la textura de texto void Instructions::fillTexture() { const int X_OFFSET = param.game.item_size + 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 = param.game.item_size + 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 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 - ((param.game.item_size - 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 float Instructions::calculateDeltaTime() { const Uint64 current_time = SDL_GetTicks(); const float delta_time = static_cast(current_time - last_time_) / 1000.0f; // Convertir ms a segundos last_time_ = current_time; return delta_time; } // Bucle para la pantalla de instrucciones 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) -> std::vector { std::vector lines; for (int y = 0; y < height; y++) { int direction = (y % 2 == 0) ? -1 : 1; // Pares a la izquierda, impares a la derecha lines.emplace_back(y, 0.0F, direction); } return lines; } // Método para mover las líneas con suavizado auto Instructions::moveLines(std::vector &lines, int width, float duration, Uint32 start_delay) -> bool { Uint32 current_time = SDL_GetTicks(); bool all_lines_off_screen = true; for (auto &line : lines) { // Establecer start_time en el primer cuadro de animación if (line.start_time == 0) { line.start_time = current_time + line.y * start_delay; } float elapsed_time = (current_time - line.start_time) / 1000.0F; // Convertir a segundos if (elapsed_time < 0) { all_lines_off_screen = false; // Si aún no se debe mover esta línea, no están todas fuera de pantalla continue; } if (elapsed_time >= duration) { continue; // Si la línea ha salido de los límites, no la muevas más } float t = elapsed_time / duration; float smooth_factor = easeInOutQuint(t); line.x = line.direction * smooth_factor * width; all_lines_off_screen = false; // Si alguna línea aún se está moviendo, no están todas fuera de pantalla } return all_lines_off_screen; } // Método para renderizar las líneas void Instructions::renderLines(SDL_Renderer *renderer, SDL_Texture *texture, const std::vector &lines) { for (const auto &line : lines) { SDL_FRect src_rect = {0, static_cast(line.y), 320, 1}; SDL_FRect dst_rect = {static_cast(line.x), static_cast(line.y), 320, 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 (convertir elapsed_time_ a equivalente de counter) float counter_equivalent = elapsed_time_ * 60.0f; // Convertir segundos a equivalente frame para UI view_.y = std::max(0.0F, param.game.height - counter_equivalent + 100); // Verifica si view_.y == 0 y gestiona el temporizador if (view_.y == 0) { 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, static_cast(LINE_START_DELAY_MS)); } } } // Comprueba si todas las líneas han terminado if (all_lines_off_screen_) { Section::name = Section::Name::TITLE; Section::options = Section::Options::TITLE_1; } }