Fade from main menu to gameplay
This commit is contained in:
@ -123,8 +123,11 @@ void GameRenderer::renderPlayingState(
|
|||||||
float logicalScale,
|
float logicalScale,
|
||||||
float winW,
|
float winW,
|
||||||
float winH,
|
float winH,
|
||||||
bool showExitConfirmPopup
|
bool showExitConfirmPopup,
|
||||||
|
int exitPopupSelectedButton,
|
||||||
|
bool suppressPauseVisuals
|
||||||
) {
|
) {
|
||||||
|
(void)exitPopupSelectedButton;
|
||||||
if (!game || !pixelFont) return;
|
if (!game || !pixelFont) return;
|
||||||
|
|
||||||
// Calculate actual content area (centered within the window)
|
// 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)
|
// Draw ghost piece (where current piece will land)
|
||||||
if (!game->isPaused()) {
|
if (allowActivePieceRender) {
|
||||||
Game::Piece ghostPiece = game->current();
|
Game::Piece ghostPiece = game->current();
|
||||||
// Find landing position
|
// Find landing position
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -270,7 +275,7 @@ void GameRenderer::renderPlayingState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw the falling piece
|
// Draw the falling piece
|
||||||
if (!game->isPaused()) {
|
if (allowActivePieceRender) {
|
||||||
drawPiece(renderer, blocksTex, game->current(), gridX, gridY, finalBlockSize, false);
|
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);
|
drawSmallPiece(renderer, blocksTex, static_cast<PieceType>(game->held().type), statsX + 60, statsY + statsH - 80, finalBlockSize * 0.6f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pause overlay
|
// Pause overlay (suppressed when requested, e.g., countdown)
|
||||||
if (game->isPaused() && !showExitConfirmPopup) {
|
if (!suppressPauseVisuals && game->isPaused() && !showExitConfirmPopup) {
|
||||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 180);
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 180);
|
||||||
SDL_FRect pauseOverlay{0, 0, logicalW, logicalH};
|
SDL_FRect pauseOverlay{0, 0, logicalW, logicalH};
|
||||||
SDL_RenderFillRect(renderer, &pauseOverlay);
|
SDL_RenderFillRect(renderer, &pauseOverlay);
|
||||||
|
|||||||
@ -26,7 +26,9 @@ public:
|
|||||||
float logicalScale,
|
float logicalScale,
|
||||||
float winW,
|
float winW,
|
||||||
float winH,
|
float winH,
|
||||||
bool showExitConfirmPopup
|
bool showExitConfirmPopup,
|
||||||
|
int exitPopupSelectedButton = 1,
|
||||||
|
bool suppressPauseVisuals = false
|
||||||
);
|
);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@ -124,7 +124,8 @@ void GameRenderer::renderPlayingState(
|
|||||||
float winW,
|
float winW,
|
||||||
float winH,
|
float winH,
|
||||||
bool showExitConfirmPopup,
|
bool showExitConfirmPopup,
|
||||||
int exitPopupSelectedButton
|
int exitPopupSelectedButton,
|
||||||
|
bool suppressPauseVisuals
|
||||||
) {
|
) {
|
||||||
if (!game || !pixelFont) return;
|
if (!game || !pixelFont) return;
|
||||||
|
|
||||||
@ -237,8 +238,10 @@ void GameRenderer::renderPlayingState(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool allowActivePieceRender = !game->isPaused() || suppressPauseVisuals;
|
||||||
|
|
||||||
// Draw ghost piece (where current piece will land)
|
// Draw ghost piece (where current piece will land)
|
||||||
if (!game->isPaused()) {
|
if (allowActivePieceRender) {
|
||||||
Game::Piece ghostPiece = game->current();
|
Game::Piece ghostPiece = game->current();
|
||||||
// Find landing position
|
// Find landing position
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -271,7 +274,7 @@ void GameRenderer::renderPlayingState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw the falling piece
|
// Draw the falling piece
|
||||||
if (!game->isPaused()) {
|
if (allowActivePieceRender) {
|
||||||
drawPiece(renderer, blocksTex, game->current(), gridX, gridY, finalBlockSize, false);
|
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);
|
drawSmallPiece(renderer, blocksTex, static_cast<PieceType>(game->held().type), statsX + 60, statsY + statsH - 80, finalBlockSize * 0.6f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pause overlay
|
// Pause overlay (skip when visuals are suppressed, e.g., countdown)
|
||||||
if (game->isPaused() && !showExitConfirmPopup) {
|
if (!suppressPauseVisuals && game->isPaused() && !showExitConfirmPopup) {
|
||||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 180);
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 180);
|
||||||
SDL_FRect pauseOverlay{0, 0, logicalW, logicalH};
|
SDL_FRect pauseOverlay{0, 0, logicalW, logicalH};
|
||||||
SDL_RenderFillRect(renderer, &pauseOverlay);
|
SDL_RenderFillRect(renderer, &pauseOverlay);
|
||||||
|
|||||||
@ -27,7 +27,8 @@ public:
|
|||||||
float winW,
|
float winW,
|
||||||
float winH,
|
float winH,
|
||||||
bool showExitConfirmPopup,
|
bool showExitConfirmPopup,
|
||||||
int exitPopupSelectedButton = 1 // 0=YES, 1=NO
|
int exitPopupSelectedButton = 1, // 0=YES, 1=NO
|
||||||
|
bool suppressPauseVisuals = false
|
||||||
);
|
);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
138
src/main.cpp
138
src/main.cpp
@ -627,6 +627,19 @@ int main(int, char **)
|
|||||||
int currentTrackLoading = 0;
|
int currentTrackLoading = 0;
|
||||||
int totalTracks = 0; // Will be set dynamically based on actual files
|
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
|
// Instantiate state manager
|
||||||
StateManager stateMgr(state);
|
StateManager stateMgr(state);
|
||||||
|
|
||||||
@ -666,6 +679,29 @@ int main(int, char **)
|
|||||||
running = false;
|
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
|
// 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);
|
||||||
@ -822,9 +858,7 @@ int main(int, char **)
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (pointInRect(buttonRects[0])) {
|
if (pointInRect(buttonRects[0])) {
|
||||||
game.reset(startLevelSelection);
|
startMenuPlayTransition();
|
||||||
state = AppState::Playing;
|
|
||||||
stateMgr.setState(state);
|
|
||||||
} else if (pointInRect(buttonRects[1])) {
|
} else if (pointInRect(buttonRects[1])) {
|
||||||
state = AppState::LevelSelector;
|
state = AppState::LevelSelector;
|
||||||
stateMgr.setState(state);
|
stateMgr.setState(state);
|
||||||
@ -1180,6 +1214,63 @@ int main(int, char **)
|
|||||||
break;
|
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 ---
|
// --- Render ---
|
||||||
SDL_SetRenderViewport(renderer, nullptr);
|
SDL_SetRenderViewport(renderer, nullptr);
|
||||||
SDL_SetRenderDrawColor(renderer, 12, 12, 16, 255);
|
SDL_SetRenderDrawColor(renderer, 12, 12, 16, 255);
|
||||||
@ -1387,7 +1478,8 @@ int main(int, char **)
|
|||||||
(float)winW,
|
(float)winW,
|
||||||
(float)winH,
|
(float)winH,
|
||||||
showExitConfirmPopup,
|
showExitConfirmPopup,
|
||||||
exitPopupSelectedButton
|
exitPopupSelectedButton,
|
||||||
|
(gameplayCountdownActive || menuPlayCountdownArmed)
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case AppState::GameOver:
|
case AppState::GameOver:
|
||||||
@ -1514,6 +1606,44 @@ int main(int, char **)
|
|||||||
break;
|
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_RenderPresent(renderer);
|
||||||
SDL_SetRenderScale(renderer, 1.f, 1.f);
|
SDL_SetRenderScale(renderer, 1.f, 1.f);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,6 +40,14 @@ void MenuState::onExit() {
|
|||||||
void MenuState::handleEvent(const SDL_Event& e) {
|
void MenuState::handleEvent(const SDL_Event& e) {
|
||||||
// Keyboard navigation for menu buttons
|
// Keyboard navigation for menu buttons
|
||||||
if (e.type == SDL_EVENT_KEY_DOWN && !e.key.repeat) {
|
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) {
|
auto setExitSelection = [&](int value) {
|
||||||
if (ctx.exitPopupSelectedButton) {
|
if (ctx.exitPopupSelectedButton) {
|
||||||
*ctx.exitPopupSelectedButton = value;
|
*ctx.exitPopupSelectedButton = value;
|
||||||
@ -115,7 +123,7 @@ void MenuState::handleEvent(const SDL_Event& e) {
|
|||||||
}
|
}
|
||||||
switch (selectedButton) {
|
switch (selectedButton) {
|
||||||
case 0:
|
case 0:
|
||||||
ctx.stateManager->setState(AppState::Playing);
|
triggerPlay();
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
ctx.stateManager->setState(AppState::LevelSelector);
|
ctx.stateManager->setState(AppState::LevelSelector);
|
||||||
|
|||||||
@ -55,6 +55,7 @@ struct StateContext {
|
|||||||
std::function<void(bool)> applyFullscreen; // Allows states to request fullscreen changes
|
std::function<void(bool)> applyFullscreen; // Allows states to request fullscreen changes
|
||||||
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
|
||||||
// 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;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user