diff --git a/source/fade.cpp b/source/fade.cpp index 65924f7..6bf6e4e 100644 --- a/source/fade.cpp +++ b/source/fade.cpp @@ -35,6 +35,12 @@ void Fade::init(Uint8 r, Uint8 g, Uint8 b) { mR = r; mG = g; mB = b; + mROriginal = r; + mGOriginal = g; + mBOriginal = b; + mLastSquareTicks = 0; + mSquaresDrawn = 0; + mFullscreenDone = false; } // Pinta una transición en pantalla @@ -42,30 +48,35 @@ void Fade::render() { if (mEnabled && !mFinished) { switch (mFadeType) { case FADE_FULLSCREEN: { - SDL_FRect fRect1 = {0, 0, (float)GAMECANVAS_WIDTH, (float)GAMECANVAS_HEIGHT}; + if (!mFullscreenDone) { + SDL_FRect fRect1 = {0, 0, (float)GAMECANVAS_WIDTH, (float)GAMECANVAS_HEIGHT}; - for (int i = 0; i < 256; i += 4) { - // Dibujamos sobre el renderizador - SDL_SetRenderTarget(mRenderer, nullptr); + int alpha = mCounter * 4; + if (alpha >= 255) { + alpha = 255; + mFullscreenDone = true; - // Copia el backbuffer con la imagen que había al renderizador - SDL_RenderTexture(mRenderer, mBackbuffer, nullptr, nullptr); + // Deja todos los buffers del mismo color + SDL_SetRenderTarget(mRenderer, mBackbuffer); + SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, 255); + SDL_RenderClear(mRenderer); - SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, i); - SDL_RenderFillRect(mRenderer, &fRect1); + SDL_SetRenderTarget(mRenderer, nullptr); + SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, 255); + SDL_RenderClear(mRenderer); - // Vuelca el renderizador en pantalla - SDL_RenderPresent(mRenderer); + mFinished = true; + } else { + // Dibujamos sobre el renderizador + SDL_SetRenderTarget(mRenderer, nullptr); + + // Copia el backbuffer con la imagen que había al renderizador + SDL_RenderTexture(mRenderer, mBackbuffer, nullptr, nullptr); + + SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, alpha); + SDL_RenderFillRect(mRenderer, &fRect1); + } } - - // Deja todos los buffers del mismo color - SDL_SetRenderTarget(mRenderer, mBackbuffer); - SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, 255); - SDL_RenderClear(mRenderer); - - SDL_SetRenderTarget(mRenderer, nullptr); - SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, 255); - SDL_RenderClear(mRenderer); break; } @@ -89,14 +100,17 @@ void Fade::render() { } case FADE_RANDOM_SQUARE: { - SDL_FRect fRs = {0, 0, 32, 32}; + Uint32 now = SDL_GetTicks(); + if (mSquaresDrawn < 50 && now - mLastSquareTicks >= 100) { + mLastSquareTicks = now; + + SDL_FRect fRs = {0, 0, 32, 32}; - for (Uint16 i = 0; i < 50; i++) { // Crea un color al azar - mR = 255 * (rand() % 2); - mG = 255 * (rand() % 2); - mB = 255 * (rand() % 2); - SDL_SetRenderDrawColor(mRenderer, mR, mG, mB, 64); + Uint8 r = 255 * (rand() % 2); + Uint8 g = 255 * (rand() % 2); + Uint8 b = 255 * (rand() % 2); + SDL_SetRenderDrawColor(mRenderer, r, g, b, 64); // Dibujamos sobre el backbuffer SDL_SetRenderTarget(mRenderer, mBackbuffer); @@ -108,12 +122,14 @@ void Fade::render() { // Volvemos a usar el renderizador de forma normal SDL_SetRenderTarget(mRenderer, nullptr); - // Copiamos el backbuffer al renderizador - SDL_RenderTexture(mRenderer, mBackbuffer, nullptr, nullptr); + mSquaresDrawn++; + } - // Volcamos el renderizador en pantalla - SDL_RenderPresent(mRenderer); - SDL_Delay(100); + // Copiamos el backbuffer al renderizador + SDL_RenderTexture(mRenderer, mBackbuffer, nullptr, nullptr); + + if (mSquaresDrawn >= 50) { + mFinished = true; } break; } @@ -140,6 +156,12 @@ void Fade::activateFade() { mEnabled = true; mFinished = false; mCounter = 0; + mSquaresDrawn = 0; + mLastSquareTicks = 0; + mFullscreenDone = false; + mR = mROriginal; + mG = mGOriginal; + mB = mBOriginal; } // Comprueba si está activo diff --git a/source/fade.h b/source/fade.h index 9bbc985..102c3fd 100644 --- a/source/fade.h +++ b/source/fade.h @@ -17,6 +17,10 @@ class Fade { bool mEnabled; // Indica si el fade está activo bool mFinished; // Indica si ha terminado la transición Uint8 mR, mG, mB; // Colores para el fade + Uint8 mROriginal, mGOriginal, mBOriginal; // Colores originales para FADE_RANDOM_SQUARE + Uint32 mLastSquareTicks; // Ticks del último cuadrado dibujado (FADE_RANDOM_SQUARE) + Uint16 mSquaresDrawn; // Número de cuadrados dibujados (FADE_RANDOM_SQUARE) + bool mFullscreenDone; // Indica si el fade fullscreen ha terminado la fase de fundido SDL_Rect mRect1; // Rectangulo usado para crear los efectos de transición SDL_Rect mRect2; // Rectangulo usado para crear los efectos de transición diff --git a/source/game.cpp b/source/game.cpp index cab5092..d9597e6 100644 --- a/source/game.cpp +++ b/source/game.cpp @@ -284,6 +284,12 @@ void Game::init() { effect.flash = false; effect.shake = false; effect.shakeCounter = SHAKE_COUNTER; + deathShake.active = false; + deathShake.step = 0; + deathShake.lastStepTicks = 0; + deathSequence.phase = DeathPhase::None; + deathSequence.phaseStartTicks = 0; + deathSequence.player = nullptr; helper.needCoffee = false; helper.needCoffeeMachine = false; helper.needPowerBall = false; @@ -2363,23 +2369,50 @@ void Game::killPlayer(Player *player) { player->removeExtraHit(); throwCoffee(player->getPosX() + (player->getWidth() / 2), player->getPosY() + (player->getHeight() / 2)); JA_PlaySound(coffeeOutSound); - } else { + } else if (deathSequence.phase == DeathPhase::None) { JA_PauseMusic(); stopAllBalloons(10); JA_PlaySound(playerCollisionSound); shakeScreen(); - SDL_Delay(500); - JA_PlaySound(coffeeOutSound); - player->setAlive(false); - if (allPlayersAreDead()) { - JA_StopMusic(); - } else { - JA_ResumeMusic(); - } + deathSequence.phase = DeathPhase::Shaking; + deathSequence.phaseStartTicks = SDL_GetTicks(); + deathSequence.player = player; } } } +// Actualiza la secuencia de muerte del jugador +void Game::updateDeathSequence() { + switch (deathSequence.phase) { + case DeathPhase::None: + case DeathPhase::Done: + break; + + case DeathPhase::Shaking: + // Espera a que termine el efecto de agitación + if (!isDeathShaking()) { + deathSequence.phase = DeathPhase::Waiting; + deathSequence.phaseStartTicks = SDL_GetTicks(); + } + break; + + case DeathPhase::Waiting: + // Espera 500ms antes de completar la muerte + if (SDL_GetTicks() - deathSequence.phaseStartTicks >= 500) { + JA_PlaySound(coffeeOutSound); + deathSequence.player->setAlive(false); + if (allPlayersAreDead()) { + JA_StopMusic(); + } else { + JA_ResumeMusic(); + } + deathSequence.phase = DeathPhase::Done; + deathSequence.player = nullptr; + } + break; + } +} + // Calcula y establece el valor de amenaza en funcion de los globos activos void Game::evaluateAndSetMenace() { menaceCurrent = 0; @@ -2439,6 +2472,15 @@ void Game::update() { // Actualiza el audio JA_Update(); + // Actualiza los efectos basados en tiempo real (no en el throttle del juego) + updateDeathShake(); + updateDeathSequence(); + + // Durante la secuencia de muerte, congela el resto del juego + if (deathSequence.phase == DeathPhase::Shaking || deathSequence.phase == DeathPhase::Waiting) { + return; + } + // Comprueba que la diferencia de ticks sea mayor a la velocidad del juego if (SDL_GetTicks() - ticks > ticksSpeed) { // Actualiza el contador de ticks @@ -2550,7 +2592,10 @@ void Game::updateBackground() { grassSprite->setSpriteClip(0, (6 * (counter / 20 % 2)), 256, 6); // Mueve los edificios en funcion de si está activo el efecto de agitarlos - if (effect.shake) { + if (deathShake.active) { + const int v[] = {-1, 1, -1, 1, -1, 1, -1, 0}; + buildingsSprite->setPosX(v[deathShake.step]); + } else if (effect.shake) { buildingsSprite->setPosX(((effect.shakeCounter % 2) * 2) - 1); } else { buildingsSprite->setPosX(0); @@ -2868,44 +2913,32 @@ void Game::disableTimeStopItem() { } } -// Agita la pantalla +// Inicia el efecto de agitación intensa de la pantalla void Game::shakeScreen() { - const int v[] = {-1, 1, -1, 1, -1, 1, -1, 0}; - for (int n = 0; n < 8; ++n) { - // Prepara para empezar a dibujar en la textura de juego - screen->start(); + deathShake.active = true; + deathShake.step = 0; + deathShake.lastStepTicks = SDL_GetTicks(); +} - // Limpia la pantalla - screen->clean(bgColor); +// Actualiza el efecto de agitación intensa +void Game::updateDeathShake() { + if (!deathShake.active) return; - // Dibuja los objetos - buildingsSprite->setPosX(0); - buildingsSprite->setWidth(1); - buildingsSprite->setSpriteClip(0, 0, 1, 160); - renderBackground(); - - buildingsSprite->setPosX(255); - buildingsSprite->setSpriteClip(255, 0, 1, 160); - buildingsSprite->render(); - - buildingsSprite->setPosX(v[n]); - buildingsSprite->setWidth(256); - buildingsSprite->setSpriteClip(0, 0, 256, 160); - buildingsSprite->render(); - - grassSprite->render(); - renderBalloons(); - renderBullets(); - renderItems(); - renderPlayers(); - renderScoreBoard(); - - // Vuelca el contenido del renderizador en pantalla - screen->blit(); - SDL_Delay(50); + Uint32 now = SDL_GetTicks(); + if (now - deathShake.lastStepTicks >= 50) { + deathShake.lastStepTicks = now; + deathShake.step++; + if (deathShake.step >= 8) { + deathShake.active = false; + } } } +// Indica si el efecto de agitación intensa está activo +bool Game::isDeathShaking() { + return deathShake.active; +} + // Bucle para el juego void Game::run() { while (section->name == SECTION_PROG_GAME) { diff --git a/source/game.h b/source/game.h index 844ecf2..2e409de 100644 --- a/source/game.h +++ b/source/game.h @@ -88,6 +88,23 @@ class Game { Uint8 shakeCounter; // Contador para medir el tiempo que dura el efecto }; + // Estado para el efecto de agitación intensa (muerte del jugador) + struct deathShake_t { + bool active; // Indica si el efecto está activo + Uint8 step; // Paso actual del efecto (0-7) + Uint32 lastStepTicks; // Ticks del último paso + }; + + // Fases de la secuencia de muerte del jugador + enum class DeathPhase { None, Shaking, Waiting, Done }; + + // Estado de la secuencia de muerte del jugador + struct deathSequence_t { + DeathPhase phase; // Fase actual + Uint32 phaseStartTicks; // Ticks del inicio de la fase actual + Player *player; // Jugador que está muriendo + }; + struct helper_t { bool needCoffee; // Indica si se necesitan cafes bool needCoffeeMachine; // Indica si se necesita PowerUp @@ -214,6 +231,8 @@ class Game { float enemySpeed; // Velocidad a la que se mueven los enemigos float defaultEnemySpeed; // Velocidad base de los enemigos, sin incrementar effect_t effect; // Variable para gestionar los efectos visuales + deathShake_t deathShake; // Variable para gestionar el efecto de agitación intensa + deathSequence_t deathSequence; // Variable para gestionar la secuencia de muerte helper_t helper; // Variable para gestionar las ayudas bool powerBallEnabled; // Indica si hay una powerball ya activa Uint8 powerBallCounter; // Contador de formaciones enemigas entre la aparicion de una PowerBall y otra @@ -459,9 +478,18 @@ class Game { // Deshabilita el efecto del item de detener el tiempo void disableTimeStopItem(); - // Agita la pantalla + // Inicia el efecto de agitación intensa de la pantalla void shakeScreen(); + // Actualiza el efecto de agitación intensa + void updateDeathShake(); + + // Indica si el efecto de agitación intensa está activo + bool isDeathShaking(); + + // Actualiza la secuencia de muerte del jugador + void updateDeathSequence(); + // Actualiza las variables del menu de pausa del juego void updatePausedGame(); diff --git a/source/title.cpp b/source/title.cpp index 5de0a2f..92d7a6b 100644 --- a/source/title.cpp +++ b/source/title.cpp @@ -120,6 +120,8 @@ void Title::init() { ticksSpeed = 15; fade->init(0x17, 0x17, 0x26); demo = true; + vibrationStep = 0; + vibrationInitialized = false; // Pone valores por defecto a las opciones de control options->input.clear(); @@ -257,21 +259,27 @@ void Title::update() { // Sección 2 - Titulo vibrando case SUBSECTION_TITLE_2: { - // Agita la pantalla - static const int v[] = {-1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 0}; - static const int a = coffeeBitmap->getPosX(); - static const int b = crisisBitmap->getPosX(); - static int step = 0; + // Captura las posiciones base y reproduce el sonido la primera vez + if (!vibrationInitialized) { + vibrationCoffeeBaseX = coffeeBitmap->getPosX(); + vibrationCrisisBaseX = crisisBitmap->getPosX(); + vibrationInitialized = true; + } - coffeeBitmap->setPosX(a + v[step / 3]); - crisisBitmap->setPosX(b + v[step / 3]); + // Agita la pantalla + const int v[] = {-1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 0}; + + coffeeBitmap->setPosX(vibrationCoffeeBaseX + v[vibrationStep / 3]); + crisisBitmap->setPosX(vibrationCrisisBaseX + v[vibrationStep / 3]); dustBitmapR->update(); dustBitmapL->update(); - step++; + vibrationStep++; - if (step == 33) { + if (vibrationStep >= 33) { section->subsection = SUBSECTION_TITLE_3; + vibrationStep = 0; + vibrationInitialized = false; } } break; @@ -539,48 +547,32 @@ void Title::render() { } break; // Sección 2 - Titulo vibrando - case SUBSECTION_TITLE_2: { // Reproduce el efecto sonoro - JA_PlaySound(crashSound); + case SUBSECTION_TITLE_2: { + // Prepara para empezar a dibujar en la textura de juego + screen->start(); - // Agita la pantalla - const int v[] = {-1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 0}; - const int a = coffeeBitmap->getPosX(); - const int b = crisisBitmap->getPosX(); - for (int n = 0; n < 11 * 3; ++n) { - // Prepara para empezar a dibujar en la textura de juego - screen->start(); + // Limpia la pantalla + screen->clean(bgColor); - // Limpia la pantalla - screen->clean(bgColor); + // Dibuja el tileado de fondo + { + SDL_FRect fSrc = {(float)backgroundWindow.x, (float)backgroundWindow.y, (float)backgroundWindow.w, (float)backgroundWindow.h}; + SDL_RenderTexture(renderer, background, &fSrc, nullptr); + }; - // Dibuja el tileado de fondo - { - SDL_FRect fSrc = {(float)backgroundWindow.x, (float)backgroundWindow.y, (float)backgroundWindow.w, (float)backgroundWindow.h}; - SDL_RenderTexture(renderer, background, &fSrc, nullptr); - }; + // Dibuja el degradado + gradient->render(); - // Dibuja el degradado - gradient->render(); + // Dibuja los objetos (posiciones ya actualizadas por update) + coffeeBitmap->render(); + crisisBitmap->render(); - // Dibuja los objetos - coffeeBitmap->setPosX(a + v[n / 3]); - crisisBitmap->setPosX(b + v[n / 3]); - coffeeBitmap->render(); - crisisBitmap->render(); + dustBitmapR->render(); + dustBitmapL->render(); - dustBitmapR->update(); - dustBitmapL->update(); - dustBitmapR->render(); - dustBitmapL->render(); - - // Vuelca el contenido del renderizador en pantalla - screen->blit(); - } - - section->subsection = SUBSECTION_TITLE_3; - } - - break; + // Vuelca el contenido del renderizador en pantalla + screen->blit(); + } break; // Sección 3 - La pantalla de titulo con el menú y la música case SUBSECTION_TITLE_3: { // Prepara para empezar a dibujar en la textura de juego diff --git a/source/title.h b/source/title.h index 807aba2..93574b5 100644 --- a/source/title.h +++ b/source/title.h @@ -22,7 +22,7 @@ struct JA_Music_t; struct JA_Sound_t; // Textos -constexpr const char *TEXT_COPYRIGHT = "@2020 JailDesigner (v2.3.3)"; +constexpr const char *TEXT_COPYRIGHT = "@2020 JailDesigner (v2.3.4)"; // Contadores constexpr int TITLE_COUNTER = 800; @@ -90,6 +90,12 @@ class Title { std::vector availableInputDevices; // Vector con todos los metodos de control disponibles std::vector deviceIndex; // Indice para el jugador [i] del vector de dispositivos de entrada disponibles + // Variables para la vibración del título (SUBSECTION_TITLE_2) + int vibrationStep; // Paso actual de la vibración + int vibrationCoffeeBaseX; // Posición X base del bitmap Coffee + int vibrationCrisisBaseX; // Posición X base del bitmap Crisis + bool vibrationInitialized; // Indica si se han capturado las posiciones base + // Inicializa los valores void init();