new background for main screen
This commit is contained in:
@ -44,6 +44,7 @@ set(TETRIS_SOURCES
|
||||
src/persistence/Scores.cpp
|
||||
src/graphics/effects/Starfield.cpp
|
||||
src/graphics/effects/Starfield3D.cpp
|
||||
src/graphics/effects/SpaceWarp.cpp
|
||||
src/graphics/ui/Font.cpp
|
||||
src/graphics/ui/HelpOverlay.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 "graphics/effects/Starfield.h"
|
||||
#include "graphics/effects/Starfield3D.h"
|
||||
#include "graphics/effects/SpaceWarp.h"
|
||||
#include "graphics/ui/Font.h"
|
||||
#include "graphics/ui/HelpOverlay.h"
|
||||
#include "gameplay/effects/LineEffect.h"
|
||||
@ -680,16 +681,15 @@ static void drawFireworks_impl(SDL_Renderer* renderer, SDL_Texture*) {
|
||||
|
||||
SDL_SetRenderDrawBlendMode(renderer, previousBlend);
|
||||
}
|
||||
// External wrappers for use by other translation units (MenuState)
|
||||
// Expect callers to pass the blocks texture via StateContext so we avoid globals.
|
||||
void menu_drawFireworks(SDL_Renderer* renderer, SDL_Texture* blocksTex) { drawFireworks_impl(renderer, blocksTex); }
|
||||
void menu_updateFireworks(double frameMs) { updateFireworks(frameMs); }
|
||||
// External wrappers retained for compatibility; now no-ops to disable the legacy fireworks effect.
|
||||
void menu_drawFireworks(SDL_Renderer*, SDL_Texture*) {}
|
||||
void menu_updateFireworks(double) {}
|
||||
double menu_getLogoAnimCounter() { return logoAnimCounter; }
|
||||
int menu_getHoveredButton() { return hoveredButton; }
|
||||
|
||||
int main(int, char **)
|
||||
{
|
||||
// Initialize random seed for fireworks
|
||||
// Initialize random seed for procedural effects
|
||||
srand(static_cast<unsigned int>(SDL_GetTicks()));
|
||||
|
||||
// Load settings
|
||||
@ -779,6 +779,8 @@ int main(int, char **)
|
||||
starfield.init(200, LOGICAL_W, LOGICAL_H);
|
||||
Starfield3D starfield3D;
|
||||
starfield3D.init(LOGICAL_W, LOGICAL_H, 200);
|
||||
SpaceWarp spaceWarp;
|
||||
spaceWarp.init(LOGICAL_W, LOGICAL_H, 420);
|
||||
|
||||
// Initialize line clearing effects
|
||||
LineEffect lineEffect;
|
||||
@ -794,6 +796,27 @@ int main(int, char **)
|
||||
// Load menu background using SDL_image (prefers JPEG)
|
||||
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.
|
||||
// States should render using `ctx.backgroundTex` rather than accessing globals.
|
||||
|
||||
@ -971,6 +994,9 @@ int main(int, char **)
|
||||
ctx.logoSmallH = logoSmallH;
|
||||
ctx.backgroundTex = backgroundTex;
|
||||
ctx.blocksTex = blocksTex;
|
||||
ctx.mainScreenTex = mainScreenTex;
|
||||
ctx.mainScreenW = mainScreenW;
|
||||
ctx.mainScreenH = mainScreenH;
|
||||
ctx.musicEnabled = &musicEnabled;
|
||||
ctx.startLevelSelection = &startLevelSelection;
|
||||
ctx.hoveredButton = &hoveredButton;
|
||||
@ -1565,21 +1591,25 @@ int main(int, char **)
|
||||
}
|
||||
previousState = state;
|
||||
|
||||
// Update starfields based on current state
|
||||
// Update background effects
|
||||
if (state == AppState::Loading) {
|
||||
starfield3D.update(float(frameMs / 1000.0f));
|
||||
starfield3D.resize(logicalVP.w, logicalVP.h); // Update for window resize
|
||||
starfield3D.resize(winW, winH);
|
||||
} else {
|
||||
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
|
||||
updateLevelBackgroundFade(levelBackgrounds, float(frameMs));
|
||||
|
||||
// Update intro animations
|
||||
if (state == AppState::Menu) {
|
||||
logoAnimCounter += frameMs * 0.0008; // Animation speed
|
||||
updateFireworks(frameMs);
|
||||
}
|
||||
|
||||
// --- Per-state update hooks (allow states to manage logic incrementally)
|
||||
@ -1671,7 +1701,7 @@ int main(int, char **)
|
||||
|
||||
// --- Render ---
|
||||
SDL_SetRenderViewport(renderer, nullptr);
|
||||
SDL_SetRenderDrawColor(renderer, 12, 12, 16, 255);
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
// Draw level-based background for gameplay, starfield for other states
|
||||
@ -1682,8 +1712,32 @@ int main(int, char **)
|
||||
} else if (state == AppState::Loading) {
|
||||
// Use 3D starfield for loading screen (full screen)
|
||||
starfield3D.draw(renderer);
|
||||
} else if (state == AppState::Menu || state == AppState::LevelSelector || state == AppState::Options) {
|
||||
// Use static background for menu, stretched to window; no starfield on sides
|
||||
} else if (state == AppState::Menu) {
|
||||
// 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) {
|
||||
SDL_FRect fullRect = { 0, 0, (float)winW, (float)winH };
|
||||
SDL_RenderTexture(renderer, backgroundTex, nullptr, &fullRect);
|
||||
@ -2021,6 +2075,8 @@ int main(int, char **)
|
||||
SDL_DestroyTexture(logoTex);
|
||||
if (backgroundTex)
|
||||
SDL_DestroyTexture(backgroundTex);
|
||||
if (mainScreenTex)
|
||||
SDL_DestroyTexture(mainScreenTex);
|
||||
resetLevelBackgrounds(levelBackgrounds);
|
||||
if (blocksTex)
|
||||
SDL_DestroyTexture(blocksTex);
|
||||
|
||||
@ -19,6 +19,8 @@
|
||||
// `ctx.musicEnabled` and `ctx.hoveredButton` instead to avoid globals.
|
||||
// Menu helper wrappers are declared in a shared header implemented in main.cpp
|
||||
#include "../audio/MenuWrappers.h"
|
||||
#include "../utils/ImagePathResolver.h"
|
||||
#include <SDL3_image/SDL_image.h>
|
||||
|
||||
MenuState::MenuState(StateContext& ctx) : State(ctx) {}
|
||||
|
||||
@ -159,9 +161,6 @@ void MenuState::handleEvent(const SDL_Event& e) {
|
||||
void MenuState::update(double frameMs) {
|
||||
// Update logo animation counter
|
||||
GlobalState::instance().logoAnimCounter += frameMs;
|
||||
|
||||
// Update fireworks particles
|
||||
GlobalState::instance().updateFireworks(frameMs);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// 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); }
|
||||
}
|
||||
if (logoToUse) {
|
||||
// Use dimensions provided by the shared context when available
|
||||
int texW = (logoToUse == ctx.logoSmallTex && ctx.logoSmallW > 0) ? ctx.logoSmallW : 872;
|
||||
int texH = (logoToUse == ctx.logoSmallTex && ctx.logoSmallH > 0) ? ctx.logoSmallH : 273;
|
||||
float maxW = LOGICAL_W * 0.6f;
|
||||
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); }
|
||||
FILE* f = fopen("tetris_trace.log", "a");
|
||||
if (f) {
|
||||
fprintf(f, "MenuState::render ctx.mainScreenTex=%llu (w=%d h=%d)\n",
|
||||
(unsigned long long)(uintptr_t)ctx.mainScreenTex,
|
||||
ctx.mainScreenW,
|
||||
ctx.mainScreenH);
|
||||
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;
|
||||
float topPlayersY = LOGICAL_H * 0.24f + contentOffsetY;
|
||||
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";
|
||||
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;
|
||||
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; // more spacing under title
|
||||
float scoresStartY = topPlayersY + 70.0f;
|
||||
static const std::vector<ScoreEntry> EMPTY_SCORES;
|
||||
const auto& hs = ctx.scores ? ctx.scores->all() : EMPTY_SCORES;
|
||||
size_t maxDisplay = std::min(hs.size(), size_t(12));
|
||||
|
||||
// Draw table header
|
||||
if (useFont) {
|
||||
float cx = LOGICAL_W * 0.5f + contentOffsetX;
|
||||
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[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});
|
||||
|
||||
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];
|
||||
std::snprintf(rankStr, sizeof(rankStr), "%zu.", i + 1);
|
||||
if (useFont) useFont->draw(renderer, colX[0], y, rankStr, 1.0f, SDL_Color{220, 220, 230, 255});
|
||||
// Draw the sci-fi overlay that sits above the scoreboard but below the buttons
|
||||
SDL_Texture* overlayTex = ctx.mainScreenTex;
|
||||
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 (useFont) useFont->draw(renderer, colX[2], y, scoreStr, 1.0f, SDL_Color{220, 220, 230, 255});
|
||||
if (!overlayTex) {
|
||||
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 (useFont) 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);
|
||||
if (useFont) 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);
|
||||
if (useFont) useFont->draw(renderer, colX[5], y, timeStr, 1.0f, SDL_Color{220, 220, 230, 255});
|
||||
if (overlayTex) {
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a");
|
||||
if (f) {
|
||||
fprintf(f, "MenuState::render overlay tex=%llu dims=%dx%d\n",
|
||||
(unsigned long long)(uintptr_t)overlayTex,
|
||||
overlayW,
|
||||
overlayH);
|
||||
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)
|
||||
|
||||
@ -39,6 +39,9 @@ struct StateContext {
|
||||
// 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.
|
||||
SDL_Texture* blocksTex = nullptr;
|
||||
SDL_Texture* mainScreenTex = nullptr;
|
||||
int mainScreenW = 0;
|
||||
int mainScreenH = 0;
|
||||
|
||||
// Audio / SFX - forward declared types in main
|
||||
// Pointers to booleans/flags used by multiple states
|
||||
|
||||
Reference in New Issue
Block a user