diff --git a/data/sounds/effects/continue.wav b/data/sounds/effects/continue.wav new file mode 100644 index 0000000..e0e6357 Binary files /dev/null and b/data/sounds/effects/continue.wav differ diff --git a/data/sounds/effects/explosion2.wav b/data/sounds/effects/explosion2.wav new file mode 100644 index 0000000..f24c6e9 Binary files /dev/null and b/data/sounds/effects/explosion2.wav differ diff --git a/data/sounds/effects/init_hud.wav b/data/sounds/effects/init_hud.wav new file mode 100644 index 0000000..70c2a85 Binary files /dev/null and b/data/sounds/effects/init_hud.wav differ diff --git a/data/sounds/effects/start.wav b/data/sounds/effects/start.wav new file mode 100644 index 0000000..078dd5b Binary files /dev/null and b/data/sounds/effects/start.wav differ diff --git a/source/core/defaults.hpp b/source/core/defaults.hpp index ffd95f9..6cc62f5 100644 --- a/source/core/defaults.hpp +++ b/source/core/defaults.hpp @@ -159,25 +159,35 @@ constexpr float P2_SPAWN_X_RATIO = 0.67f; // 67% desde izquierda constexpr float SPAWN_Y_RATIO = 0.75f; // 75% desde arriba // Continue system behavior -constexpr int CONTINUE_COUNT_START = 9; // Countdown starts at 9 -constexpr float CONTINUE_TICK_DURATION = 1.0f; // Seconds per countdown tick -constexpr int MAX_CONTINUES = 3; // Maximum continues per game -constexpr bool INFINITE_CONTINUES = false; // If true, unlimited continues +constexpr int CONTINUE_COUNT_START = 9; // Countdown starts at 9 +constexpr float CONTINUE_TICK_DURATION = 1.0f; // Seconds per countdown tick +constexpr int MAX_CONTINUES = 3; // Maximum continues per game +constexpr bool INFINITE_CONTINUES = false; // If true, unlimited continues // Continue screen visual configuration namespace ContinueScreen { // "CONTINUE" text -constexpr float CONTINUE_TEXT_SCALE = 2.0f; // Text size -constexpr float CONTINUE_TEXT_Y_RATIO = 0.35f; // 35% from top of PLAYAREA +constexpr float CONTINUE_TEXT_SCALE = 2.0f; // Text size +constexpr float CONTINUE_TEXT_Y_RATIO = 0.30f; // 35% from top of PLAYAREA // Countdown number (9, 8, 7...) constexpr float COUNTER_TEXT_SCALE = 4.0f; // Text size (large) constexpr float COUNTER_TEXT_Y_RATIO = 0.50f; // 50% from top of PLAYAREA // "CONTINUES LEFT: X" text -constexpr float INFO_TEXT_SCALE = 1.0f; // Text size (small) -constexpr float INFO_TEXT_Y_RATIO = 0.65f; // 65% from top of PLAYAREA +constexpr float INFO_TEXT_SCALE = 0.7f; // Text size (small) +constexpr float INFO_TEXT_Y_RATIO = 0.75f; // 65% from top of PLAYAREA } // namespace ContinueScreen + +// Game Over screen visual configuration +namespace GameOverScreen { +constexpr float TEXT_SCALE = 2.0f; // "GAME OVER" text size +constexpr float TEXT_SPACING = 4.0f; // Character spacing +} // namespace GameOverScreen + +// Stage message configuration (LEVEL_START, LEVEL_COMPLETED) +constexpr float STAGE_MESSAGE_Y_RATIO = 0.25f; // 25% from top of PLAYAREA +constexpr float STAGE_MESSAGE_MAX_WIDTH_RATIO = 0.9f; // 90% of PLAYAREA width } // namespace Game // Física (valores actuales del juego, sincronizados con joc_asteroides.cpp) @@ -269,19 +279,24 @@ constexpr bool ENABLED = true; // Audio habilitado por defecto // Música (pistas de fondo) namespace Music { -constexpr float VOLUME = 0.8F; // Volumen música -constexpr bool ENABLED = true; // Música habilitada -constexpr const char* GAME_TRACK = "game.ogg"; // Pista de juego -constexpr int FADE_DURATION_MS = 1000; // Fade out duration +constexpr float VOLUME = 0.8F; // Volumen música +constexpr bool ENABLED = true; // Música habilitada +constexpr const char* GAME_TRACK = "game.ogg"; // Pista de juego +constexpr const char* TITLE_TRACK = "title.ogg"; // Pista de titulo +constexpr int FADE_DURATION_MS = 1000; // Fade out duration } // namespace Music // Efectes de so (sons puntuals) namespace Sound { constexpr float VOLUME = 1.0F; // Volumen efectos constexpr bool ENABLED = true; // Sonidos habilitados +constexpr const char* CONTINUE = "effects/continue.wav"; // Cuenta atras constexpr const char* EXPLOSION = "effects/explosion.wav"; // Explosión +constexpr const char* EXPLOSION2 = "effects/explosion2.wav"; // Explosión alternativa +constexpr const char* INIT_HUD = "effects/init_hud.wav"; // Para la animación del HUD constexpr const char* LASER = "effects/laser_shoot.wav"; // Disparo constexpr const char* LOGO = "effects/logo.wav"; // Logo +constexpr const char* START = "effects/start.wav"; // El jugador pulsa START constexpr const char* GOOD_JOB_COMMANDER = "voices/good_job_commander.wav"; // Voz: "Good job, commander" } // namespace Sound @@ -473,8 +488,8 @@ constexpr float P2_FREQUENCY_MULTIPLIER = 1.12f; // 12% més ràpida namespace Layout { // Posicions verticals (anclatges des del TOP de pantalla lògica, 0.0-1.0) constexpr float LOGO_POS = 0.20f; // Logo "ORNI" -constexpr float PRESS_START_POS = 0.73f; // "PRESS START TO PLAY" -constexpr float COPYRIGHT1_POS = 0.87f; // Primera línia copyright +constexpr float PRESS_START_POS = 0.75f; // "PRESS START TO PLAY" +constexpr float COPYRIGHT1_POS = 0.90f; // Primera línia copyright // Separacions relatives (proporció respecte Game::HEIGHT = 480px) constexpr float LOGO_LINE_SPACING = 0.02f; // Entre "ORNI" i "ATTACK!" (10px) diff --git a/source/core/graphics/vector_text.cpp b/source/core/graphics/vector_text.cpp index b48c150..19a5174 100644 --- a/source/core/graphics/vector_text.cpp +++ b/source/core/graphics/vector_text.cpp @@ -236,6 +236,22 @@ void VectorText::render(const std::string& text, const Punt& posicio, float esca } } +void VectorText::render_centered(const std::string& text, const Punt& centre_punt, float escala, float spacing, float brightness) { + // Calcular dimensions del text + float text_width = get_text_width(text, escala, spacing); + float text_height = get_text_height(escala); + + // Calcular posició de l'esquina superior esquerra + // restant la meitat de les dimensions del punt central + Punt posicio_esquerra = { + centre_punt.x - (text_width / 2.0f), + centre_punt.y - (text_height / 2.0f) + }; + + // Delegar al mètode render() existent + render(text, posicio_esquerra, escala, spacing, brightness); +} + float VectorText::get_text_width(const std::string& text, float escala, float spacing) const { if (text.empty()) { return 0.0f; diff --git a/source/core/graphics/vector_text.hpp b/source/core/graphics/vector_text.hpp index 1120c7b..a5ba05f 100644 --- a/source/core/graphics/vector_text.hpp +++ b/source/core/graphics/vector_text.hpp @@ -27,6 +27,14 @@ class VectorText { // - brightness: factor de brillantor (0.0-1.0, default 1.0 = màxima brillantor) void render(const std::string& text, const Punt& posicio, float escala = 1.0f, float spacing = 2.0f, float brightness = 1.0f); + // Renderizar string centrado en un punto + // - text: cadena a renderizar + // - centre_punt: punto central del texto (no esquina superior izquierda) + // - escala: factor de escala (1.0 = 20×40 px por carácter) + // - spacing: espacio entre caracteres en píxeles (a escala 1.0) + // - brightness: factor de brillantor (0.0-1.0, default 1.0 = màxima brillantor) + void render_centered(const std::string& text, const Punt& centre_punt, float escala = 1.0f, float spacing = 2.0f, float brightness = 1.0f); + // Calcular ancho total de un string (útil para centrado) float get_text_width(const std::string& text, float escala = 1.0f, float spacing = 2.0f) const; diff --git a/source/game/effects/debris_manager.cpp b/source/game/effects/debris_manager.cpp index 749fccf..6e7c008 100644 --- a/source/game/effects/debris_manager.cpp +++ b/source/game/effects/debris_manager.cpp @@ -51,13 +51,14 @@ void DebrisManager::explotar(const std::shared_ptr& shape, float brightness, const Punt& velocitat_objecte, float velocitat_angular, - float factor_herencia_visual) { + float factor_herencia_visual, + const std::string& sound) { if (!shape || !shape->es_valida()) { return; } // Reproducir sonido de explosión - Audio::get()->playSound(Defaults::Sound::EXPLOSION, Audio::Group::GAME); + Audio::get()->playSound(sound, Audio::Group::GAME); // Obtenir centre de la forma per a transformacions const Punt& shape_centre = shape->get_centre(); diff --git a/source/game/effects/debris_manager.hpp b/source/game/effects/debris_manager.hpp index 20b1370..6c16377 100644 --- a/source/game/effects/debris_manager.hpp +++ b/source/game/effects/debris_manager.hpp @@ -37,7 +37,8 @@ class DebrisManager { float brightness = 1.0f, const Punt& velocitat_objecte = {0.0f, 0.0f}, float velocitat_angular = 0.0f, - float factor_herencia_visual = 0.0f); + float factor_herencia_visual = 0.0f, + const std::string& sound = Defaults::Sound::EXPLOSION); // Actualitzar tots els fragments actius void actualitzar(float delta_time); diff --git a/source/game/effects/gestor_puntuacio_flotant.cpp b/source/game/effects/gestor_puntuacio_flotant.cpp index 90d7b0b..2c488f1 100644 --- a/source/game/effects/gestor_puntuacio_flotant.cpp +++ b/source/game/effects/gestor_puntuacio_flotant.cpp @@ -61,16 +61,11 @@ void GestorPuntuacioFlotant::dibuixar() { if (!pf.actiu) continue; - // 1. Calcular dimensions del text per centrar-lo + // Renderitzar centrat amb brightness (fade) constexpr float escala = Defaults::FloatingScore::SCALE; constexpr float spacing = Defaults::FloatingScore::SPACING; - float text_width = text_.get_text_width(pf.text, escala, spacing); - // 2. Centrar text sobre la posició - Punt render_pos = {pf.posicio.x - text_width / 2.0f, pf.posicio.y}; - - // 3. Renderitzar amb brightness (fade) - text_.render(pf.text, render_pos, escala, spacing, pf.brightness); + text_.render_centered(pf.text, pf.posicio, escala, spacing, pf.brightness); } } diff --git a/source/game/escenes/escena_joc.cpp b/source/game/escenes/escena_joc.cpp index 6b91d32..701088b 100644 --- a/source/game/escenes/escena_joc.cpp +++ b/source/game/escenes/escena_joc.cpp @@ -526,17 +526,15 @@ void EscenaJoc::dibuixar() { // Draw centered "GAME OVER" text const std::string game_over_text = "GAME OVER"; - constexpr float escala = 2.0f; - constexpr float spacing = 4.0f; - - float text_width = text_.get_text_width(game_over_text, escala, spacing); - float text_height = text_.get_text_height(escala); + constexpr float escala = Defaults::Game::GameOverScreen::TEXT_SCALE; + constexpr float spacing = Defaults::Game::GameOverScreen::TEXT_SPACING; + // Calcular centre de l'àrea de joc usant constants const SDL_FRect& play_area = Defaults::Zones::PLAYAREA; - float x = play_area.x + (play_area.w - text_width) / 2.0f; - float y = play_area.y + (play_area.h - text_height) / 2.0f; + float centre_x = play_area.x + play_area.w / 2.0f; + float centre_y = play_area.y + play_area.h / 2.0f; - text_.render(game_over_text, {x, y}, escala, spacing); + text_.render_centered(game_over_text, {centre_x, centre_y}, escala, spacing); dibuixar_marcador(); return; @@ -577,7 +575,7 @@ void EscenaJoc::dibuixar() { if (rect_progress > 0.0f) { // [NOU] Reproduir so quan comença l'animació del rectangle if (!init_hud_rect_sound_played_) { - Audio::get()->playSound(Defaults::Sound::LOGO, Audio::Group::GAME); + Audio::get()->playSound(Defaults::Sound::INIT_HUD, Audio::Group::GAME); init_hud_rect_sound_played_ = true; } @@ -702,11 +700,10 @@ void EscenaJoc::tocado(uint8_t player_id) { naus_[player_id].get_brightness(), // Heredar brightness vel_nau_80, // Heredar 80% velocitat 0.0f, // Nave: trayectorias rectas (sin drotacio) - 0.0f // Sin herencia visual (rotación aleatoria) + 0.0f, // Sin herencia visual (rotación aleatoria) + Defaults::Sound::EXPLOSION2 // Sonido alternativo para la explosión ); - Audio::get()->playSound(Defaults::Sound::EXPLOSION, Audio::Group::GAME); - // Start death timer (non-zero to avoid re-triggering) itocado_per_jugador_[player_id] = 0.001f; } @@ -739,19 +736,13 @@ void EscenaJoc::dibuixar_marcador() { const float escala = 0.85f; const float spacing = 0.0f; - // Calcular dimensions del text - float text_width = text_.get_text_width(text, escala, spacing); - float text_height = text_.get_text_height(escala); + // Calcular centre de la zona del marcador + const SDL_FRect& scoreboard = Defaults::Zones::SCOREBOARD; + float centre_x = scoreboard.w / 2.0f; + float centre_y = scoreboard.y + scoreboard.h / 2.0f; - // Centrat horitzontal dins de la zona del marcador - float x = (Defaults::Zones::SCOREBOARD.w - text_width) / 2.0f; - - // Centrat vertical dins de la zona del marcador - float y = Defaults::Zones::SCOREBOARD.y + - (Defaults::Zones::SCOREBOARD.h - text_height) / 2.0f; - - // Renderitzar - text_.render(text, {x, y}, escala, spacing); + // Renderitzar centrat + text_.render_centered(text, {centre_x, centre_y}, escala, spacing); } void EscenaJoc::dibuixar_marges_animat(float progress) const { @@ -826,25 +817,19 @@ void EscenaJoc::dibuixar_marcador_animat(float progress) { const float escala = 0.85f; const float spacing = 0.0f; - // Calcular dimensions - float text_width = text_.get_text_width(text, escala, spacing); - float text_height = text_.get_text_height(escala); - - // Posició X final (centrada horitzontalment) - float x_final = (Defaults::Zones::SCOREBOARD.w - text_width) / 2.0f; - - // Posició Y final (centrada verticalment en la zona de scoreboard) - float y_final = Defaults::Zones::SCOREBOARD.y + - (Defaults::Zones::SCOREBOARD.h - text_height) / 2.0f; + // Calcular centre de la zona del marcador + const SDL_FRect& scoreboard = Defaults::Zones::SCOREBOARD; + float centre_x = scoreboard.w / 2.0f; + float centre_y_final = scoreboard.y + scoreboard.h / 2.0f; // Posició Y inicial (offscreen, sota de la pantalla) - float y_inicial = static_cast(Defaults::Game::HEIGHT) + text_height; + float centre_y_inicial = static_cast(Defaults::Game::HEIGHT); // Interpolació amb easing - float y_animada = y_inicial + (y_final - y_inicial) * eased_progress; + float centre_y_animada = centre_y_inicial + (centre_y_final - centre_y_inicial) * eased_progress; - // Renderitzar en posició animada - text_.render(text, {x_final, y_animada}, escala, spacing); + // Renderitzar centrat en posició animada + text_.render_centered(text, {centre_x, centre_y_animada}, escala, spacing); } Punt EscenaJoc::calcular_posicio_nau_init_hud(float progress, uint8_t player_id) const { @@ -1067,10 +1052,9 @@ void EscenaJoc::detectar_col·lisio_naus_enemics() { void EscenaJoc::dibuixar_missatge_stage(const std::string& missatge) { constexpr float escala_base = 1.0f; constexpr float spacing = 2.0f; - constexpr float max_width_ratio = 0.9f; // 90% del ancho disponible const SDL_FRect& play_area = Defaults::Zones::PLAYAREA; - const float max_width = play_area.w * max_width_ratio; // 558px + const float max_width = play_area.w * Defaults::Game::STAGE_MESSAGE_MAX_WIDTH_RATIO; // ========== TYPEWRITER EFFECT (PARAMETRIZED) ========== // Get state-specific timing configuration @@ -1123,7 +1107,7 @@ void EscenaJoc::dibuixar_missatge_stage(const std::string& missatge) { // Calculate position as if FULL text was there (for fixed position typewriter) float x = play_area.x + (play_area.w - full_text_width) / 2.0f; - float y = play_area.y + (play_area.h * 0.25f) - (text_height / 2.0f); + float y = play_area.y + (play_area.h * Defaults::Game::STAGE_MESSAGE_Y_RATIO) - (text_height / 2.0f); // Render only the partial message (typewriter effect) Punt pos = {x, y}; @@ -1190,7 +1174,7 @@ void EscenaJoc::actualitzar_continue(float delta_time) { continue_tick_timer_ = Defaults::Game::CONTINUE_TICK_DURATION; // Play tick sound - Audio::get()->playSound("continue_tick"); + Audio::get()->playSound(Defaults::Sound::CONTINUE, Audio::Group::GAME); if (continue_counter_ <= 0) { // Timeout → final game over @@ -1257,7 +1241,7 @@ void EscenaJoc::processar_input_continue() { continue_tick_timer_ = 0.0f; // Play continue confirmation sound - Audio::get()->playSound("continue_confirm"); + Audio::get()->playSound(Defaults::Sound::START, Audio::Group::GAME); return; } @@ -1272,7 +1256,7 @@ void EscenaJoc::processar_input_continue() { continue_counter_--; // Play tick sound on manual decrement - Audio::get()->playSound("continue_tick"); + Audio::get()->playSound(Defaults::Sound::CONTINUE, Audio::Group::GAME); if (continue_counter_ <= 0) { estat_game_over_ = EstatGameOver::GAME_OVER; @@ -1293,22 +1277,19 @@ void EscenaJoc::dibuixar_continue() { float escala_continue = Defaults::Game::ContinueScreen::CONTINUE_TEXT_SCALE; float y_ratio_continue = Defaults::Game::ContinueScreen::CONTINUE_TEXT_Y_RATIO; - float text_width_continue = text_.get_text_width(continue_text, escala_continue, spacing); - float x_continue = play_area.x + (play_area.w - text_width_continue) / 2.0f; - float y_continue = play_area.y + play_area.h * y_ratio_continue; + float centre_x = play_area.x + play_area.w / 2.0f; + float centre_y_continue = play_area.y + play_area.h * y_ratio_continue; - text_.render(continue_text, {x_continue, y_continue}, escala_continue, spacing); + text_.render_centered(continue_text, {centre_x, centre_y_continue}, escala_continue, spacing); // Countdown number (using constants) const std::string counter_str = std::to_string(continue_counter_); float escala_counter = Defaults::Game::ContinueScreen::COUNTER_TEXT_SCALE; float y_ratio_counter = Defaults::Game::ContinueScreen::COUNTER_TEXT_Y_RATIO; - float text_width_counter = text_.get_text_width(counter_str, escala_counter, spacing); - float x_counter = play_area.x + (play_area.w - text_width_counter) / 2.0f; - float y_counter = play_area.y + play_area.h * y_ratio_counter; + float centre_y_counter = play_area.y + play_area.h * y_ratio_counter; - text_.render(counter_str, {x_counter, y_counter}, escala_counter, spacing); + text_.render_centered(counter_str, {centre_x, centre_y_counter}, escala_counter, spacing); // "CONTINUES LEFT" (conditional + using constants) if (!Defaults::Game::INFINITE_CONTINUES) { @@ -1316,11 +1297,9 @@ void EscenaJoc::dibuixar_continue() { float escala_info = Defaults::Game::ContinueScreen::INFO_TEXT_SCALE; float y_ratio_info = Defaults::Game::ContinueScreen::INFO_TEXT_Y_RATIO; - float text_width_info = text_.get_text_width(continues_text, escala_info, spacing); - float x_info = play_area.x + (play_area.w - text_width_info) / 2.0f; - float y_info = play_area.y + play_area.h * y_ratio_info; + float centre_y_info = play_area.y + play_area.h * y_ratio_info; - text_.render(continues_text, {x_info, y_info}, escala_info, spacing); + text_.render_centered(continues_text, {centre_x, centre_y_info}, escala_info, spacing); } } diff --git a/source/game/escenes/escena_titol.cpp b/source/game/escenes/escena_titol.cpp index b7f42ef..dc7cb55 100644 --- a/source/game/escenes/escena_titol.cpp +++ b/source/game/escenes/escena_titol.cpp @@ -420,8 +420,8 @@ void EscenaTitol::actualitzar(float delta_time) { } } - // Reproducir so de LASER quan el segon jugador s'uneix - Audio::get()->playSound(Defaults::Sound::LASER, Audio::Group::GAME); + // Reproducir so de START quan el segon jugador s'uneix + Audio::get()->playSound(Defaults::Sound::START, Audio::Group::GAME); // Reiniciar el timer per allargar el temps de transició temps_acumulat_ = 0.0f; @@ -496,7 +496,7 @@ void EscenaTitol::actualitzar(float delta_time) { } Audio::get()->fadeOutMusic(MUSIC_FADE); - Audio::get()->playSound(Defaults::Sound::LASER, Audio::Group::GAME); + Audio::get()->playSound(Defaults::Sound::START, Audio::Group::GAME); } } } @@ -652,12 +652,10 @@ void EscenaTitol::dibuixar() { const std::string main_text = "PRESS START TO PLAY"; const float escala_main = Defaults::Title::Layout::PRESS_START_SCALE; - float text_width = text_.get_text_width(main_text, escala_main, spacing); + float centre_x = Defaults::Game::WIDTH / 2.0f; + float centre_y = Defaults::Game::HEIGHT * Defaults::Title::Layout::PRESS_START_POS; - float x_center = (Defaults::Game::WIDTH - text_width) / 2.0f; - float y_center = Defaults::Game::HEIGHT * Defaults::Title::Layout::PRESS_START_POS; - - text_.render(main_text, Punt{x_center, y_center}, escala_main, spacing); + text_.render_centered(main_text, {centre_x, centre_y}, escala_main, spacing); } // === Copyright a la part inferior (centrat horitzontalment, dues línies) === @@ -681,15 +679,11 @@ void EscenaTitol::dibuixar() { float y_line1 = Defaults::Game::HEIGHT * Defaults::Title::Layout::COPYRIGHT1_POS; float y_line2 = y_line1 + copy_height + line_spacing; // Línea 2 debajo de línea 1 - // Renderitzar línea 1 (original) - float width_line1 = text_.get_text_width(copyright_original, escala_copy, spacing); - float x_line1 = (Defaults::Game::WIDTH - width_line1) / 2.0f; - text_.render(copyright_original, Punt{x_line1, y_line1}, escala_copy, spacing); + // Renderitzar línees centrades + float centre_x = Defaults::Game::WIDTH / 2.0f; - // Renderitzar línea 2 (port) - float width_line2 = text_.get_text_width(copyright_port, escala_copy, spacing); - float x_line2 = (Defaults::Game::WIDTH - width_line2) / 2.0f; - text_.render(copyright_port, Punt{x_line2, y_line2}, escala_copy, spacing); + text_.render_centered(copyright_original, {centre_x, y_line1}, escala_copy, spacing); + text_.render_centered(copyright_port, {centre_x, y_line2}, escala_copy, spacing); } }