// escena_logo.cpp - Implementació de l'escena logo // © 2025 Port a C++20 #include "escena_logo.hpp" #include #include #include #include #include #include "core/audio/audio.hpp" #include "core/graphics/shape_loader.hpp" #include "core/input/input.hpp" #include "core/input/mouse.hpp" #include "core/rendering/shape_renderer.hpp" #include "core/system/context_escenes.hpp" #include "core/system/global_events.hpp" // Using declarations per simplificar el codi using GestorEscenes::ContextEscenes; using Escena = ContextEscenes::Escena; using Opcio = ContextEscenes::Opcio; // Helper: calcular el progrés individual d'una lletra // en funció del progrés global (efecte seqüencial) static float calcular_progress_letra(size_t letra_index, size_t num_letras, float global_progress, float threshold) { if (num_letras == 0) { return 1.0F; } // Calcular temps per lletra float duration_per_letra = 1.0F / static_cast(num_letras); float step = threshold * duration_per_letra; float start = static_cast(letra_index) * step; float end = start + duration_per_letra; // Interpolar progrés if (global_progress < start) { return 0.0F; // Encara no ha començat } if (global_progress >= end) { return 1.0F; // Completament apareguda } return (global_progress - start) / (end - start); } EscenaLogo::EscenaLogo(SDLManager& sdl, ContextEscenes& context) : sdl_(sdl), context_(context), estat_actual_(EstatAnimacio::PRE_ANIMATION), temps_estat_actual_(0.0F), debris_manager_(std::make_unique(sdl.obte_renderer())), lletra_explosio_index_(0), temps_des_ultima_explosio_(0.0F) { std::cout << "Escena Logo: Inicialitzant...\n"; // Consumir opcions (LOGO no processa opcions actualment) auto opcio = context_.consumir_opcio(); (void)opcio; // Suprimir warning so_reproduit_.fill(false); // Inicialitzar seguiment de sons inicialitzar_lletres(); } EscenaLogo::~EscenaLogo() { // Aturar tots els sons i la música Audio::get()->stopAllSounds(); std::cout << "Escena Logo: Sons aturats\n"; } void EscenaLogo::executar() { SDL_Event event; Uint64 last_time = SDL_GetTicks(); while (GestorEscenes::actual == Escena::LOGO) { // Calcular delta_time real Uint64 current_time = SDL_GetTicks(); float delta_time = (current_time - last_time) / 1000.0F; last_time = current_time; // Limitar delta_time per evitar grans salts delta_time = std::min(delta_time, 0.05F); // Actualitzar comptador de FPS sdl_.updateFPS(delta_time); // Actualitzar visibilitat del cursor (auto-ocultar) Mouse::updateCursorVisibility(); // Actualitzar sistema d'input ABANS del event loop Input::get()->update(); // Processar events SDL while (SDL_PollEvent(&event)) { // Manejo de finestra if (sdl_.handleWindowEvent(event)) { continue; } // Events globals (F1/F2/F3/ESC/QUIT) if (GlobalEvents::handle(event, sdl_, context_)) { continue; } // Processar events de l'escena (qualsevol tecla/clic salta al joc) processar_events(event); } // Actualitzar lògica actualitzar(delta_time); // Actualitzar colors oscil·lats (efecte verd global) sdl_.updateColors(delta_time); // Actualitzar context de renderitzat (factor d'escala global) sdl_.updateRenderingContext(); // Dibuixar dibuixar(); } std::cout << "Escena Logo: Finalitzant...\n"; } void EscenaLogo::inicialitzar_lletres() { using namespace Graphics; // Llista de fitxers .shp (A repetida per a les dues A's) std::vector fitxers = { "logo/letra_j.shp", "logo/letra_a.shp", "logo/letra_i.shp", "logo/letra_l.shp", "logo/letra_g.shp", "logo/letra_a.shp", "logo/letra_m.shp", "logo/letra_e.shp", "logo/letra_s.shp"}; // Pas 1: Carregar totes les formes i calcular amplades float ancho_total = 0.0F; for (const auto& fitxer : fitxers) { auto forma = ShapeLoader::load(fitxer); if (!forma || !forma->es_valida()) { std::cerr << "[EscenaLogo] Error carregant " << fitxer << '\n'; continue; } // Calcular bounding box de la forma (trobar ancho) float min_x = FLT_MAX; float max_x = -FLT_MAX; for (const auto& prim : forma->get_primitives()) { for (const auto& punt : prim.points) { min_x = std::min(min_x, punt.x); max_x = std::max(max_x, punt.x); } } float ancho_sin_escalar = max_x - min_x; // IMPORTANT: Escalar ancho i offset amb ESCALA_FINAL // per que les posicions finals coincideixin amb la mida real de les lletres float ancho = ancho_sin_escalar * ESCALA_FINAL; float offset_centre = (forma->get_centre().x - min_x) * ESCALA_FINAL; lletres_.push_back({forma, {.x = 0.0F, .y = 0.0F}, // Posició es calcularà després ancho, offset_centre}); ancho_total += ancho; } // Pas 2: Afegir espaiat entre lletres ancho_total += ESPAI_ENTRE_LLETRES * (lletres_.size() - 1); // Pas 3: Calcular posició inicial (centrat horitzontal) constexpr float PANTALLA_ANCHO = 640.0F; constexpr float PANTALLA_ALTO = 480.0F; float x_inicial = (PANTALLA_ANCHO - ancho_total) / 2.0F; float y_centre = PANTALLA_ALTO / 2.0F; // Pas 4: Assignar posicions a cada lletra float x_actual = x_inicial; for (auto& lletra : lletres_) { // Posicionar el centre de la forma (shape_centre) en pantalla // Usar offset_centre en lloc de ancho/2 perquè shape_centre // pot no estar exactament al mig del bounding box lletra.posicio.x = x_actual + lletra.offset_centre; lletra.posicio.y = y_centre; // Avançar per a següent lletra x_actual += lletra.ancho + ESPAI_ENTRE_LLETRES; } std::cout << "[EscenaLogo] " << lletres_.size() << " lletres carregades, ancho total: " << ancho_total << " px\n"; } void EscenaLogo::canviar_estat(EstatAnimacio nou_estat) { estat_actual_ = nou_estat; temps_estat_actual_ = 0.0F; // Reset temps // Inicialitzar estat d'explosió if (nou_estat == EstatAnimacio::EXPLOSION) { lletra_explosio_index_ = 0; temps_des_ultima_explosio_ = 0.0F; // Generar ordre aleatori d'explosions ordre_explosio_.clear(); for (size_t i = 0; i < lletres_.size(); i++) { ordre_explosio_.push_back(i); } std::random_device rd; std::mt19937 g(rd()); std::shuffle(ordre_explosio_.begin(), ordre_explosio_.end(), g); } else if (nou_estat == EstatAnimacio::POST_EXPLOSION) { Audio::get()->playMusic("title.ogg"); } std::cout << "[EscenaLogo] Canvi a estat: " << static_cast(nou_estat) << "\n"; } bool EscenaLogo::totes_lletres_completes() const { // Quan global_progress = 1.0, totes les lletres tenen letra_progress = 1.0 return temps_estat_actual_ >= DURACIO_ZOOM; } void EscenaLogo::actualitzar_explosions(float delta_time) { temps_des_ultima_explosio_ += delta_time; // Comprovar si és el moment d'explotar la següent lletra if (temps_des_ultima_explosio_ >= DELAY_ENTRE_EXPLOSIONS) { if (lletra_explosio_index_ < lletres_.size()) { // Explotar lletra actual (en ordre aleatori) size_t index_actual = ordre_explosio_[lletra_explosio_index_]; const auto& lletra = lletres_[index_actual]; debris_manager_->explotar( lletra.forma, // Forma a explotar lletra.posicio, // Posició 0.0F, // Angle (sense rotació) ESCALA_FINAL, // Escala (lletres a escala final) VELOCITAT_EXPLOSIO, // Velocitat base 1.0F, // Brightness màxim (per defecte) {.x = 0.0F, .y = 0.0F} // Sense velocitat (per defecte) ); std::cout << "[EscenaLogo] Explota lletra " << lletra_explosio_index_ << "\n"; // Passar a la següent lletra lletra_explosio_index_++; temps_des_ultima_explosio_ = 0.0F; } else { // Totes les lletres han explotat, transició a POST_EXPLOSION canviar_estat(EstatAnimacio::POST_EXPLOSION); } } } void EscenaLogo::actualitzar(float delta_time) { temps_estat_actual_ += delta_time; switch (estat_actual_) { case EstatAnimacio::PRE_ANIMATION: if (temps_estat_actual_ >= DURACIO_PRE) { canviar_estat(EstatAnimacio::ANIMATION); } break; case EstatAnimacio::ANIMATION: { // Reproduir so per cada lletra quan comença a aparèixer float global_progress = std::min(temps_estat_actual_ / DURACIO_ZOOM, 1.0F); for (size_t i = 0; i < lletres_.size() && i < so_reproduit_.size(); i++) { if (!so_reproduit_[i]) { float letra_progress = calcular_progress_letra( i, lletres_.size(), global_progress, THRESHOLD_LETRA); // Reproduir so quan la lletra comença a aparèixer (progress > 0) if (letra_progress > 0.0F) { Audio::get()->playSound(Defaults::Sound::LOGO, Audio::Group::GAME); so_reproduit_[i] = true; } } } if (totes_lletres_completes()) { canviar_estat(EstatAnimacio::POST_ANIMATION); } break; } case EstatAnimacio::POST_ANIMATION: if (temps_estat_actual_ >= DURACIO_POST_ANIMATION) { canviar_estat(EstatAnimacio::EXPLOSION); } break; case EstatAnimacio::EXPLOSION: actualitzar_explosions(delta_time); break; case EstatAnimacio::POST_EXPLOSION: if (temps_estat_actual_ >= DURACIO_POST_EXPLOSION) { // Transició a pantalla de títol context_.canviar_escena(Escena::TITOL); GestorEscenes::actual = Escena::TITOL; } break; } // Verificar botones de skip (SHOOT P1/P2) if (checkSkipButtonPressed()) { context_.canviar_escena(Escena::TITOL, Opcio::JUMP_TO_TITLE_MAIN); GestorEscenes::actual = Escena::TITOL; } // Actualitzar animacions de debris debris_manager_->actualitzar(delta_time); } void EscenaLogo::dibuixar() { // Fons negre sdl_.neteja(0, 0, 0); // PRE_ANIMATION: Només pantalla negra if (estat_actual_ == EstatAnimacio::PRE_ANIMATION) { sdl_.presenta(); return; // No renderitzar lletres } // ANIMATION o POST_ANIMATION: Dibuixar lletres amb animació if (estat_actual_ == EstatAnimacio::ANIMATION || estat_actual_ == EstatAnimacio::POST_ANIMATION) { float global_progress = (estat_actual_ == EstatAnimacio::ANIMATION) ? std::min(temps_estat_actual_ / DURACIO_ZOOM, 1.0F) : 1.0F; // POST: mantenir al 100% const Punt ORIGEN_ZOOM = {.x = ORIGEN_ZOOM_X, .y = ORIGEN_ZOOM_Y}; for (size_t i = 0; i < lletres_.size(); i++) { const auto& lletra = lletres_[i]; float letra_progress = calcular_progress_letra( i, lletres_.size(), global_progress, THRESHOLD_LETRA); if (letra_progress <= 0.0F) { continue; } Punt pos_actual; pos_actual.x = ORIGEN_ZOOM.x + ((lletra.posicio.x - ORIGEN_ZOOM.x) * letra_progress); pos_actual.y = ORIGEN_ZOOM.y + ((lletra.posicio.y - ORIGEN_ZOOM.y) * letra_progress); float t = letra_progress; float ease_factor = 1.0F - ((1.0F - t) * (1.0F - t)); float escala_actual = ESCALA_INICIAL + ((ESCALA_FINAL - ESCALA_INICIAL) * ease_factor); Rendering::render_shape( sdl_.obte_renderer(), lletra.forma, pos_actual, 0.0F, escala_actual, true, 1.0F); } } // EXPLOSION: Dibuixar només lletres que encara no han explotat if (estat_actual_ == EstatAnimacio::EXPLOSION) { // Crear conjunt de lletres ja explotades std::set explotades; for (size_t i = 0; i < lletra_explosio_index_; i++) { explotades.insert(ordre_explosio_[i]); } // Dibuixar només lletres que NO han explotat for (size_t i = 0; i < lletres_.size(); i++) { if (!explotades.contains(i)) { const auto& lletra = lletres_[i]; Rendering::render_shape( sdl_.obte_renderer(), lletra.forma, lletra.posicio, 0.0F, ESCALA_FINAL, true, 1.0F); } } } // POST_EXPLOSION: No dibuixar lletres, només debris (a baix) // Sempre dibuixar debris (si n'hi ha d'actius) debris_manager_->dibuixar(); sdl_.presenta(); } auto EscenaLogo::checkSkipButtonPressed() -> bool { return Input::get()->checkAnyPlayerAction(ARCADE_BUTTONS); } void EscenaLogo::processar_events(const SDL_Event& event) { // No procesar eventos genéricos aquí - la lógica se movió a actualitzar() }