feat(title): FSM IDLE/STARTING amb START per jugador i propagació de StartingPlayers
This commit is contained in:
1 parent
1fc3ab8e31
commit
ca64115bfe
9 files changed
+143
-26
No files matched your search
@@ -113,6 +113,13 @@ title:
|
|||||||
copyright_color: 246
|
copyright_color: 246
|
||||||
blink_visible_ms: 800
|
blink_visible_ms: 800
|
||||||
blink_hidden_ms: 200
|
blink_hidden_ms: 200
|
||||||
|
# Fase STARTING (entre la primera pulsació de START i el salt a la
|
||||||
|
# següent escena). `starting_ms` és la durada fixa; durant aquesta
|
||||||
|
# fase el parpadeig accelera per `starting_blink_speedup` (2.0 = doble
|
||||||
|
# de freqüència). Si l'altre jugador prem START dins d'aquesta
|
||||||
|
# finestra, el comptador es reseteja i s'arrenca amb BOTH.
|
||||||
|
starting_ms: 3000
|
||||||
|
starting_blink_speedup: 2.0
|
||||||
# Distorsió sinusoidal del fons (efecte de calor del desert).
|
# Distorsió sinusoidal del fons (efecte de calor del desert).
|
||||||
# `amplitude_px`: desplaçament màxim de cada fila en píxels.
|
# `amplitude_px`: desplaçament màxim de cada fila en píxels.
|
||||||
# `wavelength_px`: distància vertical entre dos pics de l'ona.
|
# `wavelength_px`: distància vertical entre dos pics de l'ona.
|
||||||
|
|||||||
@@ -251,19 +251,18 @@ Director::Director() {
|
|||||||
std::cout << "Director: start_scene=" << DebugConfig::toString(START)
|
std::cout << "Director: start_scene=" << DebugConfig::toString(START)
|
||||||
<< " starting_players=" << DebugConfig::toString(STARTING_PLAYERS) << '\n';
|
<< " starting_players=" << DebugConfig::toString(STARTING_PLAYERS) << '\n';
|
||||||
|
|
||||||
// El branch Release inicialitza STARTING_PLAYERS amb una constant
|
// `make_game` i `make_intro` reben `StartingPlayers` com a paràmetre
|
||||||
// literal i clang considera la captura redundant; en Debug el valor és
|
// perquè la TitleScene tria al moment quins jugadors entren (P1, P2,
|
||||||
// runtime i la captura sí és imprescindible. La lambda viu dins de
|
// BOTH) segons la pulsació. La IntroScene ho reenvia a la GameScene.
|
||||||
// IntroScene, més enllà del scope d'aquest ctor — capturar per
|
// El valor de `STARTING_PLAYERS` només s'usa com a punt d'entrada en
|
||||||
// referència seria UB.
|
// mode Debug quan saltem directament a INTRO o GAME (saltant TITLE).
|
||||||
// NOLINTNEXTLINE(clang-diagnostic-unused-lambda-capture)
|
auto make_game = [](DebugConfig::StartingPlayers players) -> std::unique_ptr<Scene> {
|
||||||
auto make_game = [STARTING_PLAYERS]() -> std::unique_ptr<Scene> {
|
|
||||||
auto cfg = GameScene::defaultConfig();
|
auto cfg = GameScene::defaultConfig();
|
||||||
cfg.starting_players = STARTING_PLAYERS;
|
cfg.starting_players = players;
|
||||||
return std::make_unique<GameScene>(std::move(cfg));
|
return std::make_unique<GameScene>(std::move(cfg));
|
||||||
};
|
};
|
||||||
auto make_intro = [placeholder_text, make_game]() -> std::unique_ptr<Scene> {
|
auto make_intro = [placeholder_text, make_game](DebugConfig::StartingPlayers players) -> std::unique_ptr<Scene> {
|
||||||
return std::make_unique<IntroScene>(placeholder_text, make_game);
|
return std::make_unique<IntroScene>(placeholder_text, players, make_game);
|
||||||
};
|
};
|
||||||
auto make_title = [menu_title_text, placeholder_text, title_bg_surface, mini_logo_surface, make_intro]() -> std::unique_ptr<Scene> {
|
auto make_title = [menu_title_text, placeholder_text, title_bg_surface, mini_logo_surface, make_intro]() -> std::unique_ptr<Scene> {
|
||||||
return std::make_unique<TitleScene>(menu_title_text, placeholder_text, title_bg_surface, mini_logo_surface, make_intro);
|
return std::make_unique<TitleScene>(menu_title_text, placeholder_text, title_bg_surface, mini_logo_surface, make_intro);
|
||||||
@@ -277,10 +276,10 @@ Director::Director() {
|
|||||||
current_scene_ = make_title();
|
current_scene_ = make_title();
|
||||||
break;
|
break;
|
||||||
case DebugConfig::StartScene::INTRO:
|
case DebugConfig::StartScene::INTRO:
|
||||||
current_scene_ = make_intro();
|
current_scene_ = make_intro(STARTING_PLAYERS);
|
||||||
break;
|
break;
|
||||||
case DebugConfig::StartScene::GAME:
|
case DebugConfig::StartScene::GAME:
|
||||||
current_scene_ = make_game();
|
current_scene_ = make_game(STARTING_PLAYERS);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
last_ticks_ms_ = SDL_GetTicks();
|
last_ticks_ms_ = SDL_GetTicks();
|
||||||
|
|||||||
@@ -102,6 +102,8 @@ UiConfig::UiConfig()
|
|||||||
title_copyright_color_(Defaults::UI::Title::COPYRIGHT_COLOR),
|
title_copyright_color_(Defaults::UI::Title::COPYRIGHT_COLOR),
|
||||||
title_blink_visible_ms_(Defaults::UI::Title::BLINK_VISIBLE_MS),
|
title_blink_visible_ms_(Defaults::UI::Title::BLINK_VISIBLE_MS),
|
||||||
title_blink_hidden_ms_(Defaults::UI::Title::BLINK_HIDDEN_MS),
|
title_blink_hidden_ms_(Defaults::UI::Title::BLINK_HIDDEN_MS),
|
||||||
|
title_starting_ms_(Defaults::UI::Title::STARTING_MS),
|
||||||
|
title_starting_blink_speedup_(Defaults::UI::Title::STARTING_BLINK_SPEEDUP),
|
||||||
title_heat_haze_{
|
title_heat_haze_{
|
||||||
.enabled = Defaults::UI::Title::HeatHaze::ENABLED,
|
.enabled = Defaults::UI::Title::HeatHaze::ENABLED,
|
||||||
.amplitude_px = Defaults::UI::Title::HeatHaze::AMPLITUDE_PX,
|
.amplitude_px = Defaults::UI::Title::HeatHaze::AMPLITUDE_PX,
|
||||||
@@ -224,6 +226,8 @@ void UiConfig::load(const std::string& file_path) {
|
|||||||
readColorIndex(t, "copyright_color", title_copyright_color_);
|
readColorIndex(t, "copyright_color", title_copyright_color_);
|
||||||
readField(t, "blink_visible_ms", title_blink_visible_ms_);
|
readField(t, "blink_visible_ms", title_blink_visible_ms_);
|
||||||
readField(t, "blink_hidden_ms", title_blink_hidden_ms_);
|
readField(t, "blink_hidden_ms", title_blink_hidden_ms_);
|
||||||
|
readField(t, "starting_ms", title_starting_ms_);
|
||||||
|
readField(t, "starting_blink_speedup", title_starting_blink_speedup_);
|
||||||
if (t.contains("heat_haze")) {
|
if (t.contains("heat_haze")) {
|
||||||
const auto& hh = t["heat_haze"];
|
const auto& hh = t["heat_haze"];
|
||||||
readField(hh, "enabled", title_heat_haze_.enabled);
|
readField(hh, "enabled", title_heat_haze_.enabled);
|
||||||
|
|||||||
@@ -95,6 +95,8 @@ class UiConfig {
|
|||||||
[[nodiscard]] auto titleCopyrightColor() const -> Uint8 { return title_copyright_color_; }
|
[[nodiscard]] auto titleCopyrightColor() const -> Uint8 { return title_copyright_color_; }
|
||||||
[[nodiscard]] auto titleBlinkVisibleMs() const -> int { return title_blink_visible_ms_; }
|
[[nodiscard]] auto titleBlinkVisibleMs() const -> int { return title_blink_visible_ms_; }
|
||||||
[[nodiscard]] auto titleBlinkHiddenMs() const -> int { return title_blink_hidden_ms_; }
|
[[nodiscard]] auto titleBlinkHiddenMs() const -> int { return title_blink_hidden_ms_; }
|
||||||
|
[[nodiscard]] auto titleStartingMs() const -> int { return title_starting_ms_; }
|
||||||
|
[[nodiscard]] auto titleStartingBlinkSpeedup() const -> float { return title_starting_blink_speedup_; }
|
||||||
[[nodiscard]] auto titleHeatHaze() const -> const TitleHeatHaze& { return title_heat_haze_; }
|
[[nodiscard]] auto titleHeatHaze() const -> const TitleHeatHaze& { return title_heat_haze_; }
|
||||||
|
|
||||||
// Corbes d'easing triades al YAML. Si el nom no és conegut, s'usa
|
// Corbes d'easing triades al YAML. Si el nom no és conegut, s'usa
|
||||||
@@ -167,6 +169,8 @@ class UiConfig {
|
|||||||
Uint8 title_copyright_color_;
|
Uint8 title_copyright_color_;
|
||||||
int title_blink_visible_ms_;
|
int title_blink_visible_ms_;
|
||||||
int title_blink_hidden_ms_;
|
int title_blink_hidden_ms_;
|
||||||
|
int title_starting_ms_;
|
||||||
|
float title_starting_blink_speedup_;
|
||||||
TitleHeatHaze title_heat_haze_;
|
TitleHeatHaze title_heat_haze_;
|
||||||
|
|
||||||
std::string menu_select_sound_;
|
std::string menu_select_sound_;
|
||||||
|
|||||||
@@ -30,10 +30,11 @@ namespace {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
IntroScene::IntroScene(std::shared_ptr<Text> font, SceneFactory next_factory)
|
IntroScene::IntroScene(std::shared_ptr<Text> font, DebugConfig::StartingPlayers starting_players, SceneFactory next_factory)
|
||||||
: font_(std::move(font)),
|
: font_(std::move(font)),
|
||||||
|
starting_players_(starting_players),
|
||||||
next_factory_(std::move(next_factory)) {
|
next_factory_(std::move(next_factory)) {
|
||||||
std::cout << "[scene] " << SCENE_NAME << " start\n";
|
std::cout << "[scene] " << SCENE_NAME << " start (players=" << DebugConfig::toString(starting_players_) << ")\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
IntroScene::~IntroScene() = default;
|
IntroScene::~IntroScene() = default;
|
||||||
@@ -52,7 +53,7 @@ void IntroScene::update(int delta_ms) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if ((timer_ms_ <= 0 || SKIPPED) && next_factory_) {
|
if ((timer_ms_ <= 0 || SKIPPED) && next_factory_) {
|
||||||
next_scene_ = next_factory_();
|
next_scene_ = next_factory_(starting_players_);
|
||||||
}
|
}
|
||||||
|
|
||||||
Notifier::get().update(delta_ms);
|
Notifier::get().update(delta_ms);
|
||||||
|
|||||||
@@ -4,19 +4,24 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "core/scene/scene.hpp"
|
#include "core/scene/scene.hpp"
|
||||||
|
#include "game/debug_config.hpp"
|
||||||
|
|
||||||
class Text;
|
class Text;
|
||||||
|
|
||||||
// Escena placeholder de la introducció. Com la resta de placeholders, durada
|
// Escena placeholder de la introducció. Com la resta de placeholders, durada
|
||||||
// fixa i skip amb tecla no reservada; quan expira encadena a la factory
|
// fixa i skip amb tecla no reservada; quan expira encadena a la factory
|
||||||
// injectada (en producció: GameScene).
|
// injectada (en producció: GameScene).
|
||||||
|
//
|
||||||
|
// `starting_players` és el resultat triat a la TitleScene (P1, P2 o BOTH).
|
||||||
|
// L'IntroScene només el guarda i el reenvia a la seva factory; quan demà
|
||||||
|
// l'intro tinga contingut real podrà variar segons qui jugue.
|
||||||
class IntroScene : public Scene {
|
class IntroScene : public Scene {
|
||||||
public:
|
public:
|
||||||
using SceneFactory = std::function<std::unique_ptr<Scene>()>;
|
using SceneFactory = std::function<std::unique_ptr<Scene>(DebugConfig::StartingPlayers)>;
|
||||||
|
|
||||||
static constexpr int DURATION_MS = 5000;
|
static constexpr int DURATION_MS = 5000;
|
||||||
|
|
||||||
IntroScene(std::shared_ptr<Text> font, SceneFactory next_factory);
|
IntroScene(std::shared_ptr<Text> font, DebugConfig::StartingPlayers starting_players, SceneFactory next_factory);
|
||||||
~IntroScene() override;
|
~IntroScene() override;
|
||||||
|
|
||||||
void update(int delta_ms) override;
|
void update(int delta_ms) override;
|
||||||
@@ -24,6 +29,7 @@ class IntroScene : public Scene {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<Text> font_;
|
std::shared_ptr<Text> font_;
|
||||||
|
DebugConfig::StartingPlayers starting_players_;
|
||||||
SceneFactory next_factory_;
|
SceneFactory next_factory_;
|
||||||
int timer_ms_{DURATION_MS};
|
int timer_ms_{DURATION_MS};
|
||||||
};
|
};
|
||||||
@@ -8,6 +8,8 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include "core/input/gamepad_bindings.hpp"
|
||||||
|
#include "core/input/gamepad_manager.hpp"
|
||||||
#include "core/input/global_inputs.hpp"
|
#include "core/input/global_inputs.hpp"
|
||||||
#include "core/locale/locale.hpp"
|
#include "core/locale/locale.hpp"
|
||||||
#include "core/rendering/notifier.hpp"
|
#include "core/rendering/notifier.hpp"
|
||||||
@@ -19,7 +21,7 @@
|
|||||||
#include "core/system/event_buffer.hpp"
|
#include "core/system/event_buffer.hpp"
|
||||||
#include "core/ui/ui_config.hpp"
|
#include "core/ui/ui_config.hpp"
|
||||||
#include "game/options.hpp"
|
#include "game/options.hpp"
|
||||||
#include "game/scenes/placeholder_input.hpp"
|
#include "utils/defaults.hpp"
|
||||||
#include "utils/defines.hpp"
|
#include "utils/defines.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -38,6 +40,27 @@ namespace {
|
|||||||
constexpr int COPYRIGHT_Y_PERCENT = 90;
|
constexpr int COPYRIGHT_Y_PERCENT = 90;
|
||||||
constexpr int LOGO_GAP_PX = 2; // Separació vertical entre el minilogo i el text de copyright.
|
constexpr int LOGO_GAP_PX = 2; // Separació vertical entre el minilogo i el text de copyright.
|
||||||
|
|
||||||
|
// Detecta una pulsació de START dins el frame actual per al jugador
|
||||||
|
// indicat. Combina events de teclat (scancode hardcoded a Defaults::
|
||||||
|
// KeysGame[P2]) i events de gamepad (botó remapejable via
|
||||||
|
// GamepadBindings, lligat al pad assignat al jugador via Options).
|
||||||
|
auto pressedStart(int player_id) -> bool {
|
||||||
|
const SDL_Scancode KB = (player_id == 0) ? Defaults::KeysGame::START : Defaults::KeysGameP2::START;
|
||||||
|
SDL_Gamepad* pad = GamepadManager::get().getForPlayer(player_id);
|
||||||
|
const SDL_JoystickID PAD_ID = (pad != nullptr) ? SDL_GetGamepadID(pad) : 0;
|
||||||
|
const auto& slot = (player_id == 0) ? Options::controls().gamepad_p1
|
||||||
|
: Options::controls().gamepad_p2;
|
||||||
|
const int START_BTN = GamepadBindings::get().getButton(slot.name, slot.path, GamepadBindings::Action::START);
|
||||||
|
|
||||||
|
return std::ranges::any_of(EventBuffer::events, [&](const SDL_Event& ev) {
|
||||||
|
if (ev.type == SDL_EVENT_KEY_DOWN && ev.key.scancode == KB) { return true; }
|
||||||
|
if (ev.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN && pad != nullptr && ev.gbutton.which == PAD_ID && static_cast<int>(ev.gbutton.button) == START_BTN) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Separació vertical entre la primera i segona línia del títol. Una
|
// Separació vertical entre la primera i segona línia del títol. Una
|
||||||
// alçada de línia és l'opció més llegible al canvas de 240 px sense
|
// alçada de línia és l'opció més llegible al canvas de 240 px sense
|
||||||
// entrar a configuració extra; si demà cal afinar-ho, mou-se a
|
// entrar a configuració extra; si demà cal afinar-ho, mou-se a
|
||||||
@@ -75,22 +98,70 @@ void TitleScene::update(int delta_ms) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
timer_ms_ -= delta_ms;
|
|
||||||
blink_elapsed_ms_ += delta_ms;
|
|
||||||
if (heat_haze_) { heat_haze_->tick(delta_ms); }
|
if (heat_haze_) { heat_haze_->tick(delta_ms); }
|
||||||
|
|
||||||
const bool SKIPPED = std::ranges::any_of(EventBuffer::events, [](const SDL_Event& ev) {
|
// Avanç del comptador del parpadeig: durant STARTING, accelera per
|
||||||
return ev.type == SDL_EVENT_KEY_DOWN && !Placeholder::isReservedScancode(ev.key.scancode);
|
// `starting_blink_speedup` (típicament 2.0). Es manté en enter perquè
|
||||||
});
|
// tota la lògica del cicle (visible_ms / hidden_ms) treballa amb int.
|
||||||
|
const float BLINK_RATE = (phase_ == Phase::STARTING)
|
||||||
|
? UiConfig::get().titleStartingBlinkSpeedup()
|
||||||
|
: 1.0F;
|
||||||
|
blink_elapsed_ms_ += static_cast<int>(static_cast<float>(delta_ms) * BLINK_RATE);
|
||||||
|
|
||||||
if ((timer_ms_ <= 0 || SKIPPED) && next_factory_) {
|
const bool P1_START = pressedStart(0);
|
||||||
next_scene_ = next_factory_();
|
const bool P2_START = pressedStart(1);
|
||||||
|
|
||||||
|
switch (phase_) {
|
||||||
|
case Phase::IDLE: {
|
||||||
|
timer_ms_ -= delta_ms;
|
||||||
|
if (P1_START) {
|
||||||
|
enterStarting(DebugConfig::StartingPlayers::P1);
|
||||||
|
} else if (P2_START) {
|
||||||
|
enterStarting(DebugConfig::StartingPlayers::P2);
|
||||||
|
} else if (timer_ms_ <= 0) {
|
||||||
|
// Attract: ningú ha premut. Saltem a la següent escena
|
||||||
|
// amb BOTH per defecte. Demà, quan tinguem una DemoScene,
|
||||||
|
// ací anirà la transició a demo en lloc de a intro.
|
||||||
|
advanceTo(DebugConfig::StartingPlayers::BOTH);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Phase::STARTING: {
|
||||||
|
starting_timer_ms_ -= delta_ms;
|
||||||
|
// Si l'altre jugador prem START dins de la finestra, pugem
|
||||||
|
// a BOTH i resetegem el comptador. Pulsacions repetides del
|
||||||
|
// mateix jugador ja "starting" no fan res.
|
||||||
|
const bool OTHER_PRESSED =
|
||||||
|
(pending_players_ == DebugConfig::StartingPlayers::P1 && P2_START) ||
|
||||||
|
(pending_players_ == DebugConfig::StartingPlayers::P2 && P1_START);
|
||||||
|
if (OTHER_PRESSED) {
|
||||||
|
pending_players_ = DebugConfig::StartingPlayers::BOTH;
|
||||||
|
starting_timer_ms_ = UiConfig::get().titleStartingMs();
|
||||||
|
}
|
||||||
|
if (starting_timer_ms_ <= 0) {
|
||||||
|
advanceTo(pending_players_);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Notifier::get().update(delta_ms);
|
Notifier::get().update(delta_ms);
|
||||||
RenderInfo::get().update(delta_ms, Options::buildRenderInfoFrame(UiConfig::get().renderInfoText(), UiConfig::get().renderInfoShadow()));
|
RenderInfo::get().update(delta_ms, Options::buildRenderInfoFrame(UiConfig::get().renderInfoText(), UiConfig::get().renderInfoShadow()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TitleScene::enterStarting(DebugConfig::StartingPlayers players) {
|
||||||
|
phase_ = Phase::STARTING;
|
||||||
|
pending_players_ = players;
|
||||||
|
starting_timer_ms_ = UiConfig::get().titleStartingMs();
|
||||||
|
std::cout << "[scene] " << SCENE_NAME << " STARTING (players=" << DebugConfig::toString(players) << ")\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void TitleScene::advanceTo(DebugConfig::StartingPlayers players) {
|
||||||
|
if (next_factory_) {
|
||||||
|
next_scene_ = next_factory_(players);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TitleScene::render() {
|
void TitleScene::render() {
|
||||||
const auto& ui_config = UiConfig::get();
|
const auto& ui_config = UiConfig::get();
|
||||||
const auto& palette = Resource::Cache::get().getPalette("ui");
|
const auto& palette = Resource::Cache::get().getPalette("ui");
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include "core/rendering/effects/heat_haze.hpp"
|
#include "core/rendering/effects/heat_haze.hpp"
|
||||||
#include "core/scene/scene.hpp"
|
#include "core/scene/scene.hpp"
|
||||||
|
#include "game/debug_config.hpp"
|
||||||
|
|
||||||
class Surface;
|
class Surface;
|
||||||
class Text;
|
class Text;
|
||||||
@@ -16,7 +18,17 @@ class Text;
|
|||||||
// data/ui.yaml secció `title:` per poder afinar-la sense recompilar.
|
// data/ui.yaml secció `title:` per poder afinar-la sense recompilar.
|
||||||
class TitleScene : public Scene {
|
class TitleScene : public Scene {
|
||||||
public:
|
public:
|
||||||
using SceneFactory = std::function<std::unique_ptr<Scene>()>;
|
using SceneFactory = std::function<std::unique_ptr<Scene>(DebugConfig::StartingPlayers)>;
|
||||||
|
|
||||||
|
// Fases de l'escena.
|
||||||
|
// - IDLE: animació atràctil. Cap tecla salta; només P1 START o
|
||||||
|
// P2 START fan transició. El timeout (`title.duration_ms`)
|
||||||
|
// salta a la següent escena amb BOTH (modus attract).
|
||||||
|
// - STARTING: comptador fix entre la primera pulsació de START i
|
||||||
|
// la transició. Si l'altre jugador prem START dins d'aquesta
|
||||||
|
// finestra, el comptador es reseteja amb `pending_players_=BOTH`.
|
||||||
|
enum class Phase : std::uint8_t { IDLE,
|
||||||
|
STARTING };
|
||||||
|
|
||||||
TitleScene(std::shared_ptr<Text> display_font, std::shared_ptr<Text> condensed_font, std::shared_ptr<Surface> background, std::shared_ptr<Surface> mini_logo, SceneFactory next_factory);
|
TitleScene(std::shared_ptr<Text> display_font, std::shared_ptr<Text> condensed_font, std::shared_ptr<Surface> background, std::shared_ptr<Surface> mini_logo, SceneFactory next_factory);
|
||||||
~TitleScene() override;
|
~TitleScene() override;
|
||||||
@@ -25,6 +37,9 @@ class TitleScene : public Scene {
|
|||||||
void render() override;
|
void render() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void enterStarting(DebugConfig::StartingPlayers players);
|
||||||
|
void advanceTo(DebugConfig::StartingPlayers players);
|
||||||
|
|
||||||
std::shared_ptr<Text> display_font_;
|
std::shared_ptr<Text> display_font_;
|
||||||
std::shared_ptr<Text> condensed_font_;
|
std::shared_ptr<Text> condensed_font_;
|
||||||
std::shared_ptr<Surface> background_;
|
std::shared_ptr<Surface> background_;
|
||||||
@@ -33,4 +48,7 @@ class TitleScene : public Scene {
|
|||||||
int timer_ms_;
|
int timer_ms_;
|
||||||
int blink_elapsed_ms_{0};
|
int blink_elapsed_ms_{0};
|
||||||
std::optional<Effects::HeatHaze> heat_haze_;
|
std::optional<Effects::HeatHaze> heat_haze_;
|
||||||
|
Phase phase_{Phase::IDLE};
|
||||||
|
int starting_timer_ms_{0};
|
||||||
|
DebugConfig::StartingPlayers pending_players_{DebugConfig::StartingPlayers::BOTH};
|
||||||
};
|
};
|
||||||
@@ -259,6 +259,13 @@ namespace Defaults {
|
|||||||
constexpr int BLINK_VISIBLE_MS = 800;
|
constexpr int BLINK_VISIBLE_MS = 800;
|
||||||
constexpr int BLINK_HIDDEN_MS = 200;
|
constexpr int BLINK_HIDDEN_MS = 200;
|
||||||
|
|
||||||
|
// Fase STARTING: durada fixa entre la primera pulsació de
|
||||||
|
// START i la transició a la següent escena. Durant aquesta
|
||||||
|
// fase, el parpadeig del prompt va a `BLINK_SPEEDUP` × la
|
||||||
|
// freqüència normal (2.0 = doble; 1.5 = un 50% més ràpid).
|
||||||
|
constexpr int STARTING_MS = 3000;
|
||||||
|
constexpr float STARTING_BLINK_SPEEDUP = 2.0F;
|
||||||
|
|
||||||
// Distorsió sinusoidal horitzontal de l'imatge de fons
|
// Distorsió sinusoidal horitzontal de l'imatge de fons
|
||||||
// (efecte de calor del desert). `amplitude_px` és el
|
// (efecte de calor del desert). `amplitude_px` és el
|
||||||
// desplaçament màxim per fila; `wavelength_px` la distància
|
// desplaçament màxim per fila; `wavelength_px` la distància
|
||||||
|
|||||||
Reference in new issue
Block a user