Files
orni_attack/source/game/escenes/escena_logo.cpp

424 lines
14 KiB
C++

// escena_logo.cpp - Implementació de l'escena logo
// © 2025 Port a C++20
#include "escena_logo.hpp"
#include <algorithm>
#include <cfloat>
#include <iostream>
#include <random>
#include <set>
#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<float>(num_letras);
float step = threshold * duration_per_letra;
float start = static_cast<float>(letra_index) * step;
float end = start + duration_per_letra;
// Interpolar progrés
if (global_progress < start) {
return 0.0f; // Encara no ha començat
} else if (global_progress >= end) {
return 1.0f; // Completament apareguda
} else {
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<Effects::DebrisManager>(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();
}
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
if (delta_time > 0.05f) {
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<std::string> 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 << std::endl;
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,
{0.0f, 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<int>(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)
{0.0f, 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 = {ORIGEN_ZOOM_X, 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<size_t> 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.find(i) == explotades.end()) {
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()
}