Fade from main menu to gameplay
This commit is contained in:
@ -123,8 +123,11 @@ void GameRenderer::renderPlayingState(
|
||||
float logicalScale,
|
||||
float winW,
|
||||
float winH,
|
||||
bool showExitConfirmPopup
|
||||
bool showExitConfirmPopup,
|
||||
int exitPopupSelectedButton,
|
||||
bool suppressPauseVisuals
|
||||
) {
|
||||
(void)exitPopupSelectedButton;
|
||||
if (!game || !pixelFont) return;
|
||||
|
||||
// Calculate actual content area (centered within the window)
|
||||
@ -236,8 +239,10 @@ void GameRenderer::renderPlayingState(
|
||||
}
|
||||
}
|
||||
|
||||
bool allowActivePieceRender = !game->isPaused() || suppressPauseVisuals;
|
||||
|
||||
// Draw ghost piece (where current piece will land)
|
||||
if (!game->isPaused()) {
|
||||
if (allowActivePieceRender) {
|
||||
Game::Piece ghostPiece = game->current();
|
||||
// Find landing position
|
||||
while (true) {
|
||||
@ -270,7 +275,7 @@ void GameRenderer::renderPlayingState(
|
||||
}
|
||||
|
||||
// Draw the falling piece
|
||||
if (!game->isPaused()) {
|
||||
if (allowActivePieceRender) {
|
||||
drawPiece(renderer, blocksTex, game->current(), gridX, gridY, finalBlockSize, false);
|
||||
}
|
||||
|
||||
@ -412,8 +417,8 @@ void GameRenderer::renderPlayingState(
|
||||
drawSmallPiece(renderer, blocksTex, static_cast<PieceType>(game->held().type), statsX + 60, statsY + statsH - 80, finalBlockSize * 0.6f);
|
||||
}
|
||||
|
||||
// Pause overlay
|
||||
if (game->isPaused() && !showExitConfirmPopup) {
|
||||
// Pause overlay (suppressed when requested, e.g., countdown)
|
||||
if (!suppressPauseVisuals && game->isPaused() && !showExitConfirmPopup) {
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 180);
|
||||
SDL_FRect pauseOverlay{0, 0, logicalW, logicalH};
|
||||
SDL_RenderFillRect(renderer, &pauseOverlay);
|
||||
|
||||
@ -26,7 +26,9 @@ public:
|
||||
float logicalScale,
|
||||
float winW,
|
||||
float winH,
|
||||
bool showExitConfirmPopup
|
||||
bool showExitConfirmPopup,
|
||||
int exitPopupSelectedButton = 1,
|
||||
bool suppressPauseVisuals = false
|
||||
);
|
||||
|
||||
private:
|
||||
|
||||
@ -124,7 +124,8 @@ void GameRenderer::renderPlayingState(
|
||||
float winW,
|
||||
float winH,
|
||||
bool showExitConfirmPopup,
|
||||
int exitPopupSelectedButton
|
||||
int exitPopupSelectedButton,
|
||||
bool suppressPauseVisuals
|
||||
) {
|
||||
if (!game || !pixelFont) return;
|
||||
|
||||
@ -237,8 +238,10 @@ void GameRenderer::renderPlayingState(
|
||||
}
|
||||
}
|
||||
|
||||
bool allowActivePieceRender = !game->isPaused() || suppressPauseVisuals;
|
||||
|
||||
// Draw ghost piece (where current piece will land)
|
||||
if (!game->isPaused()) {
|
||||
if (allowActivePieceRender) {
|
||||
Game::Piece ghostPiece = game->current();
|
||||
// Find landing position
|
||||
while (true) {
|
||||
@ -271,7 +274,7 @@ void GameRenderer::renderPlayingState(
|
||||
}
|
||||
|
||||
// Draw the falling piece
|
||||
if (!game->isPaused()) {
|
||||
if (allowActivePieceRender) {
|
||||
drawPiece(renderer, blocksTex, game->current(), gridX, gridY, finalBlockSize, false);
|
||||
}
|
||||
|
||||
@ -413,8 +416,8 @@ void GameRenderer::renderPlayingState(
|
||||
drawSmallPiece(renderer, blocksTex, static_cast<PieceType>(game->held().type), statsX + 60, statsY + statsH - 80, finalBlockSize * 0.6f);
|
||||
}
|
||||
|
||||
// Pause overlay
|
||||
if (game->isPaused() && !showExitConfirmPopup) {
|
||||
// Pause overlay (skip when visuals are suppressed, e.g., countdown)
|
||||
if (!suppressPauseVisuals && game->isPaused() && !showExitConfirmPopup) {
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 180);
|
||||
SDL_FRect pauseOverlay{0, 0, logicalW, logicalH};
|
||||
SDL_RenderFillRect(renderer, &pauseOverlay);
|
||||
|
||||
@ -27,7 +27,8 @@ public:
|
||||
float winW,
|
||||
float winH,
|
||||
bool showExitConfirmPopup,
|
||||
int exitPopupSelectedButton = 1 // 0=YES, 1=NO
|
||||
int exitPopupSelectedButton = 1, // 0=YES, 1=NO
|
||||
bool suppressPauseVisuals = false
|
||||
);
|
||||
|
||||
private:
|
||||
|
||||
138
src/main.cpp
138
src/main.cpp
@ -627,6 +627,19 @@ int main(int, char **)
|
||||
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);
|
||||
}
|
||||
|
||||
@ -40,6 +40,14 @@ void MenuState::onExit() {
|
||||
void MenuState::handleEvent(const SDL_Event& e) {
|
||||
// Keyboard navigation for menu buttons
|
||||
if (e.type == SDL_EVENT_KEY_DOWN && !e.key.repeat) {
|
||||
auto triggerPlay = [&]() {
|
||||
if (ctx.startPlayTransition) {
|
||||
ctx.startPlayTransition();
|
||||
} else if (ctx.stateManager) {
|
||||
ctx.stateManager->setState(AppState::Playing);
|
||||
}
|
||||
};
|
||||
|
||||
auto setExitSelection = [&](int value) {
|
||||
if (ctx.exitPopupSelectedButton) {
|
||||
*ctx.exitPopupSelectedButton = value;
|
||||
@ -115,7 +123,7 @@ void MenuState::handleEvent(const SDL_Event& e) {
|
||||
}
|
||||
switch (selectedButton) {
|
||||
case 0:
|
||||
ctx.stateManager->setState(AppState::Playing);
|
||||
triggerPlay();
|
||||
break;
|
||||
case 1:
|
||||
ctx.stateManager->setState(AppState::LevelSelector);
|
||||
|
||||
@ -55,6 +55,7 @@ struct StateContext {
|
||||
std::function<void(bool)> applyFullscreen; // Allows states to request fullscreen changes
|
||||
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
|
||||
// Pointer to the application's StateManager so states can request transitions
|
||||
StateManager* stateManager = nullptr;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user