feat(demo): transició per fosa a/desde negre en el salt títol→demo
This commit is contained in:
@@ -39,6 +39,12 @@ namespace Defaults::Game {
|
|||||||
constexpr float LEVEL_COMPLETED_DURATION = 3.0F; // Duración total
|
constexpr float LEVEL_COMPLETED_DURATION = 3.0F; // Duración total
|
||||||
constexpr float LEVEL_COMPLETED_TYPING_RATIO = 0.05F; // ~150ms de typewriter (escan ràpid però visible)
|
constexpr float LEVEL_COMPLETED_TYPING_RATIO = 0.05F; // ~150ms de typewriter (escan ràpid però visible)
|
||||||
|
|
||||||
|
// Attract mode: durada de la fosa a/desde negre a les transicions de demo.
|
||||||
|
namespace Fade {
|
||||||
|
constexpr float DEMO_OUT_DURATION = 0.6F; // TÍTOL → DEMO (fosa a negre abans del salt)
|
||||||
|
constexpr float DEMO_IN_DURATION = 0.6F; // DEMO: fosa des de negre sobre el joc ja en marxa
|
||||||
|
} // namespace Fade
|
||||||
|
|
||||||
// Transición INIT_HUD (animación inicial del HUD)
|
// Transición INIT_HUD (animación inicial del HUD)
|
||||||
constexpr float INIT_HUD_DURATION = 3.0F; // Duración total del estado
|
constexpr float INIT_HUD_DURATION = 3.0F; // Duración total del estado
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// screen_fade.cpp - Implementació de la fosa a/desde negre
|
||||||
|
// © 2026 JailDesigner
|
||||||
|
|
||||||
|
#include "core/graphics/screen_fade.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "core/defaults/game.hpp"
|
||||||
|
|
||||||
|
namespace Graphics {
|
||||||
|
|
||||||
|
ScreenFade::ScreenFade(Rendering::Renderer* renderer)
|
||||||
|
: renderer_(renderer) {}
|
||||||
|
|
||||||
|
void ScreenFade::start(float from, float to, float duration) {
|
||||||
|
from_ = from;
|
||||||
|
to_ = to;
|
||||||
|
duration_ = duration;
|
||||||
|
elapsed_ = 0.0F;
|
||||||
|
active_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenFade::update(float delta_time) {
|
||||||
|
if (!active_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
elapsed_ += delta_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ScreenFade::alpha() const -> float {
|
||||||
|
if (!active_) {
|
||||||
|
return 0.0F;
|
||||||
|
}
|
||||||
|
if (duration_ <= 0.0F) {
|
||||||
|
return to_;
|
||||||
|
}
|
||||||
|
const float T = std::clamp(elapsed_ / duration_, 0.0F, 1.0F);
|
||||||
|
return std::lerp(from_, to_, T);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ScreenFade::isDone() const -> bool {
|
||||||
|
return !active_ || elapsed_ >= duration_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenFade::draw() const {
|
||||||
|
const float A = alpha();
|
||||||
|
if (A <= 0.0F) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
renderer_->pushRect(
|
||||||
|
0.0F,
|
||||||
|
0.0F,
|
||||||
|
static_cast<float>(Defaults::Game::WIDTH),
|
||||||
|
static_cast<float>(Defaults::Game::HEIGHT),
|
||||||
|
0.0F,
|
||||||
|
0.0F,
|
||||||
|
0.0F,
|
||||||
|
A);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Graphics
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// screen_fade.hpp - Fosa a/desde negre per a transicions d'escena
|
||||||
|
// © 2026 JailDesigner
|
||||||
|
//
|
||||||
|
// Rect negre a pantalla completa amb l'alpha animat linealment. L'escena que el
|
||||||
|
// posseeix l'actualitza cada frame i el dibuixa l'ÚLTIM (per damunt de tot). En
|
||||||
|
// repòs (default-construït o fosa acabada amb alpha 0) el draw() és un no-op.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/rendering/render_context.hpp"
|
||||||
|
|
||||||
|
namespace Graphics {
|
||||||
|
|
||||||
|
class ScreenFade {
|
||||||
|
public:
|
||||||
|
explicit ScreenFade(Rendering::Renderer* renderer);
|
||||||
|
|
||||||
|
// Arrenca una fosa lineal d'alpha 'from' a 'to' en 'duration' segons.
|
||||||
|
void start(float from, float to, float duration);
|
||||||
|
|
||||||
|
// Avança el temporitzador intern.
|
||||||
|
void update(float delta_time);
|
||||||
|
|
||||||
|
// Dibuixa el rect negre a pantalla completa amb l'alpha actual.
|
||||||
|
// No fa res si l'alpha és ~0 (estalvia el pushRect).
|
||||||
|
void draw() const;
|
||||||
|
|
||||||
|
// Cert quan la fosa ha acabat (o no s'ha arrencat mai).
|
||||||
|
[[nodiscard]] auto isDone() const -> bool;
|
||||||
|
|
||||||
|
// Alpha actual ∈ [0, 1].
|
||||||
|
[[nodiscard]] auto alpha() const -> float;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Rendering::Renderer* renderer_;
|
||||||
|
float from_{0.0F};
|
||||||
|
float to_{0.0F};
|
||||||
|
float duration_{0.0F};
|
||||||
|
float elapsed_{0.0F};
|
||||||
|
bool active_{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Graphics
|
||||||
@@ -55,7 +55,8 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context)
|
|||||||
text_(sdl.getRenderer()),
|
text_(sdl.getRenderer()),
|
||||||
starfield_parallax_(sdl.getRenderer()),
|
starfield_parallax_(sdl.getRenderer()),
|
||||||
playfield_(sdl.getRenderer()),
|
playfield_(sdl.getRenderer()),
|
||||||
border_(sdl.getRenderer()) {
|
border_(sdl.getRenderer()),
|
||||||
|
fade_(sdl.getRenderer()) {
|
||||||
// Recuperar configuración de match des del context
|
// Recuperar configuración de match des del context
|
||||||
match_config_ = context_.getMatchConfig();
|
match_config_ = context_.getMatchConfig();
|
||||||
|
|
||||||
@@ -170,6 +171,8 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context)
|
|||||||
Audio::get()->enableSound(false);
|
Audio::get()->enableSound(false);
|
||||||
// El fons (graella) ha d'aparèixer ja muntat: la demo és una partida en marxa.
|
// El fons (graella) ha d'aparèixer ja muntat: la demo és una partida en marxa.
|
||||||
playfield_.completeBuild();
|
playfield_.completeBuild();
|
||||||
|
// Fosa des de negre sobre el joc ja en marxa (transició suau des del títol).
|
||||||
|
fade_.start(1.0F, 0.0F, Defaults::Game::Fade::DEMO_IN_DURATION);
|
||||||
} else {
|
} else {
|
||||||
stage_manager_->init();
|
stage_manager_->init();
|
||||||
}
|
}
|
||||||
@@ -363,6 +366,8 @@ void GameScene::updateShipsControl(float delta_time) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto GameScene::stepDemo(float delta_time) -> bool {
|
auto GameScene::stepDemo(float delta_time) -> bool {
|
||||||
|
fade_.update(delta_time); // fosa d'entrada des de negre
|
||||||
|
|
||||||
// Qualsevol input trenca la demo i torna al títol (música intacta).
|
// Qualsevol input trenca la demo i torna al títol (música intacta).
|
||||||
if (Input::get()->checkAnyPlayerAction(DEMO_EXIT_ACTIONS)) {
|
if (Input::get()->checkAnyPlayerAction(DEMO_EXIT_ACTIONS)) {
|
||||||
context_.setNextScene(SceneType::TITLE, Option::JUMP_TO_TITLE_MAIN);
|
context_.setNextScene(SceneType::TITLE, Option::JUMP_TO_TITLE_MAIN);
|
||||||
@@ -708,6 +713,10 @@ void GameScene::draw() {
|
|||||||
drawLevelCompletedState();
|
drawLevelCompletedState();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fosa d'entrada de la demo: per damunt de tot. No-op fora del mode DEMO
|
||||||
|
// (fade_ mai s'arrenca) i quan ja ha acabat (alpha 0).
|
||||||
|
fade_.draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameScene::drawEnemies() const {
|
void GameScene::drawEnemies() const {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include "core/graphics/border.hpp"
|
#include "core/graphics/border.hpp"
|
||||||
#include "core/graphics/playfield.hpp"
|
#include "core/graphics/playfield.hpp"
|
||||||
|
#include "core/graphics/screen_fade.hpp"
|
||||||
#include "core/graphics/starfield_parallax.hpp"
|
#include "core/graphics/starfield_parallax.hpp"
|
||||||
#include "core/graphics/vector_text.hpp"
|
#include "core/graphics/vector_text.hpp"
|
||||||
#include "core/physics/physics_world.hpp"
|
#include "core/physics/physics_world.hpp"
|
||||||
@@ -95,6 +96,9 @@ class GameScene final : public Scene {
|
|||||||
// Border del playfield (4 línies amb desplaçaments i flash per impactes)
|
// Border del playfield (4 línies amb desplaçaments i flash per impactes)
|
||||||
Graphics::Border border_;
|
Graphics::Border border_;
|
||||||
|
|
||||||
|
// Fosa des de negre en entrar a la demo (només mode DEMO; inactiu en partida normal).
|
||||||
|
Graphics::ScreenFade fade_;
|
||||||
|
|
||||||
// [NEW] Stage system
|
// [NEW] Stage system
|
||||||
std::unique_ptr<StageSystem::StageSystemConfig> stage_config_;
|
std::unique_ptr<StageSystem::StageSystemConfig> stage_config_;
|
||||||
std::unique_ptr<StageSystem::StageManager> stage_manager_;
|
std::unique_ptr<StageSystem::StageManager> stage_manager_;
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ namespace {
|
|||||||
TitleScene::TitleScene(SDLManager& sdl, SceneContext& context)
|
TitleScene::TitleScene(SDLManager& sdl, SceneContext& context)
|
||||||
: sdl_(sdl),
|
: sdl_(sdl),
|
||||||
context_(context),
|
context_(context),
|
||||||
text_(sdl.getRenderer()) {
|
text_(sdl.getRenderer()),
|
||||||
|
fade_(sdl.getRenderer()) {
|
||||||
std::cout << "SceneType Titol: Inicialitzant...\n";
|
std::cout << "SceneType Titol: Inicialitzant...\n";
|
||||||
|
|
||||||
match_config_.player1_active = false;
|
match_config_.player1_active = false;
|
||||||
@@ -333,6 +334,9 @@ void TitleScene::update(float delta_time) {
|
|||||||
case TitleState::BLACK_SCREEN:
|
case TitleState::BLACK_SCREEN:
|
||||||
updateBlackScreenState(delta_time);
|
updateBlackScreenState(delta_time);
|
||||||
break;
|
break;
|
||||||
|
case TitleState::DEMO_FADE_OUT:
|
||||||
|
updateDemoFadeOutState(delta_time);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Les animacions segueixen pero els inputs es bloquegen mentre el menu
|
// Les animacions segueixen pero els inputs es bloquegen mentre el menu
|
||||||
@@ -368,7 +372,12 @@ void TitleScene::update(float delta_time) {
|
|||||||
demo_cfg.player2_active = (SC.players >= 2);
|
demo_cfg.player2_active = (SC.players >= 2);
|
||||||
demo_cfg.mode = GameConfig::Mode::DEMO;
|
demo_cfg.mode = GameConfig::Mode::DEMO;
|
||||||
context_.setMatchConfig(demo_cfg);
|
context_.setMatchConfig(demo_cfg);
|
||||||
context_.setNextScene(SceneType::GAME);
|
// No saltem en sec: fosa a negre i, en acabar, canvi a GAME (el salt
|
||||||
|
// real el fa updateDemoFadeOutState). L'estat deixa de ser MAIN, així
|
||||||
|
// que ni es re-dispara la demo ni s'accepta START durant la fosa.
|
||||||
|
current_state_ = TitleState::DEMO_FADE_OUT;
|
||||||
|
fade_.start(0.0F, 1.0F, Defaults::Game::Fade::DEMO_OUT_DURATION);
|
||||||
|
temps_acumulat_ = 0.0F;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -473,6 +482,13 @@ void TitleScene::updateBlackScreenState(float delta_time) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TitleScene::updateDemoFadeOutState(float delta_time) {
|
||||||
|
fade_.update(delta_time);
|
||||||
|
if (fade_.isDone()) {
|
||||||
|
context_.setNextScene(SceneType::GAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TitleScene::handleSkipInput() {
|
void TitleScene::handleSkipInput() {
|
||||||
if (current_state_ != TitleState::STARFIELD_FADE_IN && current_state_ != TitleState::STARFIELD) {
|
if (current_state_ != TitleState::STARFIELD_FADE_IN && current_state_ != TitleState::STARFIELD) {
|
||||||
return;
|
return;
|
||||||
@@ -577,7 +593,8 @@ void TitleScene::draw() {
|
|||||||
(current_state_ == TitleState::STARFIELD_FADE_IN ||
|
(current_state_ == TitleState::STARFIELD_FADE_IN ||
|
||||||
current_state_ == TitleState::STARFIELD ||
|
current_state_ == TitleState::STARFIELD ||
|
||||||
current_state_ == TitleState::MAIN ||
|
current_state_ == TitleState::MAIN ||
|
||||||
current_state_ == TitleState::PLAYER_JOIN_PHASE)) {
|
current_state_ == TitleState::PLAYER_JOIN_PHASE ||
|
||||||
|
current_state_ == TitleState::DEMO_FADE_OUT)) {
|
||||||
ship_animator_->draw();
|
ship_animator_->draw();
|
||||||
}
|
}
|
||||||
drawFlashes();
|
drawFlashes();
|
||||||
@@ -586,7 +603,12 @@ void TitleScene::draw() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current_state_ != TitleState::MAIN && current_state_ != TitleState::PLAYER_JOIN_PHASE) {
|
// DEMO_FADE_OUT es pinta com MAIN (logo + text) perquè el títol segueixi
|
||||||
|
// visible sota la fosa a negre.
|
||||||
|
if (current_state_ != TitleState::MAIN &&
|
||||||
|
current_state_ != TitleState::PLAYER_JOIN_PHASE &&
|
||||||
|
current_state_ != TitleState::DEMO_FADE_OUT) {
|
||||||
|
fade_.draw(); // BLACK_SCREEN i altres: només la fosa (si activa)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -657,6 +679,9 @@ void TitleScene::draw() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dibuixarPeuTitol(SPACING);
|
dibuixarPeuTitol(SPACING);
|
||||||
|
|
||||||
|
// Fosa a negre (attract): per damunt de tot. No-op si no està activa.
|
||||||
|
fade_.draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto TitleScene::checkSkipButtonPressed() -> bool {
|
auto TitleScene::checkSkipButtonPressed() -> bool {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "core/graphics/camera3d.hpp"
|
#include "core/graphics/camera3d.hpp"
|
||||||
|
#include "core/graphics/screen_fade.hpp"
|
||||||
#include "core/graphics/shape.hpp"
|
#include "core/graphics/shape.hpp"
|
||||||
#include "core/graphics/starfield.hpp"
|
#include "core/graphics/starfield.hpp"
|
||||||
#include "core/graphics/vector_text.hpp"
|
#include "core/graphics/vector_text.hpp"
|
||||||
@@ -46,6 +47,7 @@ class TitleScene final : public Scene {
|
|||||||
MAIN,
|
MAIN,
|
||||||
PLAYER_JOIN_PHASE,
|
PLAYER_JOIN_PHASE,
|
||||||
BLACK_SCREEN,
|
BLACK_SCREEN,
|
||||||
|
DEMO_FADE_OUT, // Attract: fosa a negre abans de saltar a la demo
|
||||||
};
|
};
|
||||||
|
|
||||||
struct LogoLetter {
|
struct LogoLetter {
|
||||||
@@ -60,6 +62,7 @@ class TitleScene final : public Scene {
|
|||||||
SceneManager::SceneContext& context_;
|
SceneManager::SceneContext& context_;
|
||||||
GameConfig::MatchConfig match_config_;
|
GameConfig::MatchConfig match_config_;
|
||||||
Graphics::VectorText text_;
|
Graphics::VectorText text_;
|
||||||
|
Graphics::ScreenFade fade_; // Fosa a negre en saltar a la demo (attract)
|
||||||
std::unique_ptr<Graphics::Camera3D> camera_;
|
std::unique_ptr<Graphics::Camera3D> camera_;
|
||||||
std::unique_ptr<Graphics::Starfield> starfield_;
|
std::unique_ptr<Graphics::Starfield> starfield_;
|
||||||
std::unique_ptr<Title::ShipAnimator> ship_animator_;
|
std::unique_ptr<Title::ShipAnimator> ship_animator_;
|
||||||
@@ -143,6 +146,7 @@ class TitleScene final : public Scene {
|
|||||||
void updateMainState(float delta_time);
|
void updateMainState(float delta_time);
|
||||||
void updatePlayerJoinPhaseState(float delta_time);
|
void updatePlayerJoinPhaseState(float delta_time);
|
||||||
void updateBlackScreenState(float delta_time);
|
void updateBlackScreenState(float delta_time);
|
||||||
|
void updateDemoFadeOutState(float delta_time);
|
||||||
void handleSkipInput();
|
void handleSkipInput();
|
||||||
void handleStartInput();
|
void handleStartInput();
|
||||||
void triggerExitForJoinedPlayers(bool p1_was_active, bool p2_was_active, const char* log_prefix);
|
void triggerExitForJoinedPlayers(bool p1_was_active, bool p2_was_active, const char* log_prefix);
|
||||||
|
|||||||
Reference in New Issue
Block a user