From 31bbaf997f1056a31b5b636c652c068b7b0e401f Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 19 Mar 2026 08:20:03 +0100 Subject: [PATCH] treballant en PixelReveal --- CMakeLists.txt | 1 + source/core/rendering/pixel_reveal.cpp | 78 +++++++++++++ source/core/rendering/pixel_reveal.hpp | 36 ++++++ source/game/scene_manager.hpp | 2 +- source/game/scenes/credits.cpp | 36 ++---- source/game/scenes/credits.hpp | 9 +- source/game/scenes/ending.cpp | 154 +++++-------------------- source/game/scenes/ending.hpp | 16 +-- 8 files changed, 167 insertions(+), 165 deletions(-) create mode 100644 source/core/rendering/pixel_reveal.cpp create mode 100644 source/core/rendering/pixel_reveal.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 029e287..33b3a2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,7 @@ set(APP_SOURCES # Core - Rendering source/core/rendering/gif.cpp + source/core/rendering/pixel_reveal.cpp source/core/rendering/screen.cpp source/core/rendering/surface.cpp source/core/rendering/surface_animated_sprite.cpp diff --git a/source/core/rendering/pixel_reveal.cpp b/source/core/rendering/pixel_reveal.cpp new file mode 100644 index 0000000..36275e9 --- /dev/null +++ b/source/core/rendering/pixel_reveal.cpp @@ -0,0 +1,78 @@ +#include "core/rendering/pixel_reveal.hpp" + +#include // Para min +#include // Para iota +#include // Para mt19937, shuffle + +#include "core/rendering/surface.hpp" // Para Surface +#include "utils/utils.hpp" // Para PaletteColor + +// Constructor +PixelReveal::PixelReveal(int width, int height, float pixels_per_second, float step_duration, int num_steps) + : cover_surface_(std::make_shared(width, height)), + reveal_order_(height), + row_step_(height, 0), + width_(width), + height_(height), + pixels_per_second_(pixels_per_second), + step_duration_(step_duration), + num_steps_(num_steps) { + // Rellena la máscara con negro sólido + cover_surface_->clear(static_cast(PaletteColor::BLACK)); + + // Genera el orden aleatorio de columnas por fila usando la fila como semilla (reproducible) + for (int r = 0; r < height_; r++) { + reveal_order_[r].resize(width_); + std::iota(reveal_order_[r].begin(), reveal_order_[r].end(), 0); + std::mt19937 rng(static_cast(r)); + std::shuffle(reveal_order_[r].begin(), reveal_order_[r].end(), rng); + } +} + +// Destructor +PixelReveal::~PixelReveal() = default; + +// Actualiza el estado del revelado +void PixelReveal::update(float time_active) { + const auto TRANSPARENT = static_cast(PaletteColor::TRANSPARENT); + + for (int r = 0; r < height_; r++) { + const float T_START = static_cast(r) / pixels_per_second_; + const float TIME_IN_ROW = time_active - T_START; + + if (TIME_IN_ROW < 0.0F) { + continue; // Esta fila aún no ha empezado + } + + const int STEPS = std::min(num_steps_, static_cast(TIME_IN_ROW / step_duration_)); + + if (STEPS > row_step_[r]) { + // Revela los píxeles de los pasos pendientes + for (int step = row_step_[r]; step < STEPS; step++) { + const int START_IDX = step * width_ / num_steps_; + const int END_IDX = (step == num_steps_ - 1) ? width_ : (step + 1) * width_ / num_steps_; + + for (int idx = START_IDX; idx < END_IDX; idx++) { + const int COL = reveal_order_[r][idx]; + cover_surface_->putPixel(COL, r, TRANSPARENT); + } + } + row_step_[r] = STEPS; + } + } +} + +// Dibuja la máscara en la posición indicada +void PixelReveal::render(int dst_x, int dst_y) const { + cover_surface_->render(dst_x, dst_y); +} + +// Indica si el revelado ha completado todas las filas +bool PixelReveal::isComplete() const { + for (const int step : row_step_) { + if (step < num_steps_) { + return false; + } + } + return true; +} diff --git a/source/core/rendering/pixel_reveal.hpp b/source/core/rendering/pixel_reveal.hpp new file mode 100644 index 0000000..0edf4fd --- /dev/null +++ b/source/core/rendering/pixel_reveal.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include // Para shared_ptr +#include // Para vector + +class Surface; + +// Efecto de revelado pixel a pixel por filas, de arriba a abajo. +// Cada fila se revela en num_steps pasos, con píxeles aleatorios en cada paso. +class PixelReveal { + public: + // Constructor + PixelReveal(int width, int height, float pixels_per_second, float step_duration, int num_steps = 4); + + // Destructor definido en el .cpp para que unique_ptr funcione con forward declaration + ~PixelReveal(); + + // Actualiza el estado del revelado según el tiempo transcurrido + void update(float time_active); + + // Dibuja la máscara de revelado en la posición indicada + void render(int dst_x, int dst_y) const; + + // Indica si el revelado ha completado todas las filas + [[nodiscard]] bool isComplete() const; + + private: + std::shared_ptr cover_surface_; // Máscara negra que se va haciendo transparente + std::vector> reveal_order_; // Orden aleatorio de columnas por fila + std::vector row_step_; // Paso actual de revelado por fila (0..num_steps_) + int width_; + int height_; + float pixels_per_second_; // Filas reveladas por segundo + float step_duration_; // Segundos por paso dentro de una fila + int num_steps_; // Número de pasos de revelado por fila +}; diff --git a/source/game/scene_manager.hpp b/source/game/scene_manager.hpp index 9ee627a..d59618d 100644 --- a/source/game/scene_manager.hpp +++ b/source/game/scene_manager.hpp @@ -34,7 +34,7 @@ enum class Options { // --- Variables de estado globales --- #ifdef _DEBUG -inline Scene current = Scene::GAME; // Escena actual +inline Scene current = Scene::ENDING; // Escena actual inline Options options = Options::LOGO_TO_LOADING_SCREEN; // Opciones de la escena actual #else inline Scene current = Scene::LOGO; // Escena actual diff --git a/source/game/scenes/credits.cpp b/source/game/scenes/credits.cpp index a47cba2..65a5b30 100644 --- a/source/game/scenes/credits.cpp +++ b/source/game/scenes/credits.cpp @@ -2,11 +2,10 @@ #include -#include // Para min - #include "core/audio/audio.hpp" // Para Audio #include "core/input/global_inputs.hpp" // Para check #include "core/input/input.hpp" // Para Input +#include "core/rendering/pixel_reveal.hpp" // Para PixelReveal #include "core/rendering/screen.hpp" // Para Screen #include "core/rendering/surface.hpp" // Para Surface #include "core/rendering/surface_animated_sprite.hpp" // Para SAnimatedSprite @@ -19,10 +18,12 @@ #include "utils/delta_timer.hpp" // Para DeltaTimer #include "utils/utils.hpp" // Para PaletteColor +// Destructor +Credits::~Credits() = default; + // Constructor Credits::Credits() : text_surface_(std::make_shared(Options::game.width, Options::game.height)), - cover_surface_(std::make_shared(Options::game.width, Options::game.height)), shining_sprite_(std::make_shared(Resource::Cache::get()->getAnimationData("shine.yaml"))), delta_timer_(std::make_unique()) { // Configura la escena @@ -115,24 +116,8 @@ void Credits::fillTexture() { // Recoloca el sprite del brillo shining_sprite_->setPosX(POS_X + 2); - // Rellena la textura que cubre el texto con color transparente - cover_surface_->clear(static_cast(PaletteColor::TRANSPARENT)); - - // Los primeros 8 pixels crea una malla - auto color = static_cast(PaletteColor::BLACK); - for (int i = 0; i < 256; i += 2) { - cover_surface_->putPixel(i, 0, color); - cover_surface_->putPixel(i, 2, color); - cover_surface_->putPixel(i, 4, color); - cover_surface_->putPixel(i, 6, color); - - cover_surface_->putPixel(i + 1, 5, color); - cover_surface_->putPixel(i + 1, 7, color); - } - - // El resto se rellena de color sólido - SDL_FRect rect = {0, 8, 256, 192}; - cover_surface_->fillRect(&rect, color); + // Crea el efecto de revelado pixel a pixel + pixel_reveal_ = std::make_unique(Options::game.width, Options::game.height, PIXELS_PER_SECOND, STEP_DURATION, REVEAL_STEPS); } // Actualiza las variables @@ -145,6 +130,8 @@ void Credits::update() { updateState(DELTA_TIME); // Actualiza la máquina de estados + pixel_reveal_->update(reveal_time_); // Actualiza el efecto de revelado + // Actualiza el sprite con el brillo si está después del tiempo de inicio if (reveal_time_ > SHINE_START_TIME) { shining_sprite_->update(DELTA_TIME); @@ -239,11 +226,8 @@ void Credits::render() { // Dibuja la textura con el texto en pantalla text_surface_->render(0, 0); - // Dibuja la textura que cubre el texto - // OFFSET basado en reveal_time_ (que se congela durante pausas, como counter_ original) - const float OFFSET = std::min(reveal_time_ * REVEAL_SPEED / 8.0F, 192.0F / 2.0F); - SDL_FRect src_rect = {0.0F, 0.0F, 256.0F, 192.0F - (OFFSET * 2.0F)}; - cover_surface_->render(0, static_cast(OFFSET * 2.0F), &src_rect); + // Dibuja la máscara de revelado pixel a pixel + pixel_reveal_->render(0, 0); // Dibuja el sprite con el brillo if (reveal_time_ > SHINE_START_TIME) { diff --git a/source/game/scenes/credits.hpp b/source/game/scenes/credits.hpp index 2a5d588..7cb8d6b 100644 --- a/source/game/scenes/credits.hpp +++ b/source/game/scenes/credits.hpp @@ -7,13 +7,14 @@ #include // Para vector class SurfaceAnimatedSprite; // lines 11-11 class Surface; +class PixelReveal; class DeltaTimer; class Credits { public: // --- Constructor y Destructor --- Credits(); - ~Credits() = default; + ~Credits(); // --- Bucle principal --- void run(); @@ -47,7 +48,9 @@ class Credits { static constexpr float TOTAL_DURATION = 20.0F; // 1200 frames @ 60fps static constexpr float SHINE_START_TIME = 12.833F; // 770 frames @ 60fps static constexpr float FADE_OUT_START = 19.167F; // 1150 frames @ 60fps - static constexpr float REVEAL_SPEED = 60.0F; // counter equivalente por segundo @ 60fps + static constexpr float PIXELS_PER_SECOND = 15.0F; // Filas reveladas por segundo (REVEAL_SPEED/8*2 = 60/8*2 = 15) + static constexpr float STEP_DURATION = 2.0F / 60.0F; // Segundos por paso de revelado (2 frames @ 60fps) + static constexpr int REVEAL_STEPS = 16; // Pasos de revelado por fila (más pasos = efecto más visible) // --- Métodos privados --- void update(); // Actualiza las variables @@ -62,7 +65,7 @@ class Credits { // --- Variables miembro --- // Recursos gráficos std::shared_ptr text_surface_; // Textura para dibujar el texto - std::shared_ptr cover_surface_; // Textura para cubrir el texto + std::unique_ptr pixel_reveal_; // Efecto de revelado pixel a pixel std::shared_ptr shining_sprite_; // Sprite para el brillo del corazón // Temporizadores y estado diff --git a/source/game/scenes/ending.cpp b/source/game/scenes/ending.cpp index ffd1cd2..c3ac4f9 100644 --- a/source/game/scenes/ending.cpp +++ b/source/game/scenes/ending.cpp @@ -7,6 +7,7 @@ #include "core/audio/audio.hpp" // Para Audio #include "core/input/global_inputs.hpp" // Para check #include "core/input/input.hpp" // Para Input +#include "core/rendering/pixel_reveal.hpp" // Para PixelReveal #include "core/rendering/screen.hpp" // Para Screen #include "core/rendering/surface.hpp" // Para Surface #include "core/rendering/surface_sprite.hpp" // Para SSprite @@ -34,6 +35,9 @@ Ending::Ending() fillCoverTexture(); // Rellena la textura para la cortinilla } +// Destructor +Ending::~Ending() = default; + // Actualiza el objeto void Ending::update() { const float DELTA_TIME = delta_timer_->tick(); @@ -60,8 +64,9 @@ void Ending::render() { // Skip rendering durante WARMING_UP if (state_ != State::WARMING_UP) { // Dibuja las imagenes de la escena - sprite_pics_.at(current_scene_).image_sprite->render(); - sprite_pics_.at(current_scene_).cover_sprite->render(); + const auto& pic = sprite_pics_.at(current_scene_); + pic.image_sprite->render(); + pic.pixel_reveal->render(pic.pos_x, pic.pos_y); // Dibuja los textos de la escena for (const auto& ti : scenes_.at(current_scene_).text_index) { @@ -69,8 +74,9 @@ void Ending::render() { const float TRIGGER_TIME = static_cast(ti.trigger) / 60.0F; if (state_time_ > TRIGGER_TIME) { - sprite_texts_.at(ti.index).image_sprite->render(); - sprite_texts_.at(ti.index).cover_sprite->render(); + const auto& txt = sprite_texts_.at(ti.index); + txt.image_sprite->render(); + txt.pixel_reveal->render(txt.pos_x, txt.pos_y); } } @@ -219,42 +225,14 @@ void Ending::iniTexts() { // Crea el sprite st.image_sprite = std::make_shared(st.image_surface, 0, 0, st.image_surface->getWidth(), st.image_surface->getHeight()); - st.image_sprite->setPosition((Options::game.width - st.image_surface->getWidth()) / 2, txt.pos); + st.pos_x = static_cast((Options::game.width - st.image_surface->getWidth()) / 2); + st.pos_y = txt.pos; + st.image_sprite->setPosition(st.pos_x, st.pos_y); - // Crea la cover_surface - st.cover_surface = std::make_shared(WIDTH, HEIGHT + 8); - Screen::get()->setRendererSurface(st.cover_surface); + // Crea el efecto de revelado pixel a pixel + st.pixel_reveal = std::make_unique(static_cast(WIDTH), static_cast(HEIGHT), TEXT_PIXELS_PER_SECOND, STEP_DURATION, REVEAL_STEPS); - // Rellena la cover_surface con color transparente - st.cover_surface->clear(static_cast(PaletteColor::TRANSPARENT)); - - // Crea una malla de 8 pixels de alto - auto surface = Screen::get()->getRendererSurface(); - auto color = static_cast(PaletteColor::BLACK); - for (int i = 0; i < WIDTH; i += 2) { - surface->putPixel(i, 0, color); - surface->putPixel(i, 2, color); - surface->putPixel(i, 4, color); - surface->putPixel(i, 6, color); - - surface->putPixel(i + 1, 5, color); - surface->putPixel(i + 1, 7, color); - } - - // El resto se rellena de color sólido - SDL_FRect rect = {0, 8, WIDTH, HEIGHT}; - surface->fillRect(&rect, color); - - // Crea el sprite - st.cover_sprite = std::make_shared(st.cover_surface, 0, 0, st.cover_surface->getWidth(), st.cover_surface->getHeight() - 8); - st.cover_sprite->setPosition((Options::game.width - st.cover_surface->getWidth()) / 2, txt.pos); - st.cover_sprite->setClip(0, 8, st.cover_surface->getWidth(), st.cover_surface->getHeight()); - - // Inicializa variables - st.cover_clip_desp = 8; - st.cover_clip_height = HEIGHT; - - sprite_texts_.push_back(st); + sprite_texts_.push_back(std::move(st)); Screen::get()->setRendererSurface(previuos_renderer); } } @@ -283,45 +261,15 @@ void Ending::iniPics() { const float HEIGHT = sp.image_surface->getHeight(); // Crea el sprite + sp.pos_x = static_cast((Options::game.width - WIDTH) / 2); + sp.pos_y = pic.pos; sp.image_sprite = std::make_shared(sp.image_surface, 0, 0, WIDTH, HEIGHT); - sp.image_sprite->setPosition((Options::game.width - WIDTH) / 2, pic.pos); + sp.image_sprite->setPosition(sp.pos_x, sp.pos_y); - // Crea la cover_surface - sp.cover_surface = std::make_shared(WIDTH, HEIGHT + 8); - auto previuos_renderer = Screen::get()->getRendererSurface(); - Screen::get()->setRendererSurface(sp.cover_surface); + // Crea el efecto de revelado pixel a pixel + sp.pixel_reveal = std::make_unique(static_cast(WIDTH), static_cast(HEIGHT), IMAGE_PIXELS_PER_SECOND, STEP_DURATION, REVEAL_STEPS); - // Rellena la cover_surface con color transparente - sp.cover_surface->clear(static_cast(PaletteColor::TRANSPARENT)); - - // Crea una malla en los primeros 8 pixels - auto surface = Screen::get()->getRendererSurface(); - auto color = static_cast(PaletteColor::BLACK); - for (int i = 0; i < WIDTH; i += 2) { - surface->putPixel(i, 0, color); - surface->putPixel(i, 2, color); - surface->putPixel(i, 4, color); - surface->putPixel(i, 6, color); - - surface->putPixel(i + 1, 5, color); - surface->putPixel(i + 1, 7, color); - } - - // El resto se rellena de color sólido - SDL_FRect rect = {0.0F, 8.0F, WIDTH, HEIGHT}; - surface->fillRect(&rect, color); - - // Crea el sprite - sp.cover_sprite = std::make_shared(sp.cover_surface, 0, 0, sp.cover_surface->getWidth(), sp.cover_surface->getHeight() - 8); - sp.cover_sprite->setPosition((Options::game.width - sp.cover_surface->getWidth()) / 2, pic.pos); - sp.cover_sprite->setClip(0, 8, sp.cover_surface->getWidth(), sp.cover_surface->getHeight()); - - // Inicializa variables - sp.cover_clip_desp = 8; - sp.cover_clip_height = HEIGHT; - - sprite_pics_.push_back(sp); - Screen::get()->setRendererSurface(previuos_renderer); + sprite_pics_.push_back(std::move(sp)); } } @@ -426,68 +374,18 @@ void Ending::updateSpriteCovers() { return; } - // Actualiza la cortinilla de los textos + // Actualiza el revelado de los textos for (const auto& ti : scenes_.at(current_scene_).text_index) { - // Convertir trigger de frames a segundos @ 60fps const float TRIGGER_TIME = static_cast(ti.trigger) / 60.0F; if (state_time_ > TRIGGER_TIME) { - // Tiempo transcurrido desde que se activó el trigger const float TIME_SINCE_TRIGGER = state_time_ - TRIGGER_TIME; - - // Píxeles revelados: tiempo * velocidad - const float PIXELS_REVEALED = TIME_SINCE_TRIGGER * TEXT_REVEAL_SPEED; - - // Obtiene el sprite - auto sprite_text = sprite_texts_.at(ti.index); - - // Obtener altura inicial de la superficie - const float INITIAL_HEIGHT = sprite_text.image_surface->getHeight(); - const float Y_INITIAL = sprite_text.image_sprite->getY(); - - // Fase 1: Revelar malla decorativa (8 píxeles) - if (PIXELS_REVEALED < 8.0F) { - sprite_text.cover_clip_desp = static_cast(8.0F - PIXELS_REVEALED); - sprite_text.cover_clip_height = static_cast(INITIAL_HEIGHT); - sprite_text.cover_sprite->setY(Y_INITIAL); - } - // Fase 2: Revelar contenido - else { - sprite_text.cover_clip_desp = 0; - const int CONTENT_PIXELS = PIXELS_REVEALED - 8.0F; - sprite_text.cover_clip_height = std::max(0, static_cast(INITIAL_HEIGHT - CONTENT_PIXELS)); - sprite_text.cover_sprite->setY(Y_INITIAL + static_cast(CONTENT_PIXELS)); - } - - sprite_text.cover_sprite->setClip( - 0, - sprite_text.cover_clip_desp, - sprite_text.cover_sprite->getWidth(), - sprite_text.cover_clip_height); + sprite_texts_.at(ti.index).pixel_reveal->update(TIME_SINCE_TRIGGER); } } - // Actualiza la cortinilla de las imágenes (revelación continua desde el inicio de la escena) - auto sprite_pics = sprite_pics_.at(current_scene_); - const float PIXELS_REVEALED = state_time_ * IMAGE_REVEAL_SPEED; - const float INITIAL_HEIGHT = sprite_pics.image_surface->getHeight(); - const float Y_INITIAL = sprite_pics.image_sprite->getY(); - - // Fase 1: Revelar malla decorativa (8 píxeles) - if (PIXELS_REVEALED < 8.0F) { - sprite_pics.cover_clip_desp = static_cast(8.0F - PIXELS_REVEALED); - sprite_pics.cover_clip_height = static_cast(INITIAL_HEIGHT); - sprite_pics.cover_sprite->setY(Y_INITIAL); - } - // Fase 2: Revelar contenido - else { - sprite_pics.cover_clip_desp = 0; - const int CONTENT_PIXELS = PIXELS_REVEALED - 8.0F; - sprite_pics.cover_clip_height = std::max(0, static_cast(INITIAL_HEIGHT - CONTENT_PIXELS)); - sprite_pics.cover_sprite->setY(Y_INITIAL + static_cast(CONTENT_PIXELS)); - } - - sprite_pics.cover_sprite->setClip(0, sprite_pics.cover_clip_desp, sprite_pics.cover_sprite->getWidth(), sprite_pics.cover_clip_height); + // Actualiza el revelado de la imagen (desde el inicio de la escena) + sprite_pics_.at(current_scene_).pixel_reveal->update(state_time_); } // Comprueba si se ha de cambiar de escena diff --git a/source/game/scenes/ending.hpp b/source/game/scenes/ending.hpp index c4f034a..9684cbe 100644 --- a/source/game/scenes/ending.hpp +++ b/source/game/scenes/ending.hpp @@ -7,13 +7,14 @@ #include // Para vector class SurfaceSprite; // lines 8-8 class Surface; // lines 9-9 +class PixelReveal; class DeltaTimer; class Ending { public: // --- Constructor y Destructor --- Ending(); - ~Ending() = default; + ~Ending(); // --- Bucle principal --- void run(); @@ -34,10 +35,9 @@ class Ending { struct EndingSurface { std::shared_ptr image_surface; // Surface a mostrar std::shared_ptr image_sprite; // SSprite para mostrar la textura - std::shared_ptr cover_surface; // Surface que cubre a la otra textura - std::shared_ptr cover_sprite; // SSprite para mostrar la textura que cubre a la otra textura - int cover_clip_desp{0}; // Desplazamiento del spriteClip de la textura de cobertura - int cover_clip_height{0}; // Altura del spriteClip de la textura de cobertura + std::unique_ptr pixel_reveal; // Efecto de revelado pixel a pixel + int pos_x{0}; // Posición X de renderizado + int pos_y{0}; // Posición Y de renderizado }; struct TextAndPosition { @@ -64,8 +64,10 @@ class Ending { static constexpr float SCENE_2_DURATION = 16.667F; // 1000 frames @ 60fps static constexpr float SCENE_3_DURATION = 13.333F; // 800 frames @ 60fps static constexpr float SCENE_4_DURATION = 16.667F; // 1000 frames @ 60fps - static constexpr float TEXT_REVEAL_SPEED = 30.0F; // 2px cada 4 frames @ 60fps - static constexpr float IMAGE_REVEAL_SPEED = 60.0F; // 2px cada 2 frames @ 60fps + static constexpr float TEXT_PIXELS_PER_SECOND = 30.0F; // Filas de texto reveladas por segundo + static constexpr float IMAGE_PIXELS_PER_SECOND = 60.0F; // Filas de imagen reveladas por segundo + static constexpr float STEP_DURATION = 2.0F / 60.0F; // Segundos por paso de revelado (2 frames @ 60fps) + static constexpr int REVEAL_STEPS = 10; // Pasos de revelado por fila (más pasos = efecto más visible) static constexpr float TEXT_LAPSE = 1.333F; // 80 frames @ 60fps static constexpr float FADEOUT_START_OFFSET = 1.667F; // Inicio fade-out 100 frames antes del fin static constexpr float ENDING_DURATION = 2.0F; // Duración del estado ENDING (2 segundos)