added transition from levels
This commit is contained in:
171
src/main.cpp
171
src/main.cpp
@ -106,6 +106,105 @@ static SDL_Texture* loadTextureFromImage(SDL_Renderer* renderer, const std::stri
|
|||||||
return texture;
|
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 )
|
// Hover state for level popup ( -1 = none, 0..19 = hovered level )
|
||||||
// Now managed by LevelSelectorState
|
// Now managed by LevelSelectorState
|
||||||
|
|
||||||
@ -501,12 +600,7 @@ int main(int, char **)
|
|||||||
// States should render using `ctx.backgroundTex` rather than accessing globals.
|
// States should render using `ctx.backgroundTex` rather than accessing globals.
|
||||||
|
|
||||||
// Level background caching system
|
// Level background caching system
|
||||||
SDL_Texture *levelBackgroundTex = nullptr;
|
LevelBackgroundFader levelBackgrounds;
|
||||||
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;
|
|
||||||
|
|
||||||
// Default start level selection: 0 (declare here so it's in scope for all handlers)
|
// Default start level selection: 0 (declare here so it's in scope for all handlers)
|
||||||
int startLevelSelection = 0;
|
int startLevelSelection = 0;
|
||||||
@ -1180,10 +1274,7 @@ int main(int, char **)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Advance level background fade if a next texture is queued
|
// Advance level background fade if a next texture is queued
|
||||||
if (nextLevelBackgroundTex) {
|
updateLevelBackgroundFade(levelBackgrounds, float(frameMs));
|
||||||
levelFadeElapsed += float(frameMs);
|
|
||||||
levelFadeAlpha = std::min(1.0f, levelFadeElapsed / LEVEL_FADE_DURATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update intro animations
|
// Update intro animations
|
||||||
if (state == AppState::Menu) {
|
if (state == AppState::Menu) {
|
||||||
@ -1276,58 +1367,9 @@ int main(int, char **)
|
|||||||
|
|
||||||
// Draw level-based background for gameplay, starfield for other states
|
// Draw level-based background for gameplay, starfield for other states
|
||||||
if (state == AppState::Playing) {
|
if (state == AppState::Playing) {
|
||||||
// Use level-based background for gameplay with caching
|
int bgLevel = std::clamp(game.level(), 0, 32);
|
||||||
int currentLevel = game.level();
|
queueLevelBackground(levelBackgrounds, renderer, bgLevel);
|
||||||
int bgLevel = (currentLevel > 32) ? 32 : currentLevel; // Cap at level 32
|
renderLevelBackgrounds(levelBackgrounds, renderer, winW, winH);
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (state == AppState::Loading) {
|
} else if (state == AppState::Loading) {
|
||||||
// Use 3D starfield for loading screen (full screen)
|
// Use 3D starfield for loading screen (full screen)
|
||||||
starfield3D.draw(renderer);
|
starfield3D.draw(renderer);
|
||||||
@ -1648,10 +1690,7 @@ int main(int, char **)
|
|||||||
SDL_DestroyTexture(logoTex);
|
SDL_DestroyTexture(logoTex);
|
||||||
if (backgroundTex)
|
if (backgroundTex)
|
||||||
SDL_DestroyTexture(backgroundTex);
|
SDL_DestroyTexture(backgroundTex);
|
||||||
if (nextLevelBackgroundTex)
|
resetLevelBackgrounds(levelBackgrounds);
|
||||||
SDL_DestroyTexture(nextLevelBackgroundTex);
|
|
||||||
if (levelBackgroundTex)
|
|
||||||
SDL_DestroyTexture(levelBackgroundTex);
|
|
||||||
if (blocksTex)
|
if (blocksTex)
|
||||||
SDL_DestroyTexture(blocksTex);
|
SDL_DestroyTexture(blocksTex);
|
||||||
if (logoSmallTex)
|
if (logoSmallTex)
|
||||||
|
|||||||
@ -105,6 +105,105 @@ static SDL_Texture* loadTextureFromImage(SDL_Renderer* renderer, const std::stri
|
|||||||
return texture;
|
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 )
|
// Hover state for level popup ( -1 = none, 0..19 = hovered level )
|
||||||
// Now managed by LevelSelectorState
|
// Now managed by LevelSelectorState
|
||||||
|
|
||||||
@ -495,12 +594,7 @@ int main(int, char **)
|
|||||||
// States should render using `ctx.backgroundTex` rather than accessing globals.
|
// States should render using `ctx.backgroundTex` rather than accessing globals.
|
||||||
|
|
||||||
// Level background caching system
|
// Level background caching system
|
||||||
SDL_Texture *levelBackgroundTex = nullptr;
|
LevelBackgroundFader levelBackgrounds;
|
||||||
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;
|
|
||||||
|
|
||||||
// Default start level selection: 0 (declare here so it's in scope for all handlers)
|
// Default start level selection: 0 (declare here so it's in scope for all handlers)
|
||||||
int startLevelSelection = 0;
|
int startLevelSelection = 0;
|
||||||
@ -815,7 +909,6 @@ int main(int, char **)
|
|||||||
exitPopupSelectedButton = 1;
|
exitPopupSelectedButton = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settings button (gear icon area - top right)
|
|
||||||
SDL_FRect settingsBtn{LOGICAL_W - 60, 10, 50, 30};
|
SDL_FRect settingsBtn{LOGICAL_W - 60, 10, 50, 30};
|
||||||
if (lx >= settingsBtn.x && lx <= settingsBtn.x + settingsBtn.w && ly >= settingsBtn.y && ly <= settingsBtn.y + settingsBtn.h)
|
if (lx >= settingsBtn.x && lx <= settingsBtn.x + settingsBtn.w && ly >= settingsBtn.y && ly <= settingsBtn.y + settingsBtn.h)
|
||||||
{
|
{
|
||||||
@ -830,48 +923,6 @@ int main(int, char **)
|
|||||||
stateMgr.setState(state);
|
stateMgr.setState(state);
|
||||||
}
|
}
|
||||||
else if (state == AppState::Playing && showExitConfirmPopup) {
|
else if (state == AppState::Playing && showExitConfirmPopup) {
|
||||||
// Convert mouse to logical coordinates and to content-local coords
|
|
||||||
float lx = (mx - logicalVP.x) / logicalScale;
|
|
||||||
float ly = (my - logicalVP.y) / logicalScale;
|
|
||||||
// Compute content offsets (same as in render path)
|
|
||||||
float contentW = LOGICAL_W * logicalScale;
|
|
||||||
float contentH = LOGICAL_H * logicalScale;
|
|
||||||
float contentOffsetX = (winW - contentW) * 0.5f / logicalScale;
|
|
||||||
float contentOffsetY = (winH - contentH) * 0.5f / logicalScale;
|
|
||||||
// Map to content-local logical coords (what drawing code uses)
|
|
||||||
float localX = lx - contentOffsetX;
|
|
||||||
float localY = ly - contentOffsetY;
|
|
||||||
|
|
||||||
// Popup rect in logical coordinates (content-local)
|
|
||||||
float popupW = 400, popupH = 200;
|
|
||||||
float popupX = (LOGICAL_W - popupW) / 2.0f;
|
|
||||||
float popupY = (LOGICAL_H - popupH) / 2.0f;
|
|
||||||
// Simple Yes/No buttons
|
|
||||||
float btnW = 120.0f, btnH = 40.0f;
|
|
||||||
float yesX = popupX + popupW * 0.25f - btnW / 2.0f;
|
|
||||||
float noX = popupX + popupW * 0.75f - btnW / 2.0f;
|
|
||||||
float btnY = popupY + popupH - btnH - 20.0f;
|
|
||||||
|
|
||||||
if (localX >= popupX && localX <= popupX + popupW && localY >= popupY && localY <= popupY + popupH) {
|
|
||||||
// Click inside popup - check buttons
|
|
||||||
if (localX >= yesX && localX <= yesX + btnW && localY >= btnY && localY <= btnY + btnH) {
|
|
||||||
// Yes -> go back to menu
|
|
||||||
showExitConfirmPopup = false;
|
|
||||||
game.reset(startLevelSelection);
|
|
||||||
state = AppState::Menu;
|
|
||||||
stateMgr.setState(state);
|
|
||||||
} else if (localX >= noX && localX <= noX + btnW && localY >= btnY && localY <= btnY + btnH) {
|
|
||||||
// No -> close popup and resume
|
|
||||||
showExitConfirmPopup = false;
|
|
||||||
game.setPaused(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Click outside popup: cancel
|
|
||||||
showExitConfirmPopup = false;
|
|
||||||
game.setPaused(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (state == AppState::Menu && showExitConfirmPopup) {
|
|
||||||
float contentW = LOGICAL_W * logicalScale;
|
float contentW = LOGICAL_W * logicalScale;
|
||||||
float contentH = LOGICAL_H * logicalScale;
|
float contentH = LOGICAL_H * logicalScale;
|
||||||
float contentOffsetX = (winW - contentW) * 0.5f / logicalScale;
|
float contentOffsetX = (winW - contentW) * 0.5f / logicalScale;
|
||||||
@ -1088,10 +1139,7 @@ int main(int, char **)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Advance level background fade if a next texture is queued
|
// Advance level background fade if a next texture is queued
|
||||||
if (nextLevelBackgroundTex) {
|
updateLevelBackgroundFade(levelBackgrounds, float(frameMs));
|
||||||
levelFadeElapsed += float(frameMs);
|
|
||||||
levelFadeAlpha = std::min(1.0f, levelFadeElapsed / LEVEL_FADE_DURATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update intro animations
|
// Update intro animations
|
||||||
if (state == AppState::Menu) {
|
if (state == AppState::Menu) {
|
||||||
@ -1124,56 +1172,9 @@ int main(int, char **)
|
|||||||
|
|
||||||
// Draw level-based background for gameplay, starfield for other states
|
// Draw level-based background for gameplay, starfield for other states
|
||||||
if (state == AppState::Playing) {
|
if (state == AppState::Playing) {
|
||||||
// Use level-based background for gameplay with caching
|
int bgLevel = std::clamp(game.level(), 0, 32);
|
||||||
int currentLevel = game.level();
|
queueLevelBackground(levelBackgrounds, renderer, bgLevel);
|
||||||
int bgLevel = (currentLevel > 32) ? 32 : currentLevel; // Cap at level 32
|
renderLevelBackgrounds(levelBackgrounds, renderer, winW, winH);
|
||||||
|
|
||||||
// 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) {
|
|
||||||
nextLevelBackgroundTex = newLevelTex;
|
|
||||||
// start fade transition
|
|
||||||
levelFadeAlpha = 0.0f;
|
|
||||||
levelFadeElapsed = 0.0f;
|
|
||||||
cachedLevel = bgLevel;
|
|
||||||
} else {
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (state == AppState::Loading) {
|
} else if (state == AppState::Loading) {
|
||||||
// Use 3D starfield for loading screen (full screen)
|
// Use 3D starfield for loading screen (full screen)
|
||||||
starfield3D.draw(renderer);
|
starfield3D.draw(renderer);
|
||||||
@ -1714,10 +1715,7 @@ int main(int, char **)
|
|||||||
SDL_DestroyTexture(logoTex);
|
SDL_DestroyTexture(logoTex);
|
||||||
if (backgroundTex)
|
if (backgroundTex)
|
||||||
SDL_DestroyTexture(backgroundTex);
|
SDL_DestroyTexture(backgroundTex);
|
||||||
if (nextLevelBackgroundTex)
|
resetLevelBackgrounds(levelBackgrounds);
|
||||||
SDL_DestroyTexture(nextLevelBackgroundTex);
|
|
||||||
if (levelBackgroundTex)
|
|
||||||
SDL_DestroyTexture(levelBackgroundTex);
|
|
||||||
if (blocksTex)
|
if (blocksTex)
|
||||||
SDL_DestroyTexture(blocksTex);
|
SDL_DestroyTexture(blocksTex);
|
||||||
if (logoSmallTex)
|
if (logoSmallTex)
|
||||||
|
|||||||
Reference in New Issue
Block a user