From ff8cada8b483517e1ad87b7e3c39ddc2ff52930f Mon Sep 17 00:00:00 2001 From: Gregor Klevze Date: Sat, 29 Nov 2025 11:23:05 +0100 Subject: [PATCH 1/4] transition effect when afvance level --- src/main.cpp | 148 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 121 insertions(+), 27 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 7b0a8e4..5e96762 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -108,15 +108,35 @@ static SDL_Texture* loadTextureFromImage(SDL_Renderer* renderer, const std::stri return texture; } +enum class LevelBackgroundPhase { Idle, ZoomOut, ZoomIn }; + struct LevelBackgroundFader { SDL_Texture* currentTex = nullptr; SDL_Texture* nextTex = nullptr; int currentLevel = -1; int queuedLevel = -1; - float fadeElapsedMs = 0.0f; + float phaseElapsedMs = 0.0f; + float phaseDurationMs = 0.0f; float fadeDurationMs = Config::Gameplay::LEVEL_FADE_DURATION; + LevelBackgroundPhase phase = LevelBackgroundPhase::Idle; }; +static float getPhaseDurationMs(const LevelBackgroundFader& fader, LevelBackgroundPhase phase) { + const float total = std::max(1200.0f, fader.fadeDurationMs); + switch (phase) { + case LevelBackgroundPhase::ZoomOut: return total * 0.45f; + case LevelBackgroundPhase::ZoomIn: return total * 0.45f; + case LevelBackgroundPhase::Idle: + default: return 0.0f; + } +} + +static void setPhase(LevelBackgroundFader& fader, LevelBackgroundPhase nextPhase) { + fader.phase = nextPhase; + fader.phaseDurationMs = getPhaseDurationMs(fader, nextPhase); + fader.phaseElapsedMs = 0.0f; +} + static void destroyTexture(SDL_Texture*& tex) { if (tex) { SDL_DestroyTexture(tex); @@ -146,32 +166,90 @@ static bool queueLevelBackground(LevelBackgroundFader& fader, SDL_Renderer* rend destroyTexture(fader.nextTex); fader.nextTex = newTexture; fader.queuedLevel = level; - fader.fadeElapsedMs = 0.0f; if (!fader.currentTex) { + // First background load happens instantly. fader.currentTex = fader.nextTex; fader.currentLevel = fader.queuedLevel; fader.nextTex = nullptr; fader.queuedLevel = -1; + fader.phase = LevelBackgroundPhase::Idle; + fader.phaseElapsedMs = 0.0f; + fader.phaseDurationMs = 0.0f; + } else if (fader.phase == LevelBackgroundPhase::Idle) { + // Kick off fancy transition. + setPhase(fader, LevelBackgroundPhase::ZoomOut); } return true; } static void updateLevelBackgroundFade(LevelBackgroundFader& fader, float frameMs) { - if (!fader.currentTex || !fader.nextTex) { + if (fader.phase == LevelBackgroundPhase::Idle) { return; } - fader.fadeElapsedMs += frameMs; - if (fader.fadeElapsedMs >= fader.fadeDurationMs) { - destroyTexture(fader.currentTex); - fader.currentTex = fader.nextTex; - fader.currentLevel = fader.queuedLevel; - fader.nextTex = nullptr; - fader.queuedLevel = -1; - fader.fadeElapsedMs = 0.0f; + // Guard against missing textures + if (!fader.currentTex && !fader.nextTex) { + fader.phase = LevelBackgroundPhase::Idle; + return; } + + fader.phaseElapsedMs += frameMs; + if (fader.phaseElapsedMs < std::max(1.0f, fader.phaseDurationMs)) { + return; + } + + switch (fader.phase) { + case LevelBackgroundPhase::ZoomOut: + // After zoom-out, swap textures then start zoom-in. + if (fader.nextTex) { + destroyTexture(fader.currentTex); + fader.currentTex = fader.nextTex; + fader.currentLevel = fader.queuedLevel; + fader.nextTex = nullptr; + fader.queuedLevel = -1; + } + setPhase(fader, LevelBackgroundPhase::ZoomIn); + break; + case LevelBackgroundPhase::ZoomIn: + fader.phase = LevelBackgroundPhase::Idle; + fader.phaseElapsedMs = 0.0f; + fader.phaseDurationMs = 0.0f; + break; + case LevelBackgroundPhase::Idle: + default: + fader.phase = LevelBackgroundPhase::Idle; + break; + } +} + +static void renderScaledBackground(SDL_Renderer* renderer, SDL_Texture* tex, int winW, int winH, float scale, Uint8 alpha = 255) { + if (!renderer || !tex) { + return; + } + + scale = std::max(0.5f, scale); + SDL_FRect dest{ + (winW - winW * scale) * 0.5f, + (winH - winH * scale) * 0.5f, + winW * scale, + winH * scale + }; + + SDL_SetTextureAlphaMod(tex, alpha); + SDL_RenderTexture(renderer, tex, nullptr, &dest); + SDL_SetTextureAlphaMod(tex, 255); +} + +static void drawOverlay(SDL_Renderer* renderer, const SDL_FRect& rect, SDL_Color color, Uint8 alpha) { + if (!renderer || alpha == 0) { + return; + } + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, alpha); + SDL_RenderFillRect(renderer, &rect); + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); } static void renderLevelBackgrounds(const LevelBackgroundFader& fader, SDL_Renderer* renderer, int winW, int winH) { @@ -180,22 +258,36 @@ static void renderLevelBackgrounds(const LevelBackgroundFader& fader, SDL_Render } SDL_FRect fullRect{0.f, 0.f, static_cast(winW), static_cast(winH)}; + const float duration = std::max(1.0f, fader.phaseDurationMs); + const float progress = (fader.phase == LevelBackgroundPhase::Idle) ? 0.0f : std::clamp(fader.phaseElapsedMs / duration, 0.0f, 1.0f); - if (fader.currentTex && fader.nextTex) { - const float duration = std::max(1.0f, fader.fadeDurationMs); - const float alpha = std::clamp(fader.fadeElapsedMs / duration, 0.0f, 1.0f); - - SDL_SetTextureAlphaMod(fader.currentTex, Uint8((1.0f - alpha) * 255.0f)); - SDL_RenderTexture(renderer, fader.currentTex, nullptr, &fullRect); - SDL_SetTextureAlphaMod(fader.currentTex, 255); - - SDL_SetTextureAlphaMod(fader.nextTex, Uint8(alpha * 255.0f)); - SDL_RenderTexture(renderer, fader.nextTex, nullptr, &fullRect); - SDL_SetTextureAlphaMod(fader.nextTex, 255); - } else if (fader.currentTex) { - SDL_RenderTexture(renderer, fader.currentTex, nullptr, &fullRect); - } else if (fader.nextTex) { - SDL_RenderTexture(renderer, fader.nextTex, nullptr, &fullRect); + switch (fader.phase) { + case LevelBackgroundPhase::ZoomOut: { + const float scale = 1.0f + progress * 0.15f; + if (fader.currentTex) { + renderScaledBackground(renderer, fader.currentTex, winW, winH, scale, Uint8((1.0f - progress * 0.4f) * 255.0f)); + drawOverlay(renderer, fullRect, SDL_Color{0, 0, 0, 255}, Uint8(progress * 200.0f)); + } + break; + } + case LevelBackgroundPhase::ZoomIn: { + const float scale = 1.10f - progress * 0.10f; + const Uint8 alpha = Uint8((0.4f + progress * 0.6f) * 255.0f); + if (fader.currentTex) { + renderScaledBackground(renderer, fader.currentTex, winW, winH, scale, alpha); + } + break; + } + case LevelBackgroundPhase::Idle: + default: + if (fader.currentTex) { + renderScaledBackground(renderer, fader.currentTex, winW, winH, 1.0f, 255); + } else if (fader.nextTex) { + renderScaledBackground(renderer, fader.nextTex, winW, winH, 1.0f, 255); + } else { + drawOverlay(renderer, fullRect, SDL_Color{0, 0, 0, 255}, 255); + } + break; } } @@ -204,7 +296,9 @@ static void resetLevelBackgrounds(LevelBackgroundFader& fader) { destroyTexture(fader.nextTex); fader.currentLevel = -1; fader.queuedLevel = -1; - fader.fadeElapsedMs = 0.0f; + fader.phaseElapsedMs = 0.0f; + fader.phaseDurationMs = 0.0f; + fader.phase = LevelBackgroundPhase::Idle; } // Hover state for level popup ( -1 = none, 0..19 = hovered level ) From bc28a61fad84c00c79b0646399981f281daeb908 Mon Sep 17 00:00:00 2001 From: Gregor Klevze Date: Sat, 29 Nov 2025 12:16:32 +0100 Subject: [PATCH 2/4] fixed --- .gitignore | 1 + src/main.cpp | 59 +++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index b32beb6..b6c4282 100644 --- a/.gitignore +++ b/.gitignore @@ -70,4 +70,5 @@ dist_package/ # Local environment files (if any) .env +settings.ini # End of .gitignore diff --git a/src/main.cpp b/src/main.cpp index 5e96762..020a158 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -753,9 +753,18 @@ int main(int, char **) SoundEffectManager::instance().loadSound("clear_line", "assets/music/clear_line.wav"); // Load voice lines for line clears using WAV files (with MP3 fallback) - std::vector doubleSounds = {"nice_combo", "you_fire", "well_played", "keep_that_ryhtm"}; - std::vector tripleSounds = {"great_move", "smooth_clear", "impressive", "triple_strike"}; + std::vector singleSounds = {"well_played", "smooth_clear", "great_move"}; + std::vector doubleSounds = {"nice_combo", "you_fire", "keep_that_ryhtm"}; + std::vector tripleSounds = {"impressive", "triple_strike"}; std::vector tetrisSounds = {"amazing", "you_re_unstoppable", "boom_tetris", "wonderful"}; + std::vector allVoiceSounds; + auto appendVoices = [&allVoiceSounds](const std::vector& src) { + allVoiceSounds.insert(allVoiceSounds.end(), src.begin(), src.end()); + }; + appendVoices(singleSounds); + appendVoices(doubleSounds); + appendVoices(tripleSounds); + appendVoices(tetrisSounds); // Helper function to load sound with WAV/MP3 fallback and file existence check auto loadSoundWithFallback = [&](const std::string& id, const std::string& baseName) { @@ -799,20 +808,34 @@ int main(int, char **) loadSoundWithFallback("wonderful", "wonderful"); loadSoundWithFallback("lets_go", "lets_go"); // For level up - // Set up sound effect callbacks - game.setSoundCallback([&](int linesCleared) { - // Play basic line clear sound first - SoundEffectManager::instance().playSound("clear_line", 1.0f); // Increased volume - - // Then play voice line based on number of lines cleared - if (linesCleared == 2) { - SoundEffectManager::instance().playRandomSound(doubleSounds, 1.0f); // Increased volume - } else if (linesCleared == 3) { - SoundEffectManager::instance().playRandomSound(tripleSounds, 1.0f); // Increased volume - } else if (linesCleared == 4) { - SoundEffectManager::instance().playRandomSound(tetrisSounds, 1.0f); // Increased volume + auto playVoiceCue = [&](int linesCleared) { + const std::vector* bank = nullptr; + switch (linesCleared) { + case 1: bank = &singleSounds; break; + case 2: bank = &doubleSounds; break; + case 3: bank = &tripleSounds; break; + default: + if (linesCleared >= 4) { + bank = &tetrisSounds; + } + break; } - // Single line clears just play the basic clear sound (no voice in JS version) + if (bank && !bank->empty()) { + SoundEffectManager::instance().playRandomSound(*bank, 1.0f); + } + }; + + // Set up sound effect callbacks + game.setSoundCallback([&, playVoiceCue](int linesCleared) { + if (linesCleared <= 0) { + return; + } + + // Always play the core line-clear sound for consistency + SoundEffectManager::instance().playSound("clear_line", 1.0f); + + // Layer a voiced callout based on the number of cleared lines + playVoiceCue(linesCleared); }); game.setLevelUpCallback([&](int newLevel) { @@ -992,8 +1015,10 @@ int main(int, char **) } if (e.key.scancode == SDL_SCANCODE_N) { - // Test sound effects - play lets_go.wav specifically - SoundEffectManager::instance().playSound("lets_go", 1.0f); + // Manually trigger a random voice line for quick testing + if (!allVoiceSounds.empty()) { + SoundEffectManager::instance().playRandomSound(allVoiceSounds, 1.0f); + } } if (e.key.key == SDLK_F11 || (e.key.key == SDLK_RETURN && (e.key.mod & SDL_KMOD_ALT))) { From 813500878a33f9d72437b0fc07e28d0c427f47df Mon Sep 17 00:00:00 2001 From: Gregor Klevze Date: Sat, 29 Nov 2025 14:28:16 +0100 Subject: [PATCH 3/4] Better clean line effect --- .gitignore | 2 + src/gameplay/effects/LineEffect.cpp | 279 +++++++++++++++++++++------- src/gameplay/effects/LineEffect.h | 61 +++++- 3 files changed, 268 insertions(+), 74 deletions(-) diff --git a/.gitignore b/.gitignore index b6c4282..20867bd 100644 --- a/.gitignore +++ b/.gitignore @@ -70,5 +70,7 @@ dist_package/ # Local environment files (if any) .env +# Ignore local settings file settings.ini + # End of .gitignore diff --git a/src/gameplay/effects/LineEffect.cpp b/src/gameplay/effects/LineEffect.cpp index e4a8460..21a9e08 100644 --- a/src/gameplay/effects/LineEffect.cpp +++ b/src/gameplay/effects/LineEffect.cpp @@ -3,82 +3,144 @@ #include #include #include "audio/Audio.h" +#include "gameplay/core/Game.h" #ifndef M_PI #define M_PI 3.14159265358979323846 #endif -LineEffect::Particle::Particle(float px, float py) - : x(px), y(py), size(6.0f + static_cast(rand()) / RAND_MAX * 12.0f), alpha(1.0f) { - - // Random velocity for explosive effect - float angle = static_cast(rand()) / RAND_MAX * 2.0f * M_PI; - float speed = 80.0f + static_cast(rand()) / RAND_MAX * 150.0f; - vx = std::cos(angle) * speed; - vy = std::sin(angle) * speed - 30.0f; - - // Random block type for texture - blockType = rand() % 7; - - // Fallback colors if texture not available - switch (blockType % 4) { - case 0: color = {255, 140, 30, 255}; break; - case 1: color = {255, 255, 100, 255}; break; - case 2: color = {255, 255, 255, 255}; break; - case 3: color = {255, 100, 100, 255}; break; - } +namespace { +float randRange(float min, float max) { + return min + (static_cast(rand()) / static_cast(RAND_MAX)) * (max - min); +} } -void LineEffect::Particle::update() { - x += vx * 0.016f; - y += vy * 0.016f; - vy += 250.0f * 0.016f; - vx *= 0.98f; - alpha -= 0.025f; // Slower fade for blocks (longer visibility) - if (alpha < 0.0f) alpha = 0.0f; - - if (size > 2.0f) size -= 0.05f; +LineEffect::Particle::Particle(float px, float py, Style particleStyle, SDL_Color tint) + : x(px), y(py), size(0.0f), alpha(1.0f), life(0.0f), lifeSpan(0.0f), + blockType(rand() % 7), color(tint), style(particleStyle) { + float angle = randRange(0.0f, static_cast(M_PI) * 2.0f); + float speed = (style == Style::Shard) ? randRange(140.0f, 260.0f) : randRange(70.0f, 140.0f); + vx = std::cos(angle) * speed; + vy = std::sin(angle) * speed - ((style == Style::Shard) ? randRange(40.0f, 110.0f) : randRange(10.0f, 40.0f)); + size = (style == Style::Shard) ? randRange(8.0f, 16.0f) : randRange(5.0f, 10.0f); + lifeSpan = (style == Style::Shard) ? randRange(0.70f, 1.20f) : randRange(1.00f, 1.50f); +} + +void LineEffect::Particle::update(float dt) { + life += dt; + if (life >= lifeSpan) { + alpha = 0.0f; + return; + } + + const float progress = life / lifeSpan; + x += vx * dt; + y += vy * dt; + float gravity = (style == Style::Shard) ? 520.0f : 240.0f; + vy += gravity * dt; + vx *= (style == Style::Shard) ? 0.96f : 0.985f; + float shrinkRate = (style == Style::Shard) ? 24.0f : 10.0f; + size = std::max(2.0f, size - shrinkRate * dt); + alpha = 1.0f - progress; } void LineEffect::Particle::render(SDL_Renderer* renderer, SDL_Texture* blocksTex) { if (alpha <= 0.0f) return; - + if (blocksTex) { - // Render textured block fragment Uint8 prevA = 255; SDL_GetTextureAlphaMod(blocksTex, &prevA); SDL_SetTextureAlphaMod(blocksTex, static_cast(alpha * 255.0f)); - + const int SPRITE_SIZE = 90; float srcX = blockType * SPRITE_SIZE + 2; float srcY = 2; float srcW = SPRITE_SIZE - 4; float srcH = SPRITE_SIZE - 4; - + SDL_FRect srcRect = {srcX, srcY, srcW, srcH}; - SDL_FRect dstRect = {x - size/2, y - size/2, size, size}; - + SDL_FRect dstRect = {x - size/2.0f, y - size/2.0f, size, size}; SDL_RenderTexture(renderer, blocksTex, &srcRect, &dstRect); - SDL_SetTextureAlphaMod(blocksTex, prevA); } else { - // Fallback to circle rendering SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); Uint8 adjustedAlpha = static_cast(alpha * 255.0f); SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, adjustedAlpha); - - for (int i = 0; i < static_cast(size); ++i) { - for (int j = 0; j < static_cast(size); ++j) { - float dx = i - size/2.0f; - float dy = j - size/2.0f; - if (dx*dx + dy*dy <= (size/2.0f)*(size/2.0f)) { - SDL_RenderPoint(renderer, x + dx, y + dy); + float radius = size * 0.5f; + for (int iy = -static_cast(radius); iy <= static_cast(radius); ++iy) { + for (int ix = -static_cast(radius); ix <= static_cast(radius); ++ix) { + float dist2 = float(ix * ix + iy * iy); + if (dist2 <= radius * radius) { + SDL_RenderPoint(renderer, x + ix, y + iy); } } } } } +LineEffect::Spark::Spark(float px, float py, SDL_Color tint) + : x(px), y(py), vx(0.0f), vy(0.0f), life(0.0f), maxLife(randRange(0.40f, 0.80f)), + thickness(randRange(1.0f, 2.0f)), color(tint) { + float angle = randRange(0.0f, static_cast(M_PI) * 2.0f); + float speed = randRange(240.0f, 520.0f); + vx = std::cos(angle) * speed; + vy = std::sin(angle) * speed - randRange(80.0f, 150.0f); +} + +void LineEffect::Spark::update(float dt) { + life += dt; + x += vx * dt; + y += vy * dt; + vy += 420.0f * dt; + vx *= 0.99f; +} + +void LineEffect::Spark::render(SDL_Renderer* renderer) const { + if (life >= maxLife) return; + float progress = life / maxLife; + float alpha = (1.0f - progress) * 255.0f; + if (alpha <= 0.0f) return; + + SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, static_cast(alpha)); + float trail = 0.018f * (1.2f - progress) * thickness; + SDL_FPoint tail{ x - vx * trail, y - vy * trail }; + SDL_RenderLine(renderer, x, y, tail.x, tail.y); +} + +LineEffect::GlowPulse::GlowPulse(float px, float py, float baseR, float maxR, SDL_Color tint) + : x(px), y(py), baseRadius(baseR), maxRadius(maxR), life(0.0f), + maxLife(randRange(0.45f, 0.70f)), color(tint) { + if (color.a == 0) color.a = 200; +} + +void LineEffect::GlowPulse::update(float dt) { + life += dt; + if (life > maxLife) life = maxLife; +} + +void LineEffect::GlowPulse::render(SDL_Renderer* renderer) const { + if (life >= maxLife) return; + float progress = life / maxLife; + float radius = baseRadius + (maxRadius - baseRadius) * progress; + float baseAlpha = (1.0f - progress) * (color.a / 255.0f); + int intRadius = static_cast(std::ceil(radius)); + for (int iy = -intRadius; iy <= intRadius; ++iy) { + float dy = static_cast(iy); + float rowWidth = std::sqrt(std::max(0.0f, radius * radius - dy * dy)); + float falloff = std::max(0.0f, 1.0f - std::abs(dy) / radius); + Uint8 alpha = static_cast(baseAlpha * falloff * 255.0f); + if (alpha == 0) continue; + SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, alpha); + SDL_FRect segment{ + x - rowWidth, + y + dy, + rowWidth * 2.0f, + 1.4f + }; + SDL_RenderFillRect(renderer, &segment); + } +} + LineEffect::LineEffect() : renderer(nullptr), state(AnimationState::IDLE), timer(0.0f), rng(std::random_device{}()), audioStream(nullptr) { } @@ -133,6 +195,8 @@ void LineEffect::startLineClear(const std::vector& rows, int gridX, int gri state = AnimationState::FLASH_WHITE; timer = 0.0f; particles.clear(); + sparks.clear(); + glowPulses.clear(); // Create particles for each clearing row for (int row : rows) { @@ -144,22 +208,38 @@ void LineEffect::startLineClear(const std::vector& rows, int gridX, int gri } void LineEffect::createParticles(int row, int gridX, int gridY, int blockSize) { - // Create particles spread across the row with explosive pattern - int particlesPerRow = 60; // More particles for dramatic explosion effect - - for (int i = 0; i < particlesPerRow; ++i) { - // Create particles along the entire row width - float x = gridX + (static_cast(i) / (particlesPerRow - 1)) * (10 * blockSize); - float y = gridY + row * blockSize + blockSize / 2.0f; - - // Add some randomness to position - x += (static_cast(rand()) / RAND_MAX - 0.5f) * blockSize * 0.8f; - y += (static_cast(rand()) / RAND_MAX - 0.5f) * blockSize * 0.6f; - - particles.emplace_back(x, y); + const float centerY = gridY + row * blockSize + blockSize * 0.5f; + for (int col = 0; col < Game::COLS; ++col) { + float centerX = gridX + col * blockSize + blockSize * 0.5f; + SDL_Color tint = pickFireColor(); + spawnGlowPulse(centerX, centerY, static_cast(blockSize), tint); + spawnShardBurst(centerX, centerY, tint); + spawnSparkBurst(centerX, centerY, tint); } } +void LineEffect::spawnShardBurst(float x, float y, SDL_Color tint) { + int shardCount = 3 + rand() % 4; + for (int i = 0; i < shardCount; ++i) { + particles.emplace_back(x, y, Particle::Style::Shard, tint); + } + int emberCount = 2 + rand() % 3; + for (int i = 0; i < emberCount; ++i) { + particles.emplace_back(x, y, Particle::Style::Ember, tint); + } +} + +void LineEffect::spawnSparkBurst(float x, float y, SDL_Color tint) { + int sparkCount = 4 + rand() % 5; + for (int i = 0; i < sparkCount; ++i) { + sparks.emplace_back(x, y, tint); + } +} + +void LineEffect::spawnGlowPulse(float x, float y, float blockSize, SDL_Color tint) { + glowPulses.emplace_back(x, y, blockSize * 0.45f, blockSize * 2.3f, tint); +} + bool LineEffect::update(float deltaTime) { if (state == AnimationState::IDLE) return true; @@ -174,7 +254,9 @@ bool LineEffect::update(float deltaTime) { break; case AnimationState::EXPLODE_BLOCKS: - updateParticles(); + updateParticles(deltaTime); + updateSparks(deltaTime); + updateGlowPulses(deltaTime); if (timer >= EXPLODE_DURATION) { state = AnimationState::BLOCKS_DROP; timer = 0.0f; @@ -182,11 +264,15 @@ bool LineEffect::update(float deltaTime) { break; case AnimationState::BLOCKS_DROP: - updateParticles(); + updateParticles(deltaTime); + updateSparks(deltaTime); + updateGlowPulses(deltaTime); if (timer >= DROP_DURATION) { state = AnimationState::IDLE; clearingRows.clear(); particles.clear(); + sparks.clear(); + glowPulses.clear(); return true; // Effect complete } break; @@ -198,18 +284,34 @@ bool LineEffect::update(float deltaTime) { return false; // Effect still running } -void LineEffect::updateParticles() { - // Update all particles +void LineEffect::updateParticles(float dt) { for (auto& particle : particles) { - particle.update(); + particle.update(dt); } - - // Remove dead particles particles.erase( std::remove_if(particles.begin(), particles.end(), [](const Particle& p) { return !p.isAlive(); }), - particles.end() - ); + particles.end()); +} + +void LineEffect::updateSparks(float dt) { + for (auto& spark : sparks) { + spark.update(dt); + } + sparks.erase( + std::remove_if(sparks.begin(), sparks.end(), + [](const Spark& s) { return !s.isAlive(); }), + sparks.end()); +} + +void LineEffect::updateGlowPulses(float dt) { + for (auto& pulse : glowPulses) { + pulse.update(dt); + } + glowPulses.erase( + std::remove_if(glowPulses.begin(), glowPulses.end(), + [](const GlowPulse& p) { return !p.isAlive(); }), + glowPulses.end()); } void LineEffect::render(SDL_Renderer* renderer, SDL_Texture* blocksTex, int gridX, int gridY, int blockSize) { @@ -265,11 +367,46 @@ void LineEffect::renderFlash(int gridX, int gridY, int blockSize) { } void LineEffect::renderExplosion(SDL_Texture* blocksTex) { + renderGlowPulses(); + renderSparks(); + renderParticleGlows(); for (auto& particle : particles) { particle.render(renderer, blocksTex); } } +void LineEffect::renderGlowPulses() { + if (glowPulses.empty()) return; + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_ADD); + for (const auto& pulse : glowPulses) { + pulse.render(renderer); + } + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); +} + +void LineEffect::renderSparks() { + if (sparks.empty()) return; + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_ADD); + for (const auto& spark : sparks) { + spark.render(renderer); + } + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); +} + +void LineEffect::renderParticleGlows() { + if (particles.empty()) return; + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_ADD); + for (const auto& particle : particles) { + if (particle.alpha <= 0.0f) continue; + float radius = particle.size * 0.6f; + SDL_SetRenderDrawColor(renderer, particle.color.r, particle.color.g, particle.color.b, + static_cast(particle.alpha * 150.0f)); + SDL_FRect glowRect{ particle.x - radius, particle.y - radius, radius * 2.0f, radius * 2.0f }; + SDL_RenderFillRect(renderer, &glowRect); + } + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); +} + void LineEffect::playLineClearSound(int lineCount) { // Choose appropriate sound based on line count const std::vector* sample = (lineCount == 4) ? &tetrisSample : &lineClearSample; @@ -278,3 +415,15 @@ void LineEffect::playLineClearSound(int lineCount) { Audio::instance().playSfx(*sample, 2, 44100, (lineCount == 4) ? 0.9f : 0.7f); } } + +SDL_Color LineEffect::pickFireColor() const { + static const SDL_Color palette[] = { + {255, 200, 120, 210}, + {255, 150, 90, 220}, + {255, 100, 160, 200}, + {120, 200, 255, 200}, + {255, 255, 200, 210} + }; + const size_t count = sizeof(palette) / sizeof(palette[0]); + return palette[rand() % count]; +} diff --git a/src/gameplay/effects/LineEffect.h b/src/gameplay/effects/LineEffect.h index 0c9aa93..63c1ba1 100644 --- a/src/gameplay/effects/LineEffect.h +++ b/src/gameplay/effects/LineEffect.h @@ -7,17 +7,49 @@ class LineEffect { public: struct Particle { + enum class Style { Shard, Ember }; float x, y; float vx, vy; float size; float alpha; - int blockType; // Added for textured particles + float life; + float lifeSpan; + int blockType; SDL_Color color; - - Particle(float px, float py); - void update(); + Style style; + + Particle(float px, float py, Style particleStyle, SDL_Color tint); + void update(float dt); void render(SDL_Renderer* renderer, SDL_Texture* blocksTex); - bool isAlive() const { return alpha > 0.0f; } + bool isAlive() const { return life < lifeSpan && alpha > 0.01f; } + }; + + struct Spark { + float x, y; + float vx, vy; + float life; + float maxLife; + float thickness; + SDL_Color color; + + Spark(float px, float py, SDL_Color tint); + void update(float dt); + void render(SDL_Renderer* renderer) const; + bool isAlive() const { return life < maxLife; } + }; + + struct GlowPulse { + float x, y; + float baseRadius; + float maxRadius; + float life; + float maxLife; + SDL_Color color; + + GlowPulse(float px, float py, float baseR, float maxR, SDL_Color tint); + void update(float dt); + void render(SDL_Renderer* renderer) const; + bool isAlive() const { return life < maxLife; } }; enum class AnimationState { @@ -51,6 +83,8 @@ private: float timer{0.0f}; std::vector clearingRows; std::vector particles; + std::vector sparks; + std::vector glowPulses; std::mt19937 rng{std::random_device{}()}; // Audio resources @@ -59,14 +93,23 @@ private: std::vector tetrisSample; // Animation timing - Flash then immediate explosion effect - static constexpr float FLASH_DURATION = 0.12f; // Very brief white flash - static constexpr float EXPLODE_DURATION = 0.6f; // Longer explosive effect - static constexpr float DROP_DURATION = 0.05f; // Almost instant block drop + static constexpr float FLASH_DURATION = 0.18f; // Slightly longer flash for anticipation + static constexpr float EXPLODE_DURATION = 0.9f; // Extended fireworks time + static constexpr float DROP_DURATION = 0.20f; // Allow lingering sparks before collapse void createParticles(int row, int gridX, int gridY, int blockSize); - void updateParticles(); + void spawnShardBurst(float x, float y, SDL_Color tint); + void spawnSparkBurst(float x, float y, SDL_Color tint); + void spawnGlowPulse(float x, float y, float blockSize, SDL_Color tint); + void updateParticles(float dt); + void updateSparks(float dt); + void updateGlowPulses(float dt); void renderFlash(int gridX, int gridY, int blockSize); void renderExplosion(SDL_Texture* blocksTex); + void renderGlowPulses(); + void renderSparks(); + void renderParticleGlows(); bool loadAudioSample(const std::string& path, std::vector& sample); void initAudio(); + SDL_Color pickFireColor() const; }; From deca3731f1271a1e09d2f7dec657765a58874e92 Mon Sep 17 00:00:00 2001 From: Gregor Klevze Date: Sat, 29 Nov 2025 14:29:08 +0100 Subject: [PATCH 4/4] settings --- settings.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/settings.ini b/settings.ini index e96b861..ea793f9 100644 --- a/settings.ini +++ b/settings.ini @@ -6,10 +6,10 @@ Fullscreen=1 [Audio] Music=1 -Sound=0 +Sound=1 [Player] -Name=Player +Name=GREGOR [Debug] -Enabled=0 +Enabled=1