Files
orni_attack/source/game/escenes/escena_titol.cpp
Sergio Valor a3aeed4b7c afegides musiques
afegit control de brillo al starfield
2025-12-03 19:27:36 +01:00

403 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// escena_titol.cpp - Implementació de l'escena de títol
// © 2025 Port a C++20
#include "escena_titol.hpp"
#include <cfloat>
#include <cmath>
#include <iostream>
#include <string>
#include "core/audio/audio.hpp"
#include "core/graphics/shape_loader.hpp"
#include "core/input/mouse.hpp"
#include "core/rendering/shape_renderer.hpp"
#include "core/system/gestor_escenes.hpp"
#include "core/system/global_events.hpp"
#include "project.h"
namespace {
// Brightness del starfield (1.0 = default, >1.0 més brillant, <1.0 menys brillant)
constexpr float BRIGHTNESS_STARFIELD = 1.2f;
} // namespace
EscenaTitol::EscenaTitol(SDLManager& sdl)
: sdl_(sdl),
text_(sdl.obte_renderer()),
estat_actual_(EstatTitol::INIT),
temps_acumulat_(0.0f) {
std::cout << "Escena Titol: Inicialitzant...\n";
// Crear starfield de fons
Punt centre_pantalla{
Defaults::Game::WIDTH / 2.0f,
Defaults::Game::HEIGHT / 2.0f};
SDL_FRect area_completa{
0,
0,
static_cast<float>(Defaults::Game::WIDTH),
static_cast<float>(Defaults::Game::HEIGHT)};
starfield_ = std::make_unique<Graphics::Starfield>(
sdl_.obte_renderer(),
centre_pantalla,
area_completa,
150 // densitat: 150 estrelles (50 per capa)
);
// Configurar brightness del starfield
starfield_->set_brightness(BRIGHTNESS_STARFIELD);
// Inicialitzar lletres del títol "ORNI ATTACK!"
inicialitzar_titol();
// Iniciar música de títol si no està sonant
if (Audio::get()->getMusicState() != Audio::MusicState::PLAYING) {
Audio::get()->playMusic("title.ogg");
}
}
EscenaTitol::~EscenaTitol() {
// Aturar música de títol quan es destrueix l'escena
Audio::get()->stopMusic();
}
void EscenaTitol::inicialitzar_titol() {
using namespace Graphics;
// === LÍNIA 1: "ORNI" ===
std::vector<std::string> fitxers_orni = {
"title/letra_o.shp",
"title/letra_r.shp",
"title/letra_n.shp",
"title/letra_i.shp"};
// Pas 1: Carregar formes i calcular amplades per "ORNI"
float ancho_total_orni = 0.0f;
for (const auto& fitxer : fitxers_orni) {
auto forma = ShapeLoader::load(fitxer);
if (!forma || !forma->es_valida()) {
std::cerr << "[EscenaTitol] Error carregant " << fitxer << std::endl;
continue;
}
// Calcular bounding box de la forma (trobar ancho i altura)
float min_x = FLT_MAX;
float max_x = -FLT_MAX;
float min_y = FLT_MAX;
float max_y = -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);
min_y = std::min(min_y, punt.y);
max_y = std::max(max_y, punt.y);
}
}
float ancho_sin_escalar = max_x - min_x;
float altura_sin_escalar = max_y - min_y;
// Escalar ancho, altura i offset amb ESCALA_TITULO
float ancho = ancho_sin_escalar * ESCALA_TITULO;
float altura = altura_sin_escalar * ESCALA_TITULO;
float offset_centre = (forma->get_centre().x - min_x) * ESCALA_TITULO;
lletres_orni_.push_back({forma, {0.0f, 0.0f}, ancho, altura, offset_centre});
ancho_total_orni += ancho;
}
// Afegir espaiat entre lletres
ancho_total_orni += ESPAI_ENTRE_LLETRES * (lletres_orni_.size() - 1);
// Calcular posició inicial (centrat horitzontal) per "ORNI"
float x_inicial_orni = (Defaults::Game::WIDTH - ancho_total_orni) / 2.0f;
float x_actual = x_inicial_orni;
for (auto& lletra : lletres_orni_) {
lletra.posicio.x = x_actual + lletra.offset_centre;
lletra.posicio.y = Y_ORNI;
x_actual += lletra.ancho + ESPAI_ENTRE_LLETRES;
}
std::cout << "[EscenaTitol] Línia 1 (ORNI): " << lletres_orni_.size()
<< " lletres, ancho total: " << ancho_total_orni << " px\n";
// === Calcular posició Y dinàmica per "ATTACK!" ===
// Totes les lletres ORNI tenen la mateixa altura, utilitzem la primera
float altura_orni = lletres_orni_.empty() ? 50.0f : lletres_orni_[0].altura;
y_attack_dinamica_ = Y_ORNI + altura_orni + SEPARACION_LINEAS;
std::cout << "[EscenaTitol] Altura ORNI: " << altura_orni
<< " px, Y_ATTACK dinàmica: " << y_attack_dinamica_ << " px\n";
// === LÍNIA 2: "ATTACK!" ===
std::vector<std::string> fitxers_attack = {
"title/letra_a.shp",
"title/letra_t.shp",
"title/letra_t.shp", // T repetida
"title/letra_a.shp", // A repetida
"title/letra_c.shp",
"title/letra_k.shp",
"title/letra_exclamacion.shp"};
// Pas 1: Carregar formes i calcular amplades per "ATTACK!"
float ancho_total_attack = 0.0f;
for (const auto& fitxer : fitxers_attack) {
auto forma = ShapeLoader::load(fitxer);
if (!forma || !forma->es_valida()) {
std::cerr << "[EscenaTitol] Error carregant " << fitxer << std::endl;
continue;
}
// Calcular bounding box de la forma (trobar ancho i altura)
float min_x = FLT_MAX;
float max_x = -FLT_MAX;
float min_y = FLT_MAX;
float max_y = -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);
min_y = std::min(min_y, punt.y);
max_y = std::max(max_y, punt.y);
}
}
float ancho_sin_escalar = max_x - min_x;
float altura_sin_escalar = max_y - min_y;
// Escalar ancho, altura i offset amb ESCALA_TITULO
float ancho = ancho_sin_escalar * ESCALA_TITULO;
float altura = altura_sin_escalar * ESCALA_TITULO;
float offset_centre = (forma->get_centre().x - min_x) * ESCALA_TITULO;
lletres_attack_.push_back({forma, {0.0f, 0.0f}, ancho, altura, offset_centre});
ancho_total_attack += ancho;
}
// Afegir espaiat entre lletres
ancho_total_attack += ESPAI_ENTRE_LLETRES * (lletres_attack_.size() - 1);
// Calcular posició inicial (centrat horitzontal) per "ATTACK!"
float x_inicial_attack = (Defaults::Game::WIDTH - ancho_total_attack) / 2.0f;
x_actual = x_inicial_attack;
for (auto& lletra : lletres_attack_) {
lletra.posicio.x = x_actual + lletra.offset_centre;
lletra.posicio.y = y_attack_dinamica_; // Usar posició dinàmica
x_actual += lletra.ancho + ESPAI_ENTRE_LLETRES;
}
std::cout << "[EscenaTitol] Línia 2 (ATTACK!): " << lletres_attack_.size()
<< " lletres, ancho total: " << ancho_total_attack << " px\n";
}
void EscenaTitol::executar() {
SDL_Event event;
Uint64 last_time = SDL_GetTicks();
while (GestorEscenes::actual == GestorEscenes::Escena::TITOL) {
// 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();
// Processar events SDL
while (SDL_PollEvent(&event)) {
// Manejo de finestra
if (sdl_.handleWindowEvent(event)) {
continue;
}
// Events globals (F1/F2/F3/F4/ESC/QUIT)
if (GlobalEvents::handle(event, sdl_)) {
continue;
}
// Processar events de l'escena
processar_events(event);
}
// Actualitzar lògica
actualitzar(delta_time);
// Actualitzar sistema d'audio
Audio::update();
// Actualitzar colors oscil·lats
sdl_.updateColors(delta_time);
// Netejar pantalla
sdl_.neteja(0, 0, 0);
// Actualitzar context de renderitzat (factor d'escala global)
sdl_.updateRenderingContext();
// Dibuixar
dibuixar();
// Presentar renderer (swap buffers)
sdl_.presenta();
}
std::cout << "Escena Titol: Finalitzant...\n";
}
void EscenaTitol::actualitzar(float delta_time) {
// Actualitzar starfield (sempre actiu)
if (starfield_) {
starfield_->actualitzar(delta_time);
}
switch (estat_actual_) {
case EstatTitol::INIT:
temps_acumulat_ += delta_time;
if (temps_acumulat_ >= DURACIO_INIT) {
estat_actual_ = EstatTitol::MAIN;
}
break;
case EstatTitol::MAIN:
// No hi ha lògica d'actualització en l'estat MAIN
break;
case EstatTitol::TRANSITION:
temps_acumulat_ += delta_time;
if (temps_acumulat_ >= DURACIO_TRANSITION) {
// Transició a JOC (la música ja s'ha parat en el fade)
GestorEscenes::actual = GestorEscenes::Escena::JOC;
}
break;
}
}
void EscenaTitol::dibuixar() {
// Dibuixar starfield de fons (sempre, en tots els estats)
if (starfield_) {
starfield_->dibuixar();
}
// En l'estat INIT, només mostrar starfield (sense text)
if (estat_actual_ == EstatTitol::INIT) {
return;
}
// Estat MAIN i TRANSITION: Dibuixar títol i text (sobre el starfield)
if (estat_actual_ == EstatTitol::MAIN || estat_actual_ == EstatTitol::TRANSITION) {
// === Dibuixar lletres del títol "ORNI ATTACK!" ===
// Dibuixar "ORNI" (línia 1)
for (const auto& lletra : lletres_orni_) {
Rendering::render_shape(
sdl_.obte_renderer(),
lletra.forma,
lletra.posicio,
0.0f, // sense rotació
ESCALA_TITULO, // escala 80%
true, // dibuixar
1.0f // progrés complet (totalment visible)
);
}
// Dibuixar "ATTACK!" (línia 2)
for (const auto& lletra : lletres_attack_) {
Rendering::render_shape(
sdl_.obte_renderer(),
lletra.forma,
lletra.posicio,
0.0f, // sense rotació
ESCALA_TITULO, // escala 80%
true, // dibuixar
1.0f // progrés complet (totalment visible)
);
}
// === Text "PRESS BUTTON TO PLAY" ===
// En estat MAIN: sempre visible
// En estat TRANSITION: parpellejant (blink amb sinusoide)
const float spacing = 2.0f; // Espai entre caràcters (usat també per copyright)
bool mostrar_text = true;
if (estat_actual_ == EstatTitol::TRANSITION) {
// Parpelleig: sin oscil·la entre -1 i 1, volem ON quan > 0
float fase = temps_acumulat_ * BLINK_FREQUENCY * 2.0f * 3.14159f; // 2π × freq × temps
mostrar_text = (std::sin(fase) > 0.0f);
}
if (mostrar_text) {
const std::string main_text = "PRESS BUTTON TO PLAY";
const float escala_main = 1.0f;
float text_width = text_.get_text_width(main_text, escala_main, spacing);
float x_center = (Defaults::Game::WIDTH - text_width) / 2.0f;
float altura_attack = lletres_attack_.empty() ? 50.0f : lletres_attack_[0].altura;
float y_center = y_attack_dinamica_ + altura_attack + 70.0f;
text_.render(main_text, Punt{x_center, y_center}, escala_main, spacing);
}
// === Copyright a la part inferior (centrat horitzontalment) ===
// Convert to uppercase since VectorText only supports A-Z
std::string copyright = Project::COPYRIGHT;
for (char& c : copyright) {
if (c >= 'a' && c <= 'z') {
c = c - 32; // Convert to uppercase
}
}
const float escala_copy = 0.6f;
float copy_width = text_.get_text_width(copyright, escala_copy, spacing);
float copy_height = text_.get_text_height(escala_copy);
float x_copy = (Defaults::Game::WIDTH - copy_width) / 2.0f;
float y_copy = Defaults::Game::HEIGHT - copy_height - 20.0f; // 20px des del fons
text_.render(copyright, Punt{x_copy, y_copy}, escala_copy, spacing);
}
}
void EscenaTitol::processar_events(const SDL_Event& event) {
// Qualsevol tecla o clic de ratolí
if (event.type == SDL_EVENT_KEY_DOWN ||
event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
switch (estat_actual_) {
case EstatTitol::INIT:
// Saltar a MAIN
estat_actual_ = EstatTitol::MAIN;
break;
case EstatTitol::MAIN:
// Iniciar transició amb fade-out de música
estat_actual_ = EstatTitol::TRANSITION;
temps_acumulat_ = 0.0f; // Reset del comptador
Audio::get()->fadeOutMusic(MUSIC_FADE); // Fade de 300ms
break;
case EstatTitol::TRANSITION:
// Ignorar inputs durant la transició
break;
}
}
}