aligment fix in next block

This commit is contained in:
2025-12-07 13:00:05 +01:00
parent d2ba311c5f
commit 24779755a5
2 changed files with 141 additions and 16 deletions

View File

@ -51,6 +51,87 @@ std::vector<Sparkle> s_sparkles;
float s_sparkleSpawnAcc = 0.0f; float s_sparkleSpawnAcc = 0.0f;
} }
struct TransportEffectState {
bool active = false;
Uint32 startTick = 0;
float durationMs = 600.0f;
Game::Piece piece;
float startX = 0.0f; // pixel origin of piece local (0,0)
float startY = 0.0f;
float targetX = 0.0f;
float targetY = 0.0f;
float tileSize = 24.0f;
};
static TransportEffectState s_transport;
static float smoothstep(float t) {
t = std::clamp(t, 0.0f, 1.0f);
return t * t * (3.0f - 2.0f * t);
}
void GameRenderer::startTransportEffect(const Game::Piece& piece, float startX, float startY, float targetX, float targetY, float tileSize, float durationSeconds) {
s_transport.active = true;
s_transport.startTick = SDL_GetTicks();
s_transport.durationMs = std::max(8.0f, durationSeconds * 1000.0f);
s_transport.piece = piece;
s_transport.startX = startX;
s_transport.startY = startY;
s_transport.targetX = targetX;
s_transport.targetY = targetY;
s_transport.tileSize = tileSize;
}
// Draw the ongoing transport effect; called every frame from renderPlayingState
static void updateAndDrawTransport(SDL_Renderer* renderer, SDL_Texture* blocksTex) {
if (!s_transport.active) return;
Uint32 now = SDL_GetTicks();
float elapsed = static_cast<float>(now - s_transport.startTick);
float t = elapsed / s_transport.durationMs;
float eased = smoothstep(std::clamp(t, 0.0f, 1.0f));
// Draw trailing particles / beam along the path
const int trailCount = 10;
for (int i = 0; i < trailCount; ++i) {
float p = eased - (static_cast<float>(i) * 0.04f);
if (p <= 0.0f) continue;
p = std::clamp(p, 0.0f, 1.0f);
float px = std::lerp(s_transport.startX, s_transport.targetX, p);
float py = std::lerp(s_transport.startY, s_transport.targetY, p);
// jitter for sci-fi shimmer
float jitter = static_cast<float>(std::sin((now + i * 37) * 0.01f)) * (s_transport.tileSize * 0.06f);
SDL_FRect r{px + jitter, py - s_transport.tileSize * 0.06f, s_transport.tileSize * 0.18f, s_transport.tileSize * 0.18f};
SDL_SetTextureColorMod(blocksTex, 255, 255, 255);
SDL_SetTextureAlphaMod(blocksTex, static_cast<Uint8>(std::clamp(255.0f * (0.5f * (1.0f - p)), 0.0f, 255.0f)));
GameRenderer::drawBlockTexturePublic(renderer, blocksTex, r.x, r.y, r.w, static_cast<int>(s_transport.piece.type));
}
// reset texture alpha to full
if (blocksTex) SDL_SetTextureAlphaMod(blocksTex, 255);
// Draw the piece itself at interpolated position between start and target
float curX = std::lerp(s_transport.startX, s_transport.targetX, eased);
float curY = std::lerp(s_transport.startY, s_transport.targetY, eased);
// Render all filled cells of the piece at pixel coordinates
for (int cy = 0; cy < 4; ++cy) {
for (int cx = 0; cx < 4; ++cx) {
if (!Game::cellFilled(s_transport.piece, cx, cy)) continue;
float bx = curX + static_cast<float>(cx) * s_transport.tileSize;
float by = curY + static_cast<float>(cy) * s_transport.tileSize;
// pulse alpha while moving
float pulse = 0.6f + 0.4f * std::sin((now - s_transport.startTick) * 0.02f);
SDL_SetTextureAlphaMod(blocksTex, static_cast<Uint8>(std::clamp(255.0f * pulse * (1.0f - t), 0.0f, 255.0f)));
GameRenderer::drawBlockTexturePublic(renderer, blocksTex, bx, by, s_transport.tileSize, s_transport.piece.type);
}
}
if (blocksTex) SDL_SetTextureAlphaMod(blocksTex, 255);
if (t >= 1.0f) {
s_transport.active = false;
}
}
// Color constants (copied from main.cpp) // Color constants (copied from main.cpp)
static const SDL_Color COLORS[] = { static const SDL_Color COLORS[] = {
{0, 0, 0, 255}, // 0: BLACK (empty) {0, 0, 0, 255}, // 0: BLACK (empty)
@ -119,6 +200,11 @@ void GameRenderer::drawPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex, con
} }
} }
void GameRenderer::drawBlockTexturePublic(SDL_Renderer* renderer, SDL_Texture* blocksTex, float x, float y, float size, int blockType) {
// Forward to the private helper
drawBlockTexture(renderer, blocksTex, x, y, size, blockType);
}
void GameRenderer::drawSmallPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex, PieceType pieceType, float x, float y, float tileSize) { void GameRenderer::drawSmallPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex, PieceType pieceType, float x, float y, float tileSize) {
if (pieceType >= PIECE_COUNT) return; if (pieceType >= PIECE_COUNT) return;
@ -129,10 +215,29 @@ void GameRenderer::drawSmallPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex
previewPiece.x = 0; previewPiece.x = 0;
previewPiece.y = 0; previewPiece.y = 0;
// Center the piece in the preview area // Determine occupied bounds within 4x4 and center inside the 4x4 preview area
float offsetX = 0, offsetY = 0; int minCx = 4, maxCx = -1, minCy = 4, maxCy = -1;
if (pieceType == 0) { offsetX = tileSize * 0.5f; } // I-piece centering (assuming I = 0) for (int cy = 0; cy < 4; ++cy) {
else if (pieceType == 1) { offsetX = tileSize * 0.5f; } // O-piece centering (assuming O = 1) for (int cx = 0; cx < 4; ++cx) {
if (Game::cellFilled(previewPiece, cx, cy)) {
minCx = std::min(minCx, cx);
maxCx = std::max(maxCx, cx);
minCy = std::min(minCy, cy);
maxCy = std::max(maxCy, cy);
}
}
}
if (maxCx < minCx) { minCx = 0; maxCx = 0; }
if (maxCy < minCy) { minCy = 0; maxCy = 0; }
float areaW = 4.0f * tileSize;
float areaH = 4.0f * tileSize;
float pieceW = static_cast<float>(maxCx - minCx + 1) * tileSize;
float pieceH = static_cast<float>(maxCy - minCy + 1) * tileSize;
float offsetX = (areaW - pieceW) * 0.5f - static_cast<float>(minCx) * tileSize;
float offsetY = (areaH - pieceH) * 0.5f - static_cast<float>(minCy) * tileSize;
offsetX = std::round(offsetX);
offsetY = std::round(offsetY);
// Use semi-transparent alpha for preview blocks // Use semi-transparent alpha for preview blocks
Uint8 previewAlpha = 180; Uint8 previewAlpha = 180;
@ -145,7 +250,7 @@ void GameRenderer::drawSmallPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex
if (Game::cellFilled(previewPiece, cx, cy)) { if (Game::cellFilled(previewPiece, cx, cy)) {
float px = x + offsetX + cx * tileSize; float px = x + offsetX + cx * tileSize;
float py = y + offsetY + cy * tileSize; float py = y + offsetY + cy * tileSize;
drawBlockTexture(renderer, blocksTex, px, py, tileSize, pieceType); GameRenderer::drawBlockTexturePublic(renderer, blocksTex, px, py, tileSize, pieceType);
} }
} }
} }
@ -228,14 +333,22 @@ void GameRenderer::renderNextPanel(
const float previewTop = panelY + std::min(labelReserve, panelH * 0.45f); const float previewTop = panelY + std::min(labelReserve, panelH * 0.45f);
const float previewBottom = panelY + panelH - tileSize * 0.25f; const float previewBottom = panelY + panelH - tileSize * 0.25f;
const float previewCenterY = (previewTop + previewBottom) * 0.5f; const float previewCenterY = (previewTop + previewBottom) * 0.5f;
const float previewCenterX = panelX + panelW * 0.5f; const float previewCenterX = std::round(panelX + panelW * 0.5f);
const float pieceWidth = static_cast<float>(maxCx - minCx + 1) * tileSize; const float pieceWidth = static_cast<float>(maxCx - minCx + 1) * tileSize;
const float pieceHeight = static_cast<float>(maxCy - minCy + 1) * tileSize; const float pieceHeight = static_cast<float>(maxCy - minCy + 1) * tileSize;
// Nudge preview slightly to the right so it aligns with the main grid's visual columns // Center piece so its local cells fall exactly on grid-aligned pixel columns
const float previewNudgeX = tileSize * 0.5f; float startX = previewCenterX - pieceWidth * 0.5f - static_cast<float>(minCx) * tileSize;
const float startX = previewCenterX - pieceWidth * 0.5f - static_cast<float>(minCx) * tileSize + previewNudgeX; float startY = previewCenterY - pieceHeight * 0.5f - static_cast<float>(minCy) * tileSize;
const float startY = previewCenterY - pieceHeight * 0.5f - static_cast<float>(minCy) * tileSize; // Snap horizontal position to the playfield's tile grid so preview cells align exactly
// with the main grid columns. `panelX` was computed as `gridX + tileSize` in caller,
// so derive grid origin as `panelX - tileSize`.
float gridOriginX = panelX - tileSize;
float rel = startX - gridOriginX;
float nearestTile = std::round(rel / tileSize);
startX = gridOriginX + nearestTile * tileSize;
// Round Y to pixel to avoid subpixel artifacts
startY = std::round(startY);
for (int cy = 0; cy < 4; ++cy) { for (int cy = 0; cy < 4; ++cy) {
for (int cx = 0; cx < 4; ++cx) { for (int cx = 0; cx < 4; ++cx) {
@ -244,7 +357,7 @@ void GameRenderer::renderNextPanel(
} }
const float px = startX + static_cast<float>(cx) * tileSize; const float px = startX + static_cast<float>(cx) * tileSize;
const float py = startY + static_cast<float>(cy) * tileSize; const float py = startY + static_cast<float>(cy) * tileSize;
drawBlockTexture(renderer, blocksTex, px, py, tileSize, nextPiece.type); GameRenderer::drawBlockTexturePublic(renderer, blocksTex, px, py, tileSize, nextPiece.type);
} }
} }
} }
@ -328,9 +441,10 @@ void GameRenderer::renderPlayingState(
const float statsH = GRID_H; const float statsH = GRID_H;
// Next piece preview position // Next piece preview position
const float NEXT_PANEL_WIDTH = finalBlockSize * 6.0f; // +1 cell padding on each horizontal side // Make NEXT panel span the inner area of the grid with a 1-cell margin on both sides
const float NEXT_PANEL_WIDTH = GRID_W - finalBlockSize * 2.0f; // leave 1 cell on left and right
const float NEXT_PANEL_HEIGHT = finalBlockSize * 3.0f; const float NEXT_PANEL_HEIGHT = finalBlockSize * 3.0f;
const float NEXT_PANEL_X = gridX + (GRID_W - NEXT_PANEL_WIDTH) * 0.5f; const float NEXT_PANEL_X = gridX + finalBlockSize; // align panel so there's exactly one cell margin
const float NEXT_PANEL_Y = gridY - NEXT_PANEL_HEIGHT - 2.0f; // nudge up ~2px const float NEXT_PANEL_Y = gridY - NEXT_PANEL_HEIGHT - 2.0f; // nudge up ~2px
// Handle line clearing effects // Handle line clearing effects
@ -548,13 +662,14 @@ void GameRenderer::renderPlayingState(
renderNextPanel(renderer, pixelFont, blocksTex, game->next(), NEXT_PANEL_X, NEXT_PANEL_Y, NEXT_PANEL_WIDTH, NEXT_PANEL_HEIGHT, finalBlockSize); renderNextPanel(renderer, pixelFont, blocksTex, game->next(), NEXT_PANEL_X, NEXT_PANEL_Y, NEXT_PANEL_WIDTH, NEXT_PANEL_HEIGHT, finalBlockSize);
// Draw a thin horizontal connector at the bottom of the NEXT panel so it visually // Draw a small filled connector to visually merge NEXT panel and grid border
// connects to the top of the grid (appears as a single continuous frame).
SDL_SetRenderDrawColor(renderer, 60, 80, 160, 255); // same as grid border SDL_SetRenderDrawColor(renderer, 60, 80, 160, 255); // same as grid border
float connectorY = NEXT_PANEL_Y + NEXT_PANEL_HEIGHT; // bottom of next panel (near grid top) float connectorY = NEXT_PANEL_Y + NEXT_PANEL_HEIGHT; // bottom of next panel (near grid top)
// Draw a 2px-high filled connector to overwrite any existing grid border pixels
SDL_FRect connRect{ NEXT_PANEL_X, connectorY - 1.0f, NEXT_PANEL_WIDTH, 2.0f }; SDL_FRect connRect{ NEXT_PANEL_X, connectorY - 1.0f, NEXT_PANEL_WIDTH, 2.0f };
SDL_RenderFillRect(renderer, &connRect); SDL_RenderFillRect(renderer, &connRect);
// Draw transport effect if active (renders the moving piece and trail)
updateAndDrawTransport(renderer, blocksTex);
// Precompute row drop offsets (line collapse effect) // Precompute row drop offsets (line collapse effect)
std::array<float, Game::ROWS> rowDropOffsets{}; std::array<float, Game::ROWS> rowDropOffsets{};

View File

@ -48,12 +48,22 @@ public:
int selectedButton int selectedButton
); );
// Public wrapper that forwards to the private tile-drawing helper. Use this if
// calling from non-member helper functions (e.g. visual effects) that cannot
// access private class members.
static void drawBlockTexturePublic(SDL_Renderer* renderer, SDL_Texture* blocksTex, float x, float y, float size, int blockType);
private: private:
// Helper functions for drawing game elements // Helper functions for drawing game elements
static void drawBlockTexture(SDL_Renderer* renderer, SDL_Texture* blocksTex, float x, float y, float size, int blockType); static void drawBlockTexture(SDL_Renderer* renderer, SDL_Texture* blocksTex, float x, float y, float size, int blockType);
static void drawPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex, const Game::Piece& piece, float ox, float oy, float tileSize, bool isGhost = false, float pixelOffsetX = 0.0f, float pixelOffsetY = 0.0f); static void drawPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex, const Game::Piece& piece, float ox, float oy, float tileSize, bool isGhost = false, float pixelOffsetX = 0.0f, float pixelOffsetY = 0.0f);
static void drawSmallPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex, PieceType pieceType, float x, float y, float tileSize); static void drawSmallPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex, PieceType pieceType, float x, float y, float tileSize);
static void renderNextPanel(SDL_Renderer* renderer, FontAtlas* pixelFont, SDL_Texture* blocksTex, const Game::Piece& nextPiece, float panelX, float panelY, float panelW, float panelH, float tileSize); static void renderNextPanel(SDL_Renderer* renderer, FontAtlas* pixelFont, SDL_Texture* blocksTex, const Game::Piece& nextPiece, float panelX, float panelY, float panelW, float panelH, float tileSize);
// Transport/teleport visual effect: start a sci-fi "transport" animation moving
// a visual copy of `piece` from screen pixel origin (startX,startY) to
// target pixel origin (targetX,targetY). `tileSize` should be the same cell size
// used for the grid. Duration is seconds.
static void startTransportEffect(const Game::Piece& piece, float startX, float startY, float targetX, float targetY, float tileSize, float durationSeconds = 0.6f);
// Helper function for drawing rectangles // Helper function for drawing rectangles
static void drawRect(SDL_Renderer* renderer, float x, float y, float w, float h, SDL_Color c); static void drawRect(SDL_Renderer* renderer, float x, float y, float w, float h, SDL_Color c);