fade effect when switching states

This commit is contained in:
2025-11-29 19:23:41 +01:00
parent a7d67b26a5
commit 332e2efb74
5 changed files with 71 additions and 21 deletions

View File

@ -913,6 +913,31 @@ int main(int, char **)
running = false; running = false;
}; };
auto beginStateFade = [&](AppState targetState, bool armGameplayCountdown) {
if (!ctx.stateManager) {
return;
}
if (state == targetState) {
return;
}
if (menuFadePhase != MenuFadePhase::None) {
return;
}
menuFadePhase = MenuFadePhase::FadeOut;
menuFadeClockMs = 0.0;
menuFadeAlpha = 0.0f;
menuFadeTarget = targetState;
menuPlayCountdownArmed = armGameplayCountdown;
gameplayCountdownActive = false;
gameplayCountdownIndex = 0;
gameplayCountdownElapsed = 0.0;
if (!armGameplayCountdown) {
game.setPaused(false);
}
};
auto startMenuPlayTransition = [&]() { auto startMenuPlayTransition = [&]() {
if (!ctx.stateManager) { if (!ctx.stateManager) {
return; return;
@ -922,20 +947,22 @@ int main(int, char **)
ctx.stateManager->setState(state); ctx.stateManager->setState(state);
return; return;
} }
if (menuFadePhase != MenuFadePhase::None) { beginStateFade(AppState::Playing, true);
return;
}
menuFadePhase = MenuFadePhase::FadeOut;
menuFadeClockMs = 0.0;
menuFadeAlpha = 0.0f;
menuFadeTarget = AppState::Playing;
menuPlayCountdownArmed = true;
gameplayCountdownActive = false;
gameplayCountdownIndex = 0;
gameplayCountdownElapsed = 0.0;
}; };
ctx.startPlayTransition = startMenuPlayTransition; ctx.startPlayTransition = startMenuPlayTransition;
auto requestStateFade = [&](AppState targetState) {
if (!ctx.stateManager) {
return;
}
if (targetState == AppState::Playing) {
startMenuPlayTransition();
return;
}
beginStateFade(targetState, false);
};
ctx.requestFadeTransition = requestStateFade;
// Instantiate state objects // Instantiate state objects
auto loadingState = std::make_unique<LoadingState>(ctx); auto loadingState = std::make_unique<LoadingState>(ctx);
auto menuState = std::make_unique<MenuState>(ctx); auto menuState = std::make_unique<MenuState>(ctx);
@ -1100,11 +1127,9 @@ int main(int, char **)
if (pointInRect(buttonRects[0])) { if (pointInRect(buttonRects[0])) {
startMenuPlayTransition(); startMenuPlayTransition();
} else if (pointInRect(buttonRects[1])) { } else if (pointInRect(buttonRects[1])) {
state = AppState::LevelSelector; requestStateFade(AppState::LevelSelector);
stateMgr.setState(state);
} else if (pointInRect(buttonRects[2])) { } else if (pointInRect(buttonRects[2])) {
state = AppState::Options; requestStateFade(AppState::Options);
stateMgr.setState(state);
} else if (pointInRect(buttonRects[3])) { } else if (pointInRect(buttonRects[3])) {
showExitConfirmPopup = true; showExitConfirmPopup = true;
exitPopupSelectedButton = 1; exitPopupSelectedButton = 1;
@ -1461,14 +1486,23 @@ int main(int, char **)
menuFadeClockMs += frameMs; menuFadeClockMs += frameMs;
menuFadeAlpha = std::min(1.0f, float(menuFadeClockMs / MENU_PLAY_FADE_DURATION_MS)); menuFadeAlpha = std::min(1.0f, float(menuFadeClockMs / MENU_PLAY_FADE_DURATION_MS));
if (menuFadeClockMs >= MENU_PLAY_FADE_DURATION_MS) { if (menuFadeClockMs >= MENU_PLAY_FADE_DURATION_MS) {
if (menuFadeTarget == AppState::Playing) { if (state != menuFadeTarget) {
state = menuFadeTarget; state = menuFadeTarget;
stateMgr.setState(state); stateMgr.setState(state);
}
if (menuFadeTarget == AppState::Playing) {
menuPlayCountdownArmed = true; menuPlayCountdownArmed = true;
gameplayCountdownActive = false; gameplayCountdownActive = false;
gameplayCountdownIndex = 0; gameplayCountdownIndex = 0;
gameplayCountdownElapsed = 0.0; gameplayCountdownElapsed = 0.0;
game.setPaused(true); game.setPaused(true);
} else {
menuPlayCountdownArmed = false;
gameplayCountdownActive = false;
gameplayCountdownIndex = 0;
gameplayCountdownElapsed = 0.0;
game.setPaused(false);
} }
menuFadePhase = MenuFadePhase::FadeIn; menuFadePhase = MenuFadePhase::FadeIn;
menuFadeClockMs = MENU_PLAY_FADE_DURATION_MS; menuFadeClockMs = MENU_PLAY_FADE_DURATION_MS;

View File

@ -326,14 +326,18 @@ void LevelSelectorState::selectLevel(int level) {
*ctx.startLevelSelection = level; *ctx.startLevelSelection = level;
} }
// Transition back to menu // Transition back to menu
if (ctx.stateManager) { if (ctx.requestFadeTransition) {
ctx.requestFadeTransition(AppState::Menu);
} else if (ctx.stateManager) {
ctx.stateManager->setState(AppState::Menu); ctx.stateManager->setState(AppState::Menu);
} }
} }
void LevelSelectorState::closePopup() { void LevelSelectorState::closePopup() {
// Transition back to menu without changing level // Transition back to menu without changing level
if (ctx.stateManager) { if (ctx.requestFadeTransition) {
ctx.requestFadeTransition(AppState::Menu);
} else if (ctx.stateManager) {
ctx.stateManager->setState(AppState::Menu); ctx.stateManager->setState(AppState::Menu);
} }
} }

View File

@ -126,10 +126,18 @@ void MenuState::handleEvent(const SDL_Event& e) {
triggerPlay(); triggerPlay();
break; break;
case 1: case 1:
ctx.stateManager->setState(AppState::LevelSelector); if (ctx.requestFadeTransition) {
ctx.requestFadeTransition(AppState::LevelSelector);
} else if (ctx.stateManager) {
ctx.stateManager->setState(AppState::LevelSelector);
}
break; break;
case 2: case 2:
ctx.stateManager->setState(AppState::Options); if (ctx.requestFadeTransition) {
ctx.requestFadeTransition(AppState::Options);
} else if (ctx.stateManager) {
ctx.stateManager->setState(AppState::Options);
}
break; break;
case 3: case 3:
setExitPrompt(true); setExitPrompt(true);

View File

@ -267,7 +267,9 @@ void OptionsState::toggleSoundFx() {
} }
void OptionsState::exitToMenu() { void OptionsState::exitToMenu() {
if (ctx.stateManager) { if (ctx.requestFadeTransition) {
ctx.requestFadeTransition(AppState::Menu);
} else if (ctx.stateManager) {
ctx.stateManager->setState(AppState::Menu); ctx.stateManager->setState(AppState::Menu);
} }
} }

View File

@ -13,6 +13,7 @@ class Starfield;
class Starfield3D; class Starfield3D;
class FontAtlas; class FontAtlas;
class LineEffect; class LineEffect;
enum class AppState;
// Forward declare StateManager so StateContext can hold a pointer without // Forward declare StateManager so StateContext can hold a pointer without
// including the StateManager header here. // including the StateManager header here.
@ -58,6 +59,7 @@ struct StateContext {
std::function<bool()> queryFullscreen; // Optional callback if fullscreenFlag is not reliable std::function<bool()> queryFullscreen; // Optional callback if fullscreenFlag is not reliable
std::function<void()> requestQuit; // Allows menu/option states to close the app gracefully std::function<void()> requestQuit; // Allows menu/option states to close the app gracefully
std::function<void()> startPlayTransition; // Optional fade hook when transitioning from menu to gameplay std::function<void()> startPlayTransition; // Optional fade hook when transitioning from menu to gameplay
std::function<void(AppState)> requestFadeTransition; // Generic state fade requests (menu/options/level)
// Pointer to the application's StateManager so states can request transitions // Pointer to the application's StateManager so states can request transitions
StateManager* stateManager = nullptr; StateManager* stateManager = nullptr;
}; };