aligment fix in next block
This commit is contained in:
@ -51,6 +51,87 @@ std::vector<Sparkle> s_sparkles;
|
||||
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)
|
||||
static const SDL_Color COLORS[] = {
|
||||
{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) {
|
||||
if (pieceType >= PIECE_COUNT) return;
|
||||
|
||||
@ -129,10 +215,29 @@ void GameRenderer::drawSmallPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex
|
||||
previewPiece.x = 0;
|
||||
previewPiece.y = 0;
|
||||
|
||||
// Center the piece in the preview area
|
||||
float offsetX = 0, offsetY = 0;
|
||||
if (pieceType == 0) { offsetX = tileSize * 0.5f; } // I-piece centering (assuming I = 0)
|
||||
else if (pieceType == 1) { offsetX = tileSize * 0.5f; } // O-piece centering (assuming O = 1)
|
||||
// Determine occupied bounds within 4x4 and center inside the 4x4 preview area
|
||||
int minCx = 4, maxCx = -1, minCy = 4, maxCy = -1;
|
||||
for (int cy = 0; cy < 4; ++cy) {
|
||||
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
|
||||
Uint8 previewAlpha = 180;
|
||||
@ -145,7 +250,7 @@ void GameRenderer::drawSmallPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex
|
||||
if (Game::cellFilled(previewPiece, cx, cy)) {
|
||||
float px = x + offsetX + cx * 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 previewBottom = panelY + panelH - tileSize * 0.25f;
|
||||
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 pieceHeight = static_cast<float>(maxCy - minCy + 1) * tileSize;
|
||||
// Nudge preview slightly to the right so it aligns with the main grid's visual columns
|
||||
const float previewNudgeX = tileSize * 0.5f;
|
||||
const float startX = previewCenterX - pieceWidth * 0.5f - static_cast<float>(minCx) * tileSize + previewNudgeX;
|
||||
const float startY = previewCenterY - pieceHeight * 0.5f - static_cast<float>(minCy) * tileSize;
|
||||
// Center piece so its local cells fall exactly on grid-aligned pixel columns
|
||||
float startX = previewCenterX - pieceWidth * 0.5f - static_cast<float>(minCx) * tileSize;
|
||||
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 cx = 0; cx < 4; ++cx) {
|
||||
@ -244,7 +357,7 @@ void GameRenderer::renderNextPanel(
|
||||
}
|
||||
const float px = startX + static_cast<float>(cx) * 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;
|
||||
|
||||
// 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_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
|
||||
|
||||
// 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);
|
||||
|
||||
// Draw a thin horizontal connector at the bottom of the NEXT panel so it visually
|
||||
// connects to the top of the grid (appears as a single continuous frame).
|
||||
// Draw a small filled connector to visually merge NEXT panel and 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)
|
||||
// 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_RenderFillRect(renderer, &connRect);
|
||||
|
||||
// Draw transport effect if active (renders the moving piece and trail)
|
||||
updateAndDrawTransport(renderer, blocksTex);
|
||||
|
||||
// Precompute row drop offsets (line collapse effect)
|
||||
std::array<float, Game::ROWS> rowDropOffsets{};
|
||||
|
||||
Reference in New Issue
Block a user