diff --git a/data/config/param_320x240.txt b/data/config/param_320x240.txt index 7a0769c..9da3d8f 100644 --- a/data/config/param_320x240.txt +++ b/data/config/param_320x240.txt @@ -9,7 +9,7 @@ game.play_area.rect.h 200 # Alto de la zona jugable game.name_entry_idle_time 10 # Segundos para introducir el nombre al finalizar la partida si no se pulsa nada game.name_entry_total_time 60 # Segundos totales para introducir el nombre al finalizar la partida game.hit_stop true # Indica si debe haber un paro cuando el jugador es golpeado por un globo -game.hit_stop_ms 300 # Cantidad de milisegundos que dura el hit_stop +game.hit_stop_ms 500 # Cantidad de milisegundos que dura el hit_stop ## --- FADE --- fade.color 1F2B30 # Color hexadecimal para el efecto de fundido diff --git a/data/config/param_320x256.txt b/data/config/param_320x256.txt index 6f2ba8e..4a7185c 100644 --- a/data/config/param_320x256.txt +++ b/data/config/param_320x256.txt @@ -9,7 +9,7 @@ game.play_area.rect.h 216 # Alto de la zona jugable game.name_entry_idle_time 10 # Segundos para introducir el nombre al finalizar la partida si no se pulsa nada game.name_entry_total_time 60 # Segundos totales para introducir el nombre al finalizar la partida game.hit_stop true # Indica si debe haber un paro cuando el jugador es golpeado por un globo -game.hit_stop_ms 300 # Cantidad de milisegundos que dura el hit_stop +game.hit_stop_ms 500 # Cantidad de milisegundos que dura el hit_stop ## --- FADE --- fade.color 1F2B30 # Color hexadecimal para el efecto de fundido diff --git a/data/gfx/player/hit.png b/data/gfx/player/hit.png new file mode 100644 index 0000000..1239dd8 Binary files /dev/null and b/data/gfx/player/hit.png differ diff --git a/source/audio.cpp b/source/audio.cpp index a55675a..d99006a 100644 --- a/source/audio.cpp +++ b/source/audio.cpp @@ -60,6 +60,16 @@ void Audio::pauseMusic() { } } +// Continua la música pausada +void Audio::resumeMusic() { + if (music_enabled_ && music_.state == MusicState::PAUSED) { +#ifndef NO_AUDIO + JA_ResumeMusic(); +#endif + music_.state = MusicState::PLAYING; + } +} + // Detiene la música void Audio::stopMusic() { if (music_enabled_) { diff --git a/source/audio.h b/source/audio.h index 05afd93..b2f8762 100644 --- a/source/audio.h +++ b/source/audio.h @@ -25,11 +25,12 @@ class Audio { auto operator=(const Audio &) -> Audio & = delete; // Evitar asignación // --- Método principal --- - void update(); + void update(); // --- Control de Música --- void playMusic(const std::string &name, int loop = -1); // Reproducir música en bucle void pauseMusic(); // Pausar reproducción de música + void resumeMusic(); // Continua la música pausada void stopMusic(); // Detener completamente la música void fadeOutMusic(int milliseconds) const; // Fundido de salida de la música diff --git a/source/director.cpp b/source/director.cpp index 7c63e3d..4c26267 100644 --- a/source/director.cpp +++ b/source/director.cpp @@ -402,10 +402,13 @@ void Director::setFileList() { Asset::get()->add(PREFIX + "/data/gfx/player/player2_coffee2.pal", AssetType::PALETTE); Asset::get()->add(PREFIX + "/data/gfx/player/player2_invencible.pal", AssetType::PALETTE); Asset::get()->add(PREFIX + "/data/gfx/player/player2_power.png", AssetType::BITMAP); - + // Animaciones del jugador Asset::get()->add(PREFIX + "/data/gfx/player/player.ani", AssetType::ANIMATION); Asset::get()->add(PREFIX + "/data/gfx/player/player_power.ani", AssetType::ANIMATION); + + // Texturas - Golpe del jugador + Asset::get()->add(PREFIX + "/data/gfx/player/hit.png", AssetType::BITMAP); // Fuentes de texto Asset::get()->add(PREFIX + "/data/font/8bithud.png", AssetType::BITMAP); diff --git a/source/hit.h b/source/hit.h new file mode 100644 index 0000000..14cc2c7 --- /dev/null +++ b/source/hit.h @@ -0,0 +1,61 @@ +#pragma once + +#include // Para SDL_FPoint + +#include // Para std::unique_ptr y std::shared_ptr + +#include "sprite.h" // Para Sprite +#include "texture.h" // Para Texture + +// Estructura que representa una colisión o impacto visual +struct Hit { + private: + // Indica si el Hit está activo o no + bool enabled{false}; + + // Sprite asociado al Hit, gestionado con un puntero único + std::unique_ptr sprite; + + public: + // Elimina el constructor por defecto para obligar a pasar una textura + Hit() = delete; + + // Constructor que obliga a pasar una textura compartida para crear el Sprite + // Esto evita que se pueda crear un Hit sin recursos gráficos válidos + explicit Hit(std::shared_ptr texture) + : sprite(std::make_unique(texture)) {} + + // Establece la posición del Sprite en el espacio + void setPos(SDL_FPoint position) { + SDL_FPoint centered_position = {position.x - (sprite->getWidth() / 2), position.y - (sprite->getHeight() / 2)}; + sprite->setPosition(centered_position); + } + + // Activa o desactiva el Hit + void enable(bool value) { + enabled = value; + } + + // Consulta si el Hit está activo + bool isEnabled() const { + return enabled; + } + + // Crea un "Hit" en la posición especificada + void create(SDL_FPoint position) { + setPos(position); + enable(true); + } + + // Dibuja el hit + void render() { + if (enabled) { + sprite->render(); + } + } + + // Deshabilita el hit + void disable() { + enabled = false; + } +}; diff --git a/source/sections/game.cpp b/source/sections/game.cpp index 8db3d27..3647c5c 100644 --- a/source/sections/game.cpp +++ b/source/sections/game.cpp @@ -8,6 +8,7 @@ #include // Para function #include // Para std::cout, std::endl #include // Para distance, size +#include // Para std::make_unique #include "asset.h" // Para Asset #include "audio.h" // Para Audio @@ -20,6 +21,7 @@ #include "fade.h" // Para Fade, FadeType, FadeMode #include "global_events.h" // Para check #include "global_inputs.h" // Para check +#include "hit.h" // Para Hit #include "input.h" // Para InputAction, Input, INPUT_DO_NOT_ALLOW_REPEAT, INPUT_ALLOW_REPEAT, InputDevice #include "item.h" // Para Item, ItemType #include "lang.h" // Para getText @@ -49,7 +51,8 @@ Game::Game(int player_id, int current_stage, bool demo) fade_in_(std::make_unique()), fade_out_(std::make_unique()), balloon_manager_(std::make_unique()), - tabe_(std::make_unique()) { + tabe_(std::make_unique()), + hit_(Hit(Resource::get()->getTexture("hit.png"))) { // Pasa variables demo_.enabled = demo; @@ -213,7 +216,7 @@ void Game::updatePlayers() { } // En caso contrario, el jugador ha sido golpeado por un globo activo else { - handlePlayerCollision(player); + handlePlayerCollision(player, balloon); if (demo_.enabled && allPlayersAreNotPlaying()) { fade_out_->setType(FadeType::RANDOM_SQUARE); @@ -830,7 +833,7 @@ void Game::renderPathSprites() { } // Acciones a realizar cuando el jugador colisiona con un globo -void Game::handlePlayerCollision(std::shared_ptr &player) { +void Game::handlePlayerCollision(std::shared_ptr &player, std::shared_ptr &balloon) { if (!player->isPlaying() || player->isInvulnerable()) { // Si no está jugando o tiene inmunidad, no hace nada return; @@ -847,15 +850,19 @@ void Game::handlePlayerCollision(std::shared_ptr &player) { // Si no tiene cafes, muere playSound("player_collision.wav"); if (param.game.hit_stop) { + pauseMusic(); + auto position = getCollisionPoint(player->getCollider(), balloon->getCollider()); + putHitOnScreen(position); SDL_Delay(param.game.hit_stop_ms); + hit_.disable(); + resumeMusic(); } screen_->shake(); playSound("voice_no.wav"); player->setPlayingState(PlayerState::ROLLING); players_to_reorder_.push_back(player); if (allPlayersAreNotPlaying()) { - // No se puede subir poder de fase si no hay nadie jugando - Stage::power_can_be_added = false; + Stage::power_can_be_added = false; // No se puede subir poder de fase si no hay nadie jugando } } } @@ -979,6 +986,7 @@ void Game::fillCanvas() { // Dibuja los objetos background_->render(); renderPlayers(); + renderHit(); renderSmartSprites(); renderItems(); balloon_manager_->render(); @@ -1598,6 +1606,16 @@ void Game::playMusic() { Audio::get()->playMusic("playing.ogg"); } +// Pausa la música +void Game::pauseMusic() { + Audio::get()->pauseMusic(); +} + +// Retoma la música que eestaba pausada +void Game::resumeMusic() { + Audio::get()->resumeMusic(); +} + // Detiene la música void Game::stopMusic() const { if (!demo_.enabled) { @@ -1823,6 +1841,19 @@ void Game::checkServiceMenu() { service_menu_was_active_ = service_menu_is_active; } +// Dibuja el golpe que recibe el jugador al impactarle un globo +void Game::renderHit() { + hit_.render(); +} + +// Coloca el Hit en pantalla obligando a hacer un renderizado +void Game::putHitOnScreen(SDL_FPoint position) { + hit_.create(position); + fillCanvas(); + render(); + hit_.disable(); +} + #ifdef _DEBUG // Comprueba los eventos en el modo DEBUG void Game::checkDebugEvents(const SDL_Event &event) { diff --git a/source/sections/game.h b/source/sections/game.h index afc320f..26113b6 100644 --- a/source/sections/game.h +++ b/source/sections/game.h @@ -6,6 +6,7 @@ #include // Para string #include // Para vector +#include "hit.h" // Para Hit #include "item.h" // Para Item, ItemType #include "manage_hiscore_table.h" // Para HiScoreEntry #include "options.h" // Para SettingsOptions, settings @@ -147,6 +148,7 @@ class Game { int menace_threshold_ = 0; // Umbral del nivel de amenaza. Si el nivel de amenaza cae por debajo del umbral, se generan más globos. Si el umbral aumenta, aumenta el número de globos GameState state_ = GameState::FADE_IN; // Estado std::vector> players_to_reorder_; + Hit hit_; // Para representar colisiones en pantalla #ifdef _DEBUG bool auto_pop_balloons_ = false; // Si es true, incrementa automaticamente los globos explotados @@ -186,9 +188,9 @@ class Game { auto allPlayersAreNotPlaying() -> bool; // Verifica si ningún jugador está activo // --- Colisiones de jugadores --- - void handlePlayerCollision(std::shared_ptr &player); // Procesa colisión de jugador con globo - auto checkPlayerBalloonCollision(std::shared_ptr &player) -> std::shared_ptr; // Detecta colisión jugador-globo - void checkPlayerItemCollision(std::shared_ptr &player); // Detecta colisión jugador-ítem + void handlePlayerCollision(std::shared_ptr &player, std::shared_ptr &balloon); // Procesa colisión de jugador con globo + auto checkPlayerBalloonCollision(std::shared_ptr &player) -> std::shared_ptr; // Detecta colisión jugador-globo + void checkPlayerItemCollision(std::shared_ptr &player); // Detecta colisión jugador-ítem // --- Sistema de entrada (input) --- void checkInput(); // Gestiona toda la entrada durante el juego @@ -284,11 +286,16 @@ class Game { // --- Sistema de audio --- static void playMusic(); // Reproduce la música de fondo void stopMusic() const; // Detiene la reproducción de música + void pauseMusic(); // Pausa la música + void resumeMusic(); // Retoma la música que eestaba pausada void playSound(const std::string &name) const; // Reproduce un efecto de sonido específico // --- Utilidades y servicios --- void checkServiceMenu(); // Verifica si el menú de servicio está activo + void renderHit(); // Dibuja el golpe que recibe el jugador al impactarle un globo + void putHitOnScreen(SDL_FPoint position); // Coloca el Hit en pantalla obligando a hacer un renderizado + // SISTEMA DE GRABACIÓN (CONDICIONAL) #ifdef RECORDING void updateRecording(); // Actualiza variables durante modo de grabación diff --git a/source/utils.cpp b/source/utils.cpp index 05dc291..3a8bed6 100644 --- a/source/utils.cpp +++ b/source/utils.cpp @@ -24,6 +24,25 @@ auto distanceSquared(int x1, int y1, int x2, int y2) -> double { return DELTA_X * DELTA_X + DELTA_Y * DELTA_Y; } +// Obtiene el punto de colisión entre dos circulos +auto getCollisionPoint(const Circle &a, const Circle &b) -> SDL_FPoint { + float dx = b.x - a.x; + float dy = b.y - a.y; + float dist = std::sqrt(dx * dx + dy * dy); + + // Normaliza el vector + float nx = dx / dist; + float ny = dy / dist; + + // Punto en el borde del círculo A hacia B + SDL_FPoint contact; + contact.x = a.x + nx * a.r; + contact.y = a.y + ny * a.r; + + return contact; +} + + // Detector de colisiones entre dos circulos auto checkCollision(const Circle &a, const Circle &b) -> bool { // Calcula el radio total al cuadrado diff --git a/source/utils.h b/source/utils.h index 9d85813..0357f4e 100644 --- a/source/utils.h +++ b/source/utils.h @@ -79,6 +79,7 @@ struct Zone { // Colisiones y geometría auto distanceSquared(int x1, int y1, int x2, int y2) -> double; +auto getCollisionPoint(const Circle &a, const Circle &b) -> SDL_FPoint; auto checkCollision(const Circle &a, const Circle &b) -> bool; auto checkCollision(const Circle &a, const SDL_FRect &b) -> bool; auto checkCollision(const SDL_FRect &a, const SDL_FRect &b) -> bool;