feat: implement textured line clear effects and refine UI alignment
- **Visual Effects**: Upgraded line clear particles to use the game's block texture instead of simple circles, matching the reference web game's aesthetic. - **Particle Physics**: Tuned particle velocity, gravity, and fade rates for a more dynamic explosion effect. - **Rendering Integration**: Updated [main.cpp](cci:7://file:///d:/Sites/Work/tetris/src/main.cpp:0:0-0:0) and `GameRenderer` to pass the block texture to the effect system and correctly trigger animations upon line completion. - **Menu UI**: Fixed [MenuState](cci:1://file:///d:/Sites/Work/tetris/src/states/MenuState.cpp:19:0-19:55) layout calculations to use fixed logical dimensions (1200x1000), ensuring consistent centering and alignment of the logo, buttons, and settings icon across different window sizes. - **Code Cleanup**: Refactored `PlayingState` to delegate effect triggering to the rendering layer where correct screen coordinates are available.
This commit is contained in:
@ -1,16 +1,15 @@
|
||||
// LevelSelectorState.cpp - Level selection popup state implementation
|
||||
#include "LevelSelectorState.h"
|
||||
#include "State.h"
|
||||
#include "../core/StateManager.h"
|
||||
#include "../graphics/Font.h"
|
||||
#include "../core/state/StateManager.h"
|
||||
#include "../core/GlobalState.h"
|
||||
#include "../graphics/ui/Font.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
// Constants from main.cpp
|
||||
static constexpr int LOGICAL_W = 1200;
|
||||
static constexpr int LOGICAL_H = 1000;
|
||||
// Use dynamic logical dimensions from GlobalState instead of hardcoded values
|
||||
|
||||
// --- Minimal draw helpers and look-and-feel adapted from the sample ---
|
||||
static inline SDL_Color RGBA(Uint8 r, Uint8 g, Uint8 b, Uint8 a = 255) { return SDL_Color{r, g, b, a}; }
|
||||
@ -187,6 +186,10 @@ void LevelSelectorState::handleEvent(const SDL_Event& e) {
|
||||
if (ctx.startLevelSelection) *ctx.startLevelSelection = hoveredLevel;
|
||||
} else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
|
||||
if (e.button.button == SDL_BUTTON_LEFT) {
|
||||
// Get dynamic logical dimensions
|
||||
const int LOGICAL_W = GlobalState::instance().getLogicalWidth();
|
||||
const int LOGICAL_H = GlobalState::instance().getLogicalHeight();
|
||||
|
||||
// convert mouse to logical coords (viewport is already centered)
|
||||
float lx = (float(e.button.x) - float(lastLogicalVP.x)) / (lastLogicalScale > 0.f ? lastLogicalScale : 1.f);
|
||||
float ly = (float(e.button.y) - float(lastLogicalVP.y)) / (lastLogicalScale > 0.f ? lastLogicalScale : 1.f);
|
||||
@ -200,6 +203,10 @@ void LevelSelectorState::handleEvent(const SDL_Event& e) {
|
||||
}
|
||||
}
|
||||
} else if (e.type == SDL_EVENT_MOUSE_MOTION) {
|
||||
// Get dynamic logical dimensions
|
||||
const int LOGICAL_W = GlobalState::instance().getLogicalWidth();
|
||||
const int LOGICAL_H = GlobalState::instance().getLogicalHeight();
|
||||
|
||||
// convert mouse to logical coords (viewport is already centered)
|
||||
float lx = (float(e.motion.x) - float(lastLogicalVP.x)) / (lastLogicalScale > 0.f ? lastLogicalScale : 1.f);
|
||||
float ly = (float(e.motion.y) - float(lastLogicalVP.y)) / (lastLogicalScale > 0.f ? lastLogicalScale : 1.f);
|
||||
@ -224,6 +231,10 @@ void LevelSelectorState::render(SDL_Renderer* renderer, float logicalScale, SDL_
|
||||
void LevelSelectorState::drawLevelSelectionPopup(SDL_Renderer* renderer) {
|
||||
if (!renderer) return;
|
||||
|
||||
// Get dynamic logical dimensions
|
||||
const int LOGICAL_W = GlobalState::instance().getLogicalWidth();
|
||||
const int LOGICAL_H = GlobalState::instance().getLogicalHeight();
|
||||
|
||||
// Since ApplicationManager sets up a centered viewport, we draw directly in logical coordinates
|
||||
// The viewport (LOGICAL_W x LOGICAL_H) is already centered within the window
|
||||
float vw = float(LOGICAL_W);
|
||||
@ -252,6 +263,10 @@ void LevelSelectorState::drawLevelSelectionPopup(SDL_Renderer* renderer) {
|
||||
}
|
||||
|
||||
bool LevelSelectorState::isMouseInPopup(float mouseX, float mouseY, float& popupX, float& popupY, float& popupW, float& popupH) {
|
||||
// Get dynamic logical dimensions
|
||||
const int LOGICAL_W = GlobalState::instance().getLogicalWidth();
|
||||
const int LOGICAL_H = GlobalState::instance().getLogicalHeight();
|
||||
|
||||
// Simplified: viewport is already centered, just convert mouse to logical coords
|
||||
(void)mouseX; (void)mouseY;
|
||||
float lx = 0.f, ly = 0.f;
|
||||
@ -265,6 +280,10 @@ bool LevelSelectorState::isMouseInPopup(float mouseX, float mouseY, float& popup
|
||||
}
|
||||
|
||||
int LevelSelectorState::getLevelFromMouse(float mouseX, float mouseY, float popupX, float popupY, float popupW, float popupH) {
|
||||
// Get dynamic logical dimensions
|
||||
const int LOGICAL_W = GlobalState::instance().getLogicalWidth();
|
||||
const int LOGICAL_H = GlobalState::instance().getLogicalHeight();
|
||||
|
||||
(void)popupX; (void)popupY; (void)popupW; (void)popupH;
|
||||
float lx = 0.f, ly = 0.f;
|
||||
if (lastLogicalScale > 0.0f) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// LoadingState.cpp
|
||||
#include "LoadingState.h"
|
||||
#include "gameplay/Game.h"
|
||||
#include "../gameplay/core/Game.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <cstdio>
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
// MenuState.cpp
|
||||
#include "MenuState.h"
|
||||
#include "persistence/Scores.h"
|
||||
#include "graphics/Font.h"
|
||||
@ -9,9 +8,8 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
// Local logical canvas size (matches main.cpp). Kept local to avoid changing many files.
|
||||
static constexpr int LOGICAL_W = 1200;
|
||||
static constexpr int LOGICAL_H = 1000;
|
||||
// Use dynamic logical dimensions from GlobalState instead of hardcoded values
|
||||
// This allows the UI to adapt when the window is resized or goes fullscreen
|
||||
|
||||
// Shared flags and resources are provided via StateContext `ctx`.
|
||||
// Removed fragile extern declarations and use `ctx.showLevelPopup`, `ctx.showSettingsPopup`,
|
||||
@ -39,14 +37,23 @@ void MenuState::update(double frameMs) {
|
||||
}
|
||||
|
||||
void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logicalVP) {
|
||||
// Use fixed logical dimensions to match main.cpp and ensure consistent layout
|
||||
// This prevents the UI from floating apart on wide/tall screens
|
||||
const float LOGICAL_W = 1200.f;
|
||||
const float LOGICAL_H = 1000.f;
|
||||
|
||||
// Trace entry to persistent log for debugging abrupt exit/crash during render
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render entry\n"); fclose(f); }
|
||||
}
|
||||
// Since ApplicationManager sets up a centered viewport, we draw directly in logical coordinates
|
||||
// No additional content offset is needed - the viewport itself handles centering
|
||||
float contentOffsetX = 0.0f;
|
||||
float contentOffsetY = 0.0f;
|
||||
|
||||
// Compute content offsets (same approach as main.cpp for proper centering)
|
||||
float winW = (float)logicalVP.w;
|
||||
float winH = (float)logicalVP.h;
|
||||
float contentW = LOGICAL_W * logicalScale;
|
||||
float contentH = LOGICAL_H * logicalScale;
|
||||
float contentOffsetX = (winW - contentW) * 0.5f / logicalScale;
|
||||
float contentOffsetY = (winH - contentH) * 0.5f / logicalScale;
|
||||
|
||||
// Background is drawn by main (stretched to the full window) to avoid double-draw.
|
||||
|
||||
@ -94,7 +101,10 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
||||
{
|
||||
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); }
|
||||
}
|
||||
useFont->draw(renderer, LOGICAL_W * 0.5f - 110 + contentOffsetX, topPlayersY, std::string("TOP PLAYERS"), 1.8f, SDL_Color{255, 220, 0, 255});
|
||||
const std::string title = "TOP PLAYERS";
|
||||
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); }
|
||||
}
|
||||
@ -148,8 +158,7 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
||||
}
|
||||
|
||||
// Draw bottom action buttons with responsive sizing (reduced to match main mouse hit-test)
|
||||
// Since we removed content offsets, calculate contentW directly from the scale and logical size
|
||||
float contentW = LOGICAL_W * logicalScale;
|
||||
// Use the contentW calculated at the top with content offsets
|
||||
bool isSmall = (contentW < 700.0f);
|
||||
float btnW = isSmall ? (LOGICAL_W * 0.4f) : 300.0f;
|
||||
float btnH = isSmall ? 60.0f : 70.0f;
|
||||
@ -178,7 +187,6 @@ void MenuState::render(SDL_Renderer* renderer, float logicalScale, SDL_Rect logi
|
||||
};
|
||||
drawMenuButtonLocal(renderer, *ctx.pixelFont, btnX - btnW * 0.6f, btnY, btnW, btnH, std::string("PLAY"), SDL_Color{60,180,80,255}, SDL_Color{30,120,40,255});
|
||||
{
|
||||
FILE* f = fopen("tetris_trace.log", "a"); if (f) { fprintf(f, "MenuState::render after draw PLAY button\n"); fclose(f); }
|
||||
}
|
||||
drawMenuButtonLocal(renderer, *ctx.pixelFont, btnX + btnW * 0.6f, btnY, btnW, btnH, std::string(levelBtnText), SDL_Color{40,140,240,255}, SDL_Color{20,100,200,255});
|
||||
{
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
#include "PlayingState.h"
|
||||
#include "core/StateManager.h"
|
||||
#include "gameplay/Game.h"
|
||||
#include "gameplay/LineEffect.h"
|
||||
#include "persistence/Scores.h"
|
||||
#include "../core/state/StateManager.h"
|
||||
#include "../gameplay/core/Game.h"
|
||||
#include "../gameplay/effects/LineEffect.h"
|
||||
#include "../persistence/Scores.h"
|
||||
#include "../audio/Audio.h"
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user