feat(demo): transició títol→demo amb dive de càmera + cortinilla negra (substitueix el fundido)

This commit is contained in:
JailDesigner committed 2026-05-29 10:03:17 +02:00
1 parent 068f42782b
commit 2f6d6c405f
9 files changed
+204 -145

No files matched your search

+8 -7
View File
@@ -56,7 +56,7 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context)
starfield_parallax_(sdl.getRenderer()),
playfield_(sdl.getRenderer()),
border_(sdl.getRenderer()),
fade_(sdl.getRenderer()) {
curtain_(sdl.getRenderer()) {
// Recuperar configuración de match des del context
match_config_ = context_.getMatchConfig();
@@ -171,8 +171,9 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context)
Audio::get()->enableSound(false);
// El fons (graella) ha d'aparèixer ja muntat: la demo és una partida en marxa.
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);
// La cortinilla arrenca tapant i cau per destapar la demo (continua el
// moviment iniciat al títol, que va acabar amb la pantalla negra).
curtain_.reveal(Defaults::Game::Curtain::REVEAL_DURATION);
} else {
stage_manager_->init();
}
@@ -366,7 +367,7 @@ void GameScene::updateShipsControl(float delta_time) {
}
auto GameScene::stepDemo(float delta_time) -> bool {
fade_.update(delta_time); // fosa d'entrada des de negre
curtain_.update(delta_time); // cortinilla que destapa la demo
// Qualsevol input trenca la demo i torna al títol (música intacta).
if (Input::get()->checkAnyPlayerAction(DEMO_EXIT_ACTIONS)) {
@@ -714,9 +715,9 @@ void GameScene::draw() {
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();
// Cortinilla d'entrada de la demo: per damunt de tot. No-op fora del mode
// DEMO (curtain_ mai s'arrenca) i quan ja ha sortit per baix.
curtain_.draw();
}
void GameScene::drawEnemies() const {
+3 -3
View File
@@ -9,8 +9,8 @@
#include <string>
#include "core/graphics/border.hpp"
#include "core/graphics/curtain.hpp"
#include "core/graphics/playfield.hpp"
#include "core/graphics/screen_fade.hpp"
#include "core/graphics/starfield_parallax.hpp"
#include "core/graphics/vector_text.hpp"
#include "core/physics/physics_world.hpp"
@@ -96,8 +96,8 @@ class GameScene final : public Scene {
// Border del playfield (4 línies amb desplaçaments i flash per impactes)
Graphics::Border border_;
// Fosa des de negre en entrar a la demo (només mode DEMO; inactiu en partida normal).
Graphics::ScreenFade fade_;
// Cortinilla que destapa en entrar a la demo (només mode DEMO; inactiva en partida normal).
Graphics::Curtain curtain_;
// [NEW] Stage system
std::unique_ptr<StageSystem::StageSystemConfig> stage_config_;
+51 -21
View File
@@ -38,7 +38,7 @@ TitleScene::TitleScene(SDLManager& sdl, SceneContext& context)
: sdl_(sdl),
context_(context),
text_(sdl.getRenderer()),
fade_(sdl.getRenderer()) {
curtain_(sdl.getRenderer()) {
std::cout << "SceneType Titol: Inicialitzant...\n";
match_config_.player1_active = false;
@@ -277,8 +277,9 @@ void TitleScene::dibuixarPeuTitol(float spacing) const {
// a 1.0 (a la mida i posició finals, "lluny" al VP).
const float SCREEN_CENTRE_X = Defaults::Game::WIDTH / 2.0F;
const float SCREEN_CENTRE_Y = Defaults::Game::HEIGHT / 2.0F;
const float JAILGAMES_S = std::lerp(S::FOOTER_INTRO_SCALE_START, 1.0F, Easing::easeOutQuad(intro_jailgames_progress_));
const float COPYRIGHT_S = std::lerp(S::FOOTER_INTRO_SCALE_START, 1.0F, Easing::easeOutQuad(intro_copyright_progress_));
// dive_zoom_ (attract) afegeix el zoom del dive per travessar el peu.
const float JAILGAMES_S = dive_zoom_ * std::lerp(S::FOOTER_INTRO_SCALE_START, 1.0F, Easing::easeOutQuad(intro_jailgames_progress_));
const float COPYRIGHT_S = dive_zoom_ * std::lerp(S::FOOTER_INTRO_SCALE_START, 1.0F, Easing::easeOutQuad(intro_copyright_progress_));
const float JAILGAMES_RENDER_SCALE = Defaults::Title::Layout::JAILGAMES_SCALE * JAILGAMES_S;
for (const auto& letter : letters_jailgames_) {
@@ -334,8 +335,11 @@ void TitleScene::update(float delta_time) {
case TitleState::BLACK_SCREEN:
updateBlackScreenState(delta_time);
break;
case TitleState::DEMO_FADE_OUT:
updateDemoFadeOutState(delta_time);
case TitleState::DEMO_DIVE:
updateDemoDiveState(delta_time);
break;
case TitleState::DEMO_CURTAIN:
updateDemoCurtainState(delta_time);
break;
}
@@ -372,11 +376,14 @@ void TitleScene::update(float delta_time) {
demo_cfg.player2_active = (SC.players >= 2);
demo_cfg.mode = GameConfig::Mode::DEMO;
context_.setMatchConfig(demo_cfg);
// 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);
// No saltem en sec: primer un "dive" de càmera cap al punt de fuga
// (deixa enrere títol/naus/logo) i després la cortinilla. L'estat
// deixa de ser MAIN, així que ni es re-dispara la demo ni s'accepta
// START durant la transició. Amaguem ja el "PREMEU START".
press_start_visible_ = false;
current_state_ = TitleState::DEMO_DIVE;
dive_time_ = 0.0F;
dive_zoom_ = 1.0F;
temps_acumulat_ = 0.0F;
}
}
@@ -482,9 +489,29 @@ void TitleScene::updateBlackScreenState(float delta_time) {
}
}
void TitleScene::updateDemoFadeOutState(float delta_time) {
fade_.update(delta_time);
if (fade_.isDone()) {
void TitleScene::updateDemoDiveState(float delta_time) {
namespace D = Defaults::Game::Dive;
dive_time_ += delta_time;
const float T = std::min(dive_time_ / D::DURATION, 1.0F);
const float EASED = Easing::easeInQuad(T); // acceleració cap al punt de fuga
// Càmera 3D real cap a +Z: starfield i naus es projecten amb la càmera, així
// que les estrelles es rasguen i les naus creixen i s'escapen pels costats.
if (camera_ != nullptr) {
camera_->setPosition(Vec3{.x = 0.0F, .y = 0.0F, .z = EASED * D::CAMERA_DISTANCE});
}
// Logo i peu són 2D: els fakegem el dive amb un zoom des del centre.
dive_zoom_ = std::lerp(1.0F, D::ZOOM_MAX, EASED);
if (T >= 1.0F) {
current_state_ = TitleState::DEMO_CURTAIN;
curtain_.cover(Defaults::Game::Curtain::COVER_DURATION);
}
}
void TitleScene::updateDemoCurtainState(float delta_time) {
curtain_.update(delta_time);
if (curtain_.isDone()) {
context_.setNextScene(SceneType::GAME);
}
}
@@ -594,7 +621,8 @@ void TitleScene::draw() {
current_state_ == TitleState::STARFIELD ||
current_state_ == TitleState::MAIN ||
current_state_ == TitleState::PLAYER_JOIN_PHASE ||
current_state_ == TitleState::DEMO_FADE_OUT)) {
current_state_ == TitleState::DEMO_DIVE ||
current_state_ == TitleState::DEMO_CURTAIN)) {
ship_animator_->draw();
}
drawFlashes();
@@ -603,19 +631,21 @@ void TitleScene::draw() {
return;
}
// DEMO_FADE_OUT es pinta com MAIN (logo + text) perquè el títol segueixi
// visible sota la fosa a negre.
// DEMO_DIVE/DEMO_CURTAIN es pinten com MAIN (logo + peu) perquè el títol
// segueixi visible sota el dive i la cortinilla.
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)
current_state_ != TitleState::DEMO_DIVE &&
current_state_ != TitleState::DEMO_CURTAIN) {
curtain_.draw(); // BLACK_SCREEN i altres: només la cortinilla (si activa)
return;
}
// Factor d'escala+posició per simular un moviment 3D des de l'usuari (prop,
// sprite gran i posició projectada extrema) cap al VP (lluny, sprite a la
// seva mida i posició finals). Pivot: centre de pantalla (= projecció VP).
const float LOGO_S = std::lerp(Defaults::Title::Sequence::LOGO_INTRO_SCALE_START, 1.0F, Easing::easeOutQuad(intro_logo_progress_));
// El dive (attract) hi afegeix un zoom extra (dive_zoom_) per travessar-los.
const float LOGO_S = dive_zoom_ * std::lerp(Defaults::Title::Sequence::LOGO_INTRO_SCALE_START, 1.0F, Easing::easeOutQuad(intro_logo_progress_));
const float SCREEN_CENTRE_X = Defaults::Game::WIDTH / 2.0F;
const float SCREEN_CENTRE_Y = Defaults::Game::HEIGHT / 2.0F;
const float LOGO_RENDER_SCALE = Defaults::Title::Layout::LOGO_SCALE * LOGO_S;
@@ -680,8 +710,8 @@ void TitleScene::draw() {
dibuixarPeuTitol(SPACING);
// Fosa a negre (attract): per damunt de tot. No-op si no està activa.
fade_.draw();
// Cortinilla negra (attract): per damunt de tot. No-op si no està activa.
curtain_.draw();
}
auto TitleScene::checkSkipButtonPressed() -> bool {
+11 -4
View File
@@ -18,7 +18,7 @@
#include <vector>
#include "core/graphics/camera3d.hpp"
#include "core/graphics/screen_fade.hpp"
#include "core/graphics/curtain.hpp"
#include "core/graphics/shape.hpp"
#include "core/graphics/starfield.hpp"
#include "core/graphics/vector_text.hpp"
@@ -47,7 +47,8 @@ class TitleScene final : public Scene {
MAIN,
PLAYER_JOIN_PHASE,
BLACK_SCREEN,
DEMO_FADE_OUT, // Attract: fosa a negre abans de saltar a la demo
DEMO_DIVE, // Attract: dive de càmera cap al punt de fuga
DEMO_CURTAIN, // Attract: cortinilla negra que cau i tapa abans del salt
};
struct LogoLetter {
@@ -62,7 +63,7 @@ class TitleScene final : public Scene {
SceneManager::SceneContext& context_;
GameConfig::MatchConfig match_config_;
Graphics::VectorText text_;
Graphics::ScreenFade fade_; // Fosa a negre en saltar a la demo (attract)
Graphics::Curtain curtain_; // Cortinilla negra en saltar a la demo (attract)
std::unique_ptr<Graphics::Camera3D> camera_;
std::unique_ptr<Graphics::Starfield> starfield_;
std::unique_ptr<Title::ShipAnimator> ship_animator_;
@@ -84,6 +85,11 @@ class TitleScene final : public Scene {
float temps_acumulat_{0.0F};
float idle_timer_{0.0F}; // Attract mode: inactivitat acumulada al state MAIN
// Dive (attract): temps transcorregut i zoom aplicat als elements 2D
// (logo + peu) mentre la càmera avança cap al punt de fuga.
float dive_time_{0.0F};
float dive_zoom_{1.0F};
std::vector<LogoLetter> letters_orni_;
std::vector<LogoLetter> letters_attack_;
float dynamic_attack_y_{0.0F};
@@ -146,7 +152,8 @@ class TitleScene final : public Scene {
void updateMainState(float delta_time);
void updatePlayerJoinPhaseState(float delta_time);
void updateBlackScreenState(float delta_time);
void updateDemoFadeOutState(float delta_time);
void updateDemoDiveState(float delta_time);
void updateDemoCurtainState(float delta_time);
void handleSkipInput();
void handleStartInput();
void triggerExitForJoinedPlayers(bool p1_was_active, bool p2_was_active, const char* log_prefix);