diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a9d53c..1049835 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,6 +81,7 @@ set(APP_SOURCES # --- Sprites y Gráficos --- source/animated_sprite.cpp source/background.cpp + source/card_sprite.cpp source/fade.cpp source/moving_sprite.cpp source/path_sprite.cpp diff --git a/Makefile b/Makefile index 7421a7c..363aefb 100644 --- a/Makefile +++ b/Makefile @@ -94,6 +94,7 @@ APP_SOURCES := \ source/sections/title.cpp \ source/animated_sprite.cpp \ source/background.cpp \ + source/card_sprite.cpp \ source/fade.cpp \ source/moving_sprite.cpp \ source/path_sprite.cpp \ diff --git a/source/card_sprite.cpp b/source/card_sprite.cpp new file mode 100644 index 0000000..12326d5 --- /dev/null +++ b/source/card_sprite.cpp @@ -0,0 +1,203 @@ +#include "card_sprite.hpp" + +#include // Para std::clamp +#include // Para function +#include // Para move + +#include "texture.hpp" // Para Texture +#include "utils.hpp" // Para easeOutBounce + +// Constructor +CardSprite::CardSprite(std::shared_ptr texture) + : MovingSprite(std::move(texture)), + entry_easing_(easeOutBounce) {} + +// Inicia la animación de entrada (solo si está en IDLE) +void CardSprite::enable() { + if (state_ != CardState::IDLE) { + return; + } + + state_ = CardState::ENTERING; + entry_elapsed_ = 0.0F; + + // Posición fija en el punto de aterrizaje + setPos(landing_x_, landing_y_); + + // Zoom inicial grande (como si estuviera cerca de la cámara) + horizontal_zoom_ = start_zoom_; + vertical_zoom_ = start_zoom_; + + // Ángulo inicial + rotate_.angle = start_angle_; + rotate_.center = {pos_.w / 2.0F, pos_.h / 2.0F}; + + shadow_visible_ = true; +} + +// Inicia la animación de salida (solo si está en LANDED) +void CardSprite::startExit() { + if (state_ != CardState::LANDED) { + return; + } + + state_ = CardState::EXITING; + shadow_visible_ = false; + + // Velocidad y aceleración de salida + vx_ = exit_vx_; + vy_ = exit_vy_; + ax_ = exit_ax_; + ay_ = exit_ay_; + + // Rotación continua + rotate_.enabled = true; + rotate_.amount = exit_rotate_amount_; + rotate_.center = {pos_.w / 2.0F, pos_.h / 2.0F}; +} + +// Actualiza según el estado +void CardSprite::update(float delta_time) { + switch (state_) { + case CardState::ENTERING: + updateEntering(delta_time); + break; + case CardState::EXITING: + updateExiting(delta_time); + break; + default: + break; + } +} + +// Animación de entrada: interpola zoom y ángulo con easing +void CardSprite::updateEntering(float delta_time) { + entry_elapsed_ += delta_time; + + float progress = std::clamp(entry_elapsed_ / entry_duration_s_, 0.0F, 1.0F); + double eased = entry_easing_(static_cast(progress)); + + // Zoom: de start_zoom_ a 1.0 con rebote + auto current_zoom = static_cast(start_zoom_ + (1.0 - start_zoom_) * eased); + horizontal_zoom_ = current_zoom; + vertical_zoom_ = current_zoom; + + // Ángulo: de start_angle_ a 0 con rebote + rotate_.angle = start_angle_ * (1.0 - eased); + + // Offset de sombra escalado con el zoom (perspectiva) + // (se aplica en renderShadow) + + // Transición a LANDED cuando termina la animación + if (progress >= 1.0F) { + horizontal_zoom_ = 1.0F; + vertical_zoom_ = 1.0F; + rotate_.angle = 0.0; + state_ = CardState::LANDED; + } +} + +// Animación de salida: movimiento + rotación continua +void CardSprite::updateExiting(float delta_time) { + move(delta_time); + rotate(delta_time); + + if (isOffScreen()) { + state_ = CardState::FINISHED; + } +} + +// Renderiza el sprite y su sombra +void CardSprite::render() { + if (state_ == CardState::IDLE || state_ == CardState::FINISHED) { + return; + } + + // Sombra primero (debajo de la tarjeta) + if (shadow_visible_ && shadow_texture_) { + renderShadow(); + } + + // Tarjeta + MovingSprite::render(); +} + +// Renderiza la sombra con el mismo zoom y rotación que la tarjeta +void CardSprite::renderShadow() { + // Offset de sombra escalado con el zoom para efecto de perspectiva + float scaled_offset_x = shadow_offset_x_ * horizontal_zoom_; + float scaled_offset_y = shadow_offset_y_ * vertical_zoom_; + + shadow_texture_->render( + pos_.x + scaled_offset_x, + pos_.y + scaled_offset_y, + &sprite_clip_, + horizontal_zoom_, + vertical_zoom_, + rotate_.angle, + &rotate_.center, + flip_); +} + +// Comprueba si el sprite está fuera de pantalla +auto CardSprite::isOffScreen() const -> bool { + // Considerar el zoom: el sprite puede ser más grande de lo que indica pos_ + float effective_width = pos_.w * horizontal_zoom_; + float effective_height = pos_.h * vertical_zoom_; + return (pos_.x + effective_width < -OFF_SCREEN_MARGIN || + pos_.x > screen_width_ + OFF_SCREEN_MARGIN || + pos_.y + effective_height < -OFF_SCREEN_MARGIN || + pos_.y > screen_height_ + OFF_SCREEN_MARGIN); +} + +// --- Consultas de estado --- +auto CardSprite::hasLanded() const -> bool { + return state_ == CardState::LANDED || state_ == CardState::EXITING || state_ == CardState::FINISHED; +} + +auto CardSprite::hasFinished() const -> bool { + return state_ == CardState::FINISHED; +} + +auto CardSprite::isExiting() const -> bool { + return state_ == CardState::EXITING; +} + +auto CardSprite::getState() const -> CardState { + return state_; +} + +// --- Configuración --- +void CardSprite::setEntryParams(float start_zoom, double start_angle, float duration_s, std::function easing) { + start_zoom_ = start_zoom; + start_angle_ = start_angle; + entry_duration_s_ = duration_s; + entry_easing_ = std::move(easing); +} + +void CardSprite::setLandingPosition(float x, float y) { + landing_x_ = x; + landing_y_ = y; +} + +void CardSprite::setExitParams(float vx, float vy, float ax, float ay, double rotate_amount) { + exit_vx_ = vx; + exit_vy_ = vy; + exit_ax_ = ax; + exit_ay_ = ay; + exit_rotate_amount_ = rotate_amount; +} + +void CardSprite::setShadowTexture(std::shared_ptr texture) { + shadow_texture_ = std::move(texture); +} + +void CardSprite::setShadowOffset(float offset_x, float offset_y) { + shadow_offset_x_ = offset_x; + shadow_offset_y_ = offset_y; +} + +void CardSprite::setScreenBounds(float width, float height) { + screen_width_ = width; + screen_height_ = height; +} diff --git a/source/card_sprite.hpp b/source/card_sprite.hpp new file mode 100644 index 0000000..1e3a85c --- /dev/null +++ b/source/card_sprite.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include // Para SDL_FPoint + +#include // Para function +#include // Para shared_ptr + +#include "moving_sprite.hpp" // Para MovingSprite + +class Texture; + +// --- Estados de la tarjeta --- +enum class CardState { + IDLE, // No activada todavía + ENTERING, // Animación de entrada (zoom + rotación con rebote) + LANDED, // En reposo sobre la mesa + EXITING, // Saliendo de pantalla girando + FINISHED, // Fuera de pantalla +}; + +// --- Clase CardSprite: tarjeta animada con zoom, rotación y sombra integrada --- +// +// Simula una tarjeta lanzada sobre una mesa desde arriba (eje Z). +// Durante la entrada, interpola zoom y rotación con easing (rebote). +// Durante la salida, se desplaza fuera de pantalla girando, sin sombra. +class CardSprite : public MovingSprite { + public: + explicit CardSprite(std::shared_ptr texture); + ~CardSprite() override = default; + + // --- Ciclo principal --- + void update(float delta_time) override; + void render() override; + + // --- Control de estado --- + void enable(); // Inicia la animación de entrada + void startExit(); // Inicia la animación de salida + + // --- Consultas de estado --- + [[nodiscard]] auto hasLanded() const -> bool; // ¿Ha aterrizado en la mesa? + [[nodiscard]] auto hasFinished() const -> bool; // ¿Ha terminado completamente? + [[nodiscard]] auto isExiting() const -> bool; // ¿Está saliendo de pantalla? + [[nodiscard]] auto getState() const -> CardState; // Estado actual + + // --- Configuración de entrada --- + void setEntryParams(float start_zoom, double start_angle, float duration_s, std::function easing); + void setLandingPosition(float x, float y); // Posición final centrada + + // --- Configuración de salida --- + void setExitParams(float vx, float vy, float ax, float ay, double rotate_amount); + + // --- Sombra --- + void setShadowTexture(std::shared_ptr texture); + void setShadowOffset(float offset_x, float offset_y); + + // --- Limites de pantalla (para detectar salida) --- + void setScreenBounds(float width, float height); + + private: + // --- Estado --- + CardState state_ = CardState::IDLE; + + // --- Parámetros de entrada --- + float start_zoom_ = 1.8F; + double start_angle_ = 15.0; + float entry_duration_s_ = 1.5F; + float entry_elapsed_ = 0.0F; + std::function entry_easing_; + float landing_x_ = 0.0F; + float landing_y_ = 0.0F; + + // --- Parámetros de salida --- + float exit_vx_ = 0.0F; + float exit_vy_ = 0.0F; + float exit_ax_ = 0.0F; + float exit_ay_ = 0.0F; + double exit_rotate_amount_ = 0.0; + + // --- Sombra --- + std::shared_ptr shadow_texture_; + float shadow_offset_x_ = 8.0F; + float shadow_offset_y_ = 8.0F; + bool shadow_visible_ = true; + + // --- Límites de pantalla --- + float screen_width_ = 320.0F; + float screen_height_ = 240.0F; + + // --- Margen fuera de pantalla para considerar FINISHED --- + static constexpr float OFF_SCREEN_MARGIN = 50.0F; + + // --- Métodos internos --- + void updateEntering(float delta_time); + void updateExiting(float delta_time); + void renderShadow(); + [[nodiscard]] auto isOffScreen() const -> bool; +}; diff --git a/source/sections/intro.cpp b/source/sections/intro.cpp index 5baa184..e9e9a3e 100644 --- a/source/sections/intro.cpp +++ b/source/sections/intro.cpp @@ -3,25 +3,24 @@ #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 function #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 "path_sprite.hpp" // Para PathSprite, PathType #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 Zone, easeInOutExpo, easeInElastic, easeOutBounce, easeOutElastic, easeOutQuad, easeOutQuint +#include "utils.hpp" // Para easeOutBounce #include "writer.hpp" // Para Writer // Constructor @@ -31,7 +30,7 @@ Intro::Intro() Section::name = Section::Name::INTRO; Section::options = Section::Options::NONE; - // Inicializa las imagens + // Inicializa las tarjetas initSprites(); // Inicializa los textos @@ -83,100 +82,99 @@ void Intro::updateScenes() { } void Intro::updateScene0() { - // Primera imagen - UPV - enableCardAndShadow(0); + // Primera imagen - UPV: activa la tarjeta + card_sprites_.at(0)->enable(); - // Primer texto de la primera imagen - if (card_sprites_.at(0)->hasFinished() && !texts_.at(0)->hasFinished()) { + // Primer texto cuando aterriza + if (card_sprites_.at(0)->hasLanded() && !texts_.at(0)->hasFinished()) { texts_.at(0)->setEnabled(true); } - // Segundo texto de la primera imagen + // Segundo texto if (texts_.at(0)->hasFinished() && !texts_.at(1)->hasFinished()) { switchText(0, 1); } - // Tercer texto de la primera imagen + // Tercer texto if (texts_.at(1)->hasFinished() && !texts_.at(2)->hasFinished()) { switchText(1, 2); } - // Fin de la primera escena + // Fin de la primera escena: la tarjeta sale despedida if (texts_.at(2)->hasFinished()) { texts_.at(2)->setEnabled(false); - scene_++; + startCardExitAndAdvance(); } } void Intro::updateScene1() { // Segunda imagen - Máquina - enableCardAndShadow(1); + card_sprites_.at(1)->enable(); - // Primer texto de la segunda imagen - if (card_sprites_.at(1)->hasFinished() && !texts_.at(3)->hasFinished()) { + // 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_++; + startCardExitAndAdvance(); } } void Intro::updateScene2() { - // Tercera imagen junto con primer texto - GRITO + // Tercera imagen - GRITO: tarjeta y texto a la vez + card_sprites_.at(2)->enable(); if (!texts_.at(4)->hasFinished()) { - enableCardAndShadow(2); texts_.at(4)->setEnabled(true); } // Fin de la tercera escena - if (card_sprites_.at(2)->hasFinished() && texts_.at(4)->hasFinished()) { + if (card_sprites_.at(2)->hasLanded() && texts_.at(4)->hasFinished()) { texts_.at(4)->setEnabled(false); - scene_++; + startCardExitAndAdvance(); } } void Intro::updateScene3() { - // Cuarta imagen junto con primer texto - Reflexión - enableCardAndShadow(3); + // Cuarta imagen - Reflexión + card_sprites_.at(3)->enable(); if (!texts_.at(5)->hasFinished()) { texts_.at(5)->setEnabled(true); } - // Segundo texto de la cuarta imagen + // Segundo texto if (texts_.at(5)->hasFinished() && !texts_.at(6)->hasFinished()) { switchText(5, 6); } // Fin de la cuarta escena - if (card_sprites_.at(3)->hasFinished() && texts_.at(6)->hasFinished()) { + if (card_sprites_.at(3)->hasLanded() && texts_.at(6)->hasFinished()) { texts_.at(6)->setEnabled(false); - scene_++; + startCardExitAndAdvance(); } } void Intro::updateScene4() { // Quinta imagen - Patada - enableCardAndShadow(4); + card_sprites_.at(4)->enable(); - // Primer texto de la quinta imagen if (!texts_.at(7)->hasFinished()) { texts_.at(7)->setEnabled(true); } // Fin de la quinta escena - if (card_sprites_.at(4)->hasFinished() && texts_.at(7)->hasFinished()) { + if (card_sprites_.at(4)->hasLanded() && texts_.at(7)->hasFinished()) { texts_.at(7)->setEnabled(false); - scene_++; + startCardExitAndAdvance(); } } void Intro::updateScene5() { - // Sexta imagen junto con texto - Globos de café - enableCardAndShadow(5); + // Sexta imagen - Globos de café + card_sprites_.at(5)->enable(); if (!texts_.at(8)->hasFinished()) { texts_.at(8)->setEnabled(true); @@ -187,17 +185,18 @@ void Intro::updateScene5() { texts_.at(8)->setEnabled(false); } - // Acaba la ultima imagen - if (card_sprites_.at(5)->hasFinished() && texts_.at(8)->hasFinished()) { + // Ú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; } } -// Helper methods to reduce code duplication -void Intro::enableCardAndShadow(int index) { - card_sprites_.at(index)->enable(); - shadow_sprites_.at(index)->enable(); +// Inicia la salida de la tarjeta actual y avanza a la siguiente escena +void Intro::startCardExitAndAdvance() { + card_sprites_.at(scene_)->startExit(); + scene_++; } void Intro::switchText(int from_index, int to_index) { @@ -221,6 +220,7 @@ void Intro::update(float delta_time) { break; case State::POST: + updateSprites(delta_time); // La última tarjeta puede estar saliendo durante POST updatePostState(); break; } @@ -243,6 +243,7 @@ void Intro::render() { break; } case State::POST: + renderSprites(); // La última tarjeta puede estar saliendo break; } @@ -272,7 +273,7 @@ void Intro::run() { } } -// Inicializa las imagens +// Inicializa las tarjetas void Intro::initSprites() { // Listado de imagenes a usar const std::array TEXTURE_LIST = { @@ -291,24 +292,21 @@ void Intro::initSprites() { const float CARD_WIDTH = texture->getWidth() + (BORDER * 2); const float CARD_HEIGHT = texture->getHeight() + (BORDER * 2); - // Crea las texturas para las tarjetas + // Crea las texturas para las tarjetas (imagen con marco) std::vector> card_textures; for (int i = 0; i < TOTAL_SPRITES; ++i) { - // Crea la textura 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); - // Apuntamos el renderizador a la textura auto* temp = SDL_GetRenderTarget(Screen::get()->getRenderer()); card_texture->setAsRenderTarget(Screen::get()->getRenderer()); - // Limpia la textura SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 0, 0, 0); SDL_RenderClear(Screen::get()->getRenderer()); - // Pone color en el marco de la textura + // 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}; @@ -316,85 +314,87 @@ void Intro::initSprites() { SDL_RenderRect(Screen::get()->getRenderer(), &rect1); SDL_RenderRect(Screen::get()->getRenderer(), &rect2); - // Copia la textura con la imagen dentro del marco + // 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); - // Deja el renderizador como estaba y añade la textura a la lista SDL_SetRenderTarget(Screen::get()->getRenderer(), temp); card_textures.push_back(std::move(card_texture)); } - // Inicializa los sprites para las tarjetas - for (int i = 0; i < TOTAL_SPRITES; ++i) { - auto sprite = std::make_unique(card_textures.at(i)); - sprite->setWidth(CARD_WIDTH); - sprite->setHeight(CARD_HEIGHT); - sprite->setSpriteClip(0, 0, CARD_WIDTH, CARD_HEIGHT); - card_sprites_.push_back(std::move(sprite)); - } - - 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); - - card_sprites_.at(0)->addPath(-CARD_WIDTH - CARD_OFFSET_MARGIN, X_DEST, PathType::HORIZONTAL, Y_DEST, CARD_ANIM_DURATION_NORMAL, easeInOutExpo, 0.0F); - card_sprites_.at(1)->addPath(param.game.width, X_DEST, PathType::HORIZONTAL, Y_DEST, CARD_ANIM_DURATION_NORMAL, easeOutBounce, 0.0F); - card_sprites_.at(2)->addPath(-CARD_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, CARD_ANIM_DURATION_FAST, easeOutQuint, 0.0F); - card_sprites_.at(3)->addPath(param.game.height, Y_DEST, PathType::VERTICAL, X_DEST, CARD_ANIM_DURATION_VERY_SLOW, easeInOutExpo, 0.0F); - card_sprites_.at(4)->addPath(-CARD_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, CARD_ANIM_DURATION_MEDIUM, easeOutElastic, 0.0F); - card_sprites_.at(5)->addPath(-CARD_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, CARD_ANIM_DURATION_SLOW, easeOutQuad, CARD_ANIM_DELAY_LONG_S); - card_sprites_.at(5)->addPath(X_DEST, -CARD_WIDTH, PathType::HORIZONTAL, Y_DEST, CARD_ANIM_DURATION_SHORT, easeInElastic, 0.0F); - - // Constantes - const float DESP = SHADOW_OFFSET; - const float SHADOW_SPRITE_WIDTH = CARD_WIDTH; - const float SHADOW_SPRITE_HEIGHT = CARD_HEIGHT; - - // Crea la textura para las sombras de las tarjetas + // Crea la textura de sombra (compartida entre todas las tarjetas) auto shadow_texture = std::make_shared(Screen::get()->getRenderer()); - shadow_texture->createBlank(SHADOW_SPRITE_WIDTH, SHADOW_SPRITE_HEIGHT, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET); + shadow_texture->createBlank(CARD_WIDTH, CARD_HEIGHT, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET); shadow_texture->setBlendMode(SDL_BLENDMODE_BLEND); - // Apuntamos el renderizador a la textura auto* temp = SDL_GetRenderTarget(Screen::get()->getRenderer()); shadow_texture->setAsRenderTarget(Screen::get()->getRenderer()); - // Limpia la textura SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 0, 0, 0); SDL_RenderClear(Screen::get()->getRenderer()); - // Dibuja la sombra sobre la textura 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 rect1 = {.x = 1, .y = 0, .w = SHADOW_SPRITE_WIDTH - 2, .h = SHADOW_SPRITE_HEIGHT}; - SDL_FRect rect2 = {.x = 0, .y = 1, .w = SHADOW_SPRITE_WIDTH, .h = SHADOW_SPRITE_HEIGHT - 2}; - SDL_RenderFillRect(Screen::get()->getRenderer(), &rect1); - SDL_RenderFillRect(Screen::get()->getRenderer(), &rect2); + 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); - // Deja el renderizador como estaba y añade la textura a la lista SDL_SetRenderTarget(Screen::get()->getRenderer(), temp); + shadow_texture->setAlpha(shadow_color.a); - // Inicializa los sprites para la sombras usando la texturas con la sombra + // 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); + + // Ángulos de entrada por tarjeta (variedad visual) + const double ENTRY_ANGLES[] = {CARD_ANGLE_0, CARD_ANGLE_1, CARD_ANGLE_2, CARD_ANGLE_3, CARD_ANGLE_4, CARD_ANGLE_5}; + + // Direcciones de salida: cada tarjeta sale en una dirección diferente + // {vx, vy, ax, ay, rotate_amount} + struct ExitConfig { + float vx; + float vy; + float ax; + float ay; + double rotate_amount; + }; + + const ExitConfig EXIT_CONFIGS[] = { + { CARD_EXIT_SPEED, -CARD_EXIT_SPEED * 0.15F, CARD_EXIT_ACCEL, 0.0F, CARD_EXIT_ROTATION}, // 0: Derecha + leve arriba + {-CARD_EXIT_SPEED, CARD_EXIT_SPEED * 0.25F, -CARD_EXIT_ACCEL, CARD_EXIT_ACCEL * 0.2F, -CARD_EXIT_ROTATION * 1.1}, // 1: Izquierda + abajo + { CARD_EXIT_SPEED, -CARD_EXIT_SPEED * 0.4F, CARD_EXIT_ACCEL, -CARD_EXIT_ACCEL * 0.3F, CARD_EXIT_ROTATION * 0.8}, // 2: Derecha + arriba + {-CARD_EXIT_SPEED, -CARD_EXIT_SPEED * 0.2F, -CARD_EXIT_ACCEL, 0.0F, -CARD_EXIT_ROTATION}, // 3: Izquierda + leve arriba + { CARD_EXIT_SPEED * 0.2F, CARD_EXIT_SPEED, 0.0F, CARD_EXIT_ACCEL, CARD_EXIT_ROTATION * 1.2}, // 4: Abajo + leve derecha + {-CARD_EXIT_SPEED * 0.6F, -CARD_EXIT_SPEED * 0.1F, -CARD_EXIT_ACCEL * 0.5F, 0.0F, -CARD_EXIT_ROTATION * 0.7}, // 5: Izquierda suave (viento) + }; + + // Inicializa los CardSprites for (int i = 0; i < TOTAL_SPRITES; ++i) { - auto shadow_color = param.intro.shadow_color; - auto sprite = std::make_unique(shadow_texture); - sprite->setWidth(SHADOW_SPRITE_WIDTH); - sprite->setHeight(SHADOW_SPRITE_HEIGHT); - sprite->setSpriteClip(0, 0, SHADOW_SPRITE_WIDTH, SHADOW_SPRITE_HEIGHT); - sprite->getTexture()->setAlpha(shadow_color.a); - shadow_sprites_.push_back(std::move(sprite)); + 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); + + // Posición de aterrizaje + card->setLandingPosition(X_DEST, Y_DEST); + + // Parámetros de entrada: zoom, ángulo, duración, easing + card->setEntryParams(CARD_START_ZOOM, ENTRY_ANGLES[i], CARD_ENTRY_DURATION_S, easeOutBounce); + + // Parámetros de salida + const auto& exit = EXIT_CONFIGS[i]; + card->setExitParams(exit.vx, exit.vy, exit.ax, exit.ay, exit.rotate_amount); + + // 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)); } - - const float S_X_DEST = X_DEST + DESP; - const float S_Y_DEST = Y_DEST + DESP; - - shadow_sprites_.at(0)->addPath(param.game.height + CARD_OFFSET_MARGIN, S_Y_DEST, PathType::VERTICAL, S_X_DEST, CARD_ANIM_DURATION_NORMAL, easeInOutExpo, 0.0F); - shadow_sprites_.at(1)->addPath(-SHADOW_SPRITE_HEIGHT, S_Y_DEST, PathType::VERTICAL, S_X_DEST, CARD_ANIM_DURATION_NORMAL, easeOutBounce, 0.0F); - shadow_sprites_.at(2)->addPath(-SHADOW_SPRITE_WIDTH, S_X_DEST, PathType::HORIZONTAL, S_Y_DEST, CARD_ANIM_DURATION_FAST, easeOutQuint, 0.0F); - shadow_sprites_.at(3)->addPath(-SHADOW_SPRITE_HEIGHT, S_Y_DEST, PathType::VERTICAL, S_X_DEST, CARD_ANIM_DURATION_VERY_SLOW, easeInOutExpo, 0.0F); - shadow_sprites_.at(4)->addPath(param.game.height, S_Y_DEST, PathType::VERTICAL, S_X_DEST, CARD_ANIM_DURATION_MEDIUM, easeOutElastic, 0.0F); - shadow_sprites_.at(5)->addPath(param.game.width, S_X_DEST, PathType::HORIZONTAL, S_Y_DEST, CARD_ANIM_DURATION_SLOW, easeOutQuad, CARD_ANIM_DELAY_LONG_S); - shadow_sprites_.at(5)->addPath(S_X_DEST, param.game.width, PathType::HORIZONTAL, S_Y_DEST, CARD_ANIM_DURATION_SHORT, easeInElastic, 0.0F); } // Inicializa los textos @@ -456,10 +456,6 @@ void Intro::updateSprites(float delta_time) { for (auto& sprite : card_sprites_) { sprite->update(delta_time); } - - for (auto& sprite : shadow_sprites_) { - sprite->update(delta_time); - } } // Actualiza los textos @@ -469,10 +465,11 @@ void Intro::updateTexts(float delta_time) { } } -// Dibuja los sprites +// Dibuja los sprites (todas las tarjetas activas, para que convivan la saliente y la entrante) void Intro::renderSprites() { - shadow_sprites_.at(scene_)->render(); - card_sprites_.at(scene_)->render(); + for (auto& card : card_sprites_) { + card->render(); + } } // Dibuja los textos @@ -525,4 +522,4 @@ void Intro::renderTextRect() { 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_); -} \ No newline at end of file +} diff --git a/source/sections/intro.hpp b/source/sections/intro.hpp index 8940d2c..48822e7 100644 --- a/source/sections/intro.hpp +++ b/source/sections/intro.hpp @@ -5,9 +5,9 @@ #include // Para unique_ptr #include // Para vector +#include "card_sprite.hpp" // Para CardSprite #include "color.hpp" // Para Color #include "param.hpp" // Para Param, ParamIntro, param -#include "path_sprite.hpp" // Para PathSprite #include "tiled_bg.hpp" // Para TiledBG #include "writer.hpp" // Para Writer @@ -18,14 +18,11 @@ // // Funcionalidades principales: // • Sistema de escenas secuencial: 6 escenas con transiciones automáticas -// • Animaciones de tarjetas: efectos de entrada con diferentes tipos de easing +// • 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 -// -// Todas las duraciones y velocidades están configuradas mediante constantes -// para facilitar el ajuste fino de la experiencia cinematográfica. class Intro { public: @@ -38,15 +35,15 @@ class Intro { private: // --- Constantes de tiempo (en segundos) --- - static constexpr float TEXT_DISPLAY_DURATION_S = 3.0F; // Duración de visualización de texto (180 frames a 60fps) - 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 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 // --- 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 + 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) @@ -57,16 +54,20 @@ class Intro { 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 (duraciones en segundos) --- - static constexpr float CARD_ANIM_DURATION_NORMAL = 100.0F / 60.0F; // ≈ 1.6667 s - static constexpr float CARD_ANIM_DURATION_FAST = 40.0F / 60.0F; // ≈ 0.6667 s - static constexpr float CARD_ANIM_DURATION_MEDIUM = 70.0F / 60.0F; // ≈ 1.1667 s - static constexpr float CARD_ANIM_DURATION_SHORT = 80.0F / 60.0F; // ≈ 1.3333 s - static constexpr float CARD_ANIM_DURATION_SLOW = 250.0F / 60.0F; // ≈ 4.1667 s - static constexpr float CARD_ANIM_DURATION_VERY_SLOW = 300.0F / 60.0F; // ≈ 5.0000 s + // --- 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 = 180.0; // Velocidad de rotación en salida (grados/s) - static constexpr float CARD_ANIM_DELAY_LONG_S = 7.5F; // Retraso largo antes de animación - static constexpr float CARD_OFFSET_MARGIN = 10.0F; // Margen fuera de pantalla + // --- Á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 { @@ -80,17 +81,16 @@ class Intro { }; // --- Objetos --- - std::vector> card_sprites_; // Vector con los sprites inteligentes para los dibujos de la intro - std::vector> shadow_sprites_; // Vector con los sprites inteligentes para las sombras - std::vector> texts_; // Textos de la intro - std::unique_ptr tiled_bg_; // Fondo en mosaico + std::vector> card_sprites_; // Tarjetas animadas con sombra integrada + std::vector> texts_; // Textos de la intro + std::unique_ptr 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) + float state_start_time_ = 0.0F; // Tiempo de inicio del estado actual (segundos) Color bg_color_ = param.intro.bg_color; // Color de fondo // --- Métodos internos --- @@ -99,13 +99,13 @@ class Intro { static void checkInput(); // Comprueba las entradas static void checkEvents(); // Comprueba los eventos void updateScenes(); // Actualiza las escenas de la intro - void initSprites(); // Inicializa las imágenes + 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 rectangulo de fondo del texto; + 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 @@ -117,7 +117,7 @@ class Intro { void updateScene4(); void updateScene5(); - // --- Métodos auxiliares para reducir duplicación de código --- - void enableCardAndShadow(int index); + // --- Métodos auxiliares --- void switchText(int from_index, int to_index); + void startCardExitAndAdvance(); // Inicia la salida de la tarjeta actual y avanza a la siguiente escena };