839f73e1ef
El logo i el footer ara entren simulant un moviment 3D des de l'usuari cap al VP: arrenquen grans i a la posició projectada extrema (factor d'escala SCALE_START > 1, pivot al centre de pantalla) i convergeixen a la seva mida i posició finals. Substitueix l'offset Y lineal anterior.
632 lines
25 KiB
C++
632 lines
25 KiB
C++
// title_scene.cpp - Implementació de l'escena de títol 3D real
|
|
// © 2026 JailDesigner
|
|
|
|
#include "title_scene.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <cfloat>
|
|
#include <cmath>
|
|
#include <iostream>
|
|
#include <numbers>
|
|
#include <string>
|
|
|
|
#include "core/audio/audio.hpp"
|
|
#include "core/defaults.hpp"
|
|
#include "core/graphics/shape_loader.hpp"
|
|
#include "core/input/input.hpp"
|
|
#include "core/math/easing.hpp"
|
|
#include "core/rendering/shape_renderer.hpp"
|
|
#include "core/system/scene_context.hpp"
|
|
#include "project.h"
|
|
|
|
using SceneManager::SceneContext;
|
|
using SceneType = SceneContext::SceneType;
|
|
using Option = SceneContext::Option;
|
|
|
|
namespace {
|
|
|
|
// Botons per iniciar partida des de MAIN (només START).
|
|
constexpr std::array<InputAction, 1> START_GAME_BUTTONS = {InputAction::START};
|
|
|
|
} // namespace
|
|
|
|
TitleScene::TitleScene(SDLManager& sdl, SceneContext& context)
|
|
: sdl_(sdl),
|
|
context_(context),
|
|
text_(sdl.getRenderer()) {
|
|
std::cout << "SceneType Titol: Inicialitzant...\n";
|
|
|
|
match_config_.jugador1_actiu = false;
|
|
match_config_.jugador2_actiu = false;
|
|
match_config_.mode = GameConfig::Mode::NORMAL;
|
|
|
|
auto option = context_.consumeOption();
|
|
if (option == Option::JUMP_TO_TITLE_MAIN) {
|
|
std::cout << "SceneType Titol: Opció JUMP_TO_TITLE_MAIN activada\n";
|
|
estat_actual_ = TitleState::MAIN;
|
|
temps_estat_main_ = 0.0F;
|
|
}
|
|
|
|
// Càmera 3D: posicionada a l'origen, mirant cap a +Z, amb Y cap amunt.
|
|
camera_ = std::make_unique<Graphics::Camera3D>(
|
|
Vec3{.x = 0.0F, .y = 0.0F, .z = 0.0F},
|
|
Vec3{.x = 0.0F, .y = 0.0F, .z = 1.0F},
|
|
Vec3{.x = 0.0F, .y = 1.0F, .z = 0.0F},
|
|
CAMERA_FOV_Y_RAD,
|
|
static_cast<float>(Defaults::Game::WIDTH),
|
|
static_cast<float>(Defaults::Game::HEIGHT));
|
|
|
|
starfield_ = std::make_unique<Graphics::Starfield>(
|
|
sdl_.getRenderer(),
|
|
camera_.get(),
|
|
200);
|
|
starfield_->setColor(Defaults::Title::Colors::STARFIELD);
|
|
if (estat_actual_ == TitleState::MAIN) {
|
|
starfield_->setBrightness(BRIGHTNESS_STARFIELD);
|
|
} else {
|
|
starfield_->setBrightness(0.0F);
|
|
}
|
|
|
|
ship_animator_ = std::make_unique<Title::ShipAnimator>(sdl_.getRenderer(), camera_.get());
|
|
ship_animator_->init();
|
|
// Les naus comencen invisibles; updateMainState() les dispara al moment
|
|
// correcte de la intro coreografiada (també quan venim de JUMP_TO_TITLE_MAIN).
|
|
ship_animator_->setVisible(false);
|
|
|
|
initTitle();
|
|
inicialitzarJailgames();
|
|
|
|
if (Audio::getMusicState() != Audio::MusicState::PLAYING) {
|
|
Audio::get()->playMusic("title.ogg");
|
|
}
|
|
}
|
|
|
|
TitleScene::~TitleScene() {
|
|
Audio::get()->stopMusic();
|
|
}
|
|
|
|
void TitleScene::initTitle() {
|
|
using namespace Graphics;
|
|
|
|
const std::vector<std::string> FITXERS_ORNI = {
|
|
"title/letra_o.shp",
|
|
"title/letra_r.shp",
|
|
"title/letra_n.shp",
|
|
"title/letra_i.shp"};
|
|
|
|
float ancho_total_orni = 0.0F;
|
|
for (const auto& file : FITXERS_ORNI) {
|
|
auto shape = ShapeLoader::load(file);
|
|
if (!shape || !shape->isValid()) {
|
|
std::cerr << "[TitleScene] Error carregant " << file << '\n';
|
|
continue;
|
|
}
|
|
float min_x = FLT_MAX;
|
|
float max_x = -FLT_MAX;
|
|
float min_y = FLT_MAX;
|
|
float max_y = -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);
|
|
min_y = std::min(min_y, point.y);
|
|
max_y = std::max(max_y, point.y);
|
|
}
|
|
}
|
|
const float ANCHO = (max_x - min_x) * Defaults::Title::Layout::LOGO_SCALE;
|
|
const float ALTURA = (max_y - min_y) * Defaults::Title::Layout::LOGO_SCALE;
|
|
const float OFFSET_CENTRE = (shape->getCenter().x - min_x) * Defaults::Title::Layout::LOGO_SCALE;
|
|
lletres_orni_.push_back({shape, {.x = 0.0F, .y = 0.0F}, ANCHO, ALTURA, OFFSET_CENTRE});
|
|
ancho_total_orni += ANCHO;
|
|
}
|
|
ancho_total_orni += ESPAI_ENTRE_LLETRES * static_cast<float>(lletres_orni_.size() - 1);
|
|
|
|
float x_actual = (Defaults::Game::WIDTH - ancho_total_orni) / 2.0F;
|
|
for (auto& lletra : lletres_orni_) {
|
|
lletra.position.x = x_actual + lletra.offset_centre;
|
|
lletra.position.y = Defaults::Game::HEIGHT * Defaults::Title::Layout::LOGO_POS;
|
|
x_actual += lletra.ancho + ESPAI_ENTRE_LLETRES;
|
|
}
|
|
|
|
const float ALTURA_ORNI = lletres_orni_.empty() ? 50.0F : lletres_orni_[0].altura;
|
|
const float Y_ORNI = Defaults::Game::HEIGHT * Defaults::Title::Layout::LOGO_POS;
|
|
const float SEPARACION = Defaults::Game::HEIGHT * Defaults::Title::Layout::LOGO_LINE_SPACING;
|
|
y_attack_dinamica_ = Y_ORNI + ALTURA_ORNI + SEPARACION;
|
|
|
|
const std::vector<std::string> FITXERS_ATTACK = {
|
|
"title/letra_a.shp",
|
|
"title/letra_t.shp",
|
|
"title/letra_t.shp",
|
|
"title/letra_a.shp",
|
|
"title/letra_c.shp",
|
|
"title/letra_k.shp",
|
|
"title/letra_exclamacion.shp"};
|
|
|
|
float ancho_total_attack = 0.0F;
|
|
for (const auto& file : FITXERS_ATTACK) {
|
|
auto shape = ShapeLoader::load(file);
|
|
if (!shape || !shape->isValid()) {
|
|
std::cerr << "[TitleScene] Error carregant " << file << '\n';
|
|
continue;
|
|
}
|
|
float min_x = FLT_MAX;
|
|
float max_x = -FLT_MAX;
|
|
float min_y = FLT_MAX;
|
|
float max_y = -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);
|
|
min_y = std::min(min_y, point.y);
|
|
max_y = std::max(max_y, point.y);
|
|
}
|
|
}
|
|
const float ANCHO = (max_x - min_x) * Defaults::Title::Layout::LOGO_SCALE;
|
|
const float ALTURA = (max_y - min_y) * Defaults::Title::Layout::LOGO_SCALE;
|
|
const float OFFSET_CENTRE = (shape->getCenter().x - min_x) * Defaults::Title::Layout::LOGO_SCALE;
|
|
lletres_attack_.push_back({shape, {.x = 0.0F, .y = 0.0F}, ANCHO, ALTURA, OFFSET_CENTRE});
|
|
ancho_total_attack += ANCHO;
|
|
}
|
|
ancho_total_attack += ESPAI_ENTRE_LLETRES * static_cast<float>(lletres_attack_.size() - 1);
|
|
|
|
x_actual = (Defaults::Game::WIDTH - ancho_total_attack) / 2.0F;
|
|
for (auto& lletra : lletres_attack_) {
|
|
lletra.position.x = x_actual + lletra.offset_centre;
|
|
lletra.position.y = y_attack_dinamica_;
|
|
x_actual += lletra.ancho + ESPAI_ENTRE_LLETRES;
|
|
}
|
|
|
|
posicions_originals_orni_.clear();
|
|
for (const auto& lletra : lletres_orni_) {
|
|
posicions_originals_orni_.push_back(lletra.position);
|
|
}
|
|
posicions_originals_attack_.clear();
|
|
for (const auto& lletra : lletres_attack_) {
|
|
posicions_originals_attack_.push_back(lletra.position);
|
|
}
|
|
}
|
|
|
|
void TitleScene::inicialitzarJailgames() {
|
|
using namespace Graphics;
|
|
|
|
const 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"};
|
|
|
|
constexpr float SCALE = Defaults::Title::Layout::JAILGAMES_SCALE;
|
|
|
|
float ancho_total = 0.0F;
|
|
float altura_max = 0.0F;
|
|
for (const auto& file : FITXERS) {
|
|
auto shape = ShapeLoader::load(file);
|
|
if (!shape || !shape->isValid()) {
|
|
std::cerr << "[TitleScene] Error carregant " << file << '\n';
|
|
continue;
|
|
}
|
|
float min_x = FLT_MAX;
|
|
float max_x = -FLT_MAX;
|
|
float min_y = FLT_MAX;
|
|
float max_y = -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);
|
|
min_y = std::min(min_y, point.y);
|
|
max_y = std::max(max_y, point.y);
|
|
}
|
|
}
|
|
const float ANCHO = (max_x - min_x) * SCALE;
|
|
const float ALTURA = (max_y - min_y) * SCALE;
|
|
const float OFFSET_CENTRE = (shape->getCenter().x - min_x) * SCALE;
|
|
lletres_jailgames_.push_back({shape, {.x = 0.0F, .y = 0.0F}, ANCHO, ALTURA, OFFSET_CENTRE});
|
|
ancho_total += ANCHO;
|
|
altura_max = std::max(altura_max, ALTURA);
|
|
}
|
|
constexpr float ESPAI_JAILGAMES = ESPAI_ENTRE_LLETRES * SCALE;
|
|
if (!lletres_jailgames_.empty()) {
|
|
ancho_total += ESPAI_JAILGAMES * static_cast<float>(lletres_jailgames_.size() - 1);
|
|
}
|
|
|
|
const float Y_COPY = Defaults::Game::HEIGHT * Defaults::Title::Layout::COPYRIGHT1_POS;
|
|
const float GAP = Defaults::Game::HEIGHT * Defaults::Title::Layout::JAILGAMES_COPYRIGHT_GAP;
|
|
const float Y_CENTRE = Y_COPY - GAP - (altura_max / 2.0F);
|
|
const float X_INICIAL = (Defaults::Game::WIDTH - ancho_total) / 2.0F;
|
|
|
|
float x_actual = X_INICIAL;
|
|
for (auto& lletra : lletres_jailgames_) {
|
|
lletra.position.x = x_actual + lletra.offset_centre;
|
|
lletra.position.y = Y_CENTRE;
|
|
x_actual += lletra.ancho + ESPAI_JAILGAMES;
|
|
}
|
|
}
|
|
|
|
void TitleScene::dibuixarPeuTitol(float spacing) const {
|
|
namespace S = Defaults::Title::Sequence;
|
|
|
|
// Pivot al centre de pantalla (= projecció VP). Cada element s'expandeix
|
|
// des d'aquí mentre s_factor passa de SCALE_START (gran, prop de l'usuari)
|
|
// a 1.0 (a la mida i posició finals, "lluny" al VP).
|
|
const float SCREEN_CENTRE_X = Defaults::Game::WIDTH / 2.0F;
|
|
const float SCREEN_CENTRE_Y = Defaults::Game::HEIGHT / 2.0F;
|
|
const float JAILGAMES_S = std::lerp(S::FOOTER_INTRO_SCALE_START, 1.0F, Easing::easeOutQuad(intro_jailgames_progress_));
|
|
const float COPYRIGHT_S = std::lerp(S::FOOTER_INTRO_SCALE_START, 1.0F, Easing::easeOutQuad(intro_copyright_progress_));
|
|
|
|
const float JAILGAMES_RENDER_SCALE = Defaults::Title::Layout::JAILGAMES_SCALE * JAILGAMES_S;
|
|
for (const auto& lletra : lletres_jailgames_) {
|
|
const Vec2 POS{
|
|
.x = SCREEN_CENTRE_X + (JAILGAMES_S * (lletra.position.x - SCREEN_CENTRE_X)),
|
|
.y = SCREEN_CENTRE_Y + (JAILGAMES_S * (lletra.position.y - SCREEN_CENTRE_Y)),
|
|
};
|
|
Rendering::renderShape(sdl_.getRenderer(), lletra.shape, POS, 0.0F, JAILGAMES_RENDER_SCALE, 1.0F, 1.0F, Defaults::Title::Colors::JAILGAMES_LOGO);
|
|
}
|
|
std::string copyright = Project::COPYRIGHT;
|
|
for (char& c : copyright) {
|
|
if (c >= 'a' && c <= 'z') {
|
|
c = static_cast<char>(c - 32);
|
|
}
|
|
}
|
|
const float Y_COPY_FINAL = Defaults::Game::HEIGHT * Defaults::Title::Layout::COPYRIGHT1_POS;
|
|
const float COPY_X = SCREEN_CENTRE_X; // ja al centre
|
|
const float COPY_Y = SCREEN_CENTRE_Y + (COPYRIGHT_S * (Y_COPY_FINAL - SCREEN_CENTRE_Y));
|
|
const float COPY_RENDER_SCALE = Defaults::Title::Layout::COPYRIGHT_SCALE * COPYRIGHT_S;
|
|
text_.renderCentered(copyright, {.x = COPY_X, .y = COPY_Y}, COPY_RENDER_SCALE, spacing, 1.0F, Defaults::Title::Colors::COPYRIGHT);
|
|
}
|
|
|
|
auto TitleScene::isFinished() const -> bool {
|
|
return context_.nextScene() != SceneType::TITLE;
|
|
}
|
|
|
|
void TitleScene::update(float delta_time) {
|
|
if (starfield_) {
|
|
starfield_->update(delta_time);
|
|
}
|
|
if (ship_animator_ &&
|
|
(estat_actual_ == TitleState::STARFIELD_FADE_IN ||
|
|
estat_actual_ == TitleState::STARFIELD ||
|
|
estat_actual_ == TitleState::MAIN ||
|
|
estat_actual_ == TitleState::PLAYER_JOIN_PHASE)) {
|
|
ship_animator_->update(delta_time);
|
|
}
|
|
|
|
switch (estat_actual_) {
|
|
case TitleState::STARFIELD_FADE_IN:
|
|
updateStarfieldFadeInState(delta_time);
|
|
break;
|
|
case TitleState::STARFIELD:
|
|
updateStarfieldState(delta_time);
|
|
break;
|
|
case TitleState::MAIN:
|
|
updateMainState(delta_time);
|
|
break;
|
|
case TitleState::PLAYER_JOIN_PHASE:
|
|
updatePlayerJoinPhaseState(delta_time);
|
|
break;
|
|
case TitleState::BLACK_SCREEN:
|
|
updateBlackScreenState(delta_time);
|
|
break;
|
|
}
|
|
|
|
handleSkipInput();
|
|
handleStartInput();
|
|
}
|
|
|
|
void TitleScene::updateStarfieldFadeInState(float delta_time) {
|
|
temps_acumulat_ += delta_time;
|
|
const float PROGRESS = std::min(1.0F, temps_acumulat_ / DURACIO_FADE_IN);
|
|
starfield_->setBrightness(PROGRESS * BRIGHTNESS_STARFIELD);
|
|
if (temps_acumulat_ >= DURACIO_FADE_IN) {
|
|
estat_actual_ = TitleState::STARFIELD;
|
|
temps_acumulat_ = 0.0F;
|
|
starfield_->setBrightness(BRIGHTNESS_STARFIELD);
|
|
}
|
|
}
|
|
|
|
void TitleScene::updateStarfieldState(float delta_time) {
|
|
temps_acumulat_ += delta_time;
|
|
if (temps_acumulat_ >= DURACIO_INIT) {
|
|
estat_actual_ = TitleState::MAIN;
|
|
temps_estat_main_ = 0.0F;
|
|
animacio_activa_ = false;
|
|
factor_lerp_ = 0.0F;
|
|
}
|
|
}
|
|
|
|
void TitleScene::updateMainState(float delta_time) {
|
|
temps_estat_main_ += delta_time;
|
|
|
|
namespace S = Defaults::Title::Sequence;
|
|
namespace Sh = Defaults::Title::Ships;
|
|
|
|
// Thresholds derivats de la coreografia (vegeu Defaults::Title::Sequence).
|
|
constexpr float T_FOOTER_START = S::LOGO_ENTRY_DURATION;
|
|
constexpr float T_COPY_START = T_FOOTER_START + S::COPYRIGHT_STAGGER;
|
|
constexpr float T_JAILGAMES_END = T_FOOTER_START + S::JAILGAMES_ENTRY_DURATION;
|
|
constexpr float T_COPYRIGHT_END = T_COPY_START + S::COPYRIGHT_ENTRY_DURATION;
|
|
constexpr float T_FOOTER_END = std::max(T_JAILGAMES_END, T_COPYRIGHT_END);
|
|
constexpr float T_SHIPS_START = T_FOOTER_END + S::SHIPS_DELAY_AFTER_FOOTER;
|
|
constexpr float T_SHIPS_LANDED = T_SHIPS_START + Sh::ENTRY_DURATION + Sh::P2_ENTRY_DELAY;
|
|
constexpr float T_PRESS_START_VISIBLE = T_SHIPS_LANDED + S::PRESS_START_DELAY_AFTER_SHIPS;
|
|
|
|
intro_logo_progress_ = std::clamp(temps_estat_main_ / S::LOGO_ENTRY_DURATION, 0.0F, 1.0F);
|
|
intro_jailgames_progress_ = std::clamp(
|
|
(temps_estat_main_ - T_FOOTER_START) / S::JAILGAMES_ENTRY_DURATION,
|
|
0.0F,
|
|
1.0F);
|
|
intro_copyright_progress_ = std::clamp(
|
|
(temps_estat_main_ - T_COPY_START) / S::COPYRIGHT_ENTRY_DURATION,
|
|
0.0F,
|
|
1.0F);
|
|
|
|
if (!ships_intro_launched_ && temps_estat_main_ >= T_SHIPS_START &&
|
|
ship_animator_ != nullptr) {
|
|
ship_animator_->setVisible(true);
|
|
ship_animator_->startEntryAnimation();
|
|
ships_intro_launched_ = true;
|
|
}
|
|
|
|
if (!press_start_visible_ && temps_estat_main_ >= T_PRESS_START_VISIBLE) {
|
|
press_start_visible_ = true;
|
|
}
|
|
|
|
// L'oscil·lació suau del logo arrenca quan el logo ha aterrat. Així
|
|
// l'amplitud creix gradualment (lerp) durant la resta de la intro.
|
|
if (temps_estat_main_ < S::LOGO_ENTRY_DURATION) {
|
|
factor_lerp_ = 0.0F;
|
|
animacio_activa_ = false;
|
|
} else if (temps_estat_main_ < S::LOGO_ENTRY_DURATION + DURACIO_LERP) {
|
|
factor_lerp_ = (temps_estat_main_ - S::LOGO_ENTRY_DURATION) / DURACIO_LERP;
|
|
animacio_activa_ = true;
|
|
} else {
|
|
factor_lerp_ = 1.0F;
|
|
animacio_activa_ = true;
|
|
}
|
|
updateLogoAnimation(delta_time);
|
|
}
|
|
|
|
void TitleScene::updatePlayerJoinPhaseState(float delta_time) {
|
|
temps_acumulat_ += delta_time;
|
|
updateLogoAnimation(delta_time);
|
|
|
|
const bool P1_ABANS = match_config_.jugador1_actiu;
|
|
const bool P2_ABANS = match_config_.jugador2_actiu;
|
|
|
|
if (checkStartGameButtonPressed()) {
|
|
context_.setMatchConfig(match_config_);
|
|
triggerExitForJoinedPlayers(P1_ABANS, P2_ABANS, "late join - ");
|
|
Audio::get()->playSound(Defaults::Sound::START, Audio::Group::GAME);
|
|
temps_acumulat_ = 0.0F;
|
|
}
|
|
|
|
if (temps_acumulat_ >= DURACIO_TRANSITION) {
|
|
estat_actual_ = TitleState::BLACK_SCREEN;
|
|
temps_acumulat_ = 0.0F;
|
|
}
|
|
}
|
|
|
|
void TitleScene::updateBlackScreenState(float delta_time) {
|
|
temps_acumulat_ += delta_time;
|
|
if (temps_acumulat_ >= DURACIO_BLACK_SCREEN) {
|
|
context_.setNextScene(SceneType::GAME);
|
|
}
|
|
}
|
|
|
|
void TitleScene::handleSkipInput() {
|
|
if (estat_actual_ != TitleState::STARFIELD_FADE_IN && estat_actual_ != TitleState::STARFIELD) {
|
|
return;
|
|
}
|
|
if (!checkSkipButtonPressed()) {
|
|
return;
|
|
}
|
|
estat_actual_ = TitleState::MAIN;
|
|
starfield_->setBrightness(BRIGHTNESS_STARFIELD);
|
|
|
|
// Saltar la intro coreografiada: deixar tots els elements ja in-place.
|
|
namespace S = Defaults::Title::Sequence;
|
|
namespace Sh = Defaults::Title::Ships;
|
|
constexpr float T_FOOTER_END = std::max(
|
|
S::LOGO_ENTRY_DURATION + S::JAILGAMES_ENTRY_DURATION,
|
|
S::LOGO_ENTRY_DURATION + S::COPYRIGHT_STAGGER + S::COPYRIGHT_ENTRY_DURATION);
|
|
constexpr float T_PRESS_START_VISIBLE = T_FOOTER_END + S::SHIPS_DELAY_AFTER_FOOTER +
|
|
Sh::ENTRY_DURATION + Sh::P2_ENTRY_DELAY + S::PRESS_START_DELAY_AFTER_SHIPS;
|
|
temps_estat_main_ = T_PRESS_START_VISIBLE;
|
|
intro_logo_progress_ = 1.0F;
|
|
intro_jailgames_progress_ = 1.0F;
|
|
intro_copyright_progress_ = 1.0F;
|
|
press_start_visible_ = true;
|
|
ships_intro_launched_ = true;
|
|
if (ship_animator_ != nullptr) {
|
|
ship_animator_->setVisible(true);
|
|
ship_animator_->startEntryAnimation();
|
|
}
|
|
}
|
|
|
|
void TitleScene::handleStartInput() {
|
|
if (estat_actual_ != TitleState::MAIN) {
|
|
return;
|
|
}
|
|
// No acceptar START fins que la intro coreografiada haja conclòs i el
|
|
// text "PRESS START TO PLAY" siga visible.
|
|
if (!press_start_visible_) {
|
|
return;
|
|
}
|
|
const bool P1_ABANS = match_config_.jugador1_actiu;
|
|
const bool P2_ABANS = match_config_.jugador2_actiu;
|
|
|
|
if (!checkStartGameButtonPressed()) {
|
|
return;
|
|
}
|
|
|
|
if (ship_animator_ && !ship_animator_->isVisible()) {
|
|
ship_animator_->setVisible(true);
|
|
ship_animator_->skipToFloatingState();
|
|
}
|
|
|
|
context_.setMatchConfig(match_config_);
|
|
estat_actual_ = TitleState::PLAYER_JOIN_PHASE;
|
|
temps_acumulat_ = 0.0F;
|
|
|
|
triggerExitForJoinedPlayers(P1_ABANS, P2_ABANS, "");
|
|
|
|
Audio::get()->fadeOutMusic(MUSIC_FADE);
|
|
Audio::get()->playSound(Defaults::Sound::START, Audio::Group::GAME);
|
|
}
|
|
|
|
void TitleScene::triggerExitForJoinedPlayers(bool p1_was_active, bool p2_was_active, const char* log_prefix) {
|
|
if (ship_animator_ == nullptr) {
|
|
return;
|
|
}
|
|
if (match_config_.jugador1_actiu && !p1_was_active) {
|
|
ship_animator_->triggerExitAnimationForPlayer(1);
|
|
std::cout << "[TitleScene] P1 " << log_prefix << "ship exiting\n";
|
|
}
|
|
if (match_config_.jugador2_actiu && !p2_was_active) {
|
|
ship_animator_->triggerExitAnimationForPlayer(2);
|
|
std::cout << "[TitleScene] P2 " << log_prefix << "ship exiting\n";
|
|
}
|
|
}
|
|
|
|
void TitleScene::updateLogoAnimation(float delta_time) {
|
|
if (!animacio_activa_) {
|
|
return;
|
|
}
|
|
temps_animacio_ += delta_time * factor_lerp_;
|
|
|
|
const float TWO_PI = 2.0F * Defaults::Math::PI;
|
|
const float OFFSET_X = ORBIT_AMPLITUDE_X * std::sin(TWO_PI * ORBIT_FREQUENCY_X * temps_animacio_);
|
|
const float OFFSET_Y = ORBIT_AMPLITUDE_Y * std::sin((TWO_PI * ORBIT_FREQUENCY_Y * temps_animacio_) + ORBIT_PHASE_OFFSET);
|
|
|
|
for (std::size_t i = 0; i < lletres_orni_.size(); ++i) {
|
|
lletres_orni_[i].position.x = posicions_originals_orni_[i].x + std::round(OFFSET_X);
|
|
lletres_orni_[i].position.y = posicions_originals_orni_[i].y + std::round(OFFSET_Y);
|
|
}
|
|
for (std::size_t i = 0; i < lletres_attack_.size(); ++i) {
|
|
lletres_attack_[i].position.x = posicions_originals_attack_[i].x + std::round(OFFSET_X);
|
|
lletres_attack_[i].position.y = posicions_originals_attack_[i].y + std::round(OFFSET_Y);
|
|
}
|
|
}
|
|
|
|
void TitleScene::draw() {
|
|
if (starfield_ && estat_actual_ != TitleState::BLACK_SCREEN) {
|
|
starfield_->draw();
|
|
}
|
|
|
|
if (ship_animator_ &&
|
|
(estat_actual_ == TitleState::STARFIELD_FADE_IN ||
|
|
estat_actual_ == TitleState::STARFIELD ||
|
|
estat_actual_ == TitleState::MAIN ||
|
|
estat_actual_ == TitleState::PLAYER_JOIN_PHASE)) {
|
|
ship_animator_->draw();
|
|
}
|
|
|
|
if (estat_actual_ == TitleState::STARFIELD_FADE_IN || estat_actual_ == TitleState::STARFIELD) {
|
|
return;
|
|
}
|
|
|
|
if (estat_actual_ != TitleState::MAIN && estat_actual_ != TitleState::PLAYER_JOIN_PHASE) {
|
|
return;
|
|
}
|
|
|
|
// Factor d'escala+posició per simular un moviment 3D des de l'usuari (prop,
|
|
// sprite gran i posició projectada extrema) cap al VP (lluny, sprite a la
|
|
// seva mida i posició finals). Pivot: centre de pantalla (= projecció VP).
|
|
const float LOGO_S = std::lerp(Defaults::Title::Sequence::LOGO_INTRO_SCALE_START, 1.0F, Easing::easeOutQuad(intro_logo_progress_));
|
|
const float SCREEN_CENTRE_X = Defaults::Game::WIDTH / 2.0F;
|
|
const float SCREEN_CENTRE_Y = Defaults::Game::HEIGHT / 2.0F;
|
|
const float LOGO_RENDER_SCALE = Defaults::Title::Layout::LOGO_SCALE * LOGO_S;
|
|
|
|
if (animacio_activa_) {
|
|
float temps_shadow = std::max(0.0F, temps_animacio_ - SHADOW_DELAY);
|
|
const float TWO_PI = 2.0F * Defaults::Math::PI;
|
|
const float SHADOW_OX = (ORBIT_AMPLITUDE_X * std::sin(TWO_PI * ORBIT_FREQUENCY_X * temps_shadow)) + SHADOW_OFFSET_X;
|
|
const float SHADOW_OY = (ORBIT_AMPLITUDE_Y * std::sin((TWO_PI * ORBIT_FREQUENCY_Y * temps_shadow) + ORBIT_PHASE_OFFSET)) + SHADOW_OFFSET_Y;
|
|
|
|
for (std::size_t i = 0; i < lletres_orni_.size(); ++i) {
|
|
const float BASE_X = posicions_originals_orni_[i].x + std::round(SHADOW_OX);
|
|
const float BASE_Y = posicions_originals_orni_[i].y + std::round(SHADOW_OY);
|
|
const Vec2 POS_SHADOW{
|
|
.x = SCREEN_CENTRE_X + (LOGO_S * (BASE_X - SCREEN_CENTRE_X)),
|
|
.y = SCREEN_CENTRE_Y + (LOGO_S * (BASE_Y - SCREEN_CENTRE_Y)),
|
|
};
|
|
Rendering::renderShape(sdl_.getRenderer(), lletres_orni_[i].shape, POS_SHADOW, 0.0F, LOGO_RENDER_SCALE, 1.0F, SHADOW_BRIGHTNESS, Defaults::Title::Colors::LOGO_SHADOW);
|
|
}
|
|
for (std::size_t i = 0; i < lletres_attack_.size(); ++i) {
|
|
const float BASE_X = posicions_originals_attack_[i].x + std::round(SHADOW_OX);
|
|
const float BASE_Y = posicions_originals_attack_[i].y + std::round(SHADOW_OY);
|
|
const Vec2 POS_SHADOW{
|
|
.x = SCREEN_CENTRE_X + (LOGO_S * (BASE_X - SCREEN_CENTRE_X)),
|
|
.y = SCREEN_CENTRE_Y + (LOGO_S * (BASE_Y - SCREEN_CENTRE_Y)),
|
|
};
|
|
Rendering::renderShape(sdl_.getRenderer(), lletres_attack_[i].shape, POS_SHADOW, 0.0F, LOGO_RENDER_SCALE, 1.0F, SHADOW_BRIGHTNESS, Defaults::Title::Colors::LOGO_SHADOW);
|
|
}
|
|
}
|
|
|
|
for (const auto& lletra : lletres_orni_) {
|
|
const Vec2 POS{
|
|
.x = SCREEN_CENTRE_X + (LOGO_S * (lletra.position.x - SCREEN_CENTRE_X)),
|
|
.y = SCREEN_CENTRE_Y + (LOGO_S * (lletra.position.y - SCREEN_CENTRE_Y)),
|
|
};
|
|
Rendering::renderShape(sdl_.getRenderer(), lletra.shape, POS, 0.0F, LOGO_RENDER_SCALE, 1.0F, 1.0F, Defaults::Title::Colors::LOGO_MAIN);
|
|
}
|
|
for (const auto& lletra : lletres_attack_) {
|
|
const Vec2 POS{
|
|
.x = SCREEN_CENTRE_X + (LOGO_S * (lletra.position.x - SCREEN_CENTRE_X)),
|
|
.y = SCREEN_CENTRE_Y + (LOGO_S * (lletra.position.y - SCREEN_CENTRE_Y)),
|
|
};
|
|
Rendering::renderShape(sdl_.getRenderer(), lletra.shape, POS, 0.0F, LOGO_RENDER_SCALE, 1.0F, 1.0F, Defaults::Title::Colors::LOGO_MAIN);
|
|
}
|
|
|
|
const float SPACING = Defaults::Title::Layout::TEXT_SPACING;
|
|
|
|
if (press_start_visible_) {
|
|
bool mostrar_text = true;
|
|
if (estat_actual_ == TitleState::PLAYER_JOIN_PHASE) {
|
|
const float FASE = temps_acumulat_ * BLINK_FREQUENCY * 2.0F * std::numbers::pi_v<float>;
|
|
mostrar_text = (std::sin(FASE) > 0.0F);
|
|
}
|
|
if (mostrar_text) {
|
|
const std::string MAIN_TEXT = "PRESS START TO PLAY";
|
|
const float MAIN_SCALE = Defaults::Title::Layout::PRESS_START_SCALE;
|
|
const float CENTRE_X = Defaults::Game::WIDTH / 2.0F;
|
|
const float CENTRE_Y = Defaults::Game::HEIGHT * Defaults::Title::Layout::PRESS_START_POS;
|
|
text_.renderCentered(MAIN_TEXT, {.x = CENTRE_X, .y = CENTRE_Y}, MAIN_SCALE, SPACING, 1.0F, Defaults::Title::Colors::PRESS_START);
|
|
}
|
|
}
|
|
|
|
dibuixarPeuTitol(SPACING);
|
|
}
|
|
|
|
auto TitleScene::checkSkipButtonPressed() -> bool {
|
|
return Input::get()->checkAnyPlayerAction(ARCADE_BUTTONS);
|
|
}
|
|
|
|
auto TitleScene::checkStartGameButtonPressed() -> bool {
|
|
auto* input = Input::get();
|
|
bool any_pressed = false;
|
|
for (auto action : START_GAME_BUTTONS) {
|
|
if (input->checkActionPlayer1(action, Input::DO_NOT_ALLOW_REPEAT)) {
|
|
if (!match_config_.jugador1_actiu) {
|
|
match_config_.jugador1_actiu = true;
|
|
any_pressed = true;
|
|
}
|
|
}
|
|
if (input->checkActionPlayer2(action, Input::DO_NOT_ALLOW_REPEAT)) {
|
|
if (!match_config_.jugador2_actiu) {
|
|
match_config_.jugador2_actiu = true;
|
|
any_pressed = true;
|
|
}
|
|
}
|
|
}
|
|
return any_pressed;
|
|
}
|
|
|
|
void TitleScene::handleEvent(const SDL_Event& event) {
|
|
(void)event;
|
|
}
|