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

View File

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

View File

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

View File

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

View File

@ -13,6 +13,7 @@ class Starfield;
class Starfield3D;
class FontAtlas;
class LineEffect;
enum class AppState;
// Forward declare StateManager so StateContext can hold a pointer without
// including the StateManager header here.
@ -58,6 +59,7 @@ struct StateContext {
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()> 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
StateManager* stateManager = nullptr;
};