treballant en la visió original de la intro
This commit is contained in:
@@ -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
|
||||
|
||||
1
Makefile
1
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 \
|
||||
|
||||
203
source/card_sprite.cpp
Normal file
203
source/card_sprite.cpp
Normal file
@@ -0,0 +1,203 @@
|
||||
#include "card_sprite.hpp"
|
||||
|
||||
#include <algorithm> // Para std::clamp
|
||||
#include <functional> // Para function
|
||||
#include <utility> // Para move
|
||||
|
||||
#include "texture.hpp" // Para Texture
|
||||
#include "utils.hpp" // Para easeOutBounce
|
||||
|
||||
// Constructor
|
||||
CardSprite::CardSprite(std::shared_ptr<Texture> 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<double>(progress));
|
||||
|
||||
// Zoom: de start_zoom_ a 1.0 con rebote
|
||||
auto current_zoom = static_cast<float>(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<double(double)> 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> 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;
|
||||
}
|
||||
97
source/card_sprite.hpp
Normal file
97
source/card_sprite.hpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FPoint
|
||||
|
||||
#include <functional> // Para function
|
||||
#include <memory> // 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> 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<double(double)> 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> 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<double(double)> 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<Texture> 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;
|
||||
};
|
||||
@@ -3,25 +3,24 @@
|
||||
#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 <functional> // Para function
|
||||
#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 "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<std::string, 6> 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<std::shared_ptr<Texture>> card_textures;
|
||||
|
||||
for (int i = 0; i < TOTAL_SPRITES; ++i) {
|
||||
// Crea la textura
|
||||
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);
|
||||
|
||||
// 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<PathSprite>(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<Texture>(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<PathSprite>(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<CardSprite>(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_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
#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 "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<std::unique_ptr<PathSprite>> card_sprites_; // Vector con los sprites inteligentes para los dibujos de la intro
|
||||
std::vector<std::unique_ptr<PathSprite>> shadow_sprites_; // Vector con los sprites inteligentes para las sombras
|
||||
std::vector<std::unique_ptr<Writer>> texts_; // Textos de la intro
|
||||
std::unique_ptr<TiledBG> tiled_bg_; // Fondo en mosaico
|
||||
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)
|
||||
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
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user