added transition from levels

This commit is contained in:
2025-11-22 21:58:02 +01:00
parent 77a9237e25
commit 05423b4aeb
2 changed files with 211 additions and 174 deletions

View File

@ -106,6 +106,105 @@ static SDL_Texture* loadTextureFromImage(SDL_Renderer* renderer, const std::stri
return texture;
}
struct LevelBackgroundFader {
SDL_Texture* currentTex = nullptr;
SDL_Texture* nextTex = nullptr;
int currentLevel = -1;
int queuedLevel = -1;
float fadeElapsedMs = 0.0f;
float fadeDurationMs = 3500.0f;
};
static void destroyTexture(SDL_Texture*& tex) {
if (tex) {
SDL_DestroyTexture(tex);
tex = nullptr;
}
}
static bool queueLevelBackground(LevelBackgroundFader& fader, SDL_Renderer* renderer, int level) {
if (!renderer) {
return false;
}
level = std::clamp(level, 0, 32);
if (fader.currentLevel == level || fader.queuedLevel == level) {
return true;
}
char bgPath[256];
std::snprintf(bgPath, sizeof(bgPath), "assets/images/tetris_main_back_level%d.jpg", level);
SDL_Texture* newTexture = loadTextureFromImage(renderer, bgPath);
if (!newTexture) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to queue background for level %d: %s", level, bgPath);
return false;
}
destroyTexture(fader.nextTex);
fader.nextTex = newTexture;
fader.queuedLevel = level;
fader.fadeElapsedMs = 0.0f;
if (!fader.currentTex) {
fader.currentTex = fader.nextTex;
fader.currentLevel = fader.queuedLevel;
fader.nextTex = nullptr;
fader.queuedLevel = -1;
}
return true;
}
static void updateLevelBackgroundFade(LevelBackgroundFader& fader, float frameMs) {
if (!fader.currentTex || !fader.nextTex) {
return;
}
fader.fadeElapsedMs += frameMs;
if (fader.fadeElapsedMs >= fader.fadeDurationMs) {
destroyTexture(fader.currentTex);
fader.currentTex = fader.nextTex;
fader.currentLevel = fader.queuedLevel;
fader.nextTex = nullptr;
fader.queuedLevel = -1;
fader.fadeElapsedMs = 0.0f;
}
}
static void renderLevelBackgrounds(const LevelBackgroundFader& fader, SDL_Renderer* renderer, int winW, int winH) {
if (!renderer) {
return;
}
SDL_FRect fullRect{0.f, 0.f, static_cast<float>(winW), static_cast<float>(winH)};
if (fader.currentTex && fader.nextTex) {
const float duration = std::max(1.0f, fader.fadeDurationMs);
const float alpha = std::clamp(fader.fadeElapsedMs / duration, 0.0f, 1.0f);
SDL_SetTextureAlphaMod(fader.currentTex, Uint8((1.0f - alpha) * 255.0f));
SDL_RenderTexture(renderer, fader.currentTex, nullptr, &fullRect);
SDL_SetTextureAlphaMod(fader.currentTex, 255);
SDL_SetTextureAlphaMod(fader.nextTex, Uint8(alpha * 255.0f));
SDL_RenderTexture(renderer, fader.nextTex, nullptr, &fullRect);
SDL_SetTextureAlphaMod(fader.nextTex, 255);
} else if (fader.currentTex) {
SDL_RenderTexture(renderer, fader.currentTex, nullptr, &fullRect);
} else if (fader.nextTex) {
SDL_RenderTexture(renderer, fader.nextTex, nullptr, &fullRect);
}
}
static void resetLevelBackgrounds(LevelBackgroundFader& fader) {
destroyTexture(fader.currentTex);
destroyTexture(fader.nextTex);
fader.currentLevel = -1;
fader.queuedLevel = -1;
fader.fadeElapsedMs = 0.0f;
}
// Hover state for level popup ( -1 = none, 0..19 = hovered level )
// Now managed by LevelSelectorState
@ -501,12 +600,7 @@ int main(int, char **)
// States should render using `ctx.backgroundTex` rather than accessing globals.
// Level background caching system
SDL_Texture *levelBackgroundTex = nullptr;
SDL_Texture *nextLevelBackgroundTex = nullptr; // used during fade transitions
int cachedLevel = -1; // Track which level background is currently cached
float levelFadeAlpha = 0.0f; // 0..1 blend factor where 1 means next fully visible
const float LEVEL_FADE_DURATION = 3500.0f; // ms for fade transition (3.5s)
float levelFadeElapsed = 0.0f;
LevelBackgroundFader levelBackgrounds;
// Default start level selection: 0 (declare here so it's in scope for all handlers)
int startLevelSelection = 0;
@ -1180,10 +1274,7 @@ int main(int, char **)
}
// Advance level background fade if a next texture is queued
if (nextLevelBackgroundTex) {
levelFadeElapsed += float(frameMs);
levelFadeAlpha = std::min(1.0f, levelFadeElapsed / LEVEL_FADE_DURATION);
}
updateLevelBackgroundFade(levelBackgrounds, float(frameMs));
// Update intro animations
if (state == AppState::Menu) {
@ -1276,58 +1367,9 @@ int main(int, char **)
// Draw level-based background for gameplay, starfield for other states
if (state == AppState::Playing) {
// Use level-based background for gameplay with caching
int currentLevel = game.level();
int bgLevel = (currentLevel > 32) ? 32 : currentLevel; // Cap at level 32
// Only load new background if level changed
if (cachedLevel != bgLevel) {
// Load new level background into nextLevelBackgroundTex
if (nextLevelBackgroundTex) { SDL_DestroyTexture(nextLevelBackgroundTex); nextLevelBackgroundTex = nullptr; }
char bgPath[256];
snprintf(bgPath, sizeof(bgPath), "assets/images/tetris_main_back_level%d.jpg", bgLevel);
SDL_Texture* newLevelTex = loadTextureFromImage(renderer, bgPath);
if (newLevelTex) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Loaded background for level %d: %s", bgLevel, bgPath);
nextLevelBackgroundTex = newLevelTex;
// start fade transition
levelFadeAlpha = 0.0f;
levelFadeElapsed = 0.0f;
cachedLevel = bgLevel;
} else {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load background for level %d: %s", bgLevel, bgPath);
// don't change textures if file missing
cachedLevel = -1;
}
}
// Draw blended backgrounds if needed
if (levelBackgroundTex || nextLevelBackgroundTex) {
// Use actual window pixel size so backgrounds always cover full screen
SDL_FRect fullRect = { 0, 0, (float)winW, (float)winH };
// if fade in progress
if (nextLevelBackgroundTex && levelFadeAlpha < 1.0f && levelBackgroundTex) {
// draw current with inverse alpha
SDL_SetTextureAlphaMod(levelBackgroundTex, Uint8((1.0f - levelFadeAlpha) * 255));
SDL_RenderTexture(renderer, levelBackgroundTex, nullptr, &fullRect);
SDL_SetTextureAlphaMod(nextLevelBackgroundTex, Uint8(levelFadeAlpha * 255));
SDL_RenderTexture(renderer, nextLevelBackgroundTex, nullptr, &fullRect);
// reset mods
SDL_SetTextureAlphaMod(levelBackgroundTex, 255);
SDL_SetTextureAlphaMod(nextLevelBackgroundTex, 255);
}
else if (nextLevelBackgroundTex && (!levelBackgroundTex || levelFadeAlpha >= 1.0f)) {
// finalise swap
if (levelBackgroundTex) { SDL_DestroyTexture(levelBackgroundTex); }
levelBackgroundTex = nextLevelBackgroundTex;
nextLevelBackgroundTex = nullptr;
levelFadeAlpha = 0.0f;
SDL_RenderTexture(renderer, levelBackgroundTex, nullptr, &fullRect);
}
else if (levelBackgroundTex) {
SDL_RenderTexture(renderer, levelBackgroundTex, nullptr, &fullRect);
}
}
int bgLevel = std::clamp(game.level(), 0, 32);
queueLevelBackground(levelBackgrounds, renderer, bgLevel);
renderLevelBackgrounds(levelBackgrounds, renderer, winW, winH);
} else if (state == AppState::Loading) {
// Use 3D starfield for loading screen (full screen)
starfield3D.draw(renderer);
@ -1648,10 +1690,7 @@ int main(int, char **)
SDL_DestroyTexture(logoTex);
if (backgroundTex)
SDL_DestroyTexture(backgroundTex);
if (nextLevelBackgroundTex)
SDL_DestroyTexture(nextLevelBackgroundTex);
if (levelBackgroundTex)
SDL_DestroyTexture(levelBackgroundTex);
resetLevelBackgrounds(levelBackgrounds);
if (blocksTex)
SDL_DestroyTexture(blocksTex);
if (logoSmallTex)