Compare commits
10 Commits
b44de25113
...
1355ce49fe
| Author | SHA1 | Date | |
|---|---|---|---|
| 1355ce49fe | |||
| 8a549d14dc | |||
| 12110bd8b4 | |||
| f086ed3021 | |||
| f24d484496 | |||
| 26b4454eea | |||
| b531bbc798 | |||
| cb8293175b | |||
| fff14fe3e1 | |||
| ffdb67ce9b |
BIN
assets/fonts/Exo2.ttf
Normal file
BIN
assets/fonts/Orbitron.ttf
Normal file
BIN
assets/images/levels/level0.jpg
Normal file
|
After Width: | Height: | Size: 257 KiB |
BIN
assets/images/levels/level1.jpg
Normal file
|
After Width: | Height: | Size: 226 KiB |
BIN
assets/images/levels/level10.jpg
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
assets/images/levels/level11.jpg
Normal file
|
After Width: | Height: | Size: 204 KiB |
BIN
assets/images/levels/level12.jpg
Normal file
|
After Width: | Height: | Size: 157 KiB |
BIN
assets/images/levels/level13.jpg
Normal file
|
After Width: | Height: | Size: 333 KiB |
BIN
assets/images/levels/level14.jpg
Normal file
|
After Width: | Height: | Size: 265 KiB |
BIN
assets/images/levels/level15.jpg
Normal file
|
After Width: | Height: | Size: 298 KiB |
BIN
assets/images/levels/level16.jpg
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
assets/images/levels/level17.jpg
Normal file
|
After Width: | Height: | Size: 276 KiB |
BIN
assets/images/levels/level18.jpg
Normal file
|
After Width: | Height: | Size: 223 KiB |
BIN
assets/images/levels/level19.jpg
Normal file
|
After Width: | Height: | Size: 281 KiB |
BIN
assets/images/levels/level2.jpg
Normal file
|
After Width: | Height: | Size: 450 KiB |
BIN
assets/images/levels/level20.jpg
Normal file
|
After Width: | Height: | Size: 315 KiB |
BIN
assets/images/levels/level21.jpg
Normal file
|
After Width: | Height: | Size: 322 KiB |
BIN
assets/images/levels/level22.jpg
Normal file
|
After Width: | Height: | Size: 323 KiB |
BIN
assets/images/levels/level23.jpg
Normal file
|
After Width: | Height: | Size: 242 KiB |
BIN
assets/images/levels/level24.jpg
Normal file
|
After Width: | Height: | Size: 349 KiB |
BIN
assets/images/levels/level25.jpg
Normal file
|
After Width: | Height: | Size: 387 KiB |
BIN
assets/images/levels/level26.jpg
Normal file
|
After Width: | Height: | Size: 226 KiB |
BIN
assets/images/levels/level27.jpg
Normal file
|
After Width: | Height: | Size: 203 KiB |
BIN
assets/images/levels/level28.jpg
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
assets/images/levels/level29.jpg
Normal file
|
After Width: | Height: | Size: 297 KiB |
BIN
assets/images/levels/level3.jpg
Normal file
|
After Width: | Height: | Size: 328 KiB |
BIN
assets/images/levels/level30.jpg
Normal file
|
After Width: | Height: | Size: 245 KiB |
BIN
assets/images/levels/level31.jpg
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
assets/images/levels/level32.jpg
Normal file
|
After Width: | Height: | Size: 295 KiB |
BIN
assets/images/levels/level4.jpg
Normal file
|
After Width: | Height: | Size: 145 KiB |
BIN
assets/images/levels/level5.jpg
Normal file
|
After Width: | Height: | Size: 199 KiB |
BIN
assets/images/levels/level6.jpg
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
assets/images/levels/level7.jpg
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
assets/images/levels/level8.jpg
Normal file
|
After Width: | Height: | Size: 335 KiB |
BIN
assets/images/levels/level9.jpg
Normal file
|
After Width: | Height: | Size: 420 KiB |
|
Before Width: | Height: | Size: 930 KiB |
|
Before Width: | Height: | Size: 195 KiB |
|
Before Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 5.9 MiB |
BIN
assets/images/main_screen.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 2.1 MiB |
|
Before Width: | Height: | Size: 2.2 MiB |
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 756 KiB |
|
Before Width: | Height: | Size: 1007 KiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 957 KiB |
|
Before Width: | Height: | Size: 805 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 947 KiB |
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 825 KiB |
|
Before Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 970 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 2.3 MiB |
|
Before Width: | Height: | Size: 1007 KiB |
|
Before Width: | Height: | Size: 638 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 828 KiB |
|
Before Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1010 KiB |
|
Before Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 995 KiB |
@ -6,7 +6,7 @@ Fullscreen=1
|
|||||||
|
|
||||||
[Audio]
|
[Audio]
|
||||||
Music=1
|
Music=1
|
||||||
Sound=0
|
Sound=1
|
||||||
|
|
||||||
[Gameplay]
|
[Gameplay]
|
||||||
SmoothScroll=1
|
SmoothScroll=1
|
||||||
|
|||||||
@ -77,13 +77,28 @@ void SpaceWarp::setAutoPilotEnabled(bool enabled) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SpaceWarp::scheduleNewAutoTarget() {
|
void SpaceWarp::scheduleNewAutoTarget() {
|
||||||
motionTarget.forwardScale = randomRange(0.82f, 1.28f);
|
// Autopilot behavior:
|
||||||
if (randomRange(0.0f, 1.0f) < 0.12f) {
|
// - 90% of the time: gentle forward flight with small lateral/vertical drift
|
||||||
motionTarget.forwardScale = -randomRange(0.35f, 0.85f);
|
// - 10% of the time: short lateral "bank" burst (stronger lateral speed) for a while
|
||||||
|
float choice = randomRange(0.0f, 1.0f);
|
||||||
|
if (choice < 0.90f) {
|
||||||
|
// Normal forward flight
|
||||||
|
motionTarget.forwardScale = randomRange(0.95f, 1.12f);
|
||||||
|
motionTarget.lateralSpeed = randomRange(-0.18f, 0.18f);
|
||||||
|
motionTarget.verticalSpeed = randomRange(-0.12f, 0.12f);
|
||||||
|
// Longer interval between aggressive maneuvers
|
||||||
|
autoTimer = randomRange(autoMinInterval, autoMaxInterval);
|
||||||
|
} else {
|
||||||
|
// Occasional lateral bank burst
|
||||||
|
motionTarget.forwardScale = randomRange(0.90f, 1.10f);
|
||||||
|
// Pick left or right burst
|
||||||
|
float dir = (randomRange(0.0f, 1.0f) < 0.5f) ? -1.0f : 1.0f;
|
||||||
|
motionTarget.lateralSpeed = dir * randomRange(0.70f, 1.35f);
|
||||||
|
// Allow modest vertical bias during a bank
|
||||||
|
motionTarget.verticalSpeed = randomRange(-0.35f, 0.35f);
|
||||||
|
// Shorter duration for the burst so it feels like a brief maneuver
|
||||||
|
autoTimer = randomRange(1.0f, 3.0f);
|
||||||
}
|
}
|
||||||
motionTarget.lateralSpeed = randomRange(-1.35f, 1.35f);
|
|
||||||
motionTarget.verticalSpeed = randomRange(-0.75f, 0.75f);
|
|
||||||
autoTimer = randomRange(autoMinInterval, autoMaxInterval);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpaceWarp::spawnComet() {
|
void SpaceWarp::spawnComet() {
|
||||||
@ -105,10 +120,22 @@ void SpaceWarp::spawnComet() {
|
|||||||
float shade = randomRange(0.85f, 1.0f);
|
float shade = randomRange(0.85f, 1.0f);
|
||||||
Uint8 c = static_cast<Uint8>(std::clamp(220.0f + shade * 35.0f, 0.0f, 255.0f));
|
Uint8 c = static_cast<Uint8>(std::clamp(220.0f + shade * 35.0f, 0.0f, 255.0f));
|
||||||
comet.color = SDL_Color{c, Uint8(std::min(255.0f, c * 0.95f)), 255, 255};
|
comet.color = SDL_Color{c, Uint8(std::min(255.0f, c * 0.95f)), 255, 255};
|
||||||
comet.prevScreenX = centerX;
|
// Initialize screen positions based on projection so the comet is not stuck at center
|
||||||
comet.prevScreenY = centerY;
|
float sx = 0.0f, sy = 0.0f;
|
||||||
comet.screenX = centerX;
|
if (projectPoint(comet.x, comet.y, comet.z, sx, sy)) {
|
||||||
comet.screenY = centerY;
|
comet.screenX = sx;
|
||||||
|
comet.screenY = sy;
|
||||||
|
// Place prev slightly behind the head so the first frame shows motion/trail
|
||||||
|
float jitter = std::max(4.0f, comet.trailLength * 0.08f);
|
||||||
|
float ang = randomRange(0.0f, 6.28318530718f);
|
||||||
|
comet.prevScreenX = comet.screenX - std::cos(ang) * jitter;
|
||||||
|
comet.prevScreenY = comet.screenY - std::sin(ang) * jitter;
|
||||||
|
} else {
|
||||||
|
comet.prevScreenX = centerX;
|
||||||
|
comet.prevScreenY = centerY;
|
||||||
|
comet.screenX = centerX;
|
||||||
|
comet.screenY = centerY;
|
||||||
|
}
|
||||||
comets.push_back(comet);
|
comets.push_back(comet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,10 +162,22 @@ void SpaceWarp::respawn(WarpStar& star, bool randomDepth) {
|
|||||||
static constexpr Uint8 GRAY_SHADES[] = {160, 180, 200, 220, 240};
|
static constexpr Uint8 GRAY_SHADES[] = {160, 180, 200, 220, 240};
|
||||||
int idx = randomIntInclusive(rng, 0, int(std::size(GRAY_SHADES)) - 1);
|
int idx = randomIntInclusive(rng, 0, int(std::size(GRAY_SHADES)) - 1);
|
||||||
star.baseShade = GRAY_SHADES[idx];
|
star.baseShade = GRAY_SHADES[idx];
|
||||||
star.prevScreenX = centerX;
|
// Compute initial projected screen position so newly spawned stars aren't frozen at center
|
||||||
star.prevScreenY = centerY;
|
float sx = 0.0f, sy = 0.0f;
|
||||||
star.screenX = centerX;
|
if (projectPoint(star.x, star.y, star.z, sx, sy)) {
|
||||||
star.screenY = centerY;
|
star.screenX = sx;
|
||||||
|
star.screenY = sy;
|
||||||
|
// give a small previous offset so trails and motion are visible immediately
|
||||||
|
float jitter = std::max(1.0f, settings.maxTrailLength * 0.06f);
|
||||||
|
float ang = randomRange(0.0f, 6.28318530718f);
|
||||||
|
star.prevScreenX = star.screenX - std::cos(ang) * jitter;
|
||||||
|
star.prevScreenY = star.screenY - std::sin(ang) * jitter;
|
||||||
|
} else {
|
||||||
|
star.prevScreenX = centerX;
|
||||||
|
star.prevScreenY = centerY;
|
||||||
|
star.screenX = centerX;
|
||||||
|
star.screenY = centerY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SpaceWarp::project(const WarpStar& star, float& outX, float& outY) const {
|
bool SpaceWarp::project(const WarpStar& star, float& outX, float& outY) const {
|
||||||
|
|||||||
@ -53,6 +53,16 @@ void UIRenderer::drawButton(SDL_Renderer* renderer, FontAtlas* font, float cx, f
|
|||||||
bgColor.a};
|
bgColor.a};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Neon glow aura around the button to increase visibility (subtle)
|
||||||
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||||
|
for (int gi = 0; gi < 3; ++gi) {
|
||||||
|
float grow = 6.0f + gi * 3.0f;
|
||||||
|
Uint8 glowA = static_cast<Uint8>(std::max(0, (int)borderColor.a / (3 - gi)));
|
||||||
|
SDL_SetRenderDrawColor(renderer, borderColor.r, borderColor.g, borderColor.b, glowA);
|
||||||
|
SDL_FRect glowRect{x - grow, y - grow, w + grow * 2.0f, h + grow * 2.0f};
|
||||||
|
SDL_RenderRect(renderer, &glowRect);
|
||||||
|
}
|
||||||
|
|
||||||
// Draw button background with border
|
// Draw button background with border
|
||||||
SDL_SetRenderDrawColor(renderer, borderColor.r, borderColor.g, borderColor.b, borderColor.a);
|
SDL_SetRenderDrawColor(renderer, borderColor.r, borderColor.g, borderColor.b, borderColor.a);
|
||||||
SDL_FRect borderRect{x - 2, y - 2, w + 4, h + 4};
|
SDL_FRect borderRect{x - 2, y - 2, w + 4, h + 4};
|
||||||
@ -92,13 +102,17 @@ void UIRenderer::drawButton(SDL_Renderer* renderer, FontAtlas* font, float cx, f
|
|||||||
// Reset color mod
|
// Reset color mod
|
||||||
SDL_SetTextureColorMod(icon, 255, 255, 255);
|
SDL_SetTextureColorMod(icon, 255, 255, 255);
|
||||||
} else if (font) {
|
} else if (font) {
|
||||||
// Draw text
|
// Draw text (smaller scale for tighter buttons)
|
||||||
float textScale = 1.5f;
|
float textScale = 1.2f;
|
||||||
int textW = 0, textH = 0;
|
int textW = 0, textH = 0;
|
||||||
font->measure(label, textScale, textW, textH);
|
font->measure(label, textScale, textW, textH);
|
||||||
float tx = x + (w - static_cast<float>(textW)) * 0.5f;
|
float tx = x + (w - static_cast<float>(textW)) * 0.5f;
|
||||||
// Adjust vertical position for better alignment with background buttons
|
// Adjust vertical position for better alignment with background buttons
|
||||||
float ty = y + (h - static_cast<float>(textH)) * 0.5f + 2.0f;
|
// Vertically center text precisely within the button
|
||||||
|
// Vertically center text precisely within the button, then nudge down slightly
|
||||||
|
// to improve optical balance relative to icons and button art.
|
||||||
|
const float textNudge = 3.0f; // tweak this value to move labels up/down
|
||||||
|
float ty = y + (h - static_cast<float>(textH)) * 0.5f + textNudge;
|
||||||
|
|
||||||
// Choose text color based on selection state
|
// Choose text color based on selection state
|
||||||
SDL_Color textColor = {255, 255, 255, 255}; // Default white
|
SDL_Color textColor = {255, 255, 255, 255}; // Default white
|
||||||
|
|||||||
76
src/main.cpp
@ -160,7 +160,7 @@ static bool queueLevelBackground(LevelBackgroundFader& fader, SDL_Renderer* rend
|
|||||||
}
|
}
|
||||||
|
|
||||||
char bgPath[256];
|
char bgPath[256];
|
||||||
std::snprintf(bgPath, sizeof(bgPath), "assets/images/tetris_main_back_level%d.jpg", level);
|
std::snprintf(bgPath, sizeof(bgPath), "assets/images/levels/level%d.jpg", level);
|
||||||
|
|
||||||
SDL_Texture* newTexture = loadTextureFromImage(renderer, bgPath);
|
SDL_Texture* newTexture = loadTextureFromImage(renderer, bgPath);
|
||||||
if (!newTexture) {
|
if (!newTexture) {
|
||||||
@ -580,12 +580,13 @@ int main(int, char **)
|
|||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
}
|
}
|
||||||
|
|
||||||
FontAtlas font;
|
// Primary UI font (Orbitron) used for major UI text: buttons, loading, HUD
|
||||||
font.init("FreeSans.ttf", 24);
|
|
||||||
|
|
||||||
// Load PressStart2P font for loading screen and retro UI elements
|
|
||||||
FontAtlas pixelFont;
|
FontAtlas pixelFont;
|
||||||
pixelFont.init("assets/fonts/PressStart2P-Regular.ttf", 16);
|
pixelFont.init("assets/fonts/Orbitron.ttf", 22);
|
||||||
|
|
||||||
|
// Secondary font (Exo2) used for longer descriptions, settings, credits
|
||||||
|
FontAtlas font;
|
||||||
|
font.init("assets/fonts/Exo2.ttf", 20);
|
||||||
|
|
||||||
ScoreManager scores;
|
ScoreManager scores;
|
||||||
std::atomic<bool> scoresLoadComplete{false};
|
std::atomic<bool> scoresLoadComplete{false};
|
||||||
@ -623,7 +624,7 @@ int main(int, char **)
|
|||||||
// Load the new main screen overlay that sits above the background but below buttons
|
// Load the new main screen overlay that sits above the background but below buttons
|
||||||
int mainScreenW = 0;
|
int mainScreenW = 0;
|
||||||
int mainScreenH = 0;
|
int mainScreenH = 0;
|
||||||
SDL_Texture* mainScreenTex = loadTextureFromImage(renderer, "assets/images/main_screen_004.png", &mainScreenW, &mainScreenH);
|
SDL_Texture* mainScreenTex = loadTextureFromImage(renderer, "assets/images/main_screen.png", &mainScreenW, &mainScreenH);
|
||||||
if (mainScreenTex) {
|
if (mainScreenTex) {
|
||||||
SDL_SetTextureBlendMode(mainScreenTex, SDL_BLENDMODE_BLEND);
|
SDL_SetTextureBlendMode(mainScreenTex, SDL_BLENDMODE_BLEND);
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Loaded main_screen overlay %dx%d (tex=%p)", mainScreenW, mainScreenH, (void*)mainScreenTex);
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Loaded main_screen overlay %dx%d (tex=%p)", mainScreenW, mainScreenH, (void*)mainScreenTex);
|
||||||
@ -1573,28 +1574,8 @@ int main(int, char **)
|
|||||||
} else if (state == AppState::Menu) {
|
} else if (state == AppState::Menu) {
|
||||||
// Space flyover backdrop for the main screen
|
// Space flyover backdrop for the main screen
|
||||||
spaceWarp.draw(renderer, 1.0f);
|
spaceWarp.draw(renderer, 1.0f);
|
||||||
|
// `mainScreenTex` is rendered as a top layer just before presenting
|
||||||
if (mainScreenTex) {
|
// so we don't draw it here. Keep the space warp background only.
|
||||||
float texW = mainScreenW > 0 ? static_cast<float>(mainScreenW) : 0.0f;
|
|
||||||
float texH = mainScreenH > 0 ? static_cast<float>(mainScreenH) : 0.0f;
|
|
||||||
if (texW <= 0.0f || texH <= 0.0f) {
|
|
||||||
if (!SDL_GetTextureSize(mainScreenTex, &texW, &texH)) {
|
|
||||||
texW = texH = 0.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (texW > 0.0f && texH > 0.0f) {
|
|
||||||
const float drawH = static_cast<float>(winH);
|
|
||||||
const float scale = drawH / texH;
|
|
||||||
const float drawW = texW * scale;
|
|
||||||
SDL_FRect dst{
|
|
||||||
(winW - drawW) * 0.5f,
|
|
||||||
0.0f,
|
|
||||||
drawW,
|
|
||||||
drawH
|
|
||||||
};
|
|
||||||
SDL_RenderTexture(renderer, mainScreenTex, nullptr, &dst);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (state == AppState::LevelSelector || state == AppState::Options) {
|
} else if (state == AppState::LevelSelector || state == AppState::Options) {
|
||||||
if (backgroundTex) {
|
if (backgroundTex) {
|
||||||
SDL_FRect fullRect = { 0, 0, (float)winW, (float)winH };
|
SDL_FRect fullRect = { 0, 0, (float)winW, (float)winH };
|
||||||
@ -1926,6 +1907,43 @@ int main(int, char **)
|
|||||||
HelpOverlay::Render(renderer, pixelFont, LOGICAL_W, LOGICAL_H, contentOffsetX, contentOffsetY);
|
HelpOverlay::Render(renderer, pixelFont, LOGICAL_W, LOGICAL_H, contentOffsetX, contentOffsetY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Top-layer overlay: render `mainScreenTex` above all other layers when in Menu
|
||||||
|
if (state == AppState::Menu && mainScreenTex) {
|
||||||
|
SDL_SetRenderViewport(renderer, nullptr);
|
||||||
|
SDL_SetRenderScale(renderer, 1.f, 1.f);
|
||||||
|
float texW = mainScreenW > 0 ? static_cast<float>(mainScreenW) : 0.0f;
|
||||||
|
float texH = mainScreenH > 0 ? static_cast<float>(mainScreenH) : 0.0f;
|
||||||
|
if (texW <= 0.0f || texH <= 0.0f) {
|
||||||
|
float iwf = 0.0f, ihf = 0.0f;
|
||||||
|
if (SDL_GetTextureSize(mainScreenTex, &iwf, &ihf) != 0) {
|
||||||
|
iwf = ihf = 0.0f;
|
||||||
|
}
|
||||||
|
texW = iwf;
|
||||||
|
texH = ihf;
|
||||||
|
}
|
||||||
|
if (texW > 0.0f && texH > 0.0f) {
|
||||||
|
const float drawH = static_cast<float>(winH);
|
||||||
|
const float scale = drawH / texH;
|
||||||
|
const float drawW = texW * scale;
|
||||||
|
SDL_FRect dst{
|
||||||
|
(winW - drawW) * 0.5f,
|
||||||
|
0.0f,
|
||||||
|
drawW,
|
||||||
|
drawH
|
||||||
|
};
|
||||||
|
SDL_SetTextureBlendMode(mainScreenTex, SDL_BLENDMODE_BLEND);
|
||||||
|
SDL_RenderTexture(renderer, mainScreenTex, nullptr, &dst);
|
||||||
|
}
|
||||||
|
// Restore logical viewport/scale and draw the main PLAY button above the overlay
|
||||||
|
SDL_SetRenderViewport(renderer, &logicalVP);
|
||||||
|
SDL_SetRenderScale(renderer, logicalScale, logicalScale);
|
||||||
|
if (menuState) {
|
||||||
|
menuState->drawMainButtonNormally = false; // ensure it isn't double-drawn
|
||||||
|
menuState->renderMainButtonTop(renderer, logicalScale, logicalVP);
|
||||||
|
menuState->drawMainButtonNormally = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SDL_RenderPresent(renderer);
|
SDL_RenderPresent(renderer);
|
||||||
SDL_SetRenderScale(renderer, 1.f, 1.f);
|
SDL_SetRenderScale(renderer, 1.f, 1.f);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,11 @@ public:
|
|||||||
void handleEvent(const SDL_Event& e) override;
|
void handleEvent(const SDL_Event& e) override;
|
||||||
void update(double frameMs) override;
|
void update(double frameMs) override;
|
||||||
void render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logicalVP) override;
|
void render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logicalVP) override;
|
||||||
|
// When false, the main PLAY button is not drawn by `render()` and can be
|
||||||
|
// rendered separately with `renderMainButtonTop` (useful for layer ordering).
|
||||||
|
bool drawMainButtonNormally = true;
|
||||||
|
// Draw only the main PLAY button on top of other layers (expects logical coords).
|
||||||
|
void renderMainButtonTop(SDL_Renderer* renderer, float logicalScale, SDL_Rect logicalVP);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int selectedButton = 0; // 0 = PLAY, 1 = LEVEL, 2 = OPTIONS, 3 = EXIT
|
int selectedButton = 0; // 0 = PLAY, 1 = LEVEL, 2 = OPTIONS, 3 = EXIT
|
||||||
@ -19,4 +24,54 @@ private:
|
|||||||
SDL_Texture* levelIcon = nullptr;
|
SDL_Texture* levelIcon = nullptr;
|
||||||
SDL_Texture* optionsIcon = nullptr;
|
SDL_Texture* optionsIcon = nullptr;
|
||||||
SDL_Texture* exitIcon = nullptr;
|
SDL_Texture* exitIcon = nullptr;
|
||||||
|
|
||||||
|
// Options panel animation state
|
||||||
|
bool optionsVisible = false;
|
||||||
|
bool optionsAnimating = false;
|
||||||
|
double optionsTransition = 0.0; // 0..1
|
||||||
|
double optionsTransitionDurationMs = 400.0;
|
||||||
|
int optionsDirection = 1; // 1 show, -1 hide
|
||||||
|
// Which row in the inline options panel is currently selected (0..4)
|
||||||
|
int optionsSelectedRow = 0;
|
||||||
|
// Inline level selector HUD state
|
||||||
|
bool levelPanelVisible = false;
|
||||||
|
bool levelPanelAnimating = false;
|
||||||
|
double levelTransition = 0.0; // 0..1
|
||||||
|
double levelTransitionDurationMs = 400.0;
|
||||||
|
int levelDirection = 1; // 1 show, -1 hide
|
||||||
|
int levelHovered = -1; // hovered cell
|
||||||
|
int levelSelected = 0; // current selected level
|
||||||
|
// Cache logical viewport/scale for input conversion when needed
|
||||||
|
float lastLogicalScale = 1.0f;
|
||||||
|
SDL_Rect lastLogicalVP{0,0,0,0};
|
||||||
|
// Animated highlight position (world/logical coordinates)
|
||||||
|
double levelHighlightX = 0.0;
|
||||||
|
double levelHighlightY = 0.0;
|
||||||
|
bool levelHighlightInitialized = false;
|
||||||
|
// Highlight tuning parameters
|
||||||
|
double levelHighlightSpeed = 0.018; // smoothing constant - higher = snappier
|
||||||
|
double levelHighlightGlowAlpha = 0.70; // 0..1 base glow alpha
|
||||||
|
int levelHighlightThickness = 3; // inner outline thickness (px)
|
||||||
|
SDL_Color levelHighlightColor = SDL_Color{80, 200, 255, 200};
|
||||||
|
// Button group pulsing/fade parameters (applies to all four main buttons)
|
||||||
|
double buttonGroupAlpha = 1.0; // current computed alpha (0..1)
|
||||||
|
double buttonPulseTime = 0.0; // accumulator in ms
|
||||||
|
bool buttonPulseEnabled = true; // enable/disable pulsing
|
||||||
|
double buttonPulseSpeed = 1.0; // multiplier for pulse frequency
|
||||||
|
double buttonPulseMinAlpha = 0.60; // minimum alpha during pulse
|
||||||
|
double buttonPulseMaxAlpha = 1.00; // maximum alpha during pulse
|
||||||
|
// Pulse easing mode: 0=sin,1=triangle,2=exponential
|
||||||
|
int buttonPulseEasing = 1;
|
||||||
|
// Short bright flash when navigating buttons
|
||||||
|
double buttonFlash = 0.0; // transient flash amount (0..1)
|
||||||
|
double buttonFlashAmount = 0.45; // how much flash adds to group alpha
|
||||||
|
double buttonFlashDecay = 0.0025; // linear decay per ms
|
||||||
|
// Exit confirmation HUD state (inline HUD like Options/Level)
|
||||||
|
bool exitPanelVisible = false;
|
||||||
|
bool exitPanelAnimating = false;
|
||||||
|
double exitTransition = 0.0; // 0..1
|
||||||
|
double exitTransitionDurationMs = 360.0;
|
||||||
|
int exitDirection = 1; // 1 show, -1 hide
|
||||||
|
int exitSelectedButton = 0; // 0 = YES (quit), 1 = NO (cancel)
|
||||||
|
double exitScroll = 0.0; // vertical scroll offset for content
|
||||||
};
|
};
|
||||||
|
|||||||
@ -113,7 +113,8 @@ void OptionsState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect l
|
|||||||
|
|
||||||
UIRenderer::drawSciFiPanel(renderer, panel);
|
UIRenderer::drawSciFiPanel(renderer, panel);
|
||||||
|
|
||||||
FontAtlas* retroFont = ctx.pixelFont ? ctx.pixelFont : ctx.font;
|
// For options/settings we prefer the secondary (Exo2) font for longer descriptions.
|
||||||
|
FontAtlas* retroFont = ctx.font ? ctx.font : ctx.pixelFont;
|
||||||
|
|
||||||
if (!logoTexture && retroFont) {
|
if (!logoTexture && retroFont) {
|
||||||
retroFont->draw(renderer, panel.x + 24.0f, panel.y + 24.0f, "OPTIONS", 2.0f, {255, 230, 120, 255});
|
retroFont->draw(renderer, panel.x + 24.0f, panel.y + 24.0f, "OPTIONS", 2.0f, {255, 230, 120, 255});
|
||||||
@ -252,9 +253,12 @@ void OptionsState::toggleSmoothScroll() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void OptionsState::exitToMenu() {
|
void OptionsState::exitToMenu() {
|
||||||
|
// Try a graceful fade transition if available, but always ensure we
|
||||||
|
// return to the Menu state so the UI is responsive to the user's action.
|
||||||
if (ctx.requestFadeTransition) {
|
if (ctx.requestFadeTransition) {
|
||||||
ctx.requestFadeTransition(AppState::Menu);
|
ctx.requestFadeTransition(AppState::Menu);
|
||||||
} else if (ctx.stateManager) {
|
}
|
||||||
|
if (ctx.stateManager) {
|
||||||
ctx.stateManager->setState(AppState::Menu);
|
ctx.stateManager->setState(AppState::Menu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,6 +43,10 @@ struct StateContext {
|
|||||||
SDL_Texture* mainScreenTex = nullptr;
|
SDL_Texture* mainScreenTex = nullptr;
|
||||||
int mainScreenW = 0;
|
int mainScreenW = 0;
|
||||||
int mainScreenH = 0;
|
int mainScreenH = 0;
|
||||||
|
// Captured full-scene texture (used by menu for backdrop blur effects)
|
||||||
|
SDL_Texture* sceneTex = nullptr;
|
||||||
|
int sceneW = 0;
|
||||||
|
int sceneH = 0;
|
||||||
|
|
||||||
// Audio / SFX - forward declared types in main
|
// Audio / SFX - forward declared types in main
|
||||||
// Pointers to booleans/flags used by multiple states
|
// Pointers to booleans/flags used by multiple states
|
||||||
|
|||||||