aligment fix in next block
This commit is contained in:
@ -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{};
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user