Fade from main menu to gameplay
This commit is contained in:
138
src/main.cpp
138
src/main.cpp
@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user