diff --git a/source/director.cpp b/source/director.cpp index 79f7560..f7a81e2 100644 --- a/source/director.cpp +++ b/source/director.cpp @@ -44,7 +44,7 @@ Director::Director(int argc, std::span argv) { Section::name = Section::Name::GAME; Section::options = Section::Options::GAME_PLAY_1P; #elif _DEBUG - Section::name = Section::Name::GAME; + Section::name = Section::Name::CREDITS; Section::options = Section::Options::GAME_PLAY_1P; #else // NORMAL GAME Section::name = Section::Name::LOGO; diff --git a/source/sections/credits.cpp b/source/sections/credits.cpp index f722dae..0f848a4 100644 --- a/source/sections/credits.cpp +++ b/source/sections/credits.cpp @@ -45,27 +45,8 @@ Credits::Credits() if (text_texture_ == nullptr) { throw std::runtime_error("Failed to create SDL texture for text."); } - Section::name = Section::Name::CREDITS; - balloon_manager_->setPlayArea(play_area_); - - fade_in_->setColor(param.fade.color); - fade_in_->setType(Fade::Type::FULLSCREEN); - fade_in_->setPostDuration(800); - fade_in_->setMode(Fade::Mode::IN); - fade_in_->activate(); - - fade_out_->setColor(0, 0, 0); - fade_out_->setType(Fade::Type::FULLSCREEN); - fade_out_->setPostDuration(7000); - - updateRedRect(); - tiled_bg_->setColor(Color(255, 96, 96)); - tiled_bg_->setSpeed(60.0F); - - initPlayers(); - SDL_SetTextureBlendMode(text_texture_, SDL_BLENDMODE_BLEND); - fillTextTexture(); - steps_ = static_cast(std::abs((top_black_rect_.h - param.game.game_area.center_y - 1) + ((left_black_rect_.w - param.game.game_area.center_x) / 4))); + initVars(); + startCredits(); } // Destructor @@ -103,7 +84,7 @@ void Credits::run() { // Actualiza las variables (time-based) void Credits::update(float delta_time) { - const float MULTIPLIER = want_to_pass_ ? 4.0F : 1.0F; + const float MULTIPLIER = want_to_pass_ ? FAST_FORWARD_MULTIPLIER : 1.0F; const float ADJUSTED_DELTA_TIME = delta_time * MULTIPLIER; static auto* const SCREEN = Screen::get(); @@ -274,9 +255,7 @@ void Credits::fillCanvas() { SDL_RenderFillRect(Screen::get()->getRenderer(), &right_black_rect_); // Dibuja el rectangulo rojo - const Color COLOR = color_.LIGHTEN(); - SDL_SetRenderDrawColor(Screen::get()->getRenderer(), COLOR.r, COLOR.g, COLOR.b, 0xFF); - SDL_RenderRect(Screen::get()->getRenderer(), &border_rect_); + drawBorderRect(); // Si el mini_logo está en su destino, lo dibuja encima de lo anterior if (mini_logo_on_position_) { @@ -354,8 +333,8 @@ void Credits::throwBalloons(float delta_time) { // Inicializa los jugadores void Credits::initPlayers() { std::vector>> player_textures; // Vector con todas las texturas de los jugadores; - std::vector> player1_animations; // Vector con las animaciones del jugador 1 - std::vector> player2_animations; // Vector con las animaciones del jugador 2 + std::vector> player1_animations; // Vector con las animaciones del jugador 1 + std::vector> player2_animations; // Vector con las animaciones del jugador 2 // Texturas - Player1 std::vector> player1_textures; @@ -423,66 +402,114 @@ void Credits::initPlayers() { // Actualiza los rectangulos negros (time-based) void Credits::updateBlackRects(float delta_time) { - static auto current_step_ = static_cast(steps_); - constexpr float BLACK_RECT_INTERVAL_S = 4.0F / 60.0F; // ~0.067s (cada 4 frames) - if (top_black_rect_.h != param.game.game_area.center_y - 1 && bottom_black_rect_.y != param.game.game_area.center_y + 1) { - // Si los rectangulos superior e inferior no han llegado al centro + if (!initialized_) return; + if (delta_time < 0.0f) delta_time = 0.0f; + + // Fase vertical: hasta que ambos rects verticales estén exactos en su target + if (!vertical_done_) { credits_state_.black_rect_accumulator += delta_time; if (credits_state_.black_rect_accumulator >= BLACK_RECT_INTERVAL_S) { credits_state_.black_rect_accumulator -= BLACK_RECT_INTERVAL_S; - // Incrementa la altura del rectangulo superior - top_black_rect_.h = std::min(top_black_rect_.h + 1, param.game.game_area.center_y - 1); + // top + int prev_top_h = static_cast(top_black_rect_.h); + top_black_rect_.h = std::min(top_black_rect_.h + 1.0f, + static_cast(param.game.game_area.center_y - 1)); + int top_delta = static_cast(top_black_rect_.h) - prev_top_h; - // Incrementa la altura y modifica la posición del rectangulo inferior - ++bottom_black_rect_.h; - bottom_black_rect_.y = std::max(bottom_black_rect_.y - 1, param.game.game_area.center_y + 1); + // bottom + int prev_bottom_h = static_cast(bottom_black_rect_.h); + int prev_bottom_y = static_cast(bottom_black_rect_.y); + bottom_black_rect_.h = bottom_black_rect_.h + 1.0f; + bottom_black_rect_.y = std::max(bottom_black_rect_.y - 1.0f, + static_cast(param.game.game_area.center_y + 1)); + int bottom_steps_by_h = static_cast(bottom_black_rect_.h) - prev_bottom_h; + int bottom_steps_by_y = prev_bottom_y - static_cast(bottom_black_rect_.y); + int bottom_steps = std::max(0, std::max(bottom_steps_by_h, bottom_steps_by_y)); - --current_step_; - setVolume(static_cast(initial_volume_ * current_step_ / steps_)); - } - } else { - // Si los rectangulos superior e inferior han llegado al centro - if (left_black_rect_.w != param.game.game_area.center_x && right_black_rect_.x != param.game.game_area.center_x) { - constexpr int SPEED = 2; - // Si los rectangulos izquierdo y derecho no han llegado al centro - // Incrementa la anchura del rectangulo situado a la izquierda - left_black_rect_.w = std::min(left_black_rect_.w + SPEED, param.game.game_area.center_x); + int steps_done = top_delta + bottom_steps; + if (steps_done > 0) { + current_step_ = std::max(0.0f, current_step_ - static_cast(steps_done)); + float vol_f = initial_volume_ * (current_step_ / static_cast(total_steps_)); + int vol_i = static_cast(std::clamp(vol_f, 0.0f, static_cast(initial_volume_))); + Audio::get()->setMusicVolume(vol_i); // usa tu API de audio aquí + } - // Incrementa la anchura y modifica la posición del rectangulo situado a la derecha - right_black_rect_.w += SPEED; - right_black_rect_.x = std::max(right_black_rect_.x - SPEED, param.game.game_area.center_x); - - --current_step_; - setVolume(static_cast(initial_volume_ * current_step_ / steps_)); - } else { - // Si los rectangulos izquierdo y derecho han llegado al centro - setVolume(0); - Audio::get()->stopMusic(); - if (counter_pre_fade_ == 400) { - fade_out_->activate(); - } else { - // Convertir deltaTime a equivalente de frames - const float FRAME_FACTOR = delta_time * 60.0F; - counter_pre_fade_ += FRAME_FACTOR; + // Si han alcanzado los objetivos, fijarlos exactamente y marcar done + bool top_at_target = static_cast(top_black_rect_.h) == param.game.game_area.center_y - 1; + bool bottom_at_target = static_cast(bottom_black_rect_.y) == param.game.game_area.center_y + 1; + if (top_at_target && bottom_at_target) { + top_black_rect_.h = static_cast(param.game.game_area.center_y - 1); + bottom_black_rect_.y = static_cast(param.game.game_area.center_y + 1); + vertical_done_ = true; } } + // actualizar border_rect cada frame aunque todavía en fase vertical + updateBorderRect(); + return; + } + + // Fase horizontal + if (!horizontal_done_) { + int prev_left_w = static_cast(left_black_rect_.w); + left_black_rect_.w = std::min(left_black_rect_.w + static_cast(HORIZONTAL_SPEED), + static_cast(param.game.game_area.center_x)); + int left_gain = static_cast(left_black_rect_.w) - prev_left_w; + + int prev_right_x = static_cast(right_black_rect_.x); + right_black_rect_.w = right_black_rect_.w + static_cast(HORIZONTAL_SPEED); + right_black_rect_.x = std::max(right_black_rect_.x - static_cast(HORIZONTAL_SPEED), + static_cast(param.game.game_area.center_x)); + int right_move = prev_right_x - static_cast(right_black_rect_.x); + + int steps_done = left_gain + right_move; + if (steps_done > 0) { + current_step_ = std::max(0.0f, current_step_ - static_cast(steps_done)); + float vol_f = initial_volume_ * (current_step_ / static_cast(total_steps_)); + int vol_i = static_cast(std::clamp(vol_f, 0.0f, static_cast(initial_volume_))); + Audio::get()->setMusicVolume(vol_i); // usa tu API de audio aquí + } + + bool left_at_target = static_cast(left_black_rect_.w) == param.game.game_area.center_x; + bool right_at_target = static_cast(right_black_rect_.x) == param.game.game_area.center_x; + if (left_at_target && right_at_target) { + left_black_rect_.w = static_cast(param.game.game_area.center_x); + right_black_rect_.x = static_cast(param.game.game_area.center_x); + horizontal_done_ = true; + } + + updateBorderRect(); + return; + } + + // Fase final: ya completado el movimiento de rects + Audio::get()->setMusicVolume(0); + // Audio::get()->stopMusic(); // opcional, si quieres parar la reproducción + if (counter_pre_fade_ >= 400.0f) { + if (fade_out_) fade_out_->activate(); + } else { + const float frame_increment = delta_time * FRAMES_PER_SECOND; + counter_pre_fade_ += frame_increment; } } -// Actualiza el rectangulo rojo -void Credits::updateRedRect() { +// Actualiza el rectangulo del borde +void Credits::updateBorderRect() { border_rect_.x = left_black_rect_.x + left_black_rect_.w; border_rect_.y = top_black_rect_.y + top_black_rect_.h - 1; - border_rect_.w = right_black_rect_.x - border_rect_.x; - border_rect_.h = bottom_black_rect_.y - border_rect_.y + 1; + + float raw_w = right_black_rect_.x - border_rect_.x; + float raw_h = bottom_black_rect_.y - border_rect_.y + 1.0f; + + border_rect_.w = std::max(0.0f, raw_w); + border_rect_.h = std::max(0.0f, raw_h); } // Actualiza el estado de fade (time-based) void Credits::updateAllFades(float delta_time) { if (fading_) { updateBlackRects(delta_time); - updateRedRect(); + updateBorderRect(); } fade_in_->update(); @@ -512,7 +539,7 @@ void Credits::resetVolume() const { void Credits::cycleColors(float delta_time) { constexpr int UPPER_LIMIT = 140; // Límite superior constexpr int LOWER_LIMIT = 30; // Límite inferior - + // Factor para escalar los valores de incremento. // Asumimos que los valores originales estaban balanceados para 60 FPS. const float FRAME_ADJUSTMENT = delta_time * 60.0F; @@ -527,10 +554,10 @@ void Credits::cycleColors(float delta_time) { // Ajustar valores de R credits_state_.r += credits_state_.step_r * FRAME_ADJUSTMENT; if (credits_state_.r >= UPPER_LIMIT) { - credits_state_.r = UPPER_LIMIT; // Clamp para evitar que se pase + credits_state_.r = UPPER_LIMIT; // Clamp para evitar que se pase credits_state_.step_r = -credits_state_.step_r; // Cambia de dirección al alcanzar los límites } else if (credits_state_.r <= LOWER_LIMIT) { - credits_state_.r = LOWER_LIMIT; // Clamp para evitar que se pase + credits_state_.r = LOWER_LIMIT; // Clamp para evitar que se pase credits_state_.step_r = -credits_state_.step_r; } @@ -572,3 +599,114 @@ void Credits::renderPlayers() { player->render(); } } + +// Inicializa variables +void Credits::initVars() { + // Inicialización segura de rects tal y como los mostraste + top_black_rect_ = { + play_area_.x, + param.game.game_area.rect.y, + play_area_.w, + black_bars_size_}; + bottom_black_rect_ = { + play_area_.x, + param.game.game_area.rect.h - black_bars_size_, + play_area_.w, + black_bars_size_}; + left_black_rect_ = { + play_area_.x, + param.game.game_area.center_y - 1, + 0, + 2}; + right_black_rect_ = { + play_area_.x + play_area_.w, + param.game.game_area.center_y - 1, + 0, + 2}; + + initialized_ = false; + + Section::name = Section::Name::CREDITS; + balloon_manager_->setPlayArea(play_area_); + + fade_in_->setColor(param.fade.color); + fade_in_->setType(Fade::Type::FULLSCREEN); + fade_in_->setPostDuration(800); + fade_in_->setMode(Fade::Mode::IN); + fade_in_->activate(); + + fade_out_->setColor(0, 0, 0); + fade_out_->setType(Fade::Type::FULLSCREEN); + fade_out_->setPostDuration(7000); + + updateBorderRect(); + tiled_bg_->setColor(Color(255, 96, 96)); + tiled_bg_->setSpeed(60.0F); + + initPlayers(); + SDL_SetTextureBlendMode(text_texture_, SDL_BLENDMODE_BLEND); + fillTextTexture(); + steps_ = static_cast(std::abs((top_black_rect_.h - param.game.game_area.center_y - 1) + ((left_black_rect_.w - param.game.game_area.center_x) / 4))); +} + +void Credits::startCredits() { + // Guardar iniciales (enteros para contar "pasos" por píxel) + init_top_h = static_cast(top_black_rect_.h); + init_bottom_y = static_cast(bottom_black_rect_.y); + init_left_w = static_cast(left_black_rect_.w); + init_right_x = static_cast(right_black_rect_.x); + + // Objetivos + int top_target_h = param.game.game_area.center_y - 1; + int bottom_target_y = param.game.game_area.center_y + 1; + int left_target_w = param.game.game_area.center_x; + int right_target_x = param.game.game_area.center_x; + + // Pasos verticales + int pasos_top = std::max(0, top_target_h - init_top_h); + int pasos_bottom = std::max(0, init_bottom_y - bottom_target_y); + + // Pasos horizontales. right se mueve a velocidad HORIZONTAL_SPEED, contamos pasos como unidades de movimiento equivalentes + int pasos_left = std::max(0, left_target_w - init_left_w); + int dx_right = std::max(0, init_right_x - right_target_x); + int pasos_right = (dx_right + (HORIZONTAL_SPEED - 1)) / HORIZONTAL_SPEED; // ceil + + total_steps_ = pasos_top + pasos_bottom + pasos_left + pasos_right; + if (total_steps_ <= 0) total_steps_ = 1; + + current_step_ = static_cast(total_steps_); + + // Reiniciar contadores y estado + credits_state_.black_rect_accumulator = 0.0f; + counter_pre_fade_ = 0.0f; + initialized_ = true; + + // Asegurar volumen inicial consistente + if (steps_ <= 0) steps_ = 1; + float vol_f = initial_volume_ * (current_step_ / static_cast(total_steps_)); + setVolume(static_cast(std::clamp(vol_f, 0.0f, static_cast(initial_volume_)))); +} + +// Dibuja el rectángulo del borde si es visible +void Credits::drawBorderRect() { + // Umbral: cualquier valor menor que 1 píxel no se considera visible + constexpr float VISIBLE_THRESHOLD = 1.0f; + if (border_rect_.w < VISIBLE_THRESHOLD || border_rect_.h < VISIBLE_THRESHOLD) { + return; // no dibujar + } + + const Color COLOR = color_.LIGHTEN(); + SDL_Renderer* rdr = Screen::get()->getRenderer(); + SDL_SetRenderDrawColor(rdr, COLOR.r, COLOR.g, COLOR.b, 0xFF); + + // Convertir a enteros de forma conservadora para evitar líneas de 1px por redondeo extraño + SDL_Rect r; + r.x = static_cast(std::floor(border_rect_.x + 0.5f)); + r.y = static_cast(std::floor(border_rect_.y + 0.5f)); + r.w = static_cast(std::max(0.0f, std::floor(border_rect_.w + 0.5f))); + r.h = static_cast(std::max(0.0f, std::floor(border_rect_.h + 0.5f))); + + if (r.w > 0 && r.h > 0) { + SDL_RenderRect(Screen::get()->getRenderer(), &border_rect_); + } +} diff --git a/source/sections/credits.hpp b/source/sections/credits.hpp index 607b5c1..0343316 100644 --- a/source/sections/credits.hpp +++ b/source/sections/credits.hpp @@ -29,9 +29,15 @@ class Credits { // --- Métodos del bucle principal --- void update(float delta_time); // Actualización principal de la lógica (time-based) auto calculateDeltaTime() -> float; // Calcula el deltatime + void initVars(); // Inicializa variables + void startCredits(); // Inicializa mas variables // --- Constantes de clase --- static constexpr int PLAY_AREA_HEIGHT = 200; + static constexpr float FAST_FORWARD_MULTIPLIER = 6.0F; + static constexpr float BLACK_RECT_INTERVAL_S = 4.0F / 60.0F; // ~0.0667s + static constexpr float FRAMES_PER_SECOND = 60.0F; + static constexpr int HORIZONTAL_SPEED = 2; // --- Objetos principales --- std::unique_ptr balloon_manager_; // Gestión de globos @@ -45,15 +51,26 @@ class Credits { SDL_Texture* canvas_; // Textura donde se dibuja todo // --- Temporización y contadores --- - Uint64 last_time_ = 0; // Último tiempo registrado para deltaTime - float counter_ = 0; // Contador principal de lógica - float counter_pre_fade_ = 0; // Activación del fundido final - float counter_prevent_endless_ = 0; // Prevención de bucle infinito + Uint64 last_time_ = 0; // Último tiempo registrado para deltaTime + float counter_ = 0.0F; // Contador principal de lógica + float counter_pre_fade_ = 0.0F; // Activación del fundido final + float counter_prevent_endless_ = 0.0F; // Prevención de bucle infinito + float current_step_ = 0.0F; + int total_steps_ = 1; + bool initialized_ = false; + + // --- Guardar estados iniciales para cálculo de pasos --- + int init_top_h = 0; + int init_bottom_y = 0; + int init_left_w = 0; + int init_right_x = 0; // --- Variables de estado --- bool fading_ = false; // Estado del fade final bool want_to_pass_ = false; // Jugador quiere saltarse créditos bool mini_logo_on_position_ = false; // Minilogo en posición final + bool vertical_done_ = false; + bool horizontal_done_ = false; // --- Diseño y posicionamiento --- float black_bars_size_ = (param.game.game_area.rect.h - PLAY_AREA_HEIGHT) / 2; // Tamaño de las barras negras @@ -127,6 +144,7 @@ class Credits { void fillTextTexture(); // Crear textura de texto de créditos void fillCanvas(); // Renderizar todos los sprites y fondos void renderPlayers(); // Renderiza los jugadores + void drawBorderRect(); // Renderiza el rectangulo del borde // --- Métodos de lógica del juego --- void throwBalloons(float delta_time); // Lanzar globos al escenario (time-based) @@ -138,7 +156,7 @@ class Credits { // --- Métodos de interfaz --- void updateBlackRects(); // Actualizar rectángulos negros (letterbox) (frame-based) void updateBlackRects(float delta_time); // Actualizar rectángulos negros (letterbox) (time-based) - void updateRedRect(); // Actualizar rectángulo rojo (borde) + void updateBorderRect(); // Actualizar rectángulo rojo (borde) void updateTextureDstRects(); // Actualizar destinos de texturas (frame-based) void updateTextureDstRects(float delta_time); // Actualizar destinos de texturas (time-based)