378 lines
13 KiB
C++
378 lines
13 KiB
C++
// logo_scene.cpp - Implementació de l'escena logo
|
|
// © 2026 JailDesigner
|
|
|
|
#include "logo_scene.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/rendering/shape_renderer.hpp"
|
|
#include "core/system/scene_context.hpp"
|
|
|
|
// Using declarations per simplificar el codi
|
|
using SceneManager::SceneContext;
|
|
using SceneType = SceneContext::SceneType;
|
|
using Option = SceneContext::Option;
|
|
|
|
// Helper: calcular el progrés individual de una letter
|
|
// en función del progrés global (efecte seqüencial)
|
|
static auto computeLetterProgress(size_t letter_index, size_t num_letters, float global_progress, float threshold) -> float {
|
|
if (num_letters == 0) {
|
|
return 1.0F;
|
|
}
|
|
|
|
// Calcular time per letter
|
|
float duration_per_letter = 1.0F / static_cast<float>(num_letters);
|
|
float step = threshold * duration_per_letter;
|
|
float start = static_cast<float>(letter_index) * step;
|
|
float end = start + duration_per_letter;
|
|
|
|
// Interpolar progrés
|
|
if (global_progress < start) {
|
|
return 0.0F; // Aún no ha començat
|
|
}
|
|
if (global_progress >= end) {
|
|
return 1.0F; // Completament apareguda
|
|
}
|
|
return (global_progress - start) / (end - start);
|
|
}
|
|
|
|
LogoScene::LogoScene(SDLManager& sdl, SceneContext& context)
|
|
: sdl_(sdl),
|
|
context_(context),
|
|
|
|
debris_manager_(std::make_unique<Effects::DebrisManager>(sdl.getRenderer())) {
|
|
std::cout << "SceneType Logo: Inicialitzant...\n";
|
|
|
|
// Consumir opciones (LOGO no processa opciones actualment)
|
|
auto option = context_.consumeOption();
|
|
(void)option; // Suprimir warning
|
|
|
|
sound_played_.fill(false); // Inicialitzar seguiment de sons
|
|
|
|
// Si ja sona música (venim de la demo del cicle d'atracció), operar en
|
|
// silenci: ni sons de lletres ni reinici de title.ogg.
|
|
attract_silent_ = (Audio::getMusicState() == Audio::MusicState::PLAYING);
|
|
|
|
initLetters();
|
|
}
|
|
|
|
LogoScene::~LogoScene() {
|
|
// Aturar todos los sons y la música
|
|
Audio::get()->stopAllSounds();
|
|
std::cout << "SceneType Logo: Sons parados\n";
|
|
}
|
|
|
|
auto LogoScene::isFinished() const -> bool {
|
|
return context_.nextScene() != SceneType::LOGO;
|
|
}
|
|
|
|
void LogoScene::handleEvent(const SDL_Event& event) {
|
|
// La lógica de skip se decide en update() consultando el estado de Input;
|
|
// aquí no hay eventos puntuales que procesar.
|
|
(void)event;
|
|
}
|
|
|
|
void LogoScene::initLetters() {
|
|
using namespace Graphics;
|
|
|
|
// Llista de archivos .shp (A repetida para las dues A's)
|
|
std::vector<std::string> archivos = {
|
|
"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 todas las formes i calcular amplades
|
|
float total_width = 0.0F;
|
|
|
|
for (const auto& file : archivos) {
|
|
auto shape = ShapeLoader::load(file);
|
|
if (!shape || !shape->isValid()) {
|
|
std::cerr << "[LogoScene] Error carregant " << file << '\n';
|
|
continue;
|
|
}
|
|
|
|
// Calcular bounding box de la shape (trobar ancho)
|
|
float min_x = FLT_MAX;
|
|
float max_x = -FLT_MAX;
|
|
|
|
for (const auto& prim : shape->getPrimitives()) {
|
|
for (const auto& point : prim.points) {
|
|
min_x = std::min(min_x, point.x);
|
|
max_x = std::max(max_x, point.x);
|
|
}
|
|
}
|
|
|
|
float width_unscaled = max_x - min_x;
|
|
|
|
// IMPORTANT: Escalar ancho i offset con FINAL_SCALE
|
|
// per que las posicions finals coincideixin con la mida real de las lletres
|
|
float width = width_unscaled * FINAL_SCALE;
|
|
float center_offset = (shape->getCenter().x - min_x) * FINAL_SCALE;
|
|
|
|
letters_.push_back({shape,
|
|
{.x = 0.0F, .y = 0.0F}, // Posición es calcularà después
|
|
width,
|
|
center_offset});
|
|
|
|
total_width += width;
|
|
}
|
|
|
|
// Pas 2: Añadir espaiat entre lletres
|
|
total_width += LETTER_SPACING * (letters_.size() - 1);
|
|
|
|
// Pas 3: Calcular posición inicial (centrat horitzontal)
|
|
constexpr auto SCREEN_WIDTH = static_cast<float>(Defaults::Game::WIDTH);
|
|
constexpr auto PANTALLA_ALTO = static_cast<float>(Defaults::Game::HEIGHT);
|
|
|
|
float x_inicial = (SCREEN_WIDTH - total_width) / 2.0F;
|
|
float y_centre = PANTALLA_ALTO / 2.0F;
|
|
|
|
// Pas 4: Assignar posicions a cada letter
|
|
float x_actual = x_inicial;
|
|
|
|
for (auto& letter : letters_) {
|
|
// Posicionar el centro de la shape (shape_centre) en pantalla
|
|
// Usar center_offset en lloc de ancho/2 perquè shape_centre
|
|
// pot no estar exactament al mig del bounding box
|
|
letter.position.x = x_actual + letter.center_offset;
|
|
letter.position.y = y_centre;
|
|
|
|
// Avançar para següent letter
|
|
x_actual += letter.width + LETTER_SPACING;
|
|
}
|
|
|
|
std::cout << "[LogoScene] " << letters_.size()
|
|
<< " lletres carregades, ancho total: " << total_width << " px\n";
|
|
}
|
|
|
|
void LogoScene::changeState(AnimationState nou_estat) {
|
|
current_state_ = nou_estat;
|
|
temps_current_state_ = 0.0F; // Reset time
|
|
|
|
// Inicialitzar state de explosión
|
|
if (nou_estat == AnimationState::EXPLOSION) {
|
|
letter_explosion_index_ = 0;
|
|
time_since_last_explosion_ = 0.0F;
|
|
|
|
// Generar ordre aleatori de explosions
|
|
explosion_order_.clear();
|
|
for (size_t i = 0; i < letters_.size(); i++) {
|
|
explosion_order_.push_back(i);
|
|
}
|
|
std::random_device rd;
|
|
std::mt19937 g(rd());
|
|
std::shuffle(explosion_order_.begin(), explosion_order_.end(), g);
|
|
} else if (nou_estat == AnimationState::POST_EXPLOSION) {
|
|
// En el cicle d'atracció la música ja sona; no la reiniciem.
|
|
if (!attract_silent_) {
|
|
Audio::get()->playMusic("title.ogg");
|
|
}
|
|
}
|
|
|
|
std::cout << "[LogoScene] Canvi a state: " << static_cast<int>(nou_estat)
|
|
<< "\n";
|
|
}
|
|
|
|
auto LogoScene::allLettersComplete() const -> bool {
|
|
// Cuando global_progress = 1.0, todas las lletres tenen letter_progress = 1.0
|
|
return temps_current_state_ >= DURATION_ZOOM;
|
|
}
|
|
|
|
void LogoScene::updateExplosions(float delta_time) {
|
|
time_since_last_explosion_ += delta_time;
|
|
|
|
// Comprovar si es el moment de explode la següent letter
|
|
if (time_since_last_explosion_ >= DELAY_ENTRE_EXPLOSIONS) {
|
|
if (letter_explosion_index_ < letters_.size()) {
|
|
// Explotar letter actual (en ordre aleatori)
|
|
size_t index_actual = explosion_order_[letter_explosion_index_];
|
|
const auto& letter = letters_[index_actual];
|
|
|
|
debris_manager_->explode(
|
|
letter.shape, // Forma a explode
|
|
letter.position, // Posición
|
|
0.0F, // Angle (sin rotación)
|
|
FINAL_SCALE, // Escala (lletres a scale final)
|
|
SPEED_EXPLOSIO, // Velocidad base
|
|
1.0F, // Brightness màxim (per defecte)
|
|
{.x = 0.0F, .y = 0.0F} // Sin velocity (per defecte)
|
|
);
|
|
|
|
std::cout << "[LogoScene] Explota letter " << letter_explosion_index_ << "\n";
|
|
|
|
// Passar a la següent letter
|
|
letter_explosion_index_++;
|
|
time_since_last_explosion_ = 0.0F;
|
|
} else {
|
|
// Todas las lletres han explotat, transición a POST_EXPLOSION
|
|
changeState(AnimationState::POST_EXPLOSION);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LogoScene::update(float delta_time) {
|
|
temps_current_state_ += delta_time;
|
|
|
|
switch (current_state_) {
|
|
case AnimationState::PRE_ANIMATION:
|
|
if (temps_current_state_ >= DURATION_PRE) {
|
|
changeState(AnimationState::ANIMATION);
|
|
}
|
|
break;
|
|
|
|
case AnimationState::ANIMATION: {
|
|
// Reproduir so per cada letter cuando comença a aparèixer
|
|
float global_progress = std::min(temps_current_state_ / DURATION_ZOOM, 1.0F);
|
|
|
|
for (size_t i = 0; i < letters_.size() && i < sound_played_.size(); i++) {
|
|
if (!sound_played_[i]) {
|
|
float letter_progress = computeLetterProgress(
|
|
i,
|
|
letters_.size(),
|
|
global_progress,
|
|
LETTER_THRESHOLD);
|
|
|
|
// Reproduir so cuando la letter comença a aparèixer (progress > 0).
|
|
// En mode silenciós (cicle d'atracció) saltem el so però igualment
|
|
// marquem la letter per no acumular pendents.
|
|
if (letter_progress > 0.0F) {
|
|
if (!attract_silent_) {
|
|
Audio::get()->playSound(Defaults::Sound::LOGO, Audio::Group::GAME);
|
|
}
|
|
sound_played_[i] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (allLettersComplete()) {
|
|
changeState(AnimationState::POST_ANIMATION);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AnimationState::POST_ANIMATION:
|
|
if (temps_current_state_ >= DURATION_POST_ANIMATION) {
|
|
changeState(AnimationState::EXPLOSION);
|
|
}
|
|
break;
|
|
|
|
case AnimationState::EXPLOSION:
|
|
updateExplosions(delta_time);
|
|
break;
|
|
|
|
case AnimationState::POST_EXPLOSION:
|
|
if (temps_current_state_ >= DURATION_POST_EXPLOSION) {
|
|
// Transición a pantalla de título
|
|
context_.setNextScene(SceneType::TITLE);
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Verificar botones de skip (SHOOT P1/P2)
|
|
if (checkSkipButtonPressed()) {
|
|
context_.setNextScene(SceneType::TITLE, Option::JUMP_TO_TITLE_MAIN);
|
|
}
|
|
|
|
// Actualitzar animaciones de debris
|
|
debris_manager_->update(delta_time);
|
|
}
|
|
|
|
void LogoScene::draw() {
|
|
// Director ha hecho el clear; aquí solo pintamos lo de la escena.
|
|
|
|
// PRE_ANIMATION: Solo pantalla negra (no se pinta nada).
|
|
if (current_state_ == AnimationState::PRE_ANIMATION) {
|
|
return;
|
|
}
|
|
|
|
// ANIMATION o POST_ANIMATION: Dibuixar lletres con animación
|
|
if (current_state_ == AnimationState::ANIMATION ||
|
|
current_state_ == AnimationState::POST_ANIMATION) {
|
|
float global_progress =
|
|
(current_state_ == AnimationState::ANIMATION)
|
|
? std::min(temps_current_state_ / DURATION_ZOOM, 1.0F)
|
|
: 1.0F; // POST: mantenir al 100%
|
|
|
|
const Vec2 ZOOM_ORIGIN = {.x = ZOOM_ORIGIN_X, .y = ZOOM_ORIGIN_Y};
|
|
|
|
for (size_t i = 0; i < letters_.size(); i++) {
|
|
const auto& letter = letters_[i];
|
|
|
|
float letter_progress = computeLetterProgress(
|
|
i,
|
|
letters_.size(),
|
|
global_progress,
|
|
LETTER_THRESHOLD);
|
|
|
|
if (letter_progress <= 0.0F) {
|
|
continue;
|
|
}
|
|
|
|
Vec2 pos_actual;
|
|
pos_actual.x =
|
|
ZOOM_ORIGIN.x + ((letter.position.x - ZOOM_ORIGIN.x) * letter_progress);
|
|
pos_actual.y =
|
|
ZOOM_ORIGIN.y + ((letter.position.y - ZOOM_ORIGIN.y) * letter_progress);
|
|
|
|
float t = letter_progress;
|
|
float ease_factor = 1.0F - ((1.0F - t) * (1.0F - t));
|
|
float current_scale =
|
|
INITIAL_SCALE + ((FINAL_SCALE - INITIAL_SCALE) * ease_factor);
|
|
|
|
Rendering::renderShape(
|
|
sdl_.getRenderer(),
|
|
letter.shape,
|
|
pos_actual,
|
|
0.0F,
|
|
current_scale,
|
|
1.0F);
|
|
}
|
|
}
|
|
|
|
// EXPLOSION: Dibuixar solo lletres que aún no han explotat
|
|
if (current_state_ == AnimationState::EXPLOSION) {
|
|
// Crear conjunt de lletres ya explotades
|
|
std::set<size_t> explotades;
|
|
for (size_t i = 0; i < letter_explosion_index_; i++) {
|
|
explotades.insert(explosion_order_[i]);
|
|
}
|
|
|
|
// Dibuixar solo lletres que NO han explotat
|
|
for (size_t i = 0; i < letters_.size(); i++) {
|
|
if (!explotades.contains(i)) {
|
|
const auto& letter = letters_[i];
|
|
|
|
Rendering::renderShape(
|
|
sdl_.getRenderer(),
|
|
letter.shape,
|
|
letter.position,
|
|
0.0F,
|
|
FINAL_SCALE,
|
|
1.0F);
|
|
}
|
|
}
|
|
}
|
|
|
|
// POST_EXPLOSION: No draw lletres, solo debris (a baix)
|
|
|
|
// Siempre draw debris (si n'hay de active)
|
|
debris_manager_->draw();
|
|
}
|
|
|
|
auto LogoScene::checkSkipButtonPressed() -> bool {
|
|
return Input::get()->checkAnyPlayerAction(ARCADE_BUTTONS);
|
|
}
|