7ee359b910
Sweep final del naming a CamelCase/camelBack/lower_case:
Fitxers renombrats:
- effects/gestor_puntuacio_flotant.{hpp,cpp} -> floating_score_manager.{hpp,cpp}
- effects/puntuacio_flotant.hpp -> floating_score.hpp
Tipus (CamelCase):
- GestorPuntuacioFlotant -> FloatingScoreManager
- PuntuacioFlotant -> FloatingScore
- ConfigStage -> StageConfig
- ConfigSistemaStages -> StageSystemConfig
- NauTitol -> TitleShip
- EstatNau -> ShipState
Metodes publics (camelBack):
- obte_renderer -> getRenderer
- get_num_actius -> getActiveCount
- calcular_direccio_explosio -> computeExplosionDirection
- trobar_slot_lliure -> findFreeSlot
- explotar -> explode
- reiniciar -> reset
- es_valida -> isValid
- parsejar_fitxer -> parseFile
- carregar -> load
- crear_explosio -> createExplosion
- registrar_puntuacio -> registerScore
- construir_marcador -> buildScoreboard
- render_centered -> renderCentered
Camps struct publics (snake_case):
- actiu/actius -> active
- rotacio -> rotation, rotacio_visual -> visual_rotation
- acceleracio -> acceleration
- velocitat -> velocity
- escala/escala_inicial/objectiu/actual -> scale/initial_scale/...
- posicio/posicio_inicial/objectiu/actual -> position/initial_position/...
- fase_oscilacio -> oscillation_phase
- temps_estat -> state_time
- jugador_id -> player_id
- estat -> state
- brillantor -> brightness
- tipus -> type
Camps privats (sufix _):
- naus_ -> ships_, orni_ -> enemies_, bales_ -> bullets_
- gestor_puntuacio_ -> floating_score_manager_
- punt_mort_ -> death_position_, punt_spawn_ -> spawn_position_
- itocado_per_jugador_ -> hit_timer_per_player_
- vides_per_jugador_ -> lives_per_player_
- puntuacio_per_jugador_ -> score_per_player_
- estat_game_over_ -> game_over_state_
- continues_usados_ -> continues_used_
Constants:
- MARGE_ESQ/DRET/DALT/BAIX -> MARGIN_LEFT/RIGHT/TOP/BOTTOM
Variables locals i parametres comuns (snake_case):
- nau -> ship, enemic -> enemy, bala -> bullet
- forma -> shape, punt(s) -> point(s)
- jugador -> player, partida -> match
- temps -> time, missatge -> message
Diff: 59 fitxers, +1000/-1000 (simetric). Compila i enllaça.
Pendents per a futures fases (no bloquejants):
- Comentaris de capçalera en catala -> castella
- Variables locals/parametres minoritaris en catala
- Include guards (queden alguns #ifndef en lloc de #pragma once)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
425 lines
14 KiB
C++
425 lines
14 KiB
C++
// escena_logo.cpp - Implementació de l'escena logo
|
|
// © 2025 Port a C++20
|
|
|
|
#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/input/mouse.hpp"
|
|
#include "core/rendering/shape_renderer.hpp"
|
|
#include "core/system/scene_context.hpp"
|
|
#include "core/system/global_events.hpp"
|
|
|
|
// Using declarations per simplificar el codi
|
|
using SceneManager::SceneContext;
|
|
using SceneType = SceneContext::SceneType;
|
|
using Option = SceneContext::Option;
|
|
|
|
// 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 time 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
|
|
}
|
|
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),
|
|
estat_actual_(AnimationState::PRE_ANIMATION),
|
|
temps_estat_actual_(0.0F),
|
|
debris_manager_(std::make_unique<Effects::DebrisManager>(sdl.getRenderer())),
|
|
lletra_explosio_index_(0),
|
|
temps_des_ultima_explosio_(0.0F) {
|
|
std::cout << "SceneType Logo: Inicialitzant...\n";
|
|
|
|
// Consumir opcions (LOGO no processa opcions actualment)
|
|
auto option = context_.consumeOption();
|
|
(void)option; // Suprimir warning
|
|
|
|
so_reproduit_.fill(false); // Inicialitzar seguiment de sons
|
|
inicialitzar_lletres();
|
|
}
|
|
|
|
LogoScene::~LogoScene() {
|
|
// Aturar tots els sons i la música
|
|
Audio::get()->stopAllSounds();
|
|
std::cout << "SceneType Logo: Sons aturats\n";
|
|
}
|
|
|
|
void LogoScene::run() {
|
|
SDL_Event event;
|
|
Uint64 last_time = SDL_GetTicks();
|
|
|
|
while (SceneManager::actual == SceneType::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
|
|
update(delta_time);
|
|
|
|
// Actualitzar colors oscil·lats (efecte verd global)
|
|
sdl_.updateColors(delta_time);
|
|
|
|
// Actualitzar context de renderitzat (factor d'scale global)
|
|
sdl_.updateRenderingContext();
|
|
|
|
// Dibuixar
|
|
draw();
|
|
}
|
|
|
|
std::cout << "SceneType Logo: Finalitzant...\n";
|
|
}
|
|
|
|
void LogoScene::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 shape = ShapeLoader::load(fitxer);
|
|
if (!shape || !shape->isValid()) {
|
|
std::cerr << "[LogoScene] Error carregant " << fitxer << '\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->get_primitives()) {
|
|
for (const auto& point : prim.points) {
|
|
min_x = std::min(min_x, point.x);
|
|
max_x = std::max(max_x, point.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 = (shape->getCenter().x - min_x) * ESCALA_FINAL;
|
|
|
|
lletres_.push_back({shape,
|
|
{.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 shape (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.position.x = x_actual + lletra.offset_centre;
|
|
lletra.position.y = y_centre;
|
|
|
|
// Avançar per a següent lletra
|
|
x_actual += lletra.ancho + ESPAI_ENTRE_LLETRES;
|
|
}
|
|
|
|
std::cout << "[LogoScene] " << lletres_.size()
|
|
<< " lletres carregades, ancho total: " << ancho_total << " px\n";
|
|
}
|
|
|
|
void LogoScene::canviar_estat(AnimationState nou_estat) {
|
|
estat_actual_ = nou_estat;
|
|
temps_estat_actual_ = 0.0F; // Reset time
|
|
|
|
// Inicialitzar state d'explosió
|
|
if (nou_estat == AnimationState::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 == AnimationState::POST_EXPLOSION) {
|
|
Audio::get()->playMusic("title.ogg");
|
|
}
|
|
|
|
std::cout << "[LogoScene] Canvi a state: " << static_cast<int>(nou_estat)
|
|
<< "\n";
|
|
}
|
|
|
|
bool LogoScene::totes_lletres_completes() const {
|
|
// Quan global_progress = 1.0, totes les lletres tenen letra_progress = 1.0
|
|
return temps_estat_actual_ >= DURACIO_ZOOM;
|
|
}
|
|
|
|
void LogoScene::actualitzar_explosions(float delta_time) {
|
|
temps_des_ultima_explosio_ += delta_time;
|
|
|
|
// Comprovar si és el moment d'explode 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_->explode(
|
|
lletra.shape, // Forma a explode
|
|
lletra.position, // Posició
|
|
0.0F, // Angle (sense rotació)
|
|
ESCALA_FINAL, // Escala (lletres a scale final)
|
|
VELOCITAT_EXPLOSIO, // Velocitat base
|
|
1.0F, // Brightness màxim (per defecte)
|
|
{.x = 0.0F, .y = 0.0F} // Sense velocity (per defecte)
|
|
);
|
|
|
|
std::cout << "[LogoScene] 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(AnimationState::POST_EXPLOSION);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LogoScene::update(float delta_time) {
|
|
temps_estat_actual_ += delta_time;
|
|
|
|
switch (estat_actual_) {
|
|
case AnimationState::PRE_ANIMATION:
|
|
if (temps_estat_actual_ >= DURACIO_PRE) {
|
|
canviar_estat(AnimationState::ANIMATION);
|
|
}
|
|
break;
|
|
|
|
case AnimationState::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(AnimationState::POST_ANIMATION);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AnimationState::POST_ANIMATION:
|
|
if (temps_estat_actual_ >= DURACIO_POST_ANIMATION) {
|
|
canviar_estat(AnimationState::EXPLOSION);
|
|
}
|
|
break;
|
|
|
|
case AnimationState::EXPLOSION:
|
|
actualitzar_explosions(delta_time);
|
|
break;
|
|
|
|
case AnimationState::POST_EXPLOSION:
|
|
if (temps_estat_actual_ >= DURACIO_POST_EXPLOSION) {
|
|
// Transició a pantalla de títol
|
|
context_.setNextScene(SceneType::TITLE);
|
|
SceneManager::actual = SceneType::TITLE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Verificar botones de skip (SHOOT P1/P2)
|
|
if (checkSkipButtonPressed()) {
|
|
context_.setNextScene(SceneType::TITLE, Option::JUMP_TO_TITLE_MAIN);
|
|
SceneManager::actual = SceneType::TITLE;
|
|
}
|
|
|
|
// Actualitzar animacions de debris
|
|
debris_manager_->update(delta_time);
|
|
}
|
|
|
|
void LogoScene::draw() {
|
|
// Fons negre
|
|
sdl_.neteja(0, 0, 0);
|
|
|
|
// PRE_ANIMATION: Només pantalla negra
|
|
if (estat_actual_ == AnimationState::PRE_ANIMATION) {
|
|
sdl_.presenta();
|
|
return; // No renderitzar lletres
|
|
}
|
|
|
|
// ANIMATION o POST_ANIMATION: Dibuixar lletres amb animació
|
|
if (estat_actual_ == AnimationState::ANIMATION ||
|
|
estat_actual_ == AnimationState::POST_ANIMATION) {
|
|
float global_progress =
|
|
(estat_actual_ == AnimationState::ANIMATION)
|
|
? std::min(temps_estat_actual_ / DURACIO_ZOOM, 1.0F)
|
|
: 1.0F; // POST: mantenir al 100%
|
|
|
|
const Vec2 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;
|
|
}
|
|
|
|
Vec2 pos_actual;
|
|
pos_actual.x =
|
|
ORIGEN_ZOOM.x + ((lletra.position.x - ORIGEN_ZOOM.x) * letra_progress);
|
|
pos_actual.y =
|
|
ORIGEN_ZOOM.y + ((lletra.position.y - ORIGEN_ZOOM.y) * letra_progress);
|
|
|
|
float t = letra_progress;
|
|
float ease_factor = 1.0F - ((1.0F - t) * (1.0F - t));
|
|
float current_scale =
|
|
ESCALA_INICIAL + ((ESCALA_FINAL - ESCALA_INICIAL) * ease_factor);
|
|
|
|
Rendering::render_shape(
|
|
sdl_.getRenderer(),
|
|
lletra.shape,
|
|
pos_actual,
|
|
0.0F,
|
|
current_scale,
|
|
1.0F);
|
|
}
|
|
}
|
|
|
|
// EXPLOSION: Dibuixar només lletres que encara no han explotat
|
|
if (estat_actual_ == AnimationState::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.contains(i)) {
|
|
const auto& lletra = lletres_[i];
|
|
|
|
Rendering::render_shape(
|
|
sdl_.getRenderer(),
|
|
lletra.shape,
|
|
lletra.position,
|
|
0.0F,
|
|
ESCALA_FINAL,
|
|
1.0F);
|
|
}
|
|
}
|
|
}
|
|
|
|
// POST_EXPLOSION: No draw lletres, només debris (a baix)
|
|
|
|
// Sempre draw debris (si n'hi ha d'active)
|
|
debris_manager_->draw();
|
|
|
|
sdl_.presenta();
|
|
}
|
|
|
|
auto LogoScene::checkSkipButtonPressed() -> bool {
|
|
return Input::get()->checkAnyPlayerAction(ARCADE_BUTTONS);
|
|
}
|
|
|
|
void LogoScene::processar_events(const SDL_Event& event) {
|
|
// No procesar eventos genéricos aquí - la lógica se movió a update()
|
|
}
|