new background for main screen
This commit is contained in:
@ -44,6 +44,7 @@ set(TETRIS_SOURCES
|
|||||||
src/persistence/Scores.cpp
|
src/persistence/Scores.cpp
|
||||||
src/graphics/effects/Starfield.cpp
|
src/graphics/effects/Starfield.cpp
|
||||||
src/graphics/effects/Starfield3D.cpp
|
src/graphics/effects/Starfield3D.cpp
|
||||||
|
src/graphics/effects/SpaceWarp.cpp
|
||||||
src/graphics/ui/Font.cpp
|
src/graphics/ui/Font.cpp
|
||||||
src/graphics/ui/HelpOverlay.cpp
|
src/graphics/ui/HelpOverlay.cpp
|
||||||
src/graphics/renderers/GameRenderer.cpp
|
src/graphics/renderers/GameRenderer.cpp
|
||||||
|
|||||||
BIN
assets/images/main_screen.bmp
Normal file
BIN
assets/images/main_screen.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.9 MiB |
BIN
assets/images/main_screen_001.png
Normal file
BIN
assets/images/main_screen_001.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
BIN
assets/images/main_screen_002.png
Normal file
BIN
assets/images/main_screen_002.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
BIN
assets/images/main_screen_003.png
Normal file
BIN
assets/images/main_screen_003.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
BIN
assets/images/main_screen_004.png
Normal file
BIN
assets/images/main_screen_004.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 MiB |
149
src/graphics/effects/SpaceWarp.cpp
Normal file
149
src/graphics/effects/SpaceWarp.cpp
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
#include "SpaceWarp.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr float MIN_ASPECT = 0.001f;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpaceWarp::SpaceWarp() {
|
||||||
|
std::random_device rd;
|
||||||
|
rng.seed(rd());
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpaceWarp::init(int w, int h, int starCount) {
|
||||||
|
resize(w, h);
|
||||||
|
stars.resize(std::max(8, starCount));
|
||||||
|
for (auto& star : stars) {
|
||||||
|
respawn(star, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpaceWarp::resize(int w, int h) {
|
||||||
|
width = std::max(1, w);
|
||||||
|
height = std::max(1, h);
|
||||||
|
centerX = width * 0.5f;
|
||||||
|
centerY = height * 0.5f;
|
||||||
|
warpFactor = std::max(width, height) * settings.warpFactorScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpaceWarp::setSettings(const SpaceWarpSettings& newSettings) {
|
||||||
|
settings = newSettings;
|
||||||
|
warpFactor = std::max(width, height) * settings.warpFactorScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
float SpaceWarp::randomRange(float min, float max) {
|
||||||
|
std::uniform_real_distribution<float> dist(min, max);
|
||||||
|
return dist(rng);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int randomIntInclusive(std::mt19937& rng, int min, int max) {
|
||||||
|
std::uniform_int_distribution<int> dist(min, max);
|
||||||
|
return dist(rng);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpaceWarp::respawn(WarpStar& star, bool randomDepth) {
|
||||||
|
float aspect = static_cast<float>(width) / static_cast<float>(std::max(1, height));
|
||||||
|
float normalizedAspect = std::max(aspect, MIN_ASPECT);
|
||||||
|
float xRange = settings.baseSpawnRange * (aspect >= 1.0f ? aspect : 1.0f);
|
||||||
|
float yRange = settings.baseSpawnRange * (aspect >= 1.0f ? 1.0f : (1.0f / normalizedAspect));
|
||||||
|
star.x = randomRange(-xRange, xRange);
|
||||||
|
star.y = randomRange(-yRange, yRange);
|
||||||
|
star.z = randomDepth ? randomRange(minDepth, maxDepth) : maxDepth;
|
||||||
|
star.speed = randomRange(settings.minSpeed, settings.maxSpeed);
|
||||||
|
star.shade = randomRange(settings.minShade, settings.maxShade);
|
||||||
|
static constexpr Uint8 GRAY_SHADES[] = {160, 180, 200, 220, 240};
|
||||||
|
int idx = randomIntInclusive(rng, 0, int(std::size(GRAY_SHADES)) - 1);
|
||||||
|
star.baseShade = GRAY_SHADES[idx];
|
||||||
|
star.prevScreenX = centerX;
|
||||||
|
star.prevScreenY = centerY;
|
||||||
|
star.screenX = centerX;
|
||||||
|
star.screenY = centerY;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SpaceWarp::project(const WarpStar& star, float& outX, float& outY) const {
|
||||||
|
if (star.z <= minDepth) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
float perspective = warpFactor / (star.z + 0.001f);
|
||||||
|
outX = centerX + star.x * perspective;
|
||||||
|
outY = centerY + star.y * perspective;
|
||||||
|
const float margin = settings.spawnMargin;
|
||||||
|
return outX >= -margin && outX <= width + margin && outY >= -margin && outY <= height + margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpaceWarp::update(float deltaSeconds) {
|
||||||
|
if (stars.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& star : stars) {
|
||||||
|
star.z -= star.speed * deltaSeconds;
|
||||||
|
if (star.z <= minDepth) {
|
||||||
|
respawn(star, true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float sx = 0.0f;
|
||||||
|
float sy = 0.0f;
|
||||||
|
if (!project(star, sx, sy)) {
|
||||||
|
respawn(star, true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
star.prevScreenX = star.screenX;
|
||||||
|
star.prevScreenY = star.screenY;
|
||||||
|
star.screenX = sx;
|
||||||
|
star.screenY = sy;
|
||||||
|
|
||||||
|
float dx = star.screenX - star.prevScreenX;
|
||||||
|
float dy = star.screenY - star.prevScreenY;
|
||||||
|
float lenSq = dx * dx + dy * dy;
|
||||||
|
float maxStreak = std::max(settings.maxTrailLength, 0.0f);
|
||||||
|
if (maxStreak > 0.0f && lenSq > maxStreak * maxStreak) {
|
||||||
|
float len = std::sqrt(lenSq);
|
||||||
|
float scale = maxStreak / len;
|
||||||
|
star.prevScreenX = star.screenX - dx * scale;
|
||||||
|
star.prevScreenY = star.screenY - dy * scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpaceWarp::draw(SDL_Renderer* renderer, float alphaScale) {
|
||||||
|
if (stars.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_BlendMode previous = SDL_BLENDMODE_NONE;
|
||||||
|
SDL_GetRenderDrawBlendMode(renderer, &previous);
|
||||||
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_ADD);
|
||||||
|
|
||||||
|
for (const auto& star : stars) {
|
||||||
|
float depthFactor = 1.0f - std::clamp(star.z / maxDepth, 0.0f, 1.0f);
|
||||||
|
float alphaBase = std::clamp(settings.minAlpha + depthFactor * settings.alphaDepthBoost, 0.0f, 255.0f);
|
||||||
|
Uint8 alpha = static_cast<Uint8>(std::clamp(alphaBase * alphaScale, 0.0f, 255.0f));
|
||||||
|
float colorValue = std::clamp(
|
||||||
|
star.baseShade * (settings.baseShadeScale + depthFactor * settings.depthColorScale) * star.shade,
|
||||||
|
settings.minColor,
|
||||||
|
settings.maxColor);
|
||||||
|
Uint8 color = static_cast<Uint8>(colorValue);
|
||||||
|
|
||||||
|
if (settings.drawTrails) {
|
||||||
|
float trailAlphaFloat = alpha * settings.trailAlphaScale;
|
||||||
|
Uint8 trailAlpha = static_cast<Uint8>(std::clamp(trailAlphaFloat, 0.0f, 255.0f));
|
||||||
|
SDL_SetRenderDrawColor(renderer, color, color, color, trailAlpha);
|
||||||
|
SDL_RenderLine(renderer, star.prevScreenX, star.prevScreenY, star.screenX, star.screenY);
|
||||||
|
}
|
||||||
|
|
||||||
|
float dotSize = std::clamp(settings.minDotSize + depthFactor * (settings.maxDotSize - settings.minDotSize),
|
||||||
|
settings.minDotSize,
|
||||||
|
settings.maxDotSize);
|
||||||
|
SDL_FRect dot{star.screenX - dotSize * 0.5f, star.screenY - dotSize * 0.5f, dotSize, dotSize};
|
||||||
|
SDL_SetRenderDrawColor(renderer, color, color, color, alpha);
|
||||||
|
SDL_RenderFillRect(renderer, &dot);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetRenderDrawBlendMode(renderer, previous);
|
||||||
|
}
|
||||||
69
src/graphics/effects/SpaceWarp.h
Normal file
69
src/graphics/effects/SpaceWarp.h
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <random>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct SpaceWarpSettings {
|
||||||
|
float baseSpawnRange = 1.45f; // logical radius for initial star positions
|
||||||
|
float warpFactorScale = 122.85f; // scales perspective factor so stars spread faster or slower
|
||||||
|
float spawnMargin = 60.0f; // how far offscreen a star can travel before respawn
|
||||||
|
float minShade = 0.85f; // lower bound for per-star brightness multiplier
|
||||||
|
float maxShade = 1.15f; // upper bound for per-star brightness multiplier
|
||||||
|
float minSpeed = 120.0f; // slowest warp velocity (higher feels faster motion)
|
||||||
|
float maxSpeed = 280.0f; // fastest warp velocity
|
||||||
|
float minDotSize = 2.5f; // smallest star size in pixels
|
||||||
|
float maxDotSize = 4.5f; // largest star size in pixels
|
||||||
|
float minAlpha = 70.0f; // base opacity even for distant stars
|
||||||
|
float alphaDepthBoost = 160.0f; // extra opacity applied as stars approach the camera
|
||||||
|
float minColor = 180.0f; // clamp for minimum grayscale value
|
||||||
|
float maxColor = 205.0f; // clamp for maximum grayscale value
|
||||||
|
float baseShadeScale = 0.75f; // baseline multiplier applied to the sampled grayscale shade
|
||||||
|
float depthColorScale = 0.55f; // how much depth affects the grayscale brightness
|
||||||
|
bool drawTrails = true; // when true, also render streak lines for hyper-speed look
|
||||||
|
float trailAlphaScale = 0.75f; // relative opacity for streak lines vs dots
|
||||||
|
float maxTrailLength = 36.0f; // clamp length of each streak in pixels
|
||||||
|
};
|
||||||
|
|
||||||
|
class SpaceWarp {
|
||||||
|
public:
|
||||||
|
SpaceWarp();
|
||||||
|
void init(int width, int height, int starCount = 320);
|
||||||
|
void resize(int width, int height);
|
||||||
|
void update(float deltaSeconds);
|
||||||
|
void draw(SDL_Renderer* renderer, float alphaScale = 1.0f);
|
||||||
|
void setSettings(const SpaceWarpSettings& newSettings);
|
||||||
|
const SpaceWarpSettings& getSettings() const { return settings; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct WarpStar {
|
||||||
|
float x = 0.0f;
|
||||||
|
float y = 0.0f;
|
||||||
|
float z = 0.0f;
|
||||||
|
float speed = 0.0f;
|
||||||
|
float prevScreenX = 0.0f;
|
||||||
|
float prevScreenY = 0.0f;
|
||||||
|
float screenX = 0.0f;
|
||||||
|
float screenY = 0.0f;
|
||||||
|
float shade = 1.0f;
|
||||||
|
Uint8 baseShade = 220;
|
||||||
|
};
|
||||||
|
|
||||||
|
void respawn(WarpStar& star, bool randomDepth = true);
|
||||||
|
bool project(const WarpStar& star, float& outX, float& outY) const;
|
||||||
|
float randomRange(float min, float max);
|
||||||
|
|
||||||
|
std::vector<WarpStar> stars;
|
||||||
|
std::mt19937 rng;
|
||||||
|
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
float centerX = 0.0f;
|
||||||
|
float centerY = 0.0f;
|
||||||
|
float warpFactor = 520.0f;
|
||||||
|
|
||||||
|
SpaceWarpSettings settings{};
|
||||||
|
|
||||||
|
float minDepth = 2.0f;
|
||||||
|
float maxDepth = 320.0f;
|
||||||
|
};
|
||||||
78
src/main.cpp
78
src/main.cpp
@ -25,6 +25,7 @@
|
|||||||
#include "persistence/Scores.h"
|
#include "persistence/Scores.h"
|
||||||
#include "graphics/effects/Starfield.h"
|
#include "graphics/effects/Starfield.h"
|
||||||
#include "graphics/effects/Starfield3D.h"
|
#include "graphics/effects/Starfield3D.h"
|
||||||
|
#include "graphics/effects/SpaceWarp.h"
|
||||||
#include "graphics/ui/Font.h"
|
#include "graphics/ui/Font.h"
|
||||||
#include "graphics/ui/HelpOverlay.h"
|
#include "graphics/ui/HelpOverlay.h"
|
||||||
#include "gameplay/effects/LineEffect.h"
|
#include "gameplay/effects/LineEffect.h"
|
||||||
@ -680,16 +681,15 @@ static void drawFireworks_impl(SDL_Renderer* renderer, SDL_Texture*) {
|
|||||||
|
|
||||||
SDL_SetRenderDrawBlendMode(renderer, previousBlend);
|
SDL_SetRenderDrawBlendMode(renderer, previousBlend);
|
||||||
}
|
}
|
||||||
// External wrappers for use by other translation units (MenuState)
|
// External wrappers retained for compatibility; now no-ops to disable the legacy fireworks effect.
|
||||||
// Expect callers to pass the blocks texture via StateContext so we avoid globals.
|
void menu_drawFireworks(SDL_Renderer*, SDL_Texture*) {}
|
||||||
void menu_drawFireworks(SDL_Renderer* renderer, SDL_Texture* blocksTex) { drawFireworks_impl(renderer, blocksTex); }
|
void menu_updateFireworks(double) {}
|
||||||
void menu_updateFireworks(double frameMs) { updateFireworks(frameMs); }
|
|
||||||
double menu_getLogoAnimCounter() { return logoAnimCounter; }
|
double menu_getLogoAnimCounter() { return logoAnimCounter; }
|
||||||
int menu_getHoveredButton() { return hoveredButton; }
|
int menu_getHoveredButton() { return hoveredButton; }
|
||||||
|
|
||||||
int main(int, char **)
|
int main(int, char **)
|
||||||
{
|
{
|
||||||
// Initialize random seed for fireworks
|
// Initialize random seed for procedural effects
|
||||||
srand(static_cast<unsigned int>(SDL_GetTicks()));
|
srand(static_cast<unsigned int>(SDL_GetTicks()));
|
||||||
|
|
||||||
// Load settings
|
// Load settings
|
||||||
@ -779,6 +779,8 @@ int main(int, char **)
|
|||||||
starfield.init(200, LOGICAL_W, LOGICAL_H);
|
starfield.init(200, LOGICAL_W, LOGICAL_H);
|
||||||
Starfield3D starfield3D;
|
Starfield3D starfield3D;
|
||||||
starfield3D.init(LOGICAL_W, LOGICAL_H, 200);
|
starfield3D.init(LOGICAL_W, LOGICAL_H, 200);
|
||||||
|
SpaceWarp spaceWarp;
|
||||||
|
spaceWarp.init(LOGICAL_W, LOGICAL_H, 420);
|
||||||
|
|
||||||
// Initialize line clearing effects
|
// Initialize line clearing effects
|
||||||
LineEffect lineEffect;
|
LineEffect lineEffect;
|
||||||
@ -794,6 +796,27 @@ int main(int, char **)
|
|||||||
// Load menu background using SDL_image (prefers JPEG)
|
// Load menu background using SDL_image (prefers JPEG)
|
||||||
SDL_Texture* backgroundTex = loadTextureFromImage(renderer, "assets/images/main_background.bmp");
|
SDL_Texture* backgroundTex = loadTextureFromImage(renderer, "assets/images/main_background.bmp");
|
||||||
|
|
||||||
|
// Load the new main screen overlay that sits above the background but below buttons
|
||||||
|
int mainScreenW = 0;
|
||||||
|
int mainScreenH = 0;
|
||||||
|
SDL_Texture* mainScreenTex = loadTextureFromImage(renderer, "assets/images/main_screen_004.png", &mainScreenW, &mainScreenH);
|
||||||
|
if (mainScreenTex) {
|
||||||
|
SDL_SetTextureBlendMode(mainScreenTex, SDL_BLENDMODE_BLEND);
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Loaded main_screen overlay %dx%d (tex=%p)", mainScreenW, mainScreenH, (void*)mainScreenTex);
|
||||||
|
FILE* f = fopen("tetris_trace.log", "a");
|
||||||
|
if (f) {
|
||||||
|
fprintf(f, "main.cpp: loaded main_screen.bmp %dx%d tex=%p\n", mainScreenW, mainScreenH, (void*)mainScreenTex);
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Failed to load assets/images/main_screen.bmp (overlay will be skipped)");
|
||||||
|
FILE* f = fopen("tetris_trace.log", "a");
|
||||||
|
if (f) {
|
||||||
|
fprintf(f, "main.cpp: failed to load main_screen.bmp\n");
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Note: `backgroundTex` is owned by main and passed into `StateContext::backgroundTex` below.
|
// Note: `backgroundTex` is owned by main and passed into `StateContext::backgroundTex` below.
|
||||||
// States should render using `ctx.backgroundTex` rather than accessing globals.
|
// States should render using `ctx.backgroundTex` rather than accessing globals.
|
||||||
|
|
||||||
@ -971,6 +994,9 @@ int main(int, char **)
|
|||||||
ctx.logoSmallH = logoSmallH;
|
ctx.logoSmallH = logoSmallH;
|
||||||
ctx.backgroundTex = backgroundTex;
|
ctx.backgroundTex = backgroundTex;
|
||||||
ctx.blocksTex = blocksTex;
|
ctx.blocksTex = blocksTex;
|
||||||
|
ctx.mainScreenTex = mainScreenTex;
|
||||||
|
ctx.mainScreenW = mainScreenW;
|
||||||
|
ctx.mainScreenH = mainScreenH;
|
||||||
ctx.musicEnabled = &musicEnabled;
|
ctx.musicEnabled = &musicEnabled;
|
||||||
ctx.startLevelSelection = &startLevelSelection;
|
ctx.startLevelSelection = &startLevelSelection;
|
||||||
ctx.hoveredButton = &hoveredButton;
|
ctx.hoveredButton = &hoveredButton;
|
||||||
@ -1565,21 +1591,25 @@ int main(int, char **)
|
|||||||
}
|
}
|
||||||
previousState = state;
|
previousState = state;
|
||||||
|
|
||||||
// Update starfields based on current state
|
// Update background effects
|
||||||
if (state == AppState::Loading) {
|
if (state == AppState::Loading) {
|
||||||
starfield3D.update(float(frameMs / 1000.0f));
|
starfield3D.update(float(frameMs / 1000.0f));
|
||||||
starfield3D.resize(logicalVP.w, logicalVP.h); // Update for window resize
|
starfield3D.resize(winW, winH);
|
||||||
} else {
|
} else {
|
||||||
starfield.update(float(frameMs / 1000.0f), logicalVP.x * 2 + logicalVP.w, logicalVP.y * 2 + logicalVP.h);
|
starfield.update(float(frameMs / 1000.0f), logicalVP.x * 2 + logicalVP.w, logicalVP.y * 2 + logicalVP.h);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state == AppState::Menu) {
|
||||||
|
spaceWarp.resize(winW, winH);
|
||||||
|
spaceWarp.update(float(frameMs / 1000.0f));
|
||||||
|
}
|
||||||
|
|
||||||
// Advance level background fade if a next texture is queued
|
// Advance level background fade if a next texture is queued
|
||||||
updateLevelBackgroundFade(levelBackgrounds, float(frameMs));
|
updateLevelBackgroundFade(levelBackgrounds, float(frameMs));
|
||||||
|
|
||||||
// Update intro animations
|
// Update intro animations
|
||||||
if (state == AppState::Menu) {
|
if (state == AppState::Menu) {
|
||||||
logoAnimCounter += frameMs * 0.0008; // Animation speed
|
logoAnimCounter += frameMs * 0.0008; // Animation speed
|
||||||
updateFireworks(frameMs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Per-state update hooks (allow states to manage logic incrementally)
|
// --- Per-state update hooks (allow states to manage logic incrementally)
|
||||||
@ -1671,7 +1701,7 @@ int main(int, char **)
|
|||||||
|
|
||||||
// --- Render ---
|
// --- Render ---
|
||||||
SDL_SetRenderViewport(renderer, nullptr);
|
SDL_SetRenderViewport(renderer, nullptr);
|
||||||
SDL_SetRenderDrawColor(renderer, 12, 12, 16, 255);
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||||
SDL_RenderClear(renderer);
|
SDL_RenderClear(renderer);
|
||||||
|
|
||||||
// Draw level-based background for gameplay, starfield for other states
|
// Draw level-based background for gameplay, starfield for other states
|
||||||
@ -1682,8 +1712,32 @@ int main(int, char **)
|
|||||||
} 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);
|
||||||
} else if (state == AppState::Menu || state == AppState::LevelSelector || state == AppState::Options) {
|
} else if (state == AppState::Menu) {
|
||||||
// Use static background for menu, stretched to window; no starfield on sides
|
// Space flyover backdrop for the main screen
|
||||||
|
spaceWarp.draw(renderer, 1.0f);
|
||||||
|
|
||||||
|
if (mainScreenTex) {
|
||||||
|
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) {
|
||||||
if (backgroundTex) {
|
if (backgroundTex) {
|
||||||
SDL_FRect fullRect = { 0, 0, (float)winW, (float)winH };
|
SDL_FRect fullRect = { 0, 0, (float)winW, (float)winH };
|
||||||
SDL_RenderTexture(renderer, backgroundTex, nullptr, &fullRect);
|
SDL_RenderTexture(renderer, backgroundTex, nullptr, &fullRect);
|
||||||
@ -2021,6 +2075,8 @@ int main(int, char **)
|
|||||||
SDL_DestroyTexture(logoTex);
|
SDL_DestroyTexture(logoTex);
|
||||||
if (backgroundTex)
|
if (backgroundTex)
|
||||||
SDL_DestroyTexture(backgroundTex);
|
SDL_DestroyTexture(backgroundTex);
|
||||||
|
if (mainScreenTex)
|
||||||
|
SDL_DestroyTexture(mainScreenTex);
|
||||||
resetLevelBackgrounds(levelBackgrounds);
|
resetLevelBackgrounds(levelBackgrounds);
|
||||||
if (blocksTex)
|
if (blocksTex)
|
||||||
SDL_DestroyTexture(blocksTex);
|
SDL_DestroyTexture(blocksTex);
|
||||||
|
|||||||
@ -19,6 +19,8 @@
|
|||||||
// `ctx.musicEnabled` and `ctx.hoveredButton` instead to avoid globals.
|
// `ctx.musicEnabled` and `ctx.hoveredButton` instead to avoid globals.
|
||||||
// Menu helper wrappers are declared in a shared header implemented in main.cpp
|
// Menu helper wrappers are declared in a shared header implemented in main.cpp
|
||||||
#include "../audio/MenuWrappers.h"
|
#include "../audio/MenuWrappers.h"
|
||||||
|
#include "../utils/ImagePathResolver.h"
|
||||||
|
#include <SDL3_image/SDL_image.h>
|
||||||
|
|
||||||
MenuState::MenuState(StateContext& ctx) : State(ctx) {}
|
MenuState::MenuState(StateContext& ctx) : State(ctx) {}
|
||||||
|
|
||||||
@ -159,9 +161,6 @@ void MenuState::handleEvent(const SDL_Event& e) {
|
|||||||
void MenuState::update(double frameMs) {
|
void MenuState::update(double frameMs) {
|
||||||
// Update logo animation counter
|
// Update logo animation counter
|
||||||
GlobalState::instance().logoAnimCounter += frameMs;
|
GlobalState::instance().logoAnimCounter += frameMs;
|
||||||
|
|
||||||
// Update fireworks particles
|
|
||||||
GlobalState::instance().updateFireworks(frameMs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logicalVP) {
|
void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logicalVP) {
|
||||||
@ -184,67 +183,32 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
|||||||
float contentOffsetY = (winH - contentH) * 0.5f / logicalScale;
|
float contentOffsetY = (winH - contentH) * 0.5f / logicalScale;
|
||||||
|
|
||||||
// Background is drawn by main (stretched to the full window) to avoid double-draw.
|
// Background is drawn by main (stretched to the full window) to avoid double-draw.
|
||||||
|
|
||||||
// Draw the animated logo and fireworks using the small logo if available (show whole image)
|
|
||||||
SDL_Texture* logoToUse = ctx.logoSmallTex ? ctx.logoSmallTex : ctx.logoTex;
|
|
||||||
// Trace logo texture pointer
|
|
||||||
{
|
{
|
||||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render logoToUse=%llu\n", (unsigned long long)(uintptr_t)logoToUse); fclose(f); }
|
FILE* f = fopen("tetris_trace.log", "a");
|
||||||
}
|
if (f) {
|
||||||
if (logoToUse) {
|
fprintf(f, "MenuState::render ctx.mainScreenTex=%llu (w=%d h=%d)\n",
|
||||||
// Use dimensions provided by the shared context when available
|
(unsigned long long)(uintptr_t)ctx.mainScreenTex,
|
||||||
int texW = (logoToUse == ctx.logoSmallTex && ctx.logoSmallW > 0) ? ctx.logoSmallW : 872;
|
ctx.mainScreenW,
|
||||||
int texH = (logoToUse == ctx.logoSmallTex && ctx.logoSmallH > 0) ? ctx.logoSmallH : 273;
|
ctx.mainScreenH);
|
||||||
float maxW = LOGICAL_W * 0.6f;
|
fclose(f);
|
||||||
float scale = std::min(1.0f, maxW / float(texW));
|
|
||||||
float dw = texW * scale;
|
|
||||||
float dh = texH * scale;
|
|
||||||
float logoX = (LOGICAL_W - dw) / 2.f + contentOffsetX;
|
|
||||||
float logoY = LOGICAL_H * 0.05f + contentOffsetY;
|
|
||||||
SDL_FRect dst{logoX, logoY, dw, dh};
|
|
||||||
// Trace before rendering logo
|
|
||||||
{
|
|
||||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render before SDL_RenderTexture logo ptr=%llu\n", (unsigned long long)(uintptr_t)logoToUse); fclose(f); }
|
|
||||||
}
|
|
||||||
SDL_RenderTexture(renderer, logoToUse, nullptr, &dst);
|
|
||||||
// Trace after rendering logo
|
|
||||||
{
|
|
||||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render after SDL_RenderTexture logo\n"); fclose(f); }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fireworks (draw above high scores / near buttons)
|
|
||||||
{
|
|
||||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render before drawFireworks blocksTex=%llu\n", (unsigned long long)(uintptr_t)ctx.blocksTex); fclose(f); }
|
|
||||||
}
|
|
||||||
GlobalState::instance().drawFireworks(renderer, ctx.blocksTex);
|
|
||||||
{
|
|
||||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render after drawFireworks\n"); fclose(f); }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Score list and top players with a sine-wave vertical animation (use pixelFont for retro look)
|
|
||||||
float topPlayersY = LOGICAL_H * 0.30f + contentOffsetY; // more top padding
|
|
||||||
FontAtlas* useFont = ctx.pixelFont ? ctx.pixelFont : ctx.font;
|
FontAtlas* useFont = ctx.pixelFont ? ctx.pixelFont : ctx.font;
|
||||||
|
float topPlayersY = LOGICAL_H * 0.24f + contentOffsetY;
|
||||||
if (useFont) {
|
if (useFont) {
|
||||||
{
|
|
||||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render before useFont->draw TOP PLAYERS ptr=%llu\n", (unsigned long long)(uintptr_t)useFont); fclose(f); }
|
|
||||||
}
|
|
||||||
const std::string title = "TOP PLAYERS";
|
const std::string title = "TOP PLAYERS";
|
||||||
int tW = 0, tH = 0; useFont->measure(title, 1.8f, tW, tH);
|
int tW = 0, tH = 0;
|
||||||
|
useFont->measure(title, 1.8f, tW, tH);
|
||||||
float titleX = (LOGICAL_W - (float)tW) * 0.5f + contentOffsetX;
|
float titleX = (LOGICAL_W - (float)tW) * 0.5f + contentOffsetX;
|
||||||
useFont->draw(renderer, titleX, topPlayersY, title, 1.8f, SDL_Color{255, 220, 0, 255});
|
useFont->draw(renderer, titleX, topPlayersY, title, 1.8f, SDL_Color{255, 220, 0, 255});
|
||||||
{
|
|
||||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render after useFont->draw TOP PLAYERS\n"); fclose(f); }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// High scores table with wave offset
|
float scoresStartY = topPlayersY + 70.0f;
|
||||||
float scoresStartY = topPlayersY + 70; // more spacing under title
|
|
||||||
static const std::vector<ScoreEntry> EMPTY_SCORES;
|
static const std::vector<ScoreEntry> EMPTY_SCORES;
|
||||||
const auto& hs = ctx.scores ? ctx.scores->all() : EMPTY_SCORES;
|
const auto& hs = ctx.scores ? ctx.scores->all() : EMPTY_SCORES;
|
||||||
size_t maxDisplay = std::min(hs.size(), size_t(12));
|
size_t maxDisplay = std::min(hs.size(), size_t(12));
|
||||||
|
|
||||||
// Draw table header
|
|
||||||
if (useFont) {
|
if (useFont) {
|
||||||
float cx = LOGICAL_W * 0.5f + contentOffsetX;
|
float cx = LOGICAL_W * 0.5f + contentOffsetX;
|
||||||
float colX[] = { cx - 280, cx - 180, cx - 20, cx + 90, cx + 200, cx + 300 };
|
float colX[] = { cx - 280, cx - 180, cx - 20, cx + 90, cx + 200, cx + 300 };
|
||||||
@ -254,36 +218,126 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
|||||||
useFont->draw(renderer, colX[3], scoresStartY - 28, "LINES", 1.1f, SDL_Color{200,200,220,255});
|
useFont->draw(renderer, colX[3], scoresStartY - 28, "LINES", 1.1f, SDL_Color{200,200,220,255});
|
||||||
useFont->draw(renderer, colX[4], scoresStartY - 28, "LEVEL", 1.1f, SDL_Color{200,200,220,255});
|
useFont->draw(renderer, colX[4], scoresStartY - 28, "LEVEL", 1.1f, SDL_Color{200,200,220,255});
|
||||||
useFont->draw(renderer, colX[5], scoresStartY - 28, "TIME", 1.1f, SDL_Color{200,200,220,255});
|
useFont->draw(renderer, colX[5], scoresStartY - 28, "TIME", 1.1f, SDL_Color{200,200,220,255});
|
||||||
|
|
||||||
|
for (size_t i = 0; i < maxDisplay; ++i) {
|
||||||
|
float baseY = scoresStartY + i * 25.0f;
|
||||||
|
float wave = std::sin((float)GlobalState::instance().logoAnimCounter * 0.006f + i * 0.25f) * 6.0f;
|
||||||
|
float y = baseY + wave;
|
||||||
|
|
||||||
|
char rankStr[8];
|
||||||
|
std::snprintf(rankStr, sizeof(rankStr), "%zu.", i + 1);
|
||||||
|
useFont->draw(renderer, colX[0], y, rankStr, 1.0f, SDL_Color{220, 220, 230, 255});
|
||||||
|
|
||||||
|
useFont->draw(renderer, colX[1], y, hs[i].name, 1.0f, SDL_Color{220, 220, 230, 255});
|
||||||
|
|
||||||
|
char scoreStr[16];
|
||||||
|
std::snprintf(scoreStr, sizeof(scoreStr), "%d", hs[i].score);
|
||||||
|
useFont->draw(renderer, colX[2], y, scoreStr, 1.0f, SDL_Color{220, 220, 230, 255});
|
||||||
|
|
||||||
|
char linesStr[8];
|
||||||
|
std::snprintf(linesStr, sizeof(linesStr), "%d", hs[i].lines);
|
||||||
|
useFont->draw(renderer, colX[3], y, linesStr, 1.0f, SDL_Color{220, 220, 230, 255});
|
||||||
|
|
||||||
|
char levelStr[8];
|
||||||
|
std::snprintf(levelStr, sizeof(levelStr), "%d", hs[i].level);
|
||||||
|
useFont->draw(renderer, colX[4], y, levelStr, 1.0f, SDL_Color{220, 220, 230, 255});
|
||||||
|
|
||||||
|
char timeStr[16];
|
||||||
|
int mins = int(hs[i].timeSec) / 60;
|
||||||
|
int secs = int(hs[i].timeSec) % 60;
|
||||||
|
std::snprintf(timeStr, sizeof(timeStr), "%d:%02d", mins, secs);
|
||||||
|
useFont->draw(renderer, colX[5], y, timeStr, 1.0f, SDL_Color{220, 220, 230, 255});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Center columns around mid X, wider
|
|
||||||
float cx = LOGICAL_W * 0.5f + contentOffsetX;
|
|
||||||
float colX[] = { cx - 280, cx - 180, cx - 20, cx + 90, cx + 200, cx + 300 };
|
|
||||||
|
|
||||||
for (size_t i = 0; i < maxDisplay; ++i)
|
|
||||||
{
|
|
||||||
float baseY = scoresStartY + i * 25;
|
|
||||||
float wave = std::sin((float)GlobalState::instance().logoAnimCounter * 0.006f + i * 0.25f) * 6.0f; // subtle wave
|
|
||||||
float y = baseY + wave;
|
|
||||||
|
|
||||||
char rankStr[8];
|
// Draw the sci-fi overlay that sits above the scoreboard but below the buttons
|
||||||
std::snprintf(rankStr, sizeof(rankStr), "%zu.", i + 1);
|
SDL_Texture* overlayTex = ctx.mainScreenTex;
|
||||||
if (useFont) useFont->draw(renderer, colX[0], y, rankStr, 1.0f, SDL_Color{220, 220, 230, 255});
|
int overlayW = ctx.mainScreenW;
|
||||||
|
int overlayH = ctx.mainScreenH;
|
||||||
|
|
||||||
if (useFont) useFont->draw(renderer, colX[1], y, hs[i].name, 1.0f, SDL_Color{220, 220, 230, 255});
|
static SDL_Texture* fallbackOverlay = nullptr;
|
||||||
|
static int fallbackW = 0;
|
||||||
|
static int fallbackH = 0;
|
||||||
|
|
||||||
char scoreStr[16]; std::snprintf(scoreStr, sizeof(scoreStr), "%d", hs[i].score);
|
if (!overlayTex) {
|
||||||
if (useFont) useFont->draw(renderer, colX[2], y, scoreStr, 1.0f, SDL_Color{220, 220, 230, 255});
|
if (!fallbackOverlay) {
|
||||||
|
const std::string resolvedOverlay = AssetPath::resolveImagePath("assets/images/main_screen.bmp");
|
||||||
|
fallbackOverlay = IMG_LoadTexture(renderer, resolvedOverlay.c_str());
|
||||||
|
if (fallbackOverlay) {
|
||||||
|
SDL_SetTextureBlendMode(fallbackOverlay, SDL_BLENDMODE_BLEND);
|
||||||
|
float tmpW = 0.0f;
|
||||||
|
float tmpH = 0.0f;
|
||||||
|
SDL_GetTextureSize(fallbackOverlay, &tmpW, &tmpH);
|
||||||
|
fallbackW = static_cast<int>(tmpW);
|
||||||
|
fallbackH = static_cast<int>(tmpH);
|
||||||
|
FILE* f = fopen("tetris_trace.log", "a");
|
||||||
|
if (f) {
|
||||||
|
fprintf(f, "MenuState::render loaded fallback overlay texture %p path=%s size=%dx%d\n",
|
||||||
|
(void*)fallbackOverlay, resolvedOverlay.c_str(), fallbackW, fallbackH);
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FILE* f = fopen("tetris_trace.log", "a");
|
||||||
|
if (f) {
|
||||||
|
fprintf(f, "MenuState::render failed to load fallback overlay: %s\n", SDL_GetError());
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
overlayTex = fallbackOverlay;
|
||||||
|
overlayW = fallbackW;
|
||||||
|
overlayH = fallbackH;
|
||||||
|
}
|
||||||
|
|
||||||
char linesStr[8]; std::snprintf(linesStr, sizeof(linesStr), "%d", hs[i].lines);
|
if (overlayTex) {
|
||||||
if (useFont) useFont->draw(renderer, colX[3], y, linesStr, 1.0f, SDL_Color{220, 220, 230, 255});
|
{
|
||||||
|
FILE* f = fopen("tetris_trace.log", "a");
|
||||||
char levelStr[8]; std::snprintf(levelStr, sizeof(levelStr), "%d", hs[i].level);
|
if (f) {
|
||||||
if (useFont) useFont->draw(renderer, colX[4], y, levelStr, 1.0f, SDL_Color{220, 220, 230, 255});
|
fprintf(f, "MenuState::render overlay tex=%llu dims=%dx%d\n",
|
||||||
|
(unsigned long long)(uintptr_t)overlayTex,
|
||||||
char timeStr[16]; int mins = int(hs[i].timeSec) / 60; int secs = int(hs[i].timeSec) % 60;
|
overlayW,
|
||||||
std::snprintf(timeStr, sizeof(timeStr), "%d:%02d", mins, secs);
|
overlayH);
|
||||||
if (useFont) useFont->draw(renderer, colX[5], y, timeStr, 1.0f, SDL_Color{220, 220, 230, 255});
|
fclose(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
float texW = overlayW > 0 ? static_cast<float>(overlayW) : 0.0f;
|
||||||
|
float texH = overlayH > 0 ? static_cast<float>(overlayH) : 0.0f;
|
||||||
|
if (texW <= 0.0f || texH <= 0.0f) {
|
||||||
|
if (!SDL_GetTextureSize(overlayTex, &texW, &texH)) {
|
||||||
|
FILE* f = fopen("tetris_trace.log", "a");
|
||||||
|
if (f) {
|
||||||
|
fprintf(f, "MenuState::render failed to query overlay size: %s\n", SDL_GetError());
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
texW = 0.0f;
|
||||||
|
texH = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (texW > 0.0f && texH > 0.0f) {
|
||||||
|
const float drawH = LOGICAL_H;
|
||||||
|
const float scale = drawH / texH;
|
||||||
|
const float drawW = texW * scale;
|
||||||
|
SDL_FRect dst{
|
||||||
|
(LOGICAL_W - drawW) * 0.5f + contentOffsetX,
|
||||||
|
contentOffsetY,
|
||||||
|
drawW,
|
||||||
|
drawH
|
||||||
|
};
|
||||||
|
int renderResult = SDL_RenderTexture(renderer, overlayTex, nullptr, &dst);
|
||||||
|
if (renderResult < 0) {
|
||||||
|
FILE* f = fopen("tetris_trace.log", "a");
|
||||||
|
if (f) {
|
||||||
|
fprintf(f, "MenuState::render failed to draw overlay: %s\n", SDL_GetError());
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FILE* f = fopen("tetris_trace.log", "a");
|
||||||
|
if (f) {
|
||||||
|
fprintf(f, "MenuState::render no overlay texture available\n");
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw bottom action buttons with responsive sizing (reduced to match main mouse hit-test)
|
// Draw bottom action buttons with responsive sizing (reduced to match main mouse hit-test)
|
||||||
|
|||||||
@ -39,6 +39,9 @@ struct StateContext {
|
|||||||
// backgroundTex is set once in `main.cpp` and passed to states via this context.
|
// backgroundTex is set once in `main.cpp` and passed to states via this context.
|
||||||
// Prefer reading this field instead of relying on any `extern SDL_Texture*` globals.
|
// Prefer reading this field instead of relying on any `extern SDL_Texture*` globals.
|
||||||
SDL_Texture* blocksTex = nullptr;
|
SDL_Texture* blocksTex = nullptr;
|
||||||
|
SDL_Texture* mainScreenTex = nullptr;
|
||||||
|
int mainScreenW = 0;
|
||||||
|
int mainScreenH = 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
|
||||||
|
|||||||
Reference in New Issue
Block a user