diff --git a/CMakeLists.txt b/CMakeLists.txt index 306c1db..0f2be20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ # CMakeLists.txt cmake_minimum_required(VERSION 3.10) -project(orni VERSION 0.5.0) +project(orni VERSION 0.6.0) # Info del proyecto set(PROJECT_LONG_NAME "Orni Attack") diff --git a/source/core/defaults.hpp b/source/core/defaults.hpp index bbcfa24..8bacdc3 100644 --- a/source/core/defaults.hpp +++ b/source/core/defaults.hpp @@ -86,6 +86,15 @@ constexpr float LEVEL_START_TYPING_RATIO = 0.3f; // 30% escribiendo, 70% // Transición LEVEL_COMPLETED (mensaje "GOOD JOB COMMANDER!") constexpr float LEVEL_COMPLETED_DURATION = 3.0f; // Duración total constexpr float LEVEL_COMPLETED_TYPING_RATIO = 0.0f; // 0.0 = sin typewriter (directo) + +// Transición INIT_HUD (animación inicial del HUD) +constexpr float INIT_HUD_DURATION = 3.0f; // Duración total del estado +constexpr float INIT_HUD_RECT_DURATION = 2.0f; // Duración animación rectángulo +constexpr float INIT_HUD_SCORE_DURATION = 2.5f; // Duración animación marcador +constexpr float INIT_HUD_SHIP_DURATION = 2.5f; // Duración animación nave + +// Posición inicial de la nave en INIT_HUD (75% de altura de zona de juego) +constexpr float INIT_HUD_SHIP_START_Y_RATIO = 0.75f; // 75% desde el top de PLAYAREA } // namespace Game // Física (valores actuales del juego, sincronizados con joc_asteroides.cpp) diff --git a/source/core/math/easing.hpp b/source/core/math/easing.hpp new file mode 100644 index 0000000..f10a35d --- /dev/null +++ b/source/core/math/easing.hpp @@ -0,0 +1,20 @@ +// easing.hpp - Funcions d'interpolació i easing +// © 2025 Orni Attack + +#pragma once + +namespace Easing { + +// Ease-out quadratic: empieza rápido, desacelera suavemente +// t = progreso normalizado [0.0 - 1.0] +// retorna valor interpolado [0.0 - 1.0] +inline float ease_out_quad(float t) { + return 1.0f - (1.0f - t) * (1.0f - t); +} + +// Interpolación lineal básica (para referencia) +inline float lerp(float start, float end, float t) { + return start + (end - start) * t; +} + +} // namespace Easing diff --git a/source/game/entities/nau.hpp b/source/game/entities/nau.hpp index 7505031..cbf7353 100644 --- a/source/game/entities/nau.hpp +++ b/source/game/entities/nau.hpp @@ -26,6 +26,7 @@ class Nau { const Punt& get_centre() const { return centre_; } float get_angle() const { return angle_; } bool esta_viva() const { return !esta_tocada_; } + bool esta_tocada() const { return esta_tocada_; } const std::shared_ptr& get_forma() const { return forma_; } float get_brightness() const { return brightness_; } Punt get_velocitat_vector() const { @@ -35,6 +36,9 @@ class Nau { }; } + // Setters + void set_centre(const Punt& nou_centre) { centre_ = nou_centre; } + // Col·lisions (Fase 10) void marcar_tocada() { esta_tocada_ = true; } diff --git a/source/game/escenes/escena_joc.cpp b/source/game/escenes/escena_joc.cpp index 118fb58..88db8ef 100644 --- a/source/game/escenes/escena_joc.cpp +++ b/source/game/escenes/escena_joc.cpp @@ -12,6 +12,7 @@ #include "core/audio/audio.hpp" #include "core/input/mouse.hpp" +#include "core/math/easing.hpp" #include "core/rendering/line_renderer.hpp" #include "core/system/context_escenes.hpp" #include "core/system/global_events.hpp" @@ -146,11 +147,13 @@ void EscenaJoc::inicialitzar() { puntuacio_total_ = 0; gestor_puntuacio_.reiniciar(); - // Set spawn point to center of play area - Constants::obtenir_centre_zona(punt_spawn_.x, punt_spawn_.y); + // Set spawn point to center X, 75% Y (same as INIT_HUD final position) + const SDL_FRect& zona = Defaults::Zones::PLAYAREA; + punt_spawn_.x = zona.x + zona.w * 0.5f; + punt_spawn_.y = zona.y + zona.h * Defaults::Game::INIT_HUD_SHIP_START_Y_RATIO; - // Inicialitzar nau - nau_.inicialitzar(); + // Inicialitzar nau amb posició especial + nau_.inicialitzar(&punt_spawn_); // [MODIFIED] Initialize enemies as inactive (stage system will spawn them) for (auto& enemy : orni_) { @@ -164,8 +167,9 @@ void EscenaJoc::inicialitzar() { bala.inicialitzar(); } - // Iniciar música de joc (sense stopMusic, ja s'ha parat en destructor de TITOL) - Audio::get()->playMusic("game.ogg"); + // [ELIMINAT] Iniciar música de joc (ara es gestiona en stage_manager) + // La música s'inicia quan es transiciona de INIT_HUD a LEVEL_START + // Audio::get()->playMusic("game.ogg"); } void EscenaJoc::actualitzar(float delta_time) { @@ -240,6 +244,32 @@ void EscenaJoc::actualitzar(float delta_time) { StageSystem::EstatStage estat = stage_manager_->get_estat(); switch (estat) { + case StageSystem::EstatStage::INIT_HUD: { + // Update stage manager timer + stage_manager_->actualitzar(delta_time); + + // Calcular progrés de l'animació de la nau + float ship_progress = 1.0f - (stage_manager_->get_timer_transicio() / + Defaults::Game::INIT_HUD_DURATION); + ship_progress = std::min(1.0f, ship_progress); + + // Calcular quant ha avançat l'animació de la nau + float ship_anim_progress = ship_progress / + (Defaults::Game::INIT_HUD_SHIP_DURATION / Defaults::Game::INIT_HUD_DURATION); + ship_anim_progress = std::min(1.0f, ship_anim_progress); + + // Actualitzar posició de la nau segons animació + if (ship_anim_progress < 1.0f) { + Punt pos_animada = calcular_posicio_nau_init_hud(ship_anim_progress); + nau_.set_centre(pos_animada); + } + + // Una vegada l'animació acaba, permetre control normal + // però mantenir la posició inicial especial fins LEVEL_START + + break; + } + case StageSystem::EstatStage::LEVEL_START: // Update countdown timer stage_manager_->actualitzar(delta_time); @@ -312,12 +342,10 @@ void EscenaJoc::actualitzar(float delta_time) { } void EscenaJoc::dibuixar() { - // Draw borders (always visible) - dibuixar_marges(); - // Check game over state if (game_over_) { // Game over: draw enemies, bullets, debris, and "GAME OVER" text + dibuixar_marges(); for (const auto& enemy : orni_) { enemy.dibuixar(); @@ -352,7 +380,46 @@ void EscenaJoc::dibuixar() { StageSystem::EstatStage estat = stage_manager_->get_estat(); switch (estat) { + case StageSystem::EstatStage::INIT_HUD: { + // Calcular progrés de cada animació independent + float timer = stage_manager_->get_timer_transicio(); + float total_time = Defaults::Game::INIT_HUD_DURATION; + float global_progress = 1.0f - (timer / total_time); + + // Progrés del rectangle (empieza inmediatament) + float rect_progress = global_progress / + (Defaults::Game::INIT_HUD_RECT_DURATION / total_time); + rect_progress = std::min(1.0f, rect_progress); + + // Progrés del marcador (empieza inmediatament) + float score_progress = global_progress / + (Defaults::Game::INIT_HUD_SCORE_DURATION / total_time); + score_progress = std::min(1.0f, score_progress); + + // Progrés de la nau (empieza inmediatament) + float ship_progress = global_progress / + (Defaults::Game::INIT_HUD_SHIP_DURATION / total_time); + ship_progress = std::min(1.0f, ship_progress); + + // Dibuixar elements animats + if (rect_progress > 0.0f) { + dibuixar_marges_animat(rect_progress); + } + + if (score_progress > 0.0f) { + dibuixar_marcador_animat(score_progress); + } + + // Dibuixar nau (usant el sistema normal, la posició ja està actualitzada) + if (ship_progress > 0.0f && !nau_.esta_tocada()) { + nau_.dibuixar(); + } + + break; + } + case StageSystem::EstatStage::LEVEL_START: + dibuixar_marges(); // [NEW] Draw ship if alive if (itocado_ == 0.0f) { nau_.dibuixar(); @@ -373,6 +440,8 @@ void EscenaJoc::dibuixar() { break; case StageSystem::EstatStage::PLAYING: + dibuixar_marges(); + // [EXISTING] Normal rendering if (itocado_ == 0.0f) { nau_.dibuixar(); @@ -392,6 +461,7 @@ void EscenaJoc::dibuixar() { break; case StageSystem::EstatStage::LEVEL_COMPLETED: + dibuixar_marges(); // [NEW] Draw ship if alive if (itocado_ == 0.0f) { nau_.dibuixar(); @@ -558,6 +628,131 @@ void EscenaJoc::dibuixar_marcador() { text_.render(text, {x, y}, escala, spacing); } +void EscenaJoc::dibuixar_marges_animat(float progress) const { + // Animació seqüencial del rectangle amb efecte de "pinzell" + // Dos pinzells comencen al centre superior i baixen pels laterals + + const SDL_FRect& zona = Defaults::Zones::PLAYAREA; + + // Aplicar easing al progrés global + float eased_progress = Easing::ease_out_quad(progress); + + // Coordenades del rectangle complet + int x1 = static_cast(zona.x); + int y1 = static_cast(zona.y); + int x2 = static_cast(zona.x + zona.w); + int y2 = static_cast(zona.y + zona.h); + int cx = (x1 + x2) / 2; + + // Dividir en 3 fases de 33% cada una + constexpr float PHASE_1_END = 0.33f; + constexpr float PHASE_2_END = 0.66f; + + // --- FASE 1: Línies horitzontals superiors (0-33%) --- + if (eased_progress > 0.0f) { + float phase1_progress = std::min(eased_progress / PHASE_1_END, 1.0f); + + // Línia esquerra: creix des del centre cap a l'esquerra + int x1_phase1 = static_cast(cx - (cx - x1) * phase1_progress); + Rendering::linea(sdl_.obte_renderer(), cx, y1, x1_phase1, y1, true); + + // Línia dreta: creix des del centre cap a la dreta + int x2_phase1 = static_cast(cx + (x2 - cx) * phase1_progress); + Rendering::linea(sdl_.obte_renderer(), cx, y1, x2_phase1, y1, true); + } + + // --- FASE 2: Línies verticals laterals (33-66%) --- + if (eased_progress > PHASE_1_END) { + float phase2_progress = std::min((eased_progress - PHASE_1_END) / (PHASE_2_END - PHASE_1_END), 1.0f); + + // Línia esquerra: creix des de dalt cap a baix + int y2_phase2 = static_cast(y1 + (y2 - y1) * phase2_progress); + Rendering::linea(sdl_.obte_renderer(), x1, y1, x1, y2_phase2, true); + + // Línia dreta: creix des de dalt cap a baix + Rendering::linea(sdl_.obte_renderer(), x2, y1, x2, y2_phase2, true); + } + + // --- FASE 3: Línies horitzontals inferiors (66-100%) --- + if (eased_progress > PHASE_2_END) { + float phase3_progress = (eased_progress - PHASE_2_END) / (1.0f - PHASE_2_END); + + // Línia esquerra: creix des de l'esquerra cap al centre + int x_left_phase3 = static_cast(x1 + (cx - x1) * phase3_progress); + Rendering::linea(sdl_.obte_renderer(), x1, y2, x_left_phase3, y2, true); + + // Línia dreta: creix des de la dreta cap al centre + int x_right_phase3 = static_cast(x2 - (x2 - cx) * phase3_progress); + Rendering::linea(sdl_.obte_renderer(), x2, y2, x_right_phase3, y2, true); + } +} + +void EscenaJoc::dibuixar_marcador_animat(float progress) { + // Animació del marcador pujant des de baix amb easing + + // Calcular progrés amb easing + float eased_progress = Easing::ease_out_quad(progress); + + // Posició final del marcador (normal) + const float escala = 0.85f; + const float spacing = 0.0f; + + // Formatar text igual que en dibuixar_marcador() normal + uint8_t stage_num = stage_manager_->get_stage_actual(); + std::string stage_str = (stage_num < 10) ? "0" + std::to_string(stage_num) + : std::to_string(stage_num); + + std::string score_str = std::to_string(puntuacio_total_); + score_str = std::string(5 - std::min(5, static_cast(score_str.length())), '0') + score_str; + + std::string text = "SCORE: " + score_str + " LIFES: " + std::to_string(num_vides_) + + " LEVEL: " + stage_str; + + // 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; + + // Posició Y inicial (offscreen, sota de la pantalla) + float y_inicial = static_cast(Defaults::Game::HEIGHT) + text_height; + + // Interpolació amb easing + float y_animada = y_inicial + (y_final - y_inicial) * eased_progress; + + // Renderitzar en posició animada + text_.render(text, {x_final, y_animada}, escala, spacing); +} + +Punt EscenaJoc::calcular_posicio_nau_init_hud(float progress) const { + // Animació de la nau pujant des de baix amb easing + + // Calcular progrés amb easing + float eased_progress = Easing::ease_out_quad(progress); + + const SDL_FRect& zona = Defaults::Zones::PLAYAREA; + + // Posició X final (centre de la zona de joc) + float x_final = zona.x + zona.w / 2.0f; + + // Posició Y final (75% de l'altura de la zona de joc) + float y_final = zona.y + zona.h * Defaults::Game::INIT_HUD_SHIP_START_Y_RATIO; + + // Posició Y inicial (offscreen, sota de la zona de joc) + float y_inicial = zona.y + zona.h + 50.0f; // 50px sota + + // X no canvia (sempre centrada) + // Y interpola amb easing + float y_animada = y_inicial + (y_final - y_inicial) * eased_progress; + + return {x_final, y_animada}; +} + void EscenaJoc::detectar_col·lisions_bales_enemics() { // Constants amplificades per hitbox més generós (115%) constexpr float RADI_BALA = Defaults::Entities::BULLET_RADIUS; diff --git a/source/game/escenes/escena_joc.hpp b/source/game/escenes/escena_joc.hpp index b0fda4e..57179fc 100644 --- a/source/game/escenes/escena_joc.hpp +++ b/source/game/escenes/escena_joc.hpp @@ -74,6 +74,11 @@ class EscenaJoc { // [NEW] Stage system helpers void dibuixar_missatge_stage(const std::string& missatge); + + // [NEW] Funcions d'animació per INIT_HUD + void dibuixar_marges_animat(float progress) const; // Rectangle amb creixement uniforme + void dibuixar_marcador_animat(float progress); // Marcador que puja des de baix + Punt calcular_posicio_nau_init_hud(float progress) const; // Posició animada de la nau }; #endif // ESCENA_JOC_HPP diff --git a/source/game/stage_system/stage_manager.cpp b/source/game/stage_system/stage_manager.cpp index e3bee87..800f744 100644 --- a/source/game/stage_system/stage_manager.cpp +++ b/source/game/stage_system/stage_manager.cpp @@ -5,6 +5,7 @@ #include +#include "core/audio/audio.hpp" #include "core/defaults.hpp" namespace StageSystem { @@ -22,7 +23,7 @@ StageManager::StageManager(const ConfigSistemaStages* config) void StageManager::inicialitzar() { stage_actual_ = 1; carregar_stage(stage_actual_); - canviar_estat(EstatStage::LEVEL_START); + canviar_estat(EstatStage::INIT_HUD); std::cout << "[StageManager] Inicialitzat a stage " << static_cast(stage_actual_) << std::endl; @@ -30,6 +31,10 @@ void StageManager::inicialitzar() { void StageManager::actualitzar(float delta_time, bool pausar_spawn) { switch (estat_) { + case EstatStage::INIT_HUD: + processar_init_hud(delta_time); + break; + case EstatStage::LEVEL_START: processar_level_start(delta_time); break; @@ -64,7 +69,9 @@ void StageManager::canviar_estat(EstatStage nou_estat) { estat_ = nou_estat; // Set timer based on state type - if (nou_estat == EstatStage::LEVEL_START) { + if (nou_estat == EstatStage::INIT_HUD) { + timer_transicio_ = Defaults::Game::INIT_HUD_DURATION; + } else if (nou_estat == EstatStage::LEVEL_START) { timer_transicio_ = Defaults::Game::LEVEL_START_DURATION; } else if (nou_estat == EstatStage::LEVEL_COMPLETED) { timer_transicio_ = Defaults::Game::LEVEL_COMPLETED_DURATION; @@ -74,10 +81,19 @@ void StageManager::canviar_estat(EstatStage nou_estat) { if (nou_estat == EstatStage::LEVEL_START) { size_t index = static_cast(std::rand()) % Constants::MISSATGES_LEVEL_START.size(); missatge_level_start_actual_ = Constants::MISSATGES_LEVEL_START[index]; + + // [NOU] Iniciar música al entrar en LEVEL_START (després de INIT_HUD) + // Només si no està sonant ja (per evitar reiniciar en loops posteriors) + if (Audio::get()->getMusicState() != Audio::MusicState::PLAYING) { + Audio::get()->playMusic("game.ogg"); + } } std::cout << "[StageManager] Canvi d'estat: "; switch (nou_estat) { + case EstatStage::INIT_HUD: + std::cout << "INIT_HUD"; + break; case EstatStage::LEVEL_START: std::cout << "LEVEL_START"; break; @@ -91,6 +107,14 @@ void StageManager::canviar_estat(EstatStage nou_estat) { std::cout << std::endl; } +void StageManager::processar_init_hud(float delta_time) { + timer_transicio_ -= delta_time; + + if (timer_transicio_ <= 0.0f) { + canviar_estat(EstatStage::LEVEL_START); + } +} + void StageManager::processar_level_start(float delta_time) { timer_transicio_ -= delta_time; diff --git a/source/game/stage_system/stage_manager.hpp b/source/game/stage_system/stage_manager.hpp index 552a7a8..c1b76e3 100644 --- a/source/game/stage_system/stage_manager.hpp +++ b/source/game/stage_system/stage_manager.hpp @@ -13,6 +13,7 @@ namespace StageSystem { // Estats del stage system enum class EstatStage { + INIT_HUD, // Animació inicial del HUD (3s) LEVEL_START, // Pantalla "ENEMY INCOMING" (3s) PLAYING, // Gameplay normal LEVEL_COMPLETED // Pantalla "GOOD JOB COMMANDER!" (3s) @@ -52,6 +53,7 @@ class StageManager { // State transitions void canviar_estat(EstatStage nou_estat); + void processar_init_hud(float delta_time); void processar_level_start(float delta_time); void processar_playing(float delta_time, bool pausar_spawn); void processar_level_completed(float delta_time);