#include "intro.hpp" #include // 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 // Para array #include // Para basic_string, string #include // 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(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_); } // 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() { // Cuando la tarjeta actual toca la mesa por primera vez, la anterior sale despedida if (scene_ > 0 && card_sprites_.at(scene_)->hasFirstTouch()) { 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() { // Primera imagen - UPV: activa la tarjeta card_sprites_.at(0)->enable(); // 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_++; } } void Intro::updateScene1() { // Segunda imagen - Máquina card_sprites_.at(1)->enable(); // 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_++; } } void Intro::updateScene2() { // Tercera imagen - GRITO: tarjeta y texto a la vez card_sprites_.at(2)->enable(); 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_++; } } void Intro::updateScene3() { // Cuarta imagen - Reflexión card_sprites_.at(3)->enable(); 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_++; } } void Intro::updateScene4() { // Quinta imagen - Patada card_sprites_.at(4)->enable(); 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_++; } } void Intro::updateScene5() { // Sexta imagen - Globos de café card_sprites_.at(5)->enable(); 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: 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(CURRENT_TIME - last_time_) / 1000.0F; // Convertir ms a segundos last_time_ = CURRENT_TIME; return DELTA_TIME; } // Bucle principal 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 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> card_textures; for (int i = 0; i < TOTAL_SPRITES; ++i) { auto card_texture = std::make_unique(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(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(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); 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(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_); }