afegit titol al TITOL

This commit is contained in:
2025-12-03 17:40:27 +01:00
parent 622ccd22bc
commit 3b0354da54
27 changed files with 520 additions and 200 deletions

View File

@@ -1,7 +1,7 @@
# CMakeLists.txt # CMakeLists.txt
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.10)
project(orni VERSION 0.3.1) project(orni VERSION 0.4.0)
# Info del proyecto # Info del proyecto
set(PROJECT_LONG_NAME "Orni Attack") set(PROJECT_LONG_NAME "Orni Attack")

View File

@@ -0,0 +1,10 @@
# letra_a.shp
# Generado automáticamente desde jailgames.svg
# Dimensiones: 137.50 x 100.00 px
name: letra_a
scale: 1.0
center: 68.75, 50.00
polyline: 0.00,100.00 0.00,75.00 37.50,0.00 100.00,0.00 137.50,75.00 137.50,100.00 100.00,100.00 100.00,87.50 37.50,87.50 37.50,100.00 0.00,100.00
polyline: 62.50,25.00 50.00,50.00 50.00,62.50 87.50,62.50 87.50,50.00 75.00,25.00 62.50,25.00

View File

@@ -0,0 +1,9 @@
# letra_c.shp
# Generado automáticamente desde jailgames.svg
# Dimensiones: 137.50 x 100.00 px
name: letra_c
scale: 1.0
center: 68.75, 50.00
polyline: 12.50,100.00 0.00,87.50 0.00,12.50 12.50,0.00 125.00,0.00 137.50,12.50 137.50,37.50 100.00,37.50 100.00,25.00 37.50,25.00 37.50,75.00 100.00,75.00 100.00,62.50 137.50,62.50 137.50,87.50 125.00,100.00 12.50,100.00

View File

@@ -0,0 +1,10 @@
# letra_exclamacion.shp
# Generado automáticamente desde jailgames.svg
# Dimensiones: 37.51 x 100.00 px
name: letra_exclamacion
scale: 1.0
center: 18.75, 50.00
polyline: 0.00,62.50 0.00,0.00 37.51,0.00 37.51,62.50 0.00,62.50
polyline: 0.00,100.00 0.00,75.00 37.51,75.00 37.51,100.00 0.00,100.00

View File

@@ -0,0 +1,9 @@
# letra_i.shp
# Generado automáticamente desde jailgames.svg
# Dimensiones: 37.50 x 100.00 px
name: letra_i
scale: 1.0
center: 18.75, 50.00
polyline: 0.00,0.00 37.50,0.00 37.50,100.00 0.00,100.00 0.00,0.00

View File

@@ -0,0 +1,9 @@
# letra_k.shp
# Generado automáticamente desde jailgames.svg
# Dimensiones: 137.50 x 100.00 px
name: letra_k
scale: 1.0
center: 68.75, 50.00
polyline: 0.00,100.00 0.00,0.00 37.50,0.00 37.50,37.50 50.00,37.50 100.00,0.00 137.50,0.00 137.50,25.00 87.06,50.00 137.50,75.00 137.50,100.00 100.00,100.00 50.00,62.50 37.50,62.50 37.50,100.00 0.00,100.00

View File

@@ -0,0 +1,9 @@
# letra_n.shp
# Generado automáticamente desde jailgames.svg
# Dimensiones: 137.50 x 100.00 px
name: letra_n
scale: 1.0
center: 68.75, 50.00
polyline: 0.00,100.00 0.00,0.00 50.00,0.00 100.00,50.00 100.00,0.00 137.50,0.00 137.50,100.00 87.50,100.00 37.50,50.00 37.50,100.00 0.00,100.00

View File

@@ -0,0 +1,10 @@
# letra_o.shp
# Generado automáticamente desde jailgames.svg
# Dimensiones: 137.50 x 100.00 px
name: letra_o
scale: 1.0
center: 68.75, 50.00
polyline: 12.50,100.00 0.00,87.50 0.00,12.50 12.50,0.00 125.00,0.00 137.50,12.50 137.50,87.50 125.00,100.00 12.50,100.00
polyline: 100.00,25.00 37.50,25.00 37.50,75.00 100.00,75.00 100.00,25.00

View File

@@ -0,0 +1,10 @@
# letra_r.shp
# Generado automáticamente desde jailgames.svg
# Dimensiones: 137.50 x 100.00 px
name: letra_r
scale: 1.0
center: 68.75, 50.00
polyline: 0.00,100.00 0.00,0.00 125.00,0.00 137.50,12.50 137.50,62.50 125.00,62.50 137.50,75.00 137.50,100.00 100.00,100.00 100.00,75.00 37.50,75.00 37.50,100.00 0.00,100.00
polyline: 37.50,50.00 100.00,50.00 100.00,25.00 37.50,25.00 37.50,50.00

View File

@@ -0,0 +1,9 @@
# letra_t.shp
# Generado automáticamente desde jailgames.svg
# Dimensiones: 137.50 x 100.00 px
name: letra_t
scale: 1.0
center: 68.75, 50.00
polyline: 0.00,25.00 0.00,0.00 137.50,0.00 137.50,25.00 87.50,25.00 87.50,100.00 50.00,100.00 50.00,25.00 0.00,25.00

View File

@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 7875 4016" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.792849,0,0,0.792849,84.327,350.707)">
<path d="M896,1693L896,1531.23L1219.53,1531.23L1219.53,560.632L1543.07,560.632L1543.07,1531.23L1381.3,1531.23L1381.3,1693L896,1693Z" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:7.01px;"/>
<path d="M2028.37,1369.47L2028.37,1693L1704.83,1693L1704.83,722.399L1866.6,722.399L1866.6,560.632L2351.9,560.632L2351.9,722.399L2513.67,722.399L2513.67,1693L2190.14,1693L2190.14,1369.47L2028.37,1369.47ZM2028.37,722.399L2028.37,1207.7L2190.14,1207.7L2190.14,722.399L2028.37,722.399Z" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:7.01px;"/>
<rect x="2675.44" y="560.632" width="323.534" height="1132.37" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:7.01px;"/>
<path d="M3160.74,560.632L3484.27,560.632L3484.27,1531.23L3807.8,1531.23L3807.8,1693L3160.74,1693L3160.74,560.632Z" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:7.01px;"/>
<path d="M4131.34,560.632L4616.64,560.632L4616.64,722.399L4293.1,722.399L4293.1,1531.23L4454.87,1531.23L4454.87,1045.93L4778.4,1045.93L4778.4,1693L4131.34,1693L4131.34,1531.23L3969.57,1531.23L3969.57,722.399L4131.34,722.399L4131.34,560.632Z" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:7.01px;"/>
<path d="M5263.71,1369.47L5263.71,1693L4940.17,1693L4940.17,722.399L5101.94,722.399L5101.94,560.632L5587.24,560.632L5587.24,722.399L5749.01,722.399L5749.01,1693L5425.47,1693L5425.47,1369.47L5263.71,1369.47ZM5263.71,722.399L5263.71,1207.7L5425.47,1207.7L5425.47,722.399L5263.71,722.399Z" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:7.01px;"/>
<path d="M6719.61,1207.7L6557.84,1207.7L6557.84,1369.47L6396.07,1369.47L6396.07,1207.7L6234.31,1207.7L6234.31,1693L5910.77,1693L5910.77,560.632L6072.54,560.632L6072.54,722.399L6234.31,722.399L6234.31,884.166L6396.07,884.166L6396.07,1045.93L6557.84,1045.93L6557.84,884.166L6719.61,884.166L6719.61,722.399L6881.37,722.399L6881.37,560.632L7043.14,560.632L7043.14,1693L6719.61,1693L6719.61,1207.7Z" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:7.01px;"/>
<path d="M7851.98,884.166L7851.98,1045.93L7528.44,1045.93L7528.44,1531.23L8013.74,1531.23L8013.74,1693L7204.91,1693L7204.91,560.632L8013.74,560.632L8013.74,722.399L7528.44,722.399L7528.44,884.166L7851.98,884.166Z" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:7.01px;"/>
<path d="M8175.51,1531.23L8499.04,1531.23L8499.04,1207.7L8337.28,1207.7L8337.28,1045.93L8175.51,1045.93L8175.51,722.399L8337.28,722.399L8337.28,560.632L8822.58,560.632L8822.58,722.399L8499.04,722.399L8499.04,1045.93L8660.81,1045.93L8660.81,1207.7L8822.58,1207.7L8822.58,1531.23L8660.81,1531.23L8660.81,1693L8175.51,1693L8175.51,1531.23Z" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:7.01px;"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -17,10 +17,10 @@ Starfield::Starfield(SDL_Renderer* renderer,
const Punt& punt_fuga, const Punt& punt_fuga,
const SDL_FRect& area, const SDL_FRect& area,
int densitat) int densitat)
: renderer_(renderer) : renderer_(renderer),
, punt_fuga_(punt_fuga) punt_fuga_(punt_fuga),
, area_(area) area_(area),
, densitat_(densitat) { densitat_(densitat) {
// Carregar forma d'estrella // Carregar forma d'estrella
shape_estrella_ = std::make_shared<Shape>("data/shapes/star.shp"); shape_estrella_ = std::make_shared<Shape>("data/shapes/star.shp");

View File

@@ -58,8 +58,7 @@ bool linea(SDL_Renderer* renderer, int x1, int y1, int x2, int y2, bool dibuixar
SDL_SetRenderDrawColor(renderer, color_final.r, color_final.g, color_final.b, 255); SDL_SetRenderDrawColor(renderer, color_final.r, color_final.g, color_final.b, 255);
// Renderitzar amb coordenades físiques // Renderitzar amb coordenades físiques
SDL_RenderLine(renderer, static_cast<float>(px1), static_cast<float>(py1), SDL_RenderLine(renderer, static_cast<float>(px1), static_cast<float>(py1), static_cast<float>(px2), static_cast<float>(py2));
static_cast<float>(px2), static_cast<float>(py2));
} }
// Algorisme de Bresenham original (conservat per a futura detecció de // Algorisme de Bresenham original (conservat per a futura detecció de

View File

@@ -7,13 +7,13 @@
#include <cstdlib> #include <cstdlib>
#include <iostream> #include <iostream>
#include "core/audio/audio.hpp"
#include "core/defaults.hpp"
#include "core/rendering/sdl_manager.hpp"
#include "game/escenes/escena_joc.hpp" #include "game/escenes/escena_joc.hpp"
#include "game/escenes/escena_logo.hpp" #include "game/escenes/escena_logo.hpp"
#include "game/escenes/escena_titol.hpp" #include "game/escenes/escena_titol.hpp"
#include "game/options.hpp" #include "game/options.hpp"
#include "core/audio/audio.hpp"
#include "core/defaults.hpp"
#include "core/rendering/sdl_manager.hpp"
#include "gestor_escenes.hpp" #include "gestor_escenes.hpp"
#include "project.h" #include "project.h"

View File

@@ -3,9 +3,9 @@
#include "global_events.hpp" #include "global_events.hpp"
#include "core/input/mouse.hpp"
#include "core/rendering/sdl_manager.hpp" #include "core/rendering/sdl_manager.hpp"
#include "gestor_escenes.hpp" #include "gestor_escenes.hpp"
#include "core/input/mouse.hpp"
namespace GlobalEvents { namespace GlobalEvents {

View File

@@ -150,7 +150,10 @@ void Enemic::comportament_pentagon(float delta_time) {
// Obtenir límits segurs // Obtenir límits segurs
float min_x, max_x, min_y, max_y; float min_x, max_x, min_y, max_y;
Constants::obtenir_limits_zona_segurs(Defaults::Entities::ENEMY_RADIUS, Constants::obtenir_limits_zona_segurs(Defaults::Entities::ENEMY_RADIUS,
min_x, max_x, min_y, max_y); min_x,
max_x,
min_y,
max_y);
// Zigzag: canvi d'angle més freqüent en tocar límits // Zigzag: canvi d'angle més freqüent en tocar límits
if (new_y >= min_y && new_y <= max_y) { if (new_y >= min_y && new_y <= max_y) {
@@ -214,7 +217,10 @@ void Enemic::comportament_quadrat(float delta_time) {
// Obtenir límits segurs // Obtenir límits segurs
float min_x, max_x, min_y, max_y; float min_x, max_x, min_y, max_y;
Constants::obtenir_limits_zona_segurs(Defaults::Entities::ENEMY_RADIUS, Constants::obtenir_limits_zona_segurs(Defaults::Entities::ENEMY_RADIUS,
min_x, max_x, min_y, max_y); min_x,
max_x,
min_y,
max_y);
// Bounce on walls (simple reflection) // Bounce on walls (simple reflection)
if (new_y >= min_y && new_y <= max_y) { if (new_y >= min_y && new_y <= max_y) {
@@ -260,7 +266,10 @@ void Enemic::comportament_molinillo(float delta_time) {
// Obtenir límits segurs // Obtenir límits segurs
float min_x, max_x, min_y, max_y; float min_x, max_x, min_y, max_y;
Constants::obtenir_limits_zona_segurs(Defaults::Entities::ENEMY_RADIUS, Constants::obtenir_limits_zona_segurs(Defaults::Entities::ENEMY_RADIUS,
min_x, max_x, min_y, max_y); min_x,
max_x,
min_y,
max_y);
// Rare angle changes on wall hits // Rare angle changes on wall hits
if (new_y >= min_y && new_y <= max_y) { if (new_y >= min_y && new_y <= max_y) {

View File

@@ -10,14 +10,14 @@
#include <array> #include <array>
#include <cstdint> #include <cstdint>
#include "core/graphics/vector_text.hpp"
#include "core/rendering/sdl_manager.hpp"
#include "core/types.hpp"
#include "../constants.hpp" #include "../constants.hpp"
#include "../effects/debris_manager.hpp" #include "../effects/debris_manager.hpp"
#include "../entities/bala.hpp" #include "../entities/bala.hpp"
#include "../entities/enemic.hpp" #include "../entities/enemic.hpp"
#include "../entities/nau.hpp" #include "../entities/nau.hpp"
#include "core/graphics/vector_text.hpp"
#include "core/rendering/sdl_manager.hpp"
#include "core/types.hpp"
// Classe principal del joc (escena) // Classe principal del joc (escena)
class EscenaJoc { class EscenaJoc {

View File

@@ -9,11 +9,11 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include "../effects/debris_manager.hpp"
#include "core/defaults.hpp"
#include "core/graphics/shape.hpp" #include "core/graphics/shape.hpp"
#include "core/rendering/sdl_manager.hpp" #include "core/rendering/sdl_manager.hpp"
#include "core/types.hpp" #include "core/types.hpp"
#include "../effects/debris_manager.hpp"
#include "core/defaults.hpp"
class EscenaLogo { class EscenaLogo {
public: public:

View File

@@ -3,11 +3,14 @@
#include "escena_titol.hpp" #include "escena_titol.hpp"
#include <cfloat>
#include <iostream> #include <iostream>
#include <string> #include <string>
#include "core/audio/audio.hpp" #include "core/audio/audio.hpp"
#include "core/graphics/shape_loader.hpp"
#include "core/input/mouse.hpp" #include "core/input/mouse.hpp"
#include "core/rendering/shape_renderer.hpp"
#include "core/system/gestor_escenes.hpp" #include "core/system/gestor_escenes.hpp"
#include "core/system/global_events.hpp" #include "core/system/global_events.hpp"
#include "project.h" #include "project.h"
@@ -25,7 +28,8 @@ EscenaTitol::EscenaTitol(SDLManager& sdl)
Defaults::Game::HEIGHT / 2.0f}; Defaults::Game::HEIGHT / 2.0f};
SDL_FRect area_completa{ SDL_FRect area_completa{
0, 0, 0,
0,
static_cast<float>(Defaults::Game::WIDTH), static_cast<float>(Defaults::Game::WIDTH),
static_cast<float>(Defaults::Game::HEIGHT)}; static_cast<float>(Defaults::Game::HEIGHT)};
@@ -35,6 +39,146 @@ EscenaTitol::EscenaTitol(SDLManager& sdl)
area_completa, area_completa,
150 // densitat: 150 estrelles (50 per capa) 150 // densitat: 150 estrelles (50 per capa)
); );
// Inicialitzar lletres del títol "ORNI ATTACK!"
inicialitzar_titol();
}
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() { void EscenaTitol::executar() {
@@ -129,22 +273,51 @@ void EscenaTitol::dibuixar() {
return; return;
} }
// Estat MAIN: Dibuixar text de títol i copyright (sobre el starfield) // Estat MAIN: Dibuixar títol i text (sobre el starfield)
if (estat_actual_ == EstatTitol::MAIN) { if (estat_actual_ == EstatTitol::MAIN) {
// Text principal centrat (vertical i horitzontalment) // === 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" (a sota del títol) ===
const std::string main_text = "PRESS BUTTON TO PLAY"; const std::string main_text = "PRESS BUTTON TO PLAY";
const float escala_main = 1.0f; const float escala_main = 1.0f;
const float spacing = 2.0f; const float spacing = 2.0f;
float text_width = text_.get_text_width(main_text, escala_main, spacing); float text_width = text_.get_text_width(main_text, escala_main, spacing);
float text_height = text_.get_text_height(escala_main);
float x_center = (Defaults::Game::WIDTH - text_width) / 2.0f; float x_center = (Defaults::Game::WIDTH - text_width) / 2.0f;
float y_center = (Defaults::Game::HEIGHT - text_height) / 2.0f; // Usar posició dinàmica: ATTACK + altura lletres + separació
float altura_attack = lletres_attack_.empty() ? 50.0f : lletres_attack_[0].altura;
float y_center = y_attack_dinamica_ + altura_attack + 70.0f; // 70px sota "ATTACK!"
text_.render(main_text, Punt{x_center, y_center}, escala_main, spacing); text_.render(main_text, Punt{x_center, y_center}, escala_main, spacing);
// Copyright a la part inferior (centrat horitzontalment) // === Copyright a la part inferior (centrat horitzontalment) ===
// Convert to uppercase since VectorText only supports A-Z // Convert to uppercase since VectorText only supports A-Z
std::string copyright = Project::COPYRIGHT; std::string copyright = Project::COPYRIGHT;
for (char& c : copyright) { for (char& c : copyright) {
@@ -168,7 +341,6 @@ void EscenaTitol::processar_events(const SDL_Event& event) {
// Qualsevol tecla o clic de ratolí // Qualsevol tecla o clic de ratolí
if (event.type == SDL_EVENT_KEY_DOWN || if (event.type == SDL_EVENT_KEY_DOWN ||
event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) { event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
switch (estat_actual_) { switch (estat_actual_) {
case EstatTitol::INIT: case EstatTitol::INIT:
// Saltar a MAIN // Saltar a MAIN

View File

@@ -7,11 +7,14 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <memory> #include <memory>
#include <vector>
#include "core/defaults.hpp"
#include "core/graphics/shape.hpp"
#include "core/graphics/starfield.hpp" #include "core/graphics/starfield.hpp"
#include "core/graphics/vector_text.hpp" #include "core/graphics/vector_text.hpp"
#include "core/rendering/sdl_manager.hpp" #include "core/rendering/sdl_manager.hpp"
#include "core/defaults.hpp" #include "core/types.hpp"
class EscenaTitol { class EscenaTitol {
public: public:
@@ -25,17 +28,36 @@ class EscenaTitol {
MAIN // Pantalla de títol amb text MAIN // Pantalla de títol amb text
}; };
// Estructura per emmagatzemar informació de cada lletra del títol
struct LetraLogo {
std::shared_ptr<Graphics::Shape> forma; // Forma vectorial de la lletra
Punt posicio; // Posició en pantalla
float ancho; // Amplada escalada
float altura; // Altura escalada
float offset_centre; // Offset del centre per posicionament
};
SDLManager& sdl_; SDLManager& sdl_;
Graphics::VectorText text_; // Sistema de text vectorial Graphics::VectorText text_; // Sistema de text vectorial
std::unique_ptr<Graphics::Starfield> starfield_; // Camp d'estrelles de fons std::unique_ptr<Graphics::Starfield> starfield_; // Camp d'estrelles de fons
EstatTitol estat_actual_; // Estat actual de la màquina EstatTitol estat_actual_; // Estat actual de la màquina
float temps_acumulat_; // Temps acumulat per l'estat INIT float temps_acumulat_; // Temps acumulat per l'estat INIT
// Lletres del títol "ORNI ATTACK!"
std::vector<LetraLogo> lletres_orni_; // Lletres de "ORNI" (línia 1)
std::vector<LetraLogo> lletres_attack_; // Lletres de "ATTACK!" (línia 2)
float y_attack_dinamica_; // Posició Y calculada dinàmicament per "ATTACK!"
// Constants // Constants
static constexpr float DURACIO_INIT = 2.0f; // Duració de l'estat INIT (2 segons) static constexpr float DURACIO_INIT = 2.0f; // Duració de l'estat INIT (2 segons)
static constexpr float ESCALA_TITULO = 0.6f; // Escala per les lletres del títol (50%)
static constexpr float ESPAI_ENTRE_LLETRES = 10.0f; // Espai entre lletres
static constexpr float Y_ORNI = 150.0f; // Posició Y de "ORNI"
static constexpr float SEPARACION_LINEAS = 10.0f; // Separació entre "ORNI" i "ATTACK!" (0.0f = pegades)
// Mètodes privats // Mètodes privats
void actualitzar(float delta_time); void actualitzar(float delta_time);
void dibuixar(); void dibuixar();
void processar_events(const SDL_Event& event); void processar_events(const SDL_Event& event);
void inicialitzar_titol(); // Carrega i posiciona les lletres del títol
}; };

View File

@@ -44,8 +44,9 @@ def apply_transform(punto, matrix):
def parse_svg_path(d_attr): def parse_svg_path(d_attr):
""" """
Convierte comandos SVG path (M y L) a lista de puntos [(x, y), ...] Convierte comandos SVG path (M y L) a lista de polylines separadas.
Ejemplo: "M896,1693L896,1531.23L1219.53,1531.23..." → [(896, 1693), (896, 1531.23), ...] Cada comando M inicia una nueva polyline.
Retorna: lista de listas de puntos [[(x,y), ...], [(x,y), ...], ...]
""" """
# Reemplazar comas por espacios para facilitar parsing # Reemplazar comas por espacios para facilitar parsing
d_attr = d_attr.replace(',', ' ') d_attr = d_attr.replace(',', ' ')
@@ -55,7 +56,9 @@ def parse_svg_path(d_attr):
d_attr = re.sub(r'([ML])', r'|\1', d_attr) d_attr = re.sub(r'([ML])', r'|\1', d_attr)
commands = [c.strip() for c in d_attr.split('|') if c.strip()] commands = [c.strip() for c in d_attr.split('|') if c.strip()]
points = [] polylines = [] # Lista de polylines
current_polyline = [] # Polyline actual
for cmd in commands: for cmd in commands:
if not cmd: if not cmd:
continue continue
@@ -70,18 +73,29 @@ def parse_svg_path(d_attr):
# Parsear pares de coordenadas # Parsear pares de coordenadas
coords = coords_str.split() coords = coords_str.split()
# Si es comando M (MoveTo), empezar nueva polyline
if cmd_letter == 'M':
# Guardar polyline anterior si tiene puntos
if current_polyline:
polylines.append(current_polyline)
current_polyline = []
# Procesar en pares (x, y) # Procesar en pares (x, y)
i = 0 i = 0
while i < len(coords) - 1: while i < len(coords) - 1:
try: try:
x = float(coords[i]) x = float(coords[i])
y = float(coords[i + 1]) y = float(coords[i + 1])
points.append((x, y)) current_polyline.append((x, y))
i += 2 i += 2
except (ValueError, IndexError): except (ValueError, IndexError):
i += 1 i += 1
return points # No olvidar la última polyline
if current_polyline:
polylines.append(current_polyline)
return polylines
def rect_to_points(rect_elem): def rect_to_points(rect_elem):
@@ -103,11 +117,22 @@ def rect_to_points(rect_elem):
] ]
def calc_bounding_box(puntos): def calc_bounding_box(polylines):
""" """
Calcula bounding box de una lista de puntos Calcula bounding box de una o varias polylines
polylines puede ser:
- lista de puntos [(x,y), ...]
- lista de polylines [[(x,y), ...], [(x,y), ...]]
Retorna: (min_x, max_x, min_y, max_y, ancho, alto) Retorna: (min_x, max_x, min_y, max_y, ancho, alto)
""" """
# Aplanar si es lista de polylines
puntos = []
if polylines and isinstance(polylines[0], list):
for polyline in polylines:
puntos.extend(polyline)
else:
puntos = polylines
if not puntos: if not puntos:
return (0, 0, 0, 0, 0, 0) return (0, 0, 0, 0, 0, 0)
@@ -125,17 +150,18 @@ def calc_bounding_box(puntos):
return (min_x, max_x, min_y, max_y, ancho, alto) return (min_x, max_x, min_y, max_y, ancho, alto)
def normalizar_letra(nombre, puntos, altura_objetivo=100.0): def normalizar_letra(nombre, polylines, altura_objetivo=100.0):
""" """
Escala y traslada letra para que tenga altura_objetivo pixels Escala y traslada letra para que tenga altura_objetivo pixels
y esté centrada en origen (0, 0) en esquina superior izquierda y esté centrada en origen (0, 0) en esquina superior izquierda
Retorna: dict con puntos normalizados, centro, ancho, alto polylines: lista de polylines [[(x,y), ...], [(x,y), ...]]
Retorna: dict con polylines normalizadas, centro, ancho, alto
""" """
if not puntos: if not polylines:
return None return None
min_x, max_x, min_y, max_y, ancho, alto = calc_bounding_box(puntos) min_x, max_x, min_y, max_y, ancho, alto = calc_bounding_box(polylines)
if alto == 0: if alto == 0:
print(f" [WARN] Letra {nombre}: altura cero") print(f" [WARN] Letra {nombre}: altura cero")
@@ -144,14 +170,26 @@ def normalizar_letra(nombre, puntos, altura_objetivo=100.0):
# Factor de escala basado en altura # Factor de escala basado en altura
escala = altura_objetivo / alto escala = altura_objetivo / alto
# Normalizar puntos: # Normalizar cada polyline:
# 1. Trasladar a origen (restar min_x, min_y) # 1. Trasladar a origen (restar min_x, min_y)
# 2. Aplicar escala # 2. Aplicar escala
puntos_norm = [] # 3. Cerrar polyline (último punto = primer punto)
for x, y in puntos: polylines_norm = []
total_puntos = 0
for polyline in polylines:
polyline_norm = []
for x, y in polyline:
x_norm = (x - min_x) * escala x_norm = (x - min_x) * escala
y_norm = (y - min_y) * escala y_norm = (y - min_y) * escala
puntos_norm.append((x_norm, y_norm)) polyline_norm.append((x_norm, y_norm))
# Cerrar polyline si no está cerrada
if polyline_norm and polyline_norm[0] != polyline_norm[-1]:
polyline_norm.append(polyline_norm[0])
polylines_norm.append(polyline_norm)
total_puntos += len(polyline_norm)
# Calcular dimensiones finales # Calcular dimensiones finales
ancho_norm = ancho * escala ancho_norm = ancho * escala
@@ -162,10 +200,11 @@ def normalizar_letra(nombre, puntos, altura_objetivo=100.0):
return { return {
'nombre': nombre, 'nombre': nombre,
'puntos': puntos_norm, 'polylines': polylines_norm,
'centro': centro, 'centro': centro,
'ancho': ancho_norm, 'ancho': ancho_norm,
'alto': alto_norm 'alto': alto_norm,
'total_puntos': total_puntos
} }
@@ -178,6 +217,7 @@ def generar_shp(letra_norm, output_dir):
scale: 1.0 scale: 1.0
center: cx, cy center: cx, cy
polyline: x1,y1 x2,y2 x3,y3 ... polyline: x1,y1 x2,y2 x3,y3 ...
polyline: x1,y1 x2,y2 x3,y3 ... (si hay múltiples formas)
""" """
if not letra_norm: if not letra_norm:
return return
@@ -198,13 +238,13 @@ def generar_shp(letra_norm, output_dir):
f.write(f"center: {letra_norm['centro'][0]:.2f}, {letra_norm['centro'][1]:.2f}\n") f.write(f"center: {letra_norm['centro'][0]:.2f}, {letra_norm['centro'][1]:.2f}\n")
f.write(f"\n") f.write(f"\n")
# Polyline con todos los puntos # Generar una línea polyline por cada forma
f.write("polyline: ") for polyline in letra_norm['polylines']:
puntos_str = " ".join([f"{x:.2f},{y:.2f}" for x, y in letra_norm['puntos']]) puntos_str = " ".join([f"{x:.2f},{y:.2f}" for x, y in polyline])
f.write(puntos_str) f.write(f"polyline: {puntos_str}\n")
f.write("\n")
print(f"{nombre_archivo:20} ({len(letra_norm['puntos']):3} puntos, " print(f"{nombre_archivo:20} ({len(letra_norm['polylines'])} formas, "
f"{letra_norm['total_puntos']:3} puntos, "
f"{letra_norm['ancho']:6.2f} x {letra_norm['alto']:6.2f} px)") f"{letra_norm['ancho']:6.2f} x {letra_norm['alto']:6.2f} px)")
@@ -232,14 +272,18 @@ def parse_svg(filepath):
print(f"[INFO] Transform matrix: {transform_matrix}") print(f"[INFO] Transform matrix: {transform_matrix}")
# Extraer paths y rects # Extraer paths y rects (buscar recursivamente en todos los descendientes)
paths = group.findall('svg:path', ns) paths = group.findall('.//svg:path', ns)
rects = group.findall('svg:rect', ns) rects = group.findall('.//svg:rect', ns)
print(f"[INFO] Encontrados {len(paths)} paths y {len(rects)} rects") print(f"[INFO] Encontrados {len(paths)} paths y {len(rects)} rects")
# Nombres de las letras para paths (sin I que es un rect) # Nombres de las letras para "ORNI ATTACK!"
nombres_paths = ['J', 'A', 'L', 'G', 'A', 'M', 'E', 'S'] # Grupo 1 (top): O, R, N, I (3 paths + 1 rect)
# Grupo 2 (bottom): A, T, T, A, C, K, ! (6 paths + 1 path para !)
# Total: 9 paths + 1 rect
# Asumiendo orden de aparición en SVG:
nombres_paths = ['O', 'R', 'N', 'A', 'T', 'T', 'A', 'C', 'K', 'EXCLAMACION']
letras = [] letras = []
@@ -252,15 +296,18 @@ def parse_svg(filepath):
if not d_attr: if not d_attr:
continue continue
# Parsear puntos del path # Parsear polylines del path (ahora retorna lista de polylines)
puntos = parse_svg_path(d_attr) polylines = parse_svg_path(d_attr)
# Aplicar transformación # Aplicar transformación a cada polyline
puntos = [apply_transform(p, transform_matrix) for p in puntos] polylines_transformed = []
for polyline in polylines:
polyline_transformed = [apply_transform(p, transform_matrix) for p in polyline]
polylines_transformed.append(polyline_transformed)
letras.append({ letras.append({
'nombre': nombres_paths[i], 'nombre': nombres_paths[i],
'puntos': puntos 'polylines': polylines_transformed
}) })
# Procesar rects (la letra I es un rect) # Procesar rects (la letra I es un rect)
@@ -268,11 +315,12 @@ def parse_svg(filepath):
puntos = rect_to_points(rect) puntos = rect_to_points(rect)
# Aplicar transformación # Aplicar transformación
puntos = [apply_transform(p, transform_matrix) for p in puntos] puntos_transformed = [apply_transform(p, transform_matrix) for p in puntos]
# Rect es una sola polyline
letras.append({ letras.append({
'nombre': 'I', 'nombre': 'I',
'puntos': puntos 'polylines': [puntos_transformed]
}) })
return letras return letras
@@ -326,7 +374,7 @@ def main():
for nombre in sorted(letras_unicas.keys()): for nombre in sorted(letras_unicas.keys()):
letra = letras_unicas[nombre] letra = letras_unicas[nombre]
letra_norm = normalizar_letra(nombre, letra['puntos'], altura_objetivo=100.0) letra_norm = normalizar_letra(nombre, letra['polylines'], altura_objetivo=100.0)
if letra_norm: if letra_norm:
generar_shp(letra_norm, output_dir) generar_shp(letra_norm, output_dir)