Fade from main menu to gameplay

This commit is contained in:
2025-11-22 20:18:00 +01:00
parent c0bee9296a
commit 3c3a85d6d4
7 changed files with 167 additions and 17 deletions

View File

@ -626,6 +626,19 @@ int main(int, char **)
bool musicLoaded = false;
int currentTrackLoading = 0;
int totalTracks = 0; // Will be set dynamically based on actual files
enum class MenuFadePhase { None, FadeOut, FadeIn };
MenuFadePhase menuFadePhase = MenuFadePhase::None;
double menuFadeClockMs = 0.0;
float menuFadeAlpha = 0.0f;
const double MENU_PLAY_FADE_DURATION_MS = 450.0;
AppState menuFadeTarget = AppState::Menu;
bool menuPlayCountdownArmed = false;
bool gameplayCountdownActive = false;
double gameplayCountdownElapsed = 0.0;
int gameplayCountdownIndex = 0;
const double GAMEPLAY_COUNTDOWN_STEP_MS = 600.0;
const std::array<const char*, 4> GAMEPLAY_COUNTDOWN_LABELS = { "3", "2", "1", "START" };
// Instantiate state manager
StateManager stateMgr(state);
@ -666,6 +679,29 @@ int main(int, char **)
running = false;
};
auto startMenuPlayTransition = [&]() {
if (!ctx.stateManager) {
return;
}
if (state != AppState::Menu) {
state = AppState::Playing;
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;
};
ctx.startPlayTransition = startMenuPlayTransition;
// Instantiate state objects
auto loadingState = std::make_unique<LoadingState>(ctx);
auto menuState = std::make_unique<MenuState>(ctx);
@ -822,9 +858,7 @@ int main(int, char **)
};
if (pointInRect(buttonRects[0])) {
game.reset(startLevelSelection);
state = AppState::Playing;
stateMgr.setState(state);
startMenuPlayTransition();
} else if (pointInRect(buttonRects[1])) {
state = AppState::LevelSelector;
stateMgr.setState(state);
@ -1180,6 +1214,63 @@ int main(int, char **)
break;
}
if (menuFadePhase == MenuFadePhase::FadeOut) {
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) {
state = menuFadeTarget;
stateMgr.setState(state);
menuPlayCountdownArmed = true;
gameplayCountdownActive = false;
gameplayCountdownIndex = 0;
gameplayCountdownElapsed = 0.0;
game.setPaused(true);
}
menuFadePhase = MenuFadePhase::FadeIn;
menuFadeClockMs = MENU_PLAY_FADE_DURATION_MS;
menuFadeAlpha = 1.0f;
}
} else if (menuFadePhase == MenuFadePhase::FadeIn) {
menuFadeClockMs -= frameMs;
menuFadeAlpha = std::max(0.0f, float(menuFadeClockMs / MENU_PLAY_FADE_DURATION_MS));
if (menuFadeClockMs <= 0.0) {
menuFadePhase = MenuFadePhase::None;
menuFadeClockMs = 0.0;
menuFadeAlpha = 0.0f;
}
}
if (menuFadePhase == MenuFadePhase::None && menuPlayCountdownArmed && !gameplayCountdownActive && state == AppState::Playing) {
gameplayCountdownActive = true;
menuPlayCountdownArmed = false;
gameplayCountdownElapsed = 0.0;
gameplayCountdownIndex = 0;
game.setPaused(true);
}
if (gameplayCountdownActive && state == AppState::Playing) {
gameplayCountdownElapsed += frameMs;
if (gameplayCountdownElapsed >= GAMEPLAY_COUNTDOWN_STEP_MS) {
gameplayCountdownElapsed -= GAMEPLAY_COUNTDOWN_STEP_MS;
++gameplayCountdownIndex;
if (gameplayCountdownIndex >= static_cast<int>(GAMEPLAY_COUNTDOWN_LABELS.size())) {
gameplayCountdownActive = false;
gameplayCountdownElapsed = 0.0;
gameplayCountdownIndex = 0;
game.setPaused(false);
}
}
}
if (state != AppState::Playing && gameplayCountdownActive) {
gameplayCountdownActive = false;
menuPlayCountdownArmed = false;
gameplayCountdownElapsed = 0.0;
gameplayCountdownIndex = 0;
game.setPaused(false);
}
// --- Render ---
SDL_SetRenderViewport(renderer, nullptr);
SDL_SetRenderDrawColor(renderer, 12, 12, 16, 255);
@ -1387,7 +1478,8 @@ int main(int, char **)
(float)winW,
(float)winH,
showExitConfirmPopup,
exitPopupSelectedButton
exitPopupSelectedButton,
(gameplayCountdownActive || menuPlayCountdownArmed)
);
break;
case AppState::GameOver:
@ -1514,6 +1606,44 @@ int main(int, char **)
break;
}
if (menuFadeAlpha > 0.0f) {
SDL_SetRenderViewport(renderer, nullptr);
SDL_SetRenderScale(renderer, 1.f, 1.f);
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
Uint8 alpha = Uint8(std::clamp(menuFadeAlpha, 0.0f, 1.0f) * 255.0f);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, alpha);
SDL_FRect fadeRect{0.f, 0.f, (float)winW, (float)winH};
SDL_RenderFillRect(renderer, &fadeRect);
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
SDL_SetRenderViewport(renderer, &logicalVP);
SDL_SetRenderScale(renderer, logicalScale, logicalScale);
}
if (gameplayCountdownActive && state == AppState::Playing) {
SDL_SetRenderViewport(renderer, nullptr);
SDL_SetRenderScale(renderer, 1.f, 1.f);
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 160);
SDL_FRect dimRect{0.f, 0.f, (float)winW, (float)winH};
SDL_RenderFillRect(renderer, &dimRect);
SDL_SetRenderViewport(renderer, &logicalVP);
SDL_SetRenderScale(renderer, logicalScale, logicalScale);
int cappedIndex = std::min(gameplayCountdownIndex, static_cast<int>(GAMEPLAY_COUNTDOWN_LABELS.size()) - 1);
const char* label = GAMEPLAY_COUNTDOWN_LABELS[cappedIndex];
bool isFinalCue = (cappedIndex == static_cast<int>(GAMEPLAY_COUNTDOWN_LABELS.size()) - 1);
float textScale = isFinalCue ? 2.6f : 3.6f;
int textW = 0, textH = 0;
pixelFont.measure(label, textScale, textW, textH);
float textX = (LOGICAL_W - static_cast<float>(textW)) * 0.5f;
float textY = (LOGICAL_H - static_cast<float>(textH)) * 0.5f;
SDL_Color textColor = isFinalCue ? SDL_Color{255, 230, 90, 255} : SDL_Color{255, 255, 255, 255};
pixelFont.draw(renderer, textX, textY, label, textScale, textColor);
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
}
SDL_RenderPresent(renderer);
SDL_SetRenderScale(renderer, 1.f, 1.f);
}