feat(logo): scanline reveal CRT amb glitch, bursts VHS i pulse de banda
This commit is contained in:
1 parent
243ced7498
commit
e32cff9dac
9 files changed
+440
-20
No files matched your search
@@ -38,9 +38,23 @@ LogoScene::LogoScene(std::shared_ptr<Surface> logo, bool reset)
|
||||
audio->stopAllSounds();
|
||||
}
|
||||
}
|
||||
// Arranca el fade-in: el primer frame ja pinta negre opac que
|
||||
// s'esvaeix cap a la imatge durant FADE_IN_MS.
|
||||
fade_.begin(FADE_IN_MS, Transitions::FadeToBlack::Direction::FADE_IN);
|
||||
// Arranca el revelat CRT: el primer frame ja pinta zero files (banda
|
||||
// damunt del logo) i avança fins a tenir-lo visible al final.
|
||||
const auto& s = UiConfig::get().logoScan();
|
||||
scan_.begin({
|
||||
.duration_ms = s.duration_ms,
|
||||
.band_height_px = s.band_height_px,
|
||||
.band_alpha = s.band_alpha,
|
||||
.band_pulse_hz = s.band_pulse_hz,
|
||||
.band_pulse_depth = s.band_pulse_depth,
|
||||
.glitch_amplitude_px = s.glitch_amplitude_px,
|
||||
.glitch_band_radius_px = s.glitch_band_radius_px,
|
||||
.vhs_burst_per_sec = s.vhs_burst_per_sec,
|
||||
.vhs_burst_duration_ms = s.vhs_burst_duration_ms,
|
||||
.vhs_burst_height_px = s.vhs_burst_height_px,
|
||||
.vhs_burst_amplitude_px = s.vhs_burst_amplitude_px,
|
||||
.flash_ms = s.flash_ms,
|
||||
});
|
||||
}
|
||||
|
||||
LogoScene::~LogoScene() = default;
|
||||
@@ -83,12 +97,16 @@ void LogoScene::update(int delta_ms) {
|
||||
|
||||
if (phase_ != Phase::DONE) {
|
||||
phase_elapsed_ms_ += delta_ms;
|
||||
// Tick dels efectes actius. `scan_` només fa feina visible
|
||||
// durant Phase::SCAN; `fade_` només durant Phase::FADE_OUT. Tots
|
||||
// dos són no-op si el seu duration_ms intern està a 0.
|
||||
scan_.tick(delta_ms);
|
||||
fade_.tick(delta_ms);
|
||||
}
|
||||
|
||||
switch (phase_) {
|
||||
case Phase::FADE_IN:
|
||||
if (phase_elapsed_ms_ >= FADE_IN_MS) { enterMiddle(); }
|
||||
case Phase::SCAN:
|
||||
if (scan_.isFinished()) { enterMiddle(); }
|
||||
break;
|
||||
case Phase::MIDDLE:
|
||||
// Música del títol al cor de la fase central. La crida és
|
||||
@@ -121,13 +139,22 @@ void LogoScene::render() {
|
||||
if (logo_) {
|
||||
const int X = (GameCanvas::WIDTH - logo_->getWidth()) / 2;
|
||||
const int Y = (GameCanvas::HEIGHT - logo_->getHeight()) / 2;
|
||||
logo_->render(X, Y);
|
||||
if (phase_ == Phase::SCAN) {
|
||||
// Revelat CRT: la pròpia ScanReveal s'encarrega del blit
|
||||
// fila a fila, de la banda lluminosa, dels glitches i del
|
||||
// flash final.
|
||||
scan_.render(*logo_, X, Y);
|
||||
} else {
|
||||
// MIDDLE i FADE_OUT: render normal, el logo ja està a
|
||||
// pantalla complet i estable.
|
||||
logo_->render(X, Y);
|
||||
}
|
||||
}
|
||||
|
||||
Notifier::get().render();
|
||||
// Capa de fosa: durant FADE_IN i FADE_OUT pinta el negre semitransparent
|
||||
// per damunt del logo i del Notifier (no del RenderInfo, que és overlay
|
||||
// de debug i ha de mantenir-se llegible).
|
||||
// Capa de fosa: només FADE_OUT pinta negre semitransparent (el
|
||||
// FADE_IN s'ha substituït per `scan_`). Va per damunt del logo i
|
||||
// del Notifier, però per sota de RenderInfo (overlay de debug).
|
||||
fade_.render();
|
||||
RenderInfo::get().render();
|
||||
Screen::get().present();
|
||||
|
||||
@@ -3,17 +3,20 @@
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "core/rendering/effects/scan_reveal.hpp"
|
||||
#include "core/rendering/transitions/fade_to_black.hpp"
|
||||
#include "core/scene/scene.hpp"
|
||||
|
||||
class Surface;
|
||||
|
||||
// Escena del logotip de l'editora. FSM amb tres fases visibles
|
||||
// (`FADE_IN` 500 ms → `MIDDLE` 5 s → `FADE_OUT` 500 ms) i una terminal
|
||||
// (`DONE`) que dispara la transició cap a TITLE. A 2,5 s dins de
|
||||
// `MIDDLE` arrenca la música del títol perquè entri al cor de l'escena
|
||||
// i continue cap a TitleScene sense talls (Audio ja és idempotent quan
|
||||
// es demana la mateixa pista que ja sona).
|
||||
// Escena del logotip de l'editora. FSM amb tres fases visibles:
|
||||
// `SCAN` — revelat tipus scanline CRT (durada YAML, ~1500 ms)
|
||||
// `MIDDLE` — logo estable (`MIDDLE_MS`, 5 s)
|
||||
// `FADE_OUT` — fade lineal a negre (`FADE_OUT_MS`, 500 ms)
|
||||
// I una terminal `DONE` que dispara la transició cap a TITLE. A 2,5 s
|
||||
// dins de `MIDDLE` arrenca la música del títol perquè entri al cor de
|
||||
// l'escena i continue cap a TitleScene sense talls (Audio ja és
|
||||
// idempotent quan es demana la mateixa pista que ja sona).
|
||||
//
|
||||
// Skip: qualsevol tecla no reservada salta directament a la transició
|
||||
// (sense fade-out) — el TitleScene té el seu propi `intro.fade_in_ms`,
|
||||
@@ -24,7 +27,6 @@ class Surface;
|
||||
// silenciós encara que veníssem del joc en marxa.
|
||||
class LogoScene : public Scene {
|
||||
public:
|
||||
static constexpr int FADE_IN_MS = 500;
|
||||
static constexpr int MIDDLE_MS = 5000;
|
||||
static constexpr int FADE_OUT_MS = 500;
|
||||
// Offset dins de MIDDLE en què comença a sonar la pista del títol;
|
||||
@@ -40,7 +42,7 @@ class LogoScene : public Scene {
|
||||
void render() override;
|
||||
|
||||
private:
|
||||
enum class Phase : std::uint8_t { FADE_IN,
|
||||
enum class Phase : std::uint8_t { SCAN,
|
||||
MIDDLE,
|
||||
FADE_OUT,
|
||||
DONE };
|
||||
@@ -51,10 +53,11 @@ class LogoScene : public Scene {
|
||||
[[nodiscard]] static auto wantsSkip() -> bool;
|
||||
|
||||
std::shared_ptr<Surface> logo_;
|
||||
Phase phase_{Phase::FADE_IN};
|
||||
Phase phase_{Phase::SCAN};
|
||||
int phase_elapsed_ms_{0};
|
||||
bool music_started_{false};
|
||||
// Capa de fosa reutilitzada: comença amb FADE_IN al ctor i es
|
||||
// reaprofita amb FADE_OUT en entrar a la fase corresponent.
|
||||
// Capa de fosa per al FADE_OUT cap a TITLE. El FADE_IN inicial s'ha
|
||||
// substituït per `scan_` (revelat CRT amb glitch).
|
||||
Transitions::FadeToBlack fade_;
|
||||
Effects::ScanReveal scan_;
|
||||
};
|
||||
Reference in new issue
Block a user