Added intro video

This commit is contained in:
2025-12-25 09:38:06 +01:00
parent d28feb3276
commit 14cb96345c
16 changed files with 859 additions and 15 deletions

View File

@ -60,6 +60,7 @@
#include "states/MenuState.h"
#include "states/OptionsState.h"
#include "states/PlayingState.h"
#include "states/VideoState.h"
#include "states/State.h"
#include "ui/BottomMenu.h"
@ -310,11 +311,21 @@ struct TetrisApp::Impl {
std::unique_ptr<StateManager> stateMgr;
StateContext ctx{};
std::unique_ptr<LoadingState> loadingState;
std::unique_ptr<VideoState> videoState;
std::unique_ptr<MenuState> menuState;
std::unique_ptr<OptionsState> optionsState;
std::unique_ptr<LevelSelectorState> levelSelectorState;
std::unique_ptr<PlayingState> playingState;
// Startup fade-in overlay (used after intro video).
bool startupFadeActive = false;
float startupFadeAlpha = 0.0f; // 0..1 black overlay strength
double startupFadeClockMs = 0.0;
static constexpr double STARTUP_FADE_IN_MS = 650.0;
// Intro video path.
std::string introVideoPath = "assets/videos/spacetris_intro.mp4";
int init();
void runLoop();
void shutdown();
@ -671,7 +682,11 @@ int TetrisApp::Impl::init()
};
ctx.requestFadeTransition = requestStateFade;
ctx.startupFadeActive = &startupFadeActive;
ctx.startupFadeAlpha = &startupFadeAlpha;
loadingState = std::make_unique<LoadingState>(ctx);
videoState = std::make_unique<VideoState>(ctx);
menuState = std::make_unique<MenuState>(ctx);
optionsState = std::make_unique<OptionsState>(ctx);
levelSelectorState = std::make_unique<LevelSelectorState>(ctx);
@ -681,6 +696,20 @@ int TetrisApp::Impl::init()
stateMgr->registerOnEnter(AppState::Loading, [this](){ loadingState->onEnter(); loadingStarted.store(true); });
stateMgr->registerOnExit(AppState::Loading, [this](){ loadingState->onExit(); });
stateMgr->registerHandler(AppState::Video, [this](const SDL_Event& e){ if (videoState) videoState->handleEvent(e); });
stateMgr->registerOnEnter(AppState::Video, [this]() {
if (!videoState) return;
const bool ok = videoState->begin(renderer, introVideoPath);
if (!ok) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Intro video unavailable; skipping to Menu");
state = AppState::Menu;
stateMgr->setState(state);
return;
}
videoState->onEnter();
});
stateMgr->registerOnExit(AppState::Video, [this](){ if (videoState) videoState->onExit(); });
stateMgr->registerHandler(AppState::Menu, [this](const SDL_Event& e){ menuState->handleEvent(e); });
stateMgr->registerOnEnter(AppState::Menu, [this](){ menuState->onEnter(); });
stateMgr->registerOnExit(AppState::Menu, [this](){ menuState->onExit(); });
@ -832,7 +861,7 @@ void TetrisApp::Impl::runLoop()
Settings::instance().setSoundEnabled(SoundEffectManager::instance().isEnabled());
}
const bool helpToggleKey =
(e.key.scancode == SDL_SCANCODE_F1 && state != AppState::Loading && state != AppState::Menu);
(e.key.scancode == SDL_SCANCODE_F1 && state != AppState::Loading && state != AppState::Video && state != AppState::Menu);
if (helpToggleKey)
{
showHelpOverlay = !showHelpOverlay;
@ -1168,6 +1197,21 @@ void TetrisApp::Impl::runLoop()
if (frameMs > 100.0) frameMs = 100.0;
gameplayBackgroundClockMs += frameMs;
if (startupFadeActive) {
if (startupFadeClockMs <= 0.0) {
startupFadeClockMs = STARTUP_FADE_IN_MS;
startupFadeAlpha = 1.0f;
}
startupFadeClockMs -= frameMs;
if (startupFadeClockMs <= 0.0) {
startupFadeClockMs = 0.0;
startupFadeAlpha = 0.0f;
startupFadeActive = false;
} else {
startupFadeAlpha = float(std::clamp(startupFadeClockMs / STARTUP_FADE_IN_MS, 0.0, 1.0));
}
}
auto clearChallengeStory = [this]() {
challengeStoryText.clear();
challengeStoryLevel = 0;
@ -1810,7 +1854,15 @@ void TetrisApp::Impl::runLoop()
if (totalTasks > 0) {
loadingProgress = std::min(1.0, double(doneTasks) / double(totalTasks));
if (loadingProgress >= 1.0 && musicLoaded) {
state = AppState::Menu;
startupFadeActive = false;
startupFadeAlpha = 0.0f;
startupFadeClockMs = 0.0;
if (std::filesystem::exists(introVideoPath)) {
state = AppState::Video;
} else {
state = AppState::Menu;
}
stateMgr->setState(state);
}
} else {
@ -1838,7 +1890,15 @@ void TetrisApp::Impl::runLoop()
if (loadingProgress > 0.99) loadingProgress = 1.0;
if (!musicLoaded && timeProgress >= 0.1) loadingProgress = 1.0;
if (loadingProgress >= 1.0 && musicLoaded) {
state = AppState::Menu;
startupFadeActive = false;
startupFadeAlpha = 0.0f;
startupFadeClockMs = 0.0;
if (std::filesystem::exists(introVideoPath)) {
state = AppState::Video;
} else {
state = AppState::Menu;
}
stateMgr->setState(state);
}
}
@ -1905,6 +1965,9 @@ void TetrisApp::Impl::runLoop()
case AppState::Loading:
loadingState->update(frameMs);
break;
case AppState::Video:
if (videoState) videoState->update(frameMs);
break;
case AppState::Menu:
menuState->update(frameMs);
break;
@ -2207,6 +2270,11 @@ void TetrisApp::Impl::runLoop()
}
}
break;
case AppState::Video:
if (videoState) {
videoState->render(renderer, logicalScale, logicalVP);
}
break;
case AppState::Menu:
if (!mainScreenTex) {
mainScreenTex = assetLoader.getTexture(Assets::MAIN_SCREEN);
@ -2600,6 +2668,17 @@ void TetrisApp::Impl::runLoop()
HelpOverlay::Render(renderer, pixelFont, LOGICAL_W, LOGICAL_H, contentOffsetX, contentOffsetY);
}
if (startupFadeActive && startupFadeAlpha > 0.0f) {
SDL_SetRenderViewport(renderer, nullptr);
SDL_SetRenderScale(renderer, 1.0f, 1.0f);
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
const Uint8 a = (Uint8)std::clamp((int)std::lround(startupFadeAlpha * 255.0f), 0, 255);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, a);
SDL_FRect full{0.f, 0.f, (float)winW, (float)winH};
SDL_RenderFillRect(renderer, &full);
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
}
SDL_RenderPresent(renderer);
SDL_SetRenderScale(renderer, 1.f, 1.f);
}