diff --git a/source/core/rendering/animatedsprite.cpp b/source/core/rendering/animatedsprite.cpp index 43e0496..37a9a7c 100644 --- a/source/core/rendering/animatedsprite.cpp +++ b/source/core/rendering/animatedsprite.cpp @@ -8,88 +8,95 @@ namespace { -// Normalitza CRLF: fitxers .ani amb terminadors de Windows fan que -// line == "[animation]" no faci match i el parser entri en bucle -// infinit / no carregui cap animació. -void stripCr(std::string &s) { - if (!s.empty() && s.back() == '\r') { - s.pop_back(); - } -} - -void parseFramesList(const std::string &value, Animation &buffer, int frame_width, int frame_height, int frames_per_row, int max_tiles) { - std::stringstream ss(value); - std::string tmp; - SDL_Rect rect = {0, 0, frame_width, frame_height}; - while (getline(ss, tmp, ',')) { - const int NUM_TILE = std::stoi(tmp) > max_tiles ? 0 : std::stoi(tmp); - rect.x = (NUM_TILE % frames_per_row) * frame_width; - rect.y = (NUM_TILE / frames_per_row) * frame_height; - buffer.frames.push_back(rect); - } -} - -void parseAnimationField(const std::string &line, int pos, Animation &buffer, int frame_width, int frame_height, int frames_per_row, int max_tiles, const std::string &filename) { - const std::string KEY = line.substr(0, pos); - const std::string VALUE = line.substr(pos + 1, line.length()); - if (KEY == "name") { - buffer.name = VALUE; - } else if (KEY == "speed") { - buffer.speed = std::stoi(VALUE); - } else if (KEY == "loop") { - buffer.loop = std::stoi(VALUE); - } else if (KEY == "frames") { - parseFramesList(VALUE, buffer, frame_width, frame_height, frames_per_row, max_tiles); - } else { - std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << KEY.c_str() << "\"" << '\n'; - } -} - -auto parseAnimationBlock(std::istream &file, int frame_width, int frame_height, int frames_per_row, int max_tiles, const std::string &filename) -> Animation { - Animation buffer; - buffer.speed = 0; - buffer.loop = -1; - buffer.counter = 0; - buffer.current_frame = 0; - buffer.completed = false; - - std::string line; - do { - if (!std::getline(file, line)) { - break; + // Normalitza CRLF: fitxers .ani amb terminadors de Windows fan que + // line == "[animation]" no faci match i el parser entri en bucle + // infinit / no carregui cap animació. + void stripCr(std::string &s) { + if (!s.empty() && s.back() == '\r') { + s.pop_back(); } - stripCr(line); - int pos = line.find('='); - if (pos != (int)std::string::npos) { - parseAnimationField(line, pos, buffer, frame_width, frame_height, frames_per_row, max_tiles, filename); + } + + void parseFramesList(const std::string &value, Animation &buffer, int frame_width, int frame_height, int frames_per_row, int max_tiles) { + std::stringstream ss(value); + std::string tmp; + SDL_Rect rect = {0, 0, frame_width, frame_height}; + while (getline(ss, tmp, ',')) { + const int NUM_TILE = std::stoi(tmp) > max_tiles ? 0 : std::stoi(tmp); + rect.x = (NUM_TILE % frames_per_row) * frame_width; + rect.y = (NUM_TILE / frames_per_row) * frame_height; + buffer.frames.push_back(rect); } - } while (line != "[/animation]"); - - return buffer; -} - -void parseGlobalField(const std::string &line, int pos, int &frames_per_row, int &frame_width, int &frame_height, int &max_tiles, const Texture *texture, const std::string &filename) { - const std::string KEY = line.substr(0, pos); - const std::string VALUE = line.substr(pos + 1, line.length()); - if (KEY == "framesPerRow") { - frames_per_row = std::stoi(VALUE); - } else if (KEY == "frameWidth") { - frame_width = std::stoi(VALUE); - } else if (KEY == "frameHeight") { - frame_height = std::stoi(VALUE); - } else { - std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << KEY.c_str() << "\"" << '\n'; } - if (frames_per_row == 0 && frame_width > 0) { - frames_per_row = texture->getWidth() / frame_width; + void parseAnimationField(const std::string &line, int pos, Animation &buffer, int frame_width, int frame_height, int frames_per_row, int max_tiles, const std::string &filename) { + const std::string KEY = line.substr(0, pos); + const std::string VALUE = line.substr(pos + 1, line.length()); + if (KEY == "name") { + buffer.name = VALUE; + } else if (KEY == "speed") { + buffer.speed = std::stoi(VALUE); + // Time-based: el valor del .ani s'expressa en "ticks per frame + // d'animació" (assumint 60 Hz). El camp `speed` (int) es manté per al + // fallback frame-based; el nou `step_duration_s` (float) és el que + // gasta animate(dt). + buffer.step_duration_s = static_cast(buffer.speed) / 60.0F; + } else if (KEY == "loop") { + buffer.loop = std::stoi(VALUE); + } else if (KEY == "frames") { + parseFramesList(VALUE, buffer, frame_width, frame_height, frames_per_row, max_tiles); + } else { + std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << KEY.c_str() << "\"" << '\n'; + } } - if (max_tiles == 0 && frame_width > 0 && frame_height > 0) { - const int W = texture->getWidth() / frame_width; - const int H = texture->getHeight() / frame_height; - max_tiles = W * H; + + auto parseAnimationBlock(std::istream &file, int frame_width, int frame_height, int frames_per_row, int max_tiles, const std::string &filename) -> Animation { + Animation buffer; + buffer.speed = 0; + buffer.step_duration_s = 0.0F; + buffer.loop = -1; + buffer.counter = 0; + buffer.current_frame = 0; + buffer.completed = false; + buffer.time_accumulator_s = 0.0F; + + std::string line; + do { + if (!std::getline(file, line)) { + break; + } + stripCr(line); + int pos = line.find('='); + if (pos != (int)std::string::npos) { + parseAnimationField(line, pos, buffer, frame_width, frame_height, frames_per_row, max_tiles, filename); + } + } while (line != "[/animation]"); + + return buffer; + } + + void parseGlobalField(const std::string &line, int pos, int &frames_per_row, int &frame_width, int &frame_height, int &max_tiles, const Texture *texture, const std::string &filename) { + const std::string KEY = line.substr(0, pos); + const std::string VALUE = line.substr(pos + 1, line.length()); + if (KEY == "framesPerRow") { + frames_per_row = std::stoi(VALUE); + } else if (KEY == "frameWidth") { + frame_width = std::stoi(VALUE); + } else if (KEY == "frameHeight") { + frame_height = std::stoi(VALUE); + } else { + std::cout << "Warning: file " << filename.c_str() << "\n, unknown parameter \"" << KEY.c_str() << "\"" << '\n'; + } + + if (frames_per_row == 0 && frame_width > 0) { + frames_per_row = texture->getWidth() / frame_width; + } + if (max_tiles == 0 && frame_width > 0 && frame_height > 0) { + const int W = texture->getWidth() / frame_width; + const int H = texture->getHeight() / frame_height; + max_tiles = W * H; + } } -} } // namespace @@ -234,6 +241,31 @@ void AnimatedSprite::animate() { } } +// Time-based: avança l'acumulador i calcula el frame actual a partir de +// `step_duration_s`. La lògica de loop/completed és la mateixa que la +// variant frame-based. +void AnimatedSprite::animate(float dt_s) { + Animation &anim = animation_[current_animation_]; + if (!enabled_ || anim.step_duration_s <= 0.0F) { + return; + } + + anim.time_accumulator_s += dt_s; + anim.current_frame = static_cast(anim.time_accumulator_s / anim.step_duration_s); + + if (anim.current_frame >= (int)anim.frames.size()) { + if (anim.loop == -1) { + anim.current_frame = anim.frames.size(); + anim.completed = true; + } else { + anim.time_accumulator_s = 0.0F; + anim.current_frame = anim.loop; + } + } else { + setSpriteClip(anim.frames[anim.current_frame]); + } +} + // Obtiene el numero de frames de la animación actual auto AnimatedSprite::getNumFrames() -> int { return (int)animation_[current_animation_].frames.size(); @@ -350,6 +382,12 @@ void AnimatedSprite::update() { MovingSprite::update(); } +// Time-based: animate(dt) + move(dt) +void AnimatedSprite::update(float dt_s) { + animate(dt_s); + MovingSprite::update(dt_s); +} + // Establece el rectangulo para un frame de una animación void AnimatedSprite::setAnimationFrames(Uint8 index_animation, Uint8 index_frame, int x, int y, int w, int h) { animation_[index_animation].frames.push_back({x, y, w, h}); diff --git a/source/core/rendering/animatedsprite.h b/source/core/rendering/animatedsprite.h index 1f445b6..b087f8c 100644 --- a/source/core/rendering/animatedsprite.h +++ b/source/core/rendering/animatedsprite.h @@ -12,11 +12,13 @@ class Texture; struct Animation { std::string name; // Nombre de la animacion std::vector frames; // Cada uno de los frames que componen la animación - int speed; // Velocidad de la animación + int speed; // Velocidad de la animación (frame-based: ticks per frame) + float step_duration_s; // Time-based: segons per frame d'animació (derivat de speed al parse: speed/60) int loop; // Indica a que frame vuelve la animación al terminar. -1 para que no vuelva bool completed; // Indica si ha finalizado la animación int current_frame; // Frame actual - int counter; // Contador para las animaciones + int counter; // Contador per a les animacions (frame-based) + float time_accumulator_s; // Acumulador de temps (time-based) }; struct AnimatedSpriteData { @@ -37,7 +39,8 @@ class AnimatedSprite : public MovingSprite { ~AnimatedSprite() override; // Destructor - void animate(); // Calcula el frame correspondiente a la animación actual + void animate(); // Calcula el frame correspondiente a la animación actual (frame-based) + void animate(float dt_s); // Calcula el frame correspondiente a la animación actual (time-based) auto getNumFrames() -> int; // Obtiene el numero de frames de la animación actual void setCurrentFrame(int num); // Establece el frame actual de la animación void setAnimationCounter(const std::string &name, int num); // Establece el valor del contador @@ -56,13 +59,14 @@ class AnimatedSprite : public MovingSprite { auto getAnimationClip(const std::string &name = "default", Uint8 index = 0) -> SDL_Rect; // Devuelve el rectangulo de una animación y frame concreto auto getAnimationClip(int index_a = 0, Uint8 index_f = 0) -> SDL_Rect; - auto getIndex(const std::string &name) -> int; // Obtiene el indice de la animación a partir del nombre + auto getIndex(const std::string &name) -> int; // Obtiene el indice de la animación a partir del nombre auto loadFromVector(const std::vector *source) -> bool; // Carga la animación desde un vector void setCurrentAnimation(const std::string &name = "default"); // Establece la animacion actual void setCurrentAnimation(int index = 0); - void update() override; // Actualiza las variables del objeto + void update() override; // Actualiza las variables del objeto (frame-based) + void update(float dt_s) override; // Actualiza las variables del objeto (time-based) void setAnimationFrames(Uint8 index_animation, Uint8 index_frame, int x, int y, int w, int h); // OLD - Establece el rectangulo para un frame de una animación void setAnimationCounter(int value); // OLD - Establece el contador para todas las animaciones diff --git a/source/core/rendering/fade.cpp b/source/core/rendering/fade.cpp index 0495775..7d9d665 100644 --- a/source/core/rendering/fade.cpp +++ b/source/core/rendering/fade.cpp @@ -31,6 +31,7 @@ void Fade::init(Uint8 r, Uint8 g, Uint8 b) { enabled_ = false; finished_ = false; counter_ = 0; + elapsed_s_ = 0.0F; r_ = r; g_ = g; b_ = b; @@ -162,11 +163,25 @@ void Fade::update() { } } +// Time-based. Per a no haver de refactoritzar `render()` tot d'un cop +// (encara hi ha codi frame-based que també escriu counter_), aquí derivem +// `counter_` a partir de `elapsed_s_` usant la cadència de referència de la +// versió antiga (60 Hz). Així el comportament visual del fade és idèntic. +// Quan tot el codi sigui time-based, render passarà a usar elapsed_s_ amb +// constants en segons i counter_ desapareixerà. +void Fade::update(float dt_s) { + if (!enabled_) { return; } + elapsed_s_ += dt_s; + constexpr float FADE_STEPS_PER_S = 60.0F; + counter_ = static_cast(elapsed_s_ * FADE_STEPS_PER_S); +} + // Activa el fade void Fade::activateFade() { enabled_ = true; finished_ = false; counter_ = 0; + elapsed_s_ = 0.0F; squares_drawn_ = 0; last_square_ticks_ = 0; fullscreen_done_ = false; diff --git a/source/core/rendering/fade.h b/source/core/rendering/fade.h index d159d5a..108b24e 100644 --- a/source/core/rendering/fade.h +++ b/source/core/rendering/fade.h @@ -18,7 +18,8 @@ class Fade { void init(Uint8 r, Uint8 g, Uint8 b); // Inicializa las variables void render(); // Pinta una transición en pantalla - void update(); // Actualiza las variables internas + void update(); // Actualiza las variables internas (frame-based) + void update(float dt_s); // Actualiza las variables internas (time-based) void activateFade(); // Activa el fade [[nodiscard]] auto hasEnded() const -> bool; // Comprueba si ha terminado la transicion @@ -34,7 +35,8 @@ class Fade { SDL_Renderer *renderer_ = nullptr; // El renderizador de la ventana SDL_Texture *backbuffer_ = nullptr; // Textura para usar como backbuffer Type fade_type_{Type::FULLSCREEN}; // Tipo de fade a realizar - Uint16 counter_ = 0; // Contador interno + Uint16 counter_ = 0; // Contador intern (frame-based) + float elapsed_s_ = 0.0F; // Acumulador de temps (time-based) bool enabled_ = false; // Indica si el fade está activo bool finished_ = false; // Indica si ha terminado la transición Uint8 r_ = 0, g_ = 0, b_ = 0; // Colores para el fade diff --git a/source/game/scenes/title.cpp b/source/game/scenes/title.cpp index b1f157a..2e8ee40 100644 --- a/source/game/scenes/title.cpp +++ b/source/game/scenes/title.cpp @@ -2,9 +2,10 @@ #include -#include // for rand -#include // for basic_ostream, operator<<, basic_ostrea... -#include // for basic_string, operator+, char_traits +#include // for min +#include // for rand +#include // for basic_ostream, operator<<, basic_ostrea... +#include // for basic_string, operator+, char_traits #include "core/audio/audio.hpp" // for Audio #include "core/input/global_inputs.hpp" // for GlobalInputs::handle @@ -19,10 +20,11 @@ #include "core/rendering/texture.h" // for Texture #include "core/resources/asset.h" // for Asset #include "core/resources/resource.h" -#include "game/defaults.hpp" // for GAMECANVAS_CENTER_X, SECTION_PROG_QUIT -#include "game/game.h" // for Game -#include "game/options.hpp" // for Options -#include "game/ui/menu.h" // for Menu +#include "core/system/delta_time.hpp" // for DeltaTime::reset / tick +#include "game/defaults.hpp" // for GAMECANVAS_CENTER_X, SECTION_PROG_QUIT +#include "game/game.h" // for Game +#include "game/options.hpp" // for Options +#include "game/ui/menu.h" // for Menu // Constructor Title::Title(SDL_Renderer *renderer, Section *section) { @@ -70,6 +72,9 @@ Title::Title(SDL_Renderer *renderer, Section *section) { // Inicializa los valores init(); + + // Reset del rellotge: la primera crida a tick() retornarà ~0. + DeltaTime::reset(); } // Destructor @@ -92,18 +97,19 @@ Title::~Title() { void Title::init() { // Inicializa variables section_->subsection = SUBSECTION_TITLE_1; - counter_ = COUNTER; - background_counter_ = 0; + demo_remaining_s_ = DEMO_TIMEOUT_S; + bg_scroll_x_s_ = 0.0F; + bg_scroll_y_s_ = 0.0F; + bg_phase_s_ = 0.0F; + blink_phase_s_ = 0.0F; background_mode_ = rand() % 2; menu_visible_ = false; menu_.active = menu_.title; next_section_.name = SECTION_PROG_GAME; post_fade_ = 0; - ticks_ = 0; - ticks_speed_ = 15; fade_->init(0x17, 0x17, 0x26); demo_ = true; - vibration_step_ = 0; + vibration_elapsed_s_ = 0.0F; vibration_initialized_ = false; instructions_active_ = false; demo_game_active_ = false; @@ -148,12 +154,12 @@ void Title::init() { coffee_bitmap_->setWidth(167); coffee_bitmap_->setHeight(46); coffee_bitmap_->setVelX(0.0F); - coffee_bitmap_->setVelY(2.5F); + coffee_bitmap_->setVelY(150.0F); // 2.5 px/tick ⇒ 150 px/s coffee_bitmap_->setAccelX(0.0F); - coffee_bitmap_->setAccelY(0.1F); + coffee_bitmap_->setAccelY(360.0F); // 0.1 px/tick² ⇒ 360 px/s² coffee_bitmap_->setSpriteClip(0, 0, 167, 46); coffee_bitmap_->setEnabled(true); - coffee_bitmap_->setEnabledCounter(0); + coffee_bitmap_->setRemainingTime(0.0F); coffee_bitmap_->setDestX(45); coffee_bitmap_->setDestY(11); @@ -164,12 +170,12 @@ void Title::init() { crisis_bitmap_->setWidth(137); crisis_bitmap_->setHeight(46); crisis_bitmap_->setVelX(0.0F); - crisis_bitmap_->setVelY(-2.5F); + crisis_bitmap_->setVelY(-150.0F); // -2.5 px/tick ⇒ -150 px/s crisis_bitmap_->setAccelX(0.0F); - crisis_bitmap_->setAccelY(-0.1F); + crisis_bitmap_->setAccelY(-360.0F); // -0.1 px/tick² ⇒ -360 px/s² crisis_bitmap_->setSpriteClip(0, 0, 137, 46); crisis_bitmap_->setEnabled(true); - crisis_bitmap_->setEnabledCounter(0); + crisis_bitmap_->setRemainingTime(0.0F); crisis_bitmap_->setDestX(60); crisis_bitmap_->setDestY(57); @@ -209,25 +215,20 @@ void Title::init() { updateMenuLabels(); } -// Actualiza las variables del objeto -void Title::update() { +// Actualiza las variables del objeto (time-based) +void Title::update(float dt_s) { Audio::update(); checkInput(); - if (SDL_GetTicks() - ticks_ <= ticks_speed_) { - return; - } - ticks_ = SDL_GetTicks(); - switch (section_->subsection) { case SUBSECTION_TITLE_1: - updateTitle1(); + updateTitle1(dt_s); break; case SUBSECTION_TITLE_2: - updateTitle2(); + updateTitle2(dt_s); break; case SUBSECTION_TITLE_3: - updateTitle3(); + updateTitle3(dt_s); break; default: break; @@ -235,9 +236,9 @@ void Title::update() { } // Sección 1 - Titulo desplazandose -void Title::updateTitle1() { - coffee_bitmap_->update(); - crisis_bitmap_->update(); +void Title::updateTitle1(float dt_s) { + coffee_bitmap_->update(dt_s); + crisis_bitmap_->update(dt_s); // Si los objetos han llegado a su destino, cambiamos de Sección if (coffee_bitmap_->hasFinished() && crisis_bitmap_->hasFinished()) { @@ -257,7 +258,7 @@ void Title::updateTitle1() { } // Sección 2 - Titulo vibrando -void Title::updateTitle2() { +void Title::updateTitle2(float dt_s) { // Captura las posiciones base la primera vez if (!vibration_initialized_) { vibration_coffee_base_x_ = coffee_bitmap_->getPosX(); @@ -265,37 +266,44 @@ void Title::updateTitle2() { vibration_initialized_ = true; } + // Patró d'offset horitzontal (15 valors, una vegada cadascun cada 3 frames) const int V[] = {-1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 0}; - coffee_bitmap_->setPosX(vibration_coffee_base_x_ + V[vibration_step_ / 3]); - crisis_bitmap_->setPosX(vibration_crisis_base_x_ + V[vibration_step_ / 3]); - dust_bitmap_right_->update(); - dust_bitmap_left_->update(); + constexpr int V_SIZE = static_cast(sizeof(V) / sizeof(V[0])); + const int IDX = std::min(static_cast(vibration_elapsed_s_ / VIBRATION_STEP_DURATION_S), V_SIZE - 1); + coffee_bitmap_->setPosX(vibration_coffee_base_x_ + V[IDX]); + crisis_bitmap_->setPosX(vibration_crisis_base_x_ + V[IDX]); + dust_bitmap_right_->update(dt_s); + dust_bitmap_left_->update(dt_s); - vibration_step_++; + vibration_elapsed_s_ += dt_s; - if (vibration_step_ >= 33) { + if (vibration_elapsed_s_ >= VIBRATION_DURATION_S) { section_->subsection = SUBSECTION_TITLE_3; - vibration_step_ = 0; + vibration_elapsed_s_ = 0.0F; vibration_initialized_ = false; } } // Sección 3 - La pantalla de titulo con el menú y la música -void Title::updateTitle3() { - if (counter_ > 0) { +void Title::updateTitle3(float dt_s) { + if (demo_remaining_s_ > 0.0F) { if (Audio::getRealMusicState() == Audio::MusicState::STOPPED) { Audio::get()->playMusic(title_music_); } - dust_bitmap_right_->update(); - dust_bitmap_left_->update(); - fade_->update(); + dust_bitmap_right_->update(dt_s); + dust_bitmap_left_->update(dt_s); + fade_->update(dt_s); if (fade_->hasEnded()) { handlePostFadeAction(); } - updateBG(); + updateBG(dt_s); + blink_phase_s_ += dt_s; + if (blink_phase_s_ >= PRESS_ANY_KEY_PERIOD_S) { + blink_phase_s_ -= PRESS_ANY_KEY_PERIOD_S; + } if (menu_visible_ && !fade_->isEnabled()) { menu_.active->update(); @@ -312,9 +320,9 @@ void Title::updateTitle3() { } if (menu_.active->getName() == "TITLE") { - counter_--; + demo_remaining_s_ -= dt_s; } - } else if (counter_ == 0) { + } else { if (demo_) { demo_then_instructions_ = true; runDemoGame(); @@ -351,7 +359,7 @@ void Title::handlePostFadeAction() { break; case 3: // TIME OUT - counter_ = COUNTER; + demo_remaining_s_ = DEMO_TIMEOUT_S; menu_.active->reset(); if (demo_) { demo_then_instructions_ = true; @@ -602,8 +610,8 @@ void Title::render() { dust_bitmap_right_->render(); dust_bitmap_left_->render(); - // PRESS ANY KEY! - if ((counter_ % 50 > 14) && (!menu_visible_)) { + // PRESS ANY KEY! Blink: visible quan la fase passa l'umbral "off". + if ((blink_phase_s_ > PRESS_ANY_KEY_OFF_S) && (!menu_visible_)) { text1_->writeDX(Text::FLAG_CENTER | Text::FLAG_SHADOW, GAMECANVAS_CENTER_X, PLAY_AREA_THIRD_QUARTER_Y + BLOCK, Lang::get()->getText(23), 1, NO_COLOR, 1, SHADOW_COLOR); } @@ -626,15 +634,24 @@ void Title::checkInput() { GlobalInputs::handle(); } -// Actualiza el tileado de fondo -void Title::updateBG() { - if (background_mode_ == 0) { // El tileado de fondo se desplaza en diagonal - ++background_window_.x %= 64; - ++background_window_.y %= 64; - } else { // El tileado de fondo se desplaza en circulo - ++background_counter_ %= 360; - background_window_.x = 128 + (int(sin_[(background_counter_ + 270) % 360] * 128)); - background_window_.y = 96 + (int(sin_[(360 - background_counter_) % 360] * 96)); +// Actualiza el tileado de fondo (time-based). +void Title::updateBG(float dt_s) { + if (background_mode_ == 0) { + // Diagonal: 60 px/s a 60Hz ⇒ un cicle de 64 px cada 64/60 = 1.067 s. + constexpr float SCROLL_PERIOD_S = 64.0F / BG_SCROLL_SPEED_PX_PER_S; + bg_scroll_x_s_ += dt_s; + bg_scroll_y_s_ += dt_s; + if (bg_scroll_x_s_ >= SCROLL_PERIOD_S) { bg_scroll_x_s_ -= SCROLL_PERIOD_S; } + if (bg_scroll_y_s_ >= SCROLL_PERIOD_S) { bg_scroll_y_s_ -= SCROLL_PERIOD_S; } + background_window_.x = static_cast(bg_scroll_x_s_ * BG_SCROLL_SPEED_PX_PER_S); + background_window_.y = static_cast(bg_scroll_y_s_ * BG_SCROLL_SPEED_PX_PER_S); + } else { + // Cercle: 360 graus en BG_CIRCLE_PERIOD_S segons. + bg_phase_s_ += dt_s; + if (bg_phase_s_ >= BG_CIRCLE_PERIOD_S) { bg_phase_s_ -= BG_CIRCLE_PERIOD_S; } + const int ANGLE = static_cast((bg_phase_s_ / BG_CIRCLE_PERIOD_S) * 360.0F) % 360; + background_window_.x = 128 + (int(sin_[(ANGLE + 270) % 360] * 128)); + background_window_.y = 96 + (int(sin_[(360 - ANGLE) % 360] * 96)); } } @@ -892,7 +909,8 @@ void Title::iterate() { } // Ejecución normal del título - update(); + const float DELTA_TIME_S = DeltaTime::tick(); + update(DELTA_TIME_S); render(); } @@ -918,7 +936,7 @@ void Title::handleEvent(const SDL_Event *event) { if (section_->subsection == SUBSECTION_TITLE_3) { if ((event->type == SDL_EVENT_KEY_UP) || (event->type == SDL_EVENT_JOYSTICK_BUTTON_UP)) { menu_visible_ = true; - counter_ = COUNTER; + demo_remaining_s_ = DEMO_TIMEOUT_S; } } } diff --git a/source/game/scenes/title.h b/source/game/scenes/title.h index b8d5a21..0b303c4 100644 --- a/source/game/scenes/title.h +++ b/source/game/scenes/title.h @@ -34,7 +34,21 @@ class Title { private: static constexpr const char *COPYRIGHT = "@2020 JailDesigner (v2.3.4)"; - static constexpr int COUNTER = 800; + // Time-based: temps màxim a la pantalla del títol abans de tornar al + // logo o llançar el demo. 800 frames a 60Hz ⇒ 13.333 s. + static constexpr float DEMO_TIMEOUT_S = 13.333F; + // Període i fracció "off" del blink del text "PRESS ANY KEY". + // Original: counter_ % 50 > 14 ⇒ 50 frames de període, 14 frames off. + static constexpr float PRESS_ANY_KEY_PERIOD_S = 50.0F / 60.0F; + static constexpr float PRESS_ANY_KEY_OFF_S = 14.0F / 60.0F; + // Vibració post-impacte (SUBSECTION_TITLE_2): 33 frames a 60Hz ⇒ 0.55 s, + // 11 valors del patró V[] consumits a `step/3` (3 frames per pas). + static constexpr float VIBRATION_DURATION_S = 33.0F / 60.0F; + static constexpr float VIBRATION_STEP_DURATION_S = 3.0F / 60.0F; + // BG mode 0: scroll diagonal 1 px/tick a 60Hz ⇒ 60 px/s. + static constexpr float BG_SCROLL_SPEED_PX_PER_S = 60.0F; + // BG mode 1: cicle de 360 frames a 60Hz ⇒ 6 s per volta. + static constexpr float BG_CIRCLE_PERIOD_S = 6.0F; struct MenuData { Menu *title; // Menu de la pantalla de título @@ -72,19 +86,20 @@ class Title { Fade *fade_; // Objeto para realizar fundidos en pantalla // Variables - Ja::Music *title_music_; // Musica para el titulo - Ja::Sound *crash_sound_; // Sonido con el impacto del título - int background_counter_; // Temporizador para el fondo de tiles de la pantalla de titulo - int counter_; // Temporizador para la pantalla de titulo - Uint32 ticks_; // Contador de ticks para ajustar la velocidad del programa - Uint8 background_mode_; // Variable para almacenar el tipo de efecto que hará el fondo de la pantalla de titulo - float sin_[360]; // Vector con los valores del seno precalculados - bool menu_visible_; // Indicador para saber si se muestra el menu del titulo o la frase intermitente - bool demo_; // Indica si el modo demo estará activo - Section next_section_; // Indica cual es la siguiente sección a cargar cuando termine el contador del titulo - Uint32 ticks_speed_; // Velocidad a la que se repiten los bucles del programa - Uint8 post_fade_; // Opción a realizar cuando termina el fundido - MenuData menu_; // Variable con todos los objetos menus y sus variables + Ja::Music *title_music_; // Musica para el titulo + Ja::Sound *crash_sound_; // Sonido con el impacto del título + float bg_scroll_x_s_{0.0F}; // Acumulador d'scroll horitzontal (segons) per al BG mode 0 + float bg_scroll_y_s_{0.0F}; // Acumulador d'scroll vertical (segons) per al BG mode 0 + float bg_phase_s_{0.0F}; // Fase del cicle del BG mode 1 (0..BG_CIRCLE_PERIOD_S) + float demo_remaining_s_; // Temps que queda al títol abans de tornar al logo / demo + float blink_phase_s_{0.0F}; // Fase del blink de "PRESS ANY KEY" + Uint8 background_mode_; // Variable para almacenar el tipo de efecto que hará el fondo de la pantalla de titulo + float sin_[360]; // Vector con los valores del seno precalculados + bool menu_visible_; // Indicador para saber si se muestra el menu del titulo o la frase intermitente + bool demo_; // Indica si el modo demo estará activo + Section next_section_; // Indica cual es la siguiente sección a cargar cuando termine el contador del titulo + Uint8 post_fade_; // Opción a realizar cuando termina el fundido + MenuData menu_; // Variable con todos los objetos menus y sus variables // Snapshot per a permetre CANCEL al menú d'opcions. Options::Video prev_video_; Options::Window prev_window_; @@ -94,10 +109,10 @@ class Title { std::vector device_index_; // Indice para el jugador [i] del vector de dispositivos de entrada disponibles // Variables para la vibración del título (SUBSECTION_TITLE_2) - int vibration_step_; // Paso actual de la vibración - int vibration_coffee_base_x_{0}; // Posición X base del bitmap Coffee - int vibration_crisis_base_x_{0}; // Posición X base del bitmap Crisis - bool vibration_initialized_; // Indica si se han capturado las posiciones base + float vibration_elapsed_s_{0.0F}; // Temps transcorregut des de l'inici de la vibració + int vibration_coffee_base_x_{0}; // Posición X base del bitmap Coffee + int vibration_crisis_base_x_{0}; // Posición X base del bitmap Crisis + bool vibration_initialized_; // Indica si se han capturado las posiciones base // Variables para sub-estados delegados (instrucciones y demo) bool instructions_active_; // Indica si las instrucciones están activas @@ -106,20 +121,20 @@ class Title { bool demo_then_instructions_; // Indica si tras la demo hay que mostrar instrucciones void init(); // Inicializa los valores - void update(); // Actualiza las variables del objeto + void update(float dt_s); // Actualiza las variables del objeto (time-based) void render(); // Dibuja el objeto en pantalla static void checkInput(); // Comprueba las entradas (només delega a GlobalInputs) // Helpers de update, uno por cada subsección y por cada switch dentro del título 3 - void updateTitle1(); - void updateTitle2(); - void updateTitle3(); + void updateTitle1(float dt_s); + void updateTitle2(float dt_s); + void updateTitle3(float dt_s); void handlePostFadeAction(); void handleTitleMenuSelection(); void handlePlayerSelectMenuSelection(); void handleOptionsMenuSelection(); - void updateBG(); // Actualiza el tileado de fondo + void updateBG(float dt_s); // Actualiza el tileado de fondo (time-based) static void switchFullScreenModeVar(); // Cambia el valor de la variable de modo de pantalla completa void updateMenuLabels() const; // Actualiza los elementos de los menus void applyOptions(); // Aplica las opciones de menu seleccionadas