treballant en PixelReveal

This commit is contained in:
2026-03-19 08:20:03 +01:00
parent c4d4a3b930
commit 31bbaf997f
8 changed files with 167 additions and 165 deletions

View File

@@ -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

View File

@@ -0,0 +1,78 @@
#include "core/rendering/pixel_reveal.hpp"
#include <algorithm> // Para min
#include <numeric> // Para iota
#include <random> // 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<Surface>(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<Uint8>(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<unsigned int>(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<Uint8>(PaletteColor::TRANSPARENT);
for (int r = 0; r < height_; r++) {
const float T_START = static_cast<float>(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<int>(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;
}

View File

@@ -0,0 +1,36 @@
#pragma once
#include <memory> // Para shared_ptr
#include <vector> // 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<Surface> 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<Surface> cover_surface_; // Máscara negra que se va haciendo transparente
std::vector<std::vector<int>> reveal_order_; // Orden aleatorio de columnas por fila
std::vector<int> 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
};

View File

@@ -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

View File

@@ -2,11 +2,10 @@
#include <SDL3/SDL.h>
#include <algorithm> // 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<Surface>(Options::game.width, Options::game.height)),
cover_surface_(std::make_shared<Surface>(Options::game.width, Options::game.height)),
shining_sprite_(std::make_shared<SurfaceAnimatedSprite>(Resource::Cache::get()->getAnimationData("shine.yaml"))),
delta_timer_(std::make_unique<DeltaTimer>()) {
// 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<Uint8>(PaletteColor::TRANSPARENT));
// Los primeros 8 pixels crea una malla
auto color = static_cast<Uint8>(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<PixelReveal>(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<int>(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) {

View File

@@ -7,13 +7,14 @@
#include <vector> // 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<Surface> text_surface_; // Textura para dibujar el texto
std::shared_ptr<Surface> cover_surface_; // Textura para cubrir el texto
std::unique_ptr<PixelReveal> pixel_reveal_; // Efecto de revelado pixel a pixel
std::shared_ptr<SurfaceAnimatedSprite> shining_sprite_; // Sprite para el brillo del corazón
// Temporizadores y estado

View File

@@ -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<float>(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<SurfaceSprite>(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<int>((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<Surface>(WIDTH, HEIGHT + 8);
Screen::get()->setRendererSurface(st.cover_surface);
// Crea el efecto de revelado pixel a pixel
st.pixel_reveal = std::make_unique<PixelReveal>(static_cast<int>(WIDTH), static_cast<int>(HEIGHT), TEXT_PIXELS_PER_SECOND, STEP_DURATION, REVEAL_STEPS);
// Rellena la cover_surface con color transparente
st.cover_surface->clear(static_cast<Uint8>(PaletteColor::TRANSPARENT));
// Crea una malla de 8 pixels de alto
auto surface = Screen::get()->getRendererSurface();
auto color = static_cast<Uint8>(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<SurfaceSprite>(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<int>((Options::game.width - WIDTH) / 2);
sp.pos_y = pic.pos;
sp.image_sprite = std::make_shared<SurfaceSprite>(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<Surface>(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<PixelReveal>(static_cast<int>(WIDTH), static_cast<int>(HEIGHT), IMAGE_PIXELS_PER_SECOND, STEP_DURATION, REVEAL_STEPS);
// Rellena la cover_surface con color transparente
sp.cover_surface->clear(static_cast<Uint8>(PaletteColor::TRANSPARENT));
// Crea una malla en los primeros 8 pixels
auto surface = Screen::get()->getRendererSurface();
auto color = static_cast<Uint8>(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<SurfaceSprite>(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<float>(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<int>(8.0F - PIXELS_REVEALED);
sprite_text.cover_clip_height = static_cast<int>(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<int>(INITIAL_HEIGHT - CONTENT_PIXELS));
sprite_text.cover_sprite->setY(Y_INITIAL + static_cast<int>(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<int>(8.0F - PIXELS_REVEALED);
sprite_pics.cover_clip_height = static_cast<int>(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<int>(INITIAL_HEIGHT - CONTENT_PIXELS));
sprite_pics.cover_sprite->setY(Y_INITIAL + static_cast<int>(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

View File

@@ -7,13 +7,14 @@
#include <vector> // 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<Surface> image_surface; // Surface a mostrar
std::shared_ptr<SurfaceSprite> image_sprite; // SSprite para mostrar la textura
std::shared_ptr<Surface> cover_surface; // Surface que cubre a la otra textura
std::shared_ptr<SurfaceSprite> 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<PixelReveal> 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)