feat(logo,menu): reiniciar des de servei → LOGO amb reset (atura música) i logo_jailgames.gif centrat
This commit is contained in:
1 parent
8eaa91f309
commit
f7ab6a641a
7 files changed
+92
-35
No files matched your search
@@ -224,6 +224,13 @@ Director::Director() {
|
|||||||
auto mini_logo_surface = Resource::Cache::get().getSurface(MINI_LOGO_KEY);
|
auto mini_logo_surface = Resource::Cache::get().getSurface(MINI_LOGO_KEY);
|
||||||
mini_logo_surface_ = mini_logo_surface;
|
mini_logo_surface_ = mini_logo_surface;
|
||||||
|
|
||||||
|
// Logo sencer per a l'escena LOGO (centrat damunt fons negre). També
|
||||||
|
// conserva la seua paleta nadiua: els colors finals del logo viuen
|
||||||
|
// dins del propi GIF i no han de passar per la paleta UI.
|
||||||
|
constexpr const char* LOGO_FULL_KEY = "logo_jailgames.gif";
|
||||||
|
Resource::Cache::get().loadSurface(LOGO_FULL_KEY, "data/logo/logo_jailgames.gif");
|
||||||
|
logo_full_surface_ = Resource::Cache::get().getSurface(LOGO_FULL_KEY);
|
||||||
|
|
||||||
// Fons sencer de la TITLE (320×240). Igual que el minilogo, conserva
|
// Fons sencer de la TITLE (320×240). Igual que el minilogo, conserva
|
||||||
// la seua paleta nadiua perquè el GIF ja porta els colors finals.
|
// la seua paleta nadiua perquè el GIF ja porta els colors finals.
|
||||||
constexpr const char* TITLE_BG_KEY = "title_bg.gif";
|
constexpr const char* TITLE_BG_KEY = "title_bg.gif";
|
||||||
@@ -393,6 +400,16 @@ void Director::processMenuInput() {
|
|||||||
|
|
||||||
void Director::switchSceneIfRequested() {
|
void Director::switchSceneIfRequested() {
|
||||||
if (!current_scene_) { return; }
|
if (!current_scene_) { return; }
|
||||||
|
// Prioritat al menú: si un item del ServiceMenu ha encuat una
|
||||||
|
// transició (típicament "Reiniciar" → LOGO amb reset), s'aplica
|
||||||
|
// primer. Substitueix l'`ExitRequest` que pugui haver publicat
|
||||||
|
// l'escena en aquest mateix frame — l'usuari ha cancel·lat l'estat
|
||||||
|
// de joc explícitament i no ens interessa mantenir-ne la sortida.
|
||||||
|
if (auto menu_req = ServiceMenu::get().takePendingSceneRequest(); menu_req.has_value()) {
|
||||||
|
std::cout << "Director: scene transition (menu) → " << toString(menu_req->next) << '\n';
|
||||||
|
current_scene_ = buildScene(menu_req->next, menu_req->params);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// L'escena ha publicat un `ExitRequest` dins de `exit_request_`.
|
// L'escena ha publicat un `ExitRequest` dins de `exit_request_`.
|
||||||
// Director el desempaqueta i construeix la nova escena via
|
// Director el desempaqueta i construeix la nova escena via
|
||||||
// `buildScene`. Si la `std::any_cast` falla (tipus inesperat), el
|
// `buildScene`. Si la `std::any_cast` falla (tipus inesperat), el
|
||||||
@@ -415,8 +432,10 @@ auto Director::buildScene(SceneId id, const SceneParams& params) -> std::unique_
|
|||||||
// ctors antics no l'utilitzen directament. Quan totes estiguen
|
// ctors antics no l'utilitzen directament. Quan totes estiguen
|
||||||
// migrades a `ExitRequest`, aquestes lambdes desapareixeran.
|
// migrades a `ExitRequest`, aquestes lambdes desapareixeran.
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case SceneId::LOGO:
|
case SceneId::LOGO: {
|
||||||
return std::make_unique<LogoScene>(placeholder_text_);
|
const auto& p = std::get<LogoParams>(params);
|
||||||
|
return std::make_unique<LogoScene>(logo_full_surface_, p.reset);
|
||||||
|
}
|
||||||
case SceneId::TITLE:
|
case SceneId::TITLE:
|
||||||
return std::make_unique<TitleScene>(menu_title_text_, placeholder_text_, title_bg_surface_, mini_logo_surface_);
|
return std::make_unique<TitleScene>(menu_title_text_, placeholder_text_, title_bg_surface_, mini_logo_surface_);
|
||||||
case SceneId::INTRO: {
|
case SceneId::INTRO: {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class Director {
|
|||||||
std::shared_ptr<Text> menu_title_text_;
|
std::shared_ptr<Text> menu_title_text_;
|
||||||
std::shared_ptr<Surface> title_bg_surface_;
|
std::shared_ptr<Surface> title_bg_surface_;
|
||||||
std::shared_ptr<Surface> mini_logo_surface_;
|
std::shared_ptr<Surface> mini_logo_surface_;
|
||||||
|
std::shared_ptr<Surface> logo_full_surface_;
|
||||||
|
|
||||||
void createSystemFolder();
|
void createSystemFolder();
|
||||||
void processEvents();
|
void processEvents();
|
||||||
|
|||||||
@@ -5,26 +5,23 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include "core/audio/audio.hpp"
|
||||||
#include "core/input/global_inputs.hpp"
|
#include "core/input/global_inputs.hpp"
|
||||||
#include "core/rendering/notifier.hpp"
|
#include "core/rendering/notifier.hpp"
|
||||||
#include "core/rendering/render_info.hpp"
|
#include "core/rendering/render_info.hpp"
|
||||||
#include "core/rendering/screen.hpp"
|
#include "core/rendering/screen.hpp"
|
||||||
#include "core/rendering/text.hpp"
|
#include "core/rendering/surface.hpp"
|
||||||
#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 "game/scenes/placeholder_input.hpp"
|
||||||
#include "game/scenes/scene_flow.hpp"
|
#include "game/scenes/scene_flow.hpp"
|
||||||
#include "utils/defaults.hpp"
|
|
||||||
#include "utils/defines.hpp"
|
#include "utils/defines.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr int NAME_Y_OFFSET = -8;
|
|
||||||
constexpr int TIMER_Y_OFFSET = 8;
|
|
||||||
// TODO: migrar a un logger unificat quan es decideixi (veure INC-03 a
|
// TODO: migrar a un logger unificat quan es decideixi (veure INC-03 a
|
||||||
// docs/audit-core-rendering.md). De moment std::cout per rastrejar el
|
// docs/audit-core-rendering.md). De moment std::cout per rastrejar el
|
||||||
// flux d'escenes als smoke runs.
|
// flux d'escenes als smoke runs.
|
||||||
@@ -32,9 +29,19 @@ namespace {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
LogoScene::LogoScene(std::shared_ptr<Text> font)
|
LogoScene::LogoScene(std::shared_ptr<Surface> logo, bool reset)
|
||||||
: font_(std::move(font)) {
|
: logo_(std::move(logo)) {
|
||||||
std::cout << "[scene] " << SCENE_NAME << " start\n";
|
std::cout << "[scene] " << SCENE_NAME << (reset ? " start (reset)\n" : " start\n");
|
||||||
|
// RESET = "Reiniciar" del menú: tornem al punt zero del joc, així que
|
||||||
|
// qualsevol música o so en marxa (la del joc, els sons d'UI, etc.) ha
|
||||||
|
// d'apagar-se abans que el LOGO comenci. En el flux normal no toquem
|
||||||
|
// res — la música de TITLE pot seguir sonant durant l'attract-out.
|
||||||
|
if (reset) {
|
||||||
|
if (auto* audio = Audio::get(); audio != nullptr) {
|
||||||
|
audio->stopMusic();
|
||||||
|
audio->stopAllSounds();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LogoScene::~LogoScene() = default;
|
LogoScene::~LogoScene() = default;
|
||||||
@@ -64,20 +71,10 @@ void LogoScene::render() {
|
|||||||
constexpr std::uint32_t BLACK_ARGB = 0xFF000000U;
|
constexpr std::uint32_t BLACK_ARGB = 0xFF000000U;
|
||||||
Screen::get().fillRect(0, 0, GameCanvas::WIDTH, GameCanvas::HEIGHT, BLACK_ARGB);
|
Screen::get().fillRect(0, 0, GameCanvas::WIDTH, GameCanvas::HEIGHT, BLACK_ARGB);
|
||||||
|
|
||||||
if (font_) {
|
if (logo_) {
|
||||||
const int SECS_LEFT = std::max(1, (timer_ms_ + 999) / 1000);
|
const int X = (GameCanvas::WIDTH - logo_->getWidth()) / 2;
|
||||||
font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
|
const int Y = (GameCanvas::HEIGHT - logo_->getHeight()) / 2;
|
||||||
GameCanvas::CENTER_X,
|
logo_->render(X, Y);
|
||||||
GameCanvas::CENTER_Y + NAME_Y_OFFSET,
|
|
||||||
SCENE_NAME,
|
|
||||||
1,
|
|
||||||
UiConfig::get().notificationText());
|
|
||||||
font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
|
|
||||||
GameCanvas::CENTER_X,
|
|
||||||
GameCanvas::CENTER_Y + TIMER_Y_OFFSET,
|
|
||||||
std::to_string(SECS_LEFT),
|
|
||||||
1,
|
|
||||||
UiConfig::get().notificationText());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Notifier::get().render();
|
Notifier::get().render();
|
||||||
|
|||||||
@@ -4,23 +4,29 @@
|
|||||||
|
|
||||||
#include "core/scene/scene.hpp"
|
#include "core/scene/scene.hpp"
|
||||||
|
|
||||||
class Text;
|
class Surface;
|
||||||
|
|
||||||
// Escena placeholder del logotip. Durada fixa (5 s) o skip amb qualsevol tecla
|
// Escena del logotip de l'editora. Durada fixa (5 s) o skip amb qualsevol
|
||||||
// no reservada. Quan expira, publica un `ExitRequest` cap a TITLE; Director
|
// tecla no reservada. Quan expira, publica un `ExitRequest` cap a TITLE;
|
||||||
// és qui construeix la nova escena (l'escena no coneix els altres tipus i
|
// Director és qui construeix la nova escena (l'escena no coneix els altres
|
||||||
// es pot exercir en tests sense arrossegar dependències).
|
// tipus i es pot exercir en tests sense arrossegar dependències).
|
||||||
|
//
|
||||||
|
// Pinta el GIF `data/logo/logo_jailgames.gif` centrat dins del canvas
|
||||||
|
// lògic, amb la seua paleta nadiua (no s'aplica la paleta UI). Si arriba
|
||||||
|
// amb `reset=true` (acció "Reiniciar" del menú de servei), atura la
|
||||||
|
// música i tots els sons al constructor — així el següent LOGO arrenca
|
||||||
|
// silenciós encara que veníssem del joc en marxa.
|
||||||
class LogoScene : public Scene {
|
class LogoScene : public Scene {
|
||||||
public:
|
public:
|
||||||
static constexpr int DURATION_MS = 5000;
|
static constexpr int DURATION_MS = 5000;
|
||||||
|
|
||||||
explicit LogoScene(std::shared_ptr<Text> font);
|
LogoScene(std::shared_ptr<Surface> logo, bool reset);
|
||||||
~LogoScene() override;
|
~LogoScene() override;
|
||||||
|
|
||||||
void update(int delta_ms) override;
|
void update(int delta_ms) override;
|
||||||
void render() override;
|
void render() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<Text> font_;
|
std::shared_ptr<Surface> logo_;
|
||||||
int timer_ms_{DURATION_MS};
|
int timer_ms_{DURATION_MS};
|
||||||
};
|
};
|
||||||
@@ -30,7 +30,15 @@ enum class SceneId : std::uint8_t {
|
|||||||
// rep cap dada de l'anterior; en cas contrari, només els camps que
|
// rep cap dada de l'anterior; en cas contrari, només els camps que
|
||||||
// fluixen pel canal de transició (els recursos, defaults i config
|
// fluixen pel canal de transició (els recursos, defaults i config
|
||||||
// interna els resol Director quan construeix).
|
// interna els resol Director quan construeix).
|
||||||
struct LogoParams {};
|
struct LogoParams {
|
||||||
|
// Quan val `true`, indica que aquesta entrada al LOGO és una reinicialització
|
||||||
|
// completa (acció "Reiniciar" del menú de servei): l'escena haurà d'aturar
|
||||||
|
// música i sons abans de mostrar-se. En el flux normal LOGO→TITLE→… es
|
||||||
|
// queda a `false` perquè qualsevol música prèvia (p. ex. la pista de
|
||||||
|
// TITLE durant l'attract-out) continui sonant fins que la nova escena la
|
||||||
|
// substituïsca.
|
||||||
|
bool reset{false};
|
||||||
|
};
|
||||||
struct TitleParams {};
|
struct TitleParams {};
|
||||||
struct IntroParams {
|
struct IntroParams {
|
||||||
DebugConfig::StartingPlayers starting_players{DebugConfig::StartingPlayers::BOTH};
|
DebugConfig::StartingPlayers starting_players{DebugConfig::StartingPlayers::BOTH};
|
||||||
|
|||||||
@@ -1168,12 +1168,18 @@ auto ServiceMenu::buildSystem() -> Page {
|
|||||||
page.items.push_back(makeLabel(
|
page.items.push_back(makeLabel(
|
||||||
std::string("ver.") + Texts::VERSION + " (" + Version::GIT_HASH + ")"));
|
std::string("ver.") + Texts::VERSION + " (" + Version::GIT_HASH + ")"));
|
||||||
|
|
||||||
// Reiniciar el joc: placeholder sense efecte fins que Director exposi
|
// Reiniciar el joc: tornem al LOGO amb `reset=true` perquè LogoScene
|
||||||
// un pas de restart en calent (fora d'abast d'aquesta iteració).
|
// ature música i sons abans de mostrar-se. Tanquem el menú a la
|
||||||
|
// mateixa acció — Director recollirà la transició al final del frame
|
||||||
|
// via `takePendingSceneRequest()` i reconstruirà l'escena.
|
||||||
page.items.push_back(makeAction(
|
page.items.push_back(makeAction(
|
||||||
"menu.items.restart",
|
"menu.items.restart",
|
||||||
[]() {
|
[]() {
|
||||||
// TODO: connectar amb un hipotètic Director::requestRestart().
|
ServiceMenu::get().requestSceneTransition(ExitRequest{
|
||||||
|
.next = SceneId::LOGO,
|
||||||
|
.params = LogoParams{.reset = true},
|
||||||
|
});
|
||||||
|
ServiceMenu::get().close();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
page.items.push_back(makeAction(
|
page.items.push_back(makeAction(
|
||||||
|
|||||||
@@ -6,11 +6,13 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "core/input/define_buttons.hpp"
|
#include "core/input/define_buttons.hpp"
|
||||||
#include "core/rendering/surface.hpp" // Palette
|
#include "core/rendering/surface.hpp" // Palette
|
||||||
|
#include "game/scenes/scene_flow.hpp"
|
||||||
|
|
||||||
class Text;
|
class Text;
|
||||||
|
|
||||||
@@ -73,6 +75,19 @@ class ServiceMenu {
|
|||||||
// `define_buttons_->enable(...)` de coffee_crisis_arcade_edition.
|
// `define_buttons_->enable(...)` de coffee_crisis_arcade_edition.
|
||||||
void startDefineButtons(SDL_Gamepad* pad, const std::string& name, const std::string& path);
|
void startDefineButtons(SDL_Gamepad* pad, const std::string& name, const std::string& path);
|
||||||
|
|
||||||
|
// Bústia de transició d'escena des del menú: alguns items (p.ex.
|
||||||
|
// "Reiniciar" a Sistema) no es poden expressar com a `ExitRequest` de
|
||||||
|
// l'escena actual perquè el menú és global i no té handle a l'escena.
|
||||||
|
// Director consulta `takePendingSceneRequest()` cada frame i, si hi ha
|
||||||
|
// valor, força la transició — el mateix camí que per a `ExitRequest`
|
||||||
|
// d'una escena, però el dispara el menú directament.
|
||||||
|
void requestSceneTransition(ExitRequest req) { pending_request_ = req; }
|
||||||
|
[[nodiscard]] auto takePendingSceneRequest() -> std::optional<ExitRequest> {
|
||||||
|
auto out = pending_request_;
|
||||||
|
pending_request_.reset();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// --- Struct d'un item d'una pàgina ---
|
// --- Struct d'un item d'una pàgina ---
|
||||||
//
|
//
|
||||||
@@ -289,4 +304,9 @@ class ServiceMenu {
|
|||||||
// persisteix la nova configuració via `GamepadBindings`. Replica el
|
// persisteix la nova configuració via `GamepadBindings`. Replica el
|
||||||
// patró `define_buttons_` de coffee_crisis_arcade_edition.
|
// patró `define_buttons_` de coffee_crisis_arcade_edition.
|
||||||
DefineButtons define_buttons_;
|
DefineButtons define_buttons_;
|
||||||
|
|
||||||
|
// Sol·licitud de canvi d'escena pendent (vore `requestSceneTransition`).
|
||||||
|
// La consumeix Director al final del frame; si l'item no força la
|
||||||
|
// transició, queda buida.
|
||||||
|
std::optional<ExitRequest> pending_request_;
|
||||||
};
|
};
|
||||||
Reference in new issue
Block a user