Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a3aeed4b7c | |||
| 3b0354da54 | |||
| 622ccd22bc | |||
| 1441134aea |
@@ -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")
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -318,7 +318,7 @@ endif
|
|||||||
|
|
||||||
# Backup to remote server
|
# Backup to remote server
|
||||||
backup:
|
backup:
|
||||||
@echo "Backing up project to maverick:/home/sergio/git-backup/asteroids..."
|
@echo "Backing up project to maverick:/home/sergio/git-backup/orni..."
|
||||||
rsync -a --delete \
|
rsync -a --delete \
|
||||||
--exclude='build/' \
|
--exclude='build/' \
|
||||||
--exclude='*.o' \
|
--exclude='*.o' \
|
||||||
@@ -326,7 +326,7 @@ backup:
|
|||||||
--exclude='orni' \
|
--exclude='orni' \
|
||||||
--exclude='orni_debug' \
|
--exclude='orni_debug' \
|
||||||
--exclude='*_release/' \
|
--exclude='*_release/' \
|
||||||
$(DIR_ROOT) maverick:/home/sergio/git-backup/asteroids/
|
$(DIR_ROOT) maverick:/home/sergio/git-backup/orni/
|
||||||
@echo "Backup completed successfully"
|
@echo "Backup completed successfully"
|
||||||
|
|
||||||
# Help target
|
# Help target
|
||||||
|
|||||||
BIN
data/music/game.ogg
Normal file
BIN
data/music/game.ogg
Normal file
Binary file not shown.
BIN
data/music/title.ogg
Normal file
BIN
data/music/title.ogg
Normal file
Binary file not shown.
30
data/shapes/enemy_pinwheel.shp
Normal file
30
data/shapes/enemy_pinwheel.shp
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# enemy_pinwheel.shp - ORNI enemic (molinillo de 4 triangles)
|
||||||
|
# © 2025 Port a C++20 amb SDL3
|
||||||
|
|
||||||
|
name: enemy_pinwheel
|
||||||
|
scale: 1.0
|
||||||
|
center: 0, 0
|
||||||
|
|
||||||
|
# Molinillo: 4 triangles, un en cada quadrant
|
||||||
|
# Cada triangle comparteix el centre (0,0) i té:
|
||||||
|
# - Un vèrtex en un eix (±20, 0) o (0, ±20)
|
||||||
|
# - Un vèrtex en la diagonal del quadrant (±14.14, ±14.14)
|
||||||
|
# - El tercer vèrtex al centre (0,0)
|
||||||
|
#
|
||||||
|
# Geometria:
|
||||||
|
# Triangle 1 (quadrant superior-dret): centre → eix dret → diagonal
|
||||||
|
# Triangle 2 (quadrant superior-esq): centre → eix superior → diagonal
|
||||||
|
# Triangle 3 (quadrant inferior-esq): centre → eix esquerre → diagonal
|
||||||
|
# Triangle 4 (quadrant inferior-dret): centre → eix inferior → diagonal
|
||||||
|
|
||||||
|
# Triangle 1: quadrant superior-dret
|
||||||
|
polyline: 0,0 20,0 14.14,-14.14 0,0
|
||||||
|
|
||||||
|
# Triangle 2: quadrant superior-esquerre
|
||||||
|
polyline: 0,0 0,-20 -14.14,-14.14 0,0
|
||||||
|
|
||||||
|
# Triangle 3: quadrant inferior-esquerre
|
||||||
|
polyline: 0,0 -20,0 -14.14,14.14 0,0
|
||||||
|
|
||||||
|
# Triangle 4: quadrant inferior-dret
|
||||||
|
polyline: 0,0 0,20 14.14,14.14 0,0
|
||||||
19
data/shapes/enemy_square.shp
Normal file
19
data/shapes/enemy_square.shp
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# enemy_square.shp - ORNI enemic (quadrat regular)
|
||||||
|
# © 2025 Port a C++20 amb SDL3
|
||||||
|
|
||||||
|
name: enemy_square
|
||||||
|
scale: 1.0
|
||||||
|
center: 0, 0
|
||||||
|
|
||||||
|
# Quadrat regular radi=20 (circumscrit)
|
||||||
|
# 4 punts equidistants al voltant d'un cercle (90° entre ells)
|
||||||
|
# Començant a angle=-90° (amunt), rotant sentit horari
|
||||||
|
#
|
||||||
|
# Angles: -90°, 0°, 90°, 180°
|
||||||
|
# Conversió polar→cartesià (SDL: Y creix cap avall):
|
||||||
|
# angle=-90°: (0.00, -20.00)
|
||||||
|
# angle=0°: (20.00, 0.00)
|
||||||
|
# angle=90°: (0.00, 20.00)
|
||||||
|
# angle=180°: (-20.00, 0.00)
|
||||||
|
|
||||||
|
polyline: 0,-20 20,0 0,20 -20,0 0,-20
|
||||||
10
data/shapes/title/letra_a.shp
Normal file
10
data/shapes/title/letra_a.shp
Normal 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
|
||||||
9
data/shapes/title/letra_c.shp
Normal file
9
data/shapes/title/letra_c.shp
Normal 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
|
||||||
10
data/shapes/title/letra_exclamacion.shp
Normal file
10
data/shapes/title/letra_exclamacion.shp
Normal 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
|
||||||
9
data/shapes/title/letra_i.shp
Normal file
9
data/shapes/title/letra_i.shp
Normal 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
|
||||||
9
data/shapes/title/letra_k.shp
Normal file
9
data/shapes/title/letra_k.shp
Normal 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
|
||||||
9
data/shapes/title/letra_n.shp
Normal file
9
data/shapes/title/letra_n.shp
Normal 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
|
||||||
10
data/shapes/title/letra_o.shp
Normal file
10
data/shapes/title/letra_o.shp
Normal 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
|
||||||
10
data/shapes/title/letra_r.shp
Normal file
10
data/shapes/title/letra_r.shp
Normal 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
|
||||||
9
data/shapes/title/letra_t.shp
Normal file
9
data/shapes/title/letra_t.shp
Normal 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
|
||||||
@@ -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 |
BIN
sample.png
BIN
sample.png
Binary file not shown.
|
Before Width: | Height: | Size: 36 KiB |
@@ -73,6 +73,14 @@ constexpr float ENEMY_RADIUS = 20.0f;
|
|||||||
constexpr float BULLET_RADIUS = 5.0f;
|
constexpr float BULLET_RADIUS = 5.0f;
|
||||||
} // namespace Entities
|
} // namespace Entities
|
||||||
|
|
||||||
|
// Game rules (lives, respawn, game over)
|
||||||
|
namespace Game {
|
||||||
|
constexpr int STARTING_LIVES = 3; // Initial lives
|
||||||
|
constexpr float DEATH_DURATION = 3.0f; // Seconds of death animation
|
||||||
|
constexpr float GAME_OVER_DURATION = 5.0f; // Seconds to display game over
|
||||||
|
constexpr float COLLISION_SHIP_ENEMY_AMPLIFIER = 0.80f; // 80% hitbox (generous)
|
||||||
|
} // namespace Game
|
||||||
|
|
||||||
// Física (valores actuales del juego, sincronizados con joc_asteroides.cpp)
|
// Física (valores actuales del juego, sincronizados con joc_asteroides.cpp)
|
||||||
namespace Physics {
|
namespace Physics {
|
||||||
constexpr float ROTATION_SPEED = 3.14f; // rad/s (~180°/s)
|
constexpr float ROTATION_SPEED = 3.14f; // rad/s (~180°/s)
|
||||||
@@ -90,7 +98,8 @@ constexpr float VARIACIO_VELOCITAT = 40.0f; // ±variació aleatòria (px/s)
|
|||||||
constexpr float ACCELERACIO = -60.0f; // Fricció/desacceleració (px/s²)
|
constexpr float ACCELERACIO = -60.0f; // Fricció/desacceleració (px/s²)
|
||||||
constexpr float ROTACIO_MIN = 0.1f; // Rotació mínima (rad/s ~5.7°/s)
|
constexpr float ROTACIO_MIN = 0.1f; // Rotació mínima (rad/s ~5.7°/s)
|
||||||
constexpr float ROTACIO_MAX = 0.3f; // Rotació màxima (rad/s ~17.2°/s)
|
constexpr float ROTACIO_MAX = 0.3f; // Rotació màxima (rad/s ~17.2°/s)
|
||||||
constexpr float TEMPS_VIDA = 2.0f; // Duració màxima (segons)
|
constexpr float TEMPS_VIDA = 2.0f; // Duració màxima (segons) - enemy/bullet debris
|
||||||
|
constexpr float TEMPS_VIDA_NAU = 3.0f; // Ship debris lifetime (matches DEATH_DURATION)
|
||||||
constexpr float SHRINK_RATE = 0.5f; // Reducció de mida (factor/s)
|
constexpr float SHRINK_RATE = 0.5f; // Reducció de mida (factor/s)
|
||||||
} // namespace Debris
|
} // namespace Debris
|
||||||
} // namespace Physics
|
} // namespace Physics
|
||||||
@@ -164,4 +173,58 @@ constexpr bool ENABLED = true; // Sonidos habilitados
|
|||||||
constexpr const char* EXPLOSION = "explosion.wav"; // Explosión
|
constexpr const char* EXPLOSION = "explosion.wav"; // Explosión
|
||||||
constexpr const char* LASER = "laser_shoot.wav"; // Disparo
|
constexpr const char* LASER = "laser_shoot.wav"; // Disparo
|
||||||
} // namespace Sound
|
} // namespace Sound
|
||||||
|
|
||||||
|
// Enemy type configuration (tipus d'enemics)
|
||||||
|
namespace Enemies {
|
||||||
|
// Pentagon (esquivador - zigzag evasion)
|
||||||
|
namespace Pentagon {
|
||||||
|
constexpr float VELOCITAT = 35.0f; // px/s (slightly slower)
|
||||||
|
constexpr float CANVI_ANGLE_PROB = 0.20f; // 20% per wall hit (frequent zigzag)
|
||||||
|
constexpr float CANVI_ANGLE_MAX = 1.0f; // Max random angle change (rad)
|
||||||
|
constexpr float DROTACIO_MIN = 0.5f; // Min visual rotation (rad/s)
|
||||||
|
constexpr float DROTACIO_MAX = 2.5f; // Max visual rotation (rad/s)
|
||||||
|
constexpr const char* SHAPE_FILE = "enemy_pentagon.shp";
|
||||||
|
} // namespace Pentagon
|
||||||
|
|
||||||
|
// Quadrat (perseguidor - tracks player)
|
||||||
|
namespace Quadrat {
|
||||||
|
constexpr float VELOCITAT = 40.0f; // px/s (medium speed)
|
||||||
|
constexpr float TRACKING_STRENGTH = 0.5f; // Interpolation toward player (0.0-1.0)
|
||||||
|
constexpr float TRACKING_INTERVAL = 1.0f; // Seconds between angle updates
|
||||||
|
constexpr float DROTACIO_MIN = 0.2f; // Slow rotation
|
||||||
|
constexpr float DROTACIO_MAX = 1.0f;
|
||||||
|
constexpr const char* SHAPE_FILE = "enemy_square.shp";
|
||||||
|
} // namespace Quadrat
|
||||||
|
|
||||||
|
// Molinillo (agressiu - fast straight lines, proximity spin-up)
|
||||||
|
namespace Molinillo {
|
||||||
|
constexpr float VELOCITAT = 50.0f; // px/s (fastest)
|
||||||
|
constexpr float CANVI_ANGLE_PROB = 0.05f; // 5% per wall hit (rare direction change)
|
||||||
|
constexpr float CANVI_ANGLE_MAX = 0.3f; // Small angle adjustments
|
||||||
|
constexpr float DROTACIO_MIN = 2.0f; // Base rotation (rad/s)
|
||||||
|
constexpr float DROTACIO_MAX = 4.0f;
|
||||||
|
constexpr float DROTACIO_PROXIMITY_MULTIPLIER = 3.0f; // Spin-up multiplier when near ship
|
||||||
|
constexpr float PROXIMITY_DISTANCE = 100.0f; // Distance threshold (px)
|
||||||
|
constexpr const char* SHAPE_FILE = "enemy_pinwheel.shp";
|
||||||
|
} // namespace Molinillo
|
||||||
|
|
||||||
|
// Animation parameters (shared)
|
||||||
|
namespace Animation {
|
||||||
|
// Palpitation
|
||||||
|
constexpr float PALPITACIO_TRIGGER_PROB = 0.01f; // 1% chance per second
|
||||||
|
constexpr float PALPITACIO_DURACIO_MIN = 1.0f; // Min duration (seconds)
|
||||||
|
constexpr float PALPITACIO_DURACIO_MAX = 3.0f; // Max duration (seconds)
|
||||||
|
constexpr float PALPITACIO_AMPLITUD_MIN = 0.08f; // Min scale variation
|
||||||
|
constexpr float PALPITACIO_AMPLITUD_MAX = 0.20f; // Max scale variation
|
||||||
|
constexpr float PALPITACIO_FREQ_MIN = 1.5f; // Min frequency (Hz)
|
||||||
|
constexpr float PALPITACIO_FREQ_MAX = 3.0f; // Max frequency (Hz)
|
||||||
|
|
||||||
|
// Rotation acceleration
|
||||||
|
constexpr float ROTACIO_ACCEL_TRIGGER_PROB = 0.005f; // 0.5% chance per second
|
||||||
|
constexpr float ROTACIO_ACCEL_DURACIO_MIN = 3.0f; // Min transition time
|
||||||
|
constexpr float ROTACIO_ACCEL_DURACIO_MAX = 8.0f; // Max transition time
|
||||||
|
constexpr float ROTACIO_ACCEL_MULTIPLIER_MIN = 0.5f; // Min speed multiplier
|
||||||
|
constexpr float ROTACIO_ACCEL_MULTIPLIER_MAX = 2.5f; // Max speed multiplier
|
||||||
|
} // namespace Animation
|
||||||
|
} // namespace Enemies
|
||||||
} // namespace Defaults
|
} // namespace Defaults
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|
||||||
@@ -103,9 +103,12 @@ float Starfield::calcular_escala(const Estrella& estrella) const {
|
|||||||
float Starfield::calcular_brightness(const Estrella& estrella) const {
|
float Starfield::calcular_brightness(const Estrella& estrella) const {
|
||||||
// Interpolació lineal: estrelles properes (vora) més brillants
|
// Interpolació lineal: estrelles properes (vora) més brillants
|
||||||
// distancia_centre: 0.0 (centre, llunyanes) → 1.0 (vora, properes)
|
// distancia_centre: 0.0 (centre, llunyanes) → 1.0 (vora, properes)
|
||||||
return Defaults::Brightness::STARFIELD_MIN +
|
float brightness_base = Defaults::Brightness::STARFIELD_MIN +
|
||||||
(Defaults::Brightness::STARFIELD_MAX - Defaults::Brightness::STARFIELD_MIN) *
|
(Defaults::Brightness::STARFIELD_MAX - Defaults::Brightness::STARFIELD_MIN) *
|
||||||
estrella.distancia_centre;
|
estrella.distancia_centre;
|
||||||
|
|
||||||
|
// Aplicar multiplicador i limitar a 1.0
|
||||||
|
return std::min(1.0f, brightness_base * multiplicador_brightness_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualitzar posicions de les estrelles
|
// Actualitzar posicions de les estrelles
|
||||||
@@ -135,6 +138,11 @@ void Starfield::actualitzar(float delta_time) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Establir multiplicador de brightness
|
||||||
|
void Starfield::set_brightness(float multiplier) {
|
||||||
|
multiplicador_brightness_ = std::max(0.0f, multiplier); // Evitar valors negatius
|
||||||
|
}
|
||||||
|
|
||||||
// Dibuixar totes les estrelles
|
// Dibuixar totes les estrelles
|
||||||
void Starfield::dibuixar() {
|
void Starfield::dibuixar() {
|
||||||
if (!shape_estrella_->es_valida()) {
|
if (!shape_estrella_->es_valida()) {
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class Starfield {
|
|||||||
|
|
||||||
// Setters per ajustar paràmetres en temps real
|
// Setters per ajustar paràmetres en temps real
|
||||||
void set_punt_fuga(const Punt& punt) { punt_fuga_ = punt; }
|
void set_punt_fuga(const Punt& punt) { punt_fuga_ = punt; }
|
||||||
|
void set_brightness(float multiplier);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Estructura interna per cada estrella
|
// Estructura interna per cada estrella
|
||||||
@@ -75,6 +76,7 @@ class Starfield {
|
|||||||
SDL_FRect area_; // Àrea activa
|
SDL_FRect area_; // Àrea activa
|
||||||
float radi_max_; // Distància màxima del centre al límit de pantalla
|
float radi_max_; // Distància màxima del centre al límit de pantalla
|
||||||
int densitat_; // Nombre total d'estrelles
|
int densitat_; // Nombre total d'estrelles
|
||||||
|
float multiplicador_brightness_{1.0f}; // Multiplicador de brillantor (1.0 = default)
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Graphics
|
} // namespace Graphics
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -7,13 +7,14 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "core/audio/audio.hpp"
|
||||||
|
#include "core/audio/audio_cache.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"
|
||||||
|
|
||||||
@@ -161,6 +162,13 @@ auto Director::run() -> int {
|
|||||||
// Inicialitzar sistema d'audio
|
// Inicialitzar sistema d'audio
|
||||||
Audio::init();
|
Audio::init();
|
||||||
|
|
||||||
|
// Precachejar música per evitar lag al començar
|
||||||
|
AudioCache::getMusic("title.ogg");
|
||||||
|
if (Options::console) {
|
||||||
|
std::cout << "Música precachejada: "
|
||||||
|
<< AudioCache::getMusicCacheSize() << " fitxers\n";
|
||||||
|
}
|
||||||
|
|
||||||
// Bucle principal de gestió d'escenes
|
// Bucle principal de gestió d'escenes
|
||||||
while (GestorEscenes::actual != GestorEscenes::Escena::EIXIR) {
|
while (GestorEscenes::actual != GestorEscenes::Escena::EIXIR) {
|
||||||
switch (GestorEscenes::actual) {
|
switch (GestorEscenes::actual) {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|
||||||
|
|||||||
@@ -21,25 +21,53 @@ Enemic::Enemic(SDL_Renderer* renderer)
|
|||||||
drotacio_(0.0f),
|
drotacio_(0.0f),
|
||||||
rotacio_(0.0f),
|
rotacio_(0.0f),
|
||||||
esta_(false),
|
esta_(false),
|
||||||
brightness_(Defaults::Brightness::ENEMIC) {
|
brightness_(Defaults::Brightness::ENEMIC),
|
||||||
// [NUEVO] Carregar forma compartida des de fitxer
|
tipus_(TipusEnemic::PENTAGON),
|
||||||
forma_ = Graphics::ShapeLoader::load("enemy_pentagon.shp");
|
tracking_timer_(0.0f),
|
||||||
|
ship_position_(nullptr) {
|
||||||
if (!forma_ || !forma_->es_valida()) {
|
// [NUEVO] Forma es carrega a inicialitzar() segons el tipus
|
||||||
std::cerr << "[Enemic] Error: no s'ha pogut carregar enemy_pentagon.shp"
|
// Constructor no carrega forma per permetre tipus diferents
|
||||||
<< std::endl;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Enemic::inicialitzar() {
|
void Enemic::inicialitzar(TipusEnemic tipus) {
|
||||||
// Inicialitzar enemic (pentàgon)
|
// Guardar tipus
|
||||||
// Copiat de joc_asteroides.cpp línies 41-54
|
tipus_ = tipus;
|
||||||
|
|
||||||
// [NUEVO] Ja no cal crear_poligon_regular - la geometria es carrega del
|
// Carregar forma segons el tipus
|
||||||
// fitxer Només inicialitzem l'estat de la instància
|
const char* shape_file;
|
||||||
|
float drotacio_min, drotacio_max;
|
||||||
|
|
||||||
|
switch (tipus_) {
|
||||||
|
case TipusEnemic::PENTAGON:
|
||||||
|
shape_file = Defaults::Enemies::Pentagon::SHAPE_FILE;
|
||||||
|
velocitat_ = Defaults::Enemies::Pentagon::VELOCITAT;
|
||||||
|
drotacio_min = Defaults::Enemies::Pentagon::DROTACIO_MIN;
|
||||||
|
drotacio_max = Defaults::Enemies::Pentagon::DROTACIO_MAX;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TipusEnemic::QUADRAT:
|
||||||
|
shape_file = Defaults::Enemies::Quadrat::SHAPE_FILE;
|
||||||
|
velocitat_ = Defaults::Enemies::Quadrat::VELOCITAT;
|
||||||
|
drotacio_min = Defaults::Enemies::Quadrat::DROTACIO_MIN;
|
||||||
|
drotacio_max = Defaults::Enemies::Quadrat::DROTACIO_MAX;
|
||||||
|
tracking_timer_ = 0.0f;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TipusEnemic::MOLINILLO:
|
||||||
|
shape_file = Defaults::Enemies::Molinillo::SHAPE_FILE;
|
||||||
|
velocitat_ = Defaults::Enemies::Molinillo::VELOCITAT;
|
||||||
|
drotacio_min = Defaults::Enemies::Molinillo::DROTACIO_MIN;
|
||||||
|
drotacio_max = Defaults::Enemies::Molinillo::DROTACIO_MAX;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carregar forma
|
||||||
|
forma_ = Graphics::ShapeLoader::load(shape_file);
|
||||||
|
if (!forma_ || !forma_->es_valida()) {
|
||||||
|
std::cerr << "[Enemic] Error: no s'ha pogut carregar " << shape_file << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
// Posició aleatòria dins de l'àrea de joc
|
// Posició aleatòria dins de l'àrea de joc
|
||||||
// Calcular rangs segurs amb radi de l'enemic
|
|
||||||
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,
|
min_x,
|
||||||
@@ -47,7 +75,6 @@ void Enemic::inicialitzar() {
|
|||||||
min_y,
|
min_y,
|
||||||
max_y);
|
max_y);
|
||||||
|
|
||||||
// Spawn aleatori dins dels límits segurs
|
|
||||||
int range_x = static_cast<int>(max_x - min_x);
|
int range_x = static_cast<int>(max_x - min_x);
|
||||||
int range_y = static_cast<int>(max_y - min_y);
|
int range_y = static_cast<int>(max_y - min_y);
|
||||||
centre_.x = static_cast<float>((std::rand() % range_x) + static_cast<int>(min_x));
|
centre_.x = static_cast<float>((std::rand() % range_x) + static_cast<int>(min_x));
|
||||||
@@ -56,14 +83,17 @@ void Enemic::inicialitzar() {
|
|||||||
// Angle aleatori de moviment
|
// Angle aleatori de moviment
|
||||||
angle_ = (std::rand() % 360) * Constants::PI / 180.0f;
|
angle_ = (std::rand() % 360) * Constants::PI / 180.0f;
|
||||||
|
|
||||||
// Velocitat (2 px/frame original * 20 FPS = 40 px/s)
|
// Rotació visual aleatòria (rad/s) dins del rang del tipus
|
||||||
velocitat_ = 40.0f;
|
float drotacio_range = drotacio_max - drotacio_min;
|
||||||
|
drotacio_ = drotacio_min + (static_cast<float>(std::rand()) / RAND_MAX) * drotacio_range;
|
||||||
// Rotació visual aleatòria (rad/s)
|
|
||||||
// Original Pascal: random * 0.1 rad/frame * 20 FPS ≈ 2 rad/s
|
|
||||||
drotacio_ = (static_cast<float>(std::rand()) / RAND_MAX) * 2.0f;
|
|
||||||
rotacio_ = 0.0f;
|
rotacio_ = 0.0f;
|
||||||
|
|
||||||
|
// Inicialitzar estat d'animació
|
||||||
|
animacio_ = AnimacioEnemic(); // Reset to defaults
|
||||||
|
animacio_.drotacio_base = drotacio_;
|
||||||
|
animacio_.drotacio_objetivo = drotacio_;
|
||||||
|
animacio_.drotacio_t = 1.0f; // Start without interpolating
|
||||||
|
|
||||||
// Activar
|
// Activar
|
||||||
esta_ = true;
|
esta_ = true;
|
||||||
}
|
}
|
||||||
@@ -73,6 +103,9 @@ void Enemic::actualitzar(float delta_time) {
|
|||||||
// Moviment autònom
|
// Moviment autònom
|
||||||
mou(delta_time);
|
mou(delta_time);
|
||||||
|
|
||||||
|
// Actualitzar animacions (palpitació, rotació accelerada)
|
||||||
|
actualitzar_animacio(delta_time);
|
||||||
|
|
||||||
// Rotació visual (time-based: drotacio_ està en rad/s)
|
// Rotació visual (time-based: drotacio_ està en rad/s)
|
||||||
rotacio_ += drotacio_ * delta_time;
|
rotacio_ += drotacio_ * delta_time;
|
||||||
}
|
}
|
||||||
@@ -80,21 +113,31 @@ void Enemic::actualitzar(float delta_time) {
|
|||||||
|
|
||||||
void Enemic::dibuixar() const {
|
void Enemic::dibuixar() const {
|
||||||
if (esta_ && forma_) {
|
if (esta_ && forma_) {
|
||||||
// [NUEVO] Usar render_shape en lloc de rota_pol
|
// [NUEVO] Usar render_shape amb escala animada
|
||||||
Rendering::render_shape(renderer_, forma_, centre_, rotacio_, 1.0f, true, 1.0f, brightness_);
|
float escala = calcular_escala_actual();
|
||||||
|
Rendering::render_shape(renderer_, forma_, centre_, rotacio_, escala, true, 1.0f, brightness_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Enemic::mou(float delta_time) {
|
void Enemic::mou(float delta_time) {
|
||||||
// Moviment autònom d'ORNI (enemic pentàgon)
|
// Dispatcher: crida el comportament específic segons el tipus
|
||||||
// Basat EXACTAMENT en el codi Pascal original: ASTEROID.PAS lines 279-293
|
switch (tipus_) {
|
||||||
// Copiat EXACTAMENT de joc_asteroides.cpp línies 348-394
|
case TipusEnemic::PENTAGON:
|
||||||
//
|
comportament_pentagon(delta_time);
|
||||||
// IMPORTANT: El Pascal original NO té canvi aleatori continu!
|
break;
|
||||||
// Només ajusta l'angle quan toca una paret.
|
case TipusEnemic::QUADRAT:
|
||||||
|
comportament_quadrat(delta_time);
|
||||||
|
break;
|
||||||
|
case TipusEnemic::MOLINILLO:
|
||||||
|
comportament_molinillo(delta_time);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Enemic::comportament_pentagon(float delta_time) {
|
||||||
|
// Pentagon: zigzag esquivador (frequent direction changes)
|
||||||
|
// Similar a comportament original però amb probabilitat més alta
|
||||||
|
|
||||||
// Calcular nova posició PROPUESTA (time-based, però lògica Pascal)
|
|
||||||
// velocitat_ ja està en px/s (40 px/s), multiplicar per delta_time
|
|
||||||
float velocitat_efectiva = velocitat_ * delta_time;
|
float velocitat_efectiva = velocitat_ * delta_time;
|
||||||
|
|
||||||
// Calcular desplaçament (angle-PI/2 perquè angle=0 apunta amunt)
|
// Calcular desplaçament (angle-PI/2 perquè angle=0 apunta amunt)
|
||||||
@@ -104,7 +147,7 @@ void Enemic::mou(float delta_time) {
|
|||||||
float new_y = centre_.y + dy;
|
float new_y = centre_.y + dy;
|
||||||
float new_x = centre_.x + dx;
|
float new_x = centre_.x + dx;
|
||||||
|
|
||||||
// Obtenir límits segurs compensant el radi de l'enemic
|
// 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,
|
min_x,
|
||||||
@@ -112,33 +155,242 @@ void Enemic::mou(float delta_time) {
|
|||||||
min_y,
|
min_y,
|
||||||
max_y);
|
max_y);
|
||||||
|
|
||||||
// Lògica Pascal: Actualitza Y si dins, sinó ajusta angle aleatòriament
|
// Zigzag: canvi d'angle més freqüent en tocar límits
|
||||||
// if (dy>marge_dalt) and (dy<marge_baix) then orni.centre.y:=round(Dy)
|
|
||||||
// else orni.angle:=orni.angle+(random(256)/512)*(random(3)-1);
|
|
||||||
// CORRECCIÓ: Usar inequalitats inclusives (>= i <=) per evitar fugides
|
|
||||||
if (new_y >= min_y && new_y <= max_y) {
|
if (new_y >= min_y && new_y <= max_y) {
|
||||||
centre_.y = new_y;
|
centre_.y = new_y;
|
||||||
} else {
|
} else {
|
||||||
// Pequeño ajuste aleatorio: (random(256)/512)*(random(3)-1)
|
// Probabilitat més alta de canvi d'angle
|
||||||
// random(256) = 0..255, /512 = 0..0.498
|
if (static_cast<float>(std::rand()) / RAND_MAX < Defaults::Enemies::Pentagon::CANVI_ANGLE_PROB) {
|
||||||
// random(3) = 0,1,2, -1 = -1,0,1
|
float rand_angle = (static_cast<float>(std::rand()) / RAND_MAX) *
|
||||||
// Resultado: ±0.5 rad aprox
|
Defaults::Enemies::Pentagon::CANVI_ANGLE_MAX;
|
||||||
float rand1 = (static_cast<float>(std::rand() % 256) / 512.0f);
|
angle_ += (std::rand() % 2 == 0) ? rand_angle : -rand_angle;
|
||||||
int rand2 = (std::rand() % 3) - 1; // -1, 0, o 1
|
}
|
||||||
angle_ += rand1 * static_cast<float>(rand2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lògica Pascal: Actualitza X si dins, sinó ajusta angle aleatòriament
|
|
||||||
// if (dx>marge_esq) and (dx<marge_dret) then orni.centre.x:=round(Dx)
|
|
||||||
// else orni.angle:=orni.angle+(random(256)/512)*(random(3)-1);
|
|
||||||
// CORRECCIÓ: Usar inequalitats inclusives (>= i <=) per evitar fugides
|
|
||||||
if (new_x >= min_x && new_x <= max_x) {
|
if (new_x >= min_x && new_x <= max_x) {
|
||||||
centre_.x = new_x;
|
centre_.x = new_x;
|
||||||
} else {
|
} else {
|
||||||
float rand1 = (static_cast<float>(std::rand() % 256) / 512.0f);
|
if (static_cast<float>(std::rand()) / RAND_MAX < Defaults::Enemies::Pentagon::CANVI_ANGLE_PROB) {
|
||||||
int rand2 = (std::rand() % 3) - 1;
|
float rand_angle = (static_cast<float>(std::rand()) / RAND_MAX) *
|
||||||
angle_ += rand1 * static_cast<float>(rand2);
|
Defaults::Enemies::Pentagon::CANVI_ANGLE_MAX;
|
||||||
|
angle_ += (std::rand() % 2 == 0) ? rand_angle : -rand_angle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Enemic::comportament_quadrat(float delta_time) {
|
||||||
|
// Quadrat: perseguidor (tracks player position)
|
||||||
|
|
||||||
|
// Update tracking timer
|
||||||
|
tracking_timer_ += delta_time;
|
||||||
|
|
||||||
|
// Periodically update angle toward ship
|
||||||
|
if (tracking_timer_ >= Defaults::Enemies::Quadrat::TRACKING_INTERVAL) {
|
||||||
|
tracking_timer_ = 0.0f;
|
||||||
|
|
||||||
|
if (ship_position_) {
|
||||||
|
// Calculate angle to ship
|
||||||
|
float dx = ship_position_->x - centre_.x;
|
||||||
|
float dy = ship_position_->y - centre_.y;
|
||||||
|
float target_angle = std::atan2(dy, dx) + Constants::PI / 2.0f;
|
||||||
|
|
||||||
|
// Interpolate toward target angle
|
||||||
|
float angle_diff = target_angle - angle_;
|
||||||
|
|
||||||
|
// Normalize angle difference to [-π, π]
|
||||||
|
while (angle_diff > Constants::PI) angle_diff -= 2.0f * Constants::PI;
|
||||||
|
while (angle_diff < -Constants::PI) angle_diff += 2.0f * Constants::PI;
|
||||||
|
|
||||||
|
// Apply tracking strength
|
||||||
|
angle_ += angle_diff * Defaults::Enemies::Quadrat::TRACKING_STRENGTH;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nota: La rotació visual (rotacio_ += drotacio_) ja es fa a actualitzar()
|
// Move in current direction
|
||||||
|
float velocitat_efectiva = velocitat_ * delta_time;
|
||||||
|
float dy = velocitat_efectiva * std::sin(angle_ - Constants::PI / 2.0f);
|
||||||
|
float dx = velocitat_efectiva * std::cos(angle_ - Constants::PI / 2.0f);
|
||||||
|
|
||||||
|
float new_y = centre_.y + dy;
|
||||||
|
float new_x = centre_.x + dx;
|
||||||
|
|
||||||
|
// Obtenir límits segurs
|
||||||
|
float min_x, max_x, min_y, max_y;
|
||||||
|
Constants::obtenir_limits_zona_segurs(Defaults::Entities::ENEMY_RADIUS,
|
||||||
|
min_x,
|
||||||
|
max_x,
|
||||||
|
min_y,
|
||||||
|
max_y);
|
||||||
|
|
||||||
|
// Bounce on walls (simple reflection)
|
||||||
|
if (new_y >= min_y && new_y <= max_y) {
|
||||||
|
centre_.y = new_y;
|
||||||
|
} else {
|
||||||
|
angle_ = -angle_; // Vertical reflection
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_x >= min_x && new_x <= max_x) {
|
||||||
|
centre_.x = new_x;
|
||||||
|
} else {
|
||||||
|
angle_ = Constants::PI - angle_; // Horizontal reflection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Enemic::comportament_molinillo(float delta_time) {
|
||||||
|
// Molinillo: agressiu (fast, straight lines, proximity spin-up)
|
||||||
|
|
||||||
|
// Check proximity to ship for spin-up effect
|
||||||
|
if (ship_position_) {
|
||||||
|
float dx = ship_position_->x - centre_.x;
|
||||||
|
float dy = ship_position_->y - centre_.y;
|
||||||
|
float distance = std::sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
if (distance < Defaults::Enemies::Molinillo::PROXIMITY_DISTANCE) {
|
||||||
|
// Temporarily boost rotation speed when near ship
|
||||||
|
float boost = Defaults::Enemies::Molinillo::DROTACIO_PROXIMITY_MULTIPLIER;
|
||||||
|
drotacio_ = animacio_.drotacio_base * boost;
|
||||||
|
} else {
|
||||||
|
// Normal rotation speed
|
||||||
|
drotacio_ = animacio_.drotacio_base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast straight-line movement
|
||||||
|
float velocitat_efectiva = velocitat_ * delta_time;
|
||||||
|
float dy = velocitat_efectiva * std::sin(angle_ - Constants::PI / 2.0f);
|
||||||
|
float dx = velocitat_efectiva * std::cos(angle_ - Constants::PI / 2.0f);
|
||||||
|
|
||||||
|
float new_y = centre_.y + dy;
|
||||||
|
float new_x = centre_.x + dx;
|
||||||
|
|
||||||
|
// Obtenir límits segurs
|
||||||
|
float min_x, max_x, min_y, max_y;
|
||||||
|
Constants::obtenir_limits_zona_segurs(Defaults::Entities::ENEMY_RADIUS,
|
||||||
|
min_x,
|
||||||
|
max_x,
|
||||||
|
min_y,
|
||||||
|
max_y);
|
||||||
|
|
||||||
|
// Rare angle changes on wall hits
|
||||||
|
if (new_y >= min_y && new_y <= max_y) {
|
||||||
|
centre_.y = new_y;
|
||||||
|
} else {
|
||||||
|
if (static_cast<float>(std::rand()) / RAND_MAX < Defaults::Enemies::Molinillo::CANVI_ANGLE_PROB) {
|
||||||
|
float rand_angle = (static_cast<float>(std::rand()) / RAND_MAX) *
|
||||||
|
Defaults::Enemies::Molinillo::CANVI_ANGLE_MAX;
|
||||||
|
angle_ += (std::rand() % 2 == 0) ? rand_angle : -rand_angle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_x >= min_x && new_x <= max_x) {
|
||||||
|
centre_.x = new_x;
|
||||||
|
} else {
|
||||||
|
if (static_cast<float>(std::rand()) / RAND_MAX < Defaults::Enemies::Molinillo::CANVI_ANGLE_PROB) {
|
||||||
|
float rand_angle = (static_cast<float>(std::rand()) / RAND_MAX) *
|
||||||
|
Defaults::Enemies::Molinillo::CANVI_ANGLE_MAX;
|
||||||
|
angle_ += (std::rand() % 2 == 0) ? rand_angle : -rand_angle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Enemic::actualitzar_animacio(float delta_time) {
|
||||||
|
actualitzar_palpitacio(delta_time);
|
||||||
|
actualitzar_rotacio_accelerada(delta_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Enemic::actualitzar_palpitacio(float delta_time) {
|
||||||
|
if (animacio_.palpitacio_activa) {
|
||||||
|
// Advance phase (2π * frequency * dt)
|
||||||
|
animacio_.palpitacio_fase += 2.0f * Constants::PI * animacio_.palpitacio_frequencia * delta_time;
|
||||||
|
|
||||||
|
// Decrement timer
|
||||||
|
animacio_.palpitacio_temps_restant -= delta_time;
|
||||||
|
|
||||||
|
// Deactivate when timer expires
|
||||||
|
if (animacio_.palpitacio_temps_restant <= 0.0f) {
|
||||||
|
animacio_.palpitacio_activa = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Random trigger (probability per second)
|
||||||
|
float rand_val = static_cast<float>(std::rand()) / RAND_MAX;
|
||||||
|
float trigger_prob = Defaults::Enemies::Animation::PALPITACIO_TRIGGER_PROB * delta_time;
|
||||||
|
|
||||||
|
if (rand_val < trigger_prob) {
|
||||||
|
// Activate palpitation
|
||||||
|
animacio_.palpitacio_activa = true;
|
||||||
|
animacio_.palpitacio_fase = 0.0f;
|
||||||
|
|
||||||
|
// Randomize parameters
|
||||||
|
float freq_range = Defaults::Enemies::Animation::PALPITACIO_FREQ_MAX -
|
||||||
|
Defaults::Enemies::Animation::PALPITACIO_FREQ_MIN;
|
||||||
|
animacio_.palpitacio_frequencia = Defaults::Enemies::Animation::PALPITACIO_FREQ_MIN +
|
||||||
|
(static_cast<float>(std::rand()) / RAND_MAX) * freq_range;
|
||||||
|
|
||||||
|
float amp_range = Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MAX -
|
||||||
|
Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MIN;
|
||||||
|
animacio_.palpitacio_amplitud = Defaults::Enemies::Animation::PALPITACIO_AMPLITUD_MIN +
|
||||||
|
(static_cast<float>(std::rand()) / RAND_MAX) * amp_range;
|
||||||
|
|
||||||
|
float dur_range = Defaults::Enemies::Animation::PALPITACIO_DURACIO_MAX -
|
||||||
|
Defaults::Enemies::Animation::PALPITACIO_DURACIO_MIN;
|
||||||
|
animacio_.palpitacio_temps_restant = Defaults::Enemies::Animation::PALPITACIO_DURACIO_MIN +
|
||||||
|
(static_cast<float>(std::rand()) / RAND_MAX) * dur_range;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Enemic::actualitzar_rotacio_accelerada(float delta_time) {
|
||||||
|
if (animacio_.drotacio_t < 1.0f) {
|
||||||
|
// Transitioning to new target
|
||||||
|
animacio_.drotacio_t += delta_time / animacio_.drotacio_duracio;
|
||||||
|
|
||||||
|
if (animacio_.drotacio_t >= 1.0f) {
|
||||||
|
animacio_.drotacio_t = 1.0f;
|
||||||
|
animacio_.drotacio_base = animacio_.drotacio_objetivo; // Reached target
|
||||||
|
drotacio_ = animacio_.drotacio_base;
|
||||||
|
} else {
|
||||||
|
// Smoothstep interpolation: t² * (3 - 2t)
|
||||||
|
float t = animacio_.drotacio_t;
|
||||||
|
float smooth_t = t * t * (3.0f - 2.0f * t);
|
||||||
|
|
||||||
|
// Interpolate between base and target
|
||||||
|
float initial = animacio_.drotacio_base;
|
||||||
|
float target = animacio_.drotacio_objetivo;
|
||||||
|
drotacio_ = initial + (target - initial) * smooth_t;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Random trigger for new acceleration
|
||||||
|
float rand_val = static_cast<float>(std::rand()) / RAND_MAX;
|
||||||
|
float trigger_prob = Defaults::Enemies::Animation::ROTACIO_ACCEL_TRIGGER_PROB * delta_time;
|
||||||
|
|
||||||
|
if (rand_val < trigger_prob) {
|
||||||
|
// Start new transition
|
||||||
|
animacio_.drotacio_t = 0.0f;
|
||||||
|
|
||||||
|
// Randomize target speed (multiplier * base)
|
||||||
|
float mult_range = Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MAX -
|
||||||
|
Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MIN;
|
||||||
|
float multiplier = Defaults::Enemies::Animation::ROTACIO_ACCEL_MULTIPLIER_MIN +
|
||||||
|
(static_cast<float>(std::rand()) / RAND_MAX) * mult_range;
|
||||||
|
|
||||||
|
animacio_.drotacio_objetivo = animacio_.drotacio_base * multiplier;
|
||||||
|
|
||||||
|
// Randomize duration
|
||||||
|
float dur_range = Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MAX -
|
||||||
|
Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MIN;
|
||||||
|
animacio_.drotacio_duracio = Defaults::Enemies::Animation::ROTACIO_ACCEL_DURACIO_MIN +
|
||||||
|
(static_cast<float>(std::rand()) / RAND_MAX) * dur_range;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float Enemic::calcular_escala_actual() const {
|
||||||
|
float escala = 1.0f;
|
||||||
|
|
||||||
|
if (animacio_.palpitacio_activa) {
|
||||||
|
// Add pulsating scale variation
|
||||||
|
escala += animacio_.palpitacio_amplitud * std::sin(animacio_.palpitacio_fase);
|
||||||
|
}
|
||||||
|
|
||||||
|
return escala;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,36 @@
|
|||||||
#include "core/graphics/shape.hpp"
|
#include "core/graphics/shape.hpp"
|
||||||
#include "core/types.hpp"
|
#include "core/types.hpp"
|
||||||
|
|
||||||
|
// Tipus d'enemic
|
||||||
|
enum class TipusEnemic : uint8_t {
|
||||||
|
PENTAGON = 0, // Pentàgon esquivador (zigzag)
|
||||||
|
QUADRAT = 1, // Quadrat perseguidor (tracks ship)
|
||||||
|
MOLINILLO = 2 // Molinillo agressiu (fast, spinning)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Estat d'animació (palpitació i rotació accelerada)
|
||||||
|
struct AnimacioEnemic {
|
||||||
|
// Palpitation (breathing effect)
|
||||||
|
bool palpitacio_activa = false;
|
||||||
|
float palpitacio_fase = 0.0f; // Phase in cycle (0.0-2π)
|
||||||
|
float palpitacio_frequencia = 2.0f; // Hz (cycles per second)
|
||||||
|
float palpitacio_amplitud = 0.15f; // Scale variation (±15%)
|
||||||
|
float palpitacio_temps_restant = 0.0f; // Time remaining (seconds)
|
||||||
|
|
||||||
|
// Rotation acceleration (long-term spin modulation)
|
||||||
|
float drotacio_base = 0.0f; // Base rotation speed (rad/s)
|
||||||
|
float drotacio_objetivo = 0.0f; // Target rotation speed (rad/s)
|
||||||
|
float drotacio_t = 0.0f; // Interpolation progress (0.0-1.0)
|
||||||
|
float drotacio_duracio = 0.0f; // Duration of transition (seconds)
|
||||||
|
};
|
||||||
|
|
||||||
class Enemic {
|
class Enemic {
|
||||||
public:
|
public:
|
||||||
Enemic()
|
Enemic()
|
||||||
: renderer_(nullptr) {}
|
: renderer_(nullptr) {}
|
||||||
Enemic(SDL_Renderer* renderer);
|
Enemic(SDL_Renderer* renderer);
|
||||||
|
|
||||||
void inicialitzar();
|
void inicialitzar(TipusEnemic tipus = TipusEnemic::PENTAGON);
|
||||||
void actualitzar(float delta_time);
|
void actualitzar(float delta_time);
|
||||||
void dibuixar() const;
|
void dibuixar() const;
|
||||||
|
|
||||||
@@ -26,6 +49,9 @@ class Enemic {
|
|||||||
const std::shared_ptr<Graphics::Shape>& get_forma() const { return forma_; }
|
const std::shared_ptr<Graphics::Shape>& get_forma() const { return forma_; }
|
||||||
void destruir() { esta_ = false; }
|
void destruir() { esta_ = false; }
|
||||||
|
|
||||||
|
// Set ship position reference for tracking behavior
|
||||||
|
void set_ship_position(const Punt* ship_pos) { ship_position_ = ship_pos; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SDL_Renderer* renderer_;
|
SDL_Renderer* renderer_;
|
||||||
|
|
||||||
@@ -41,5 +67,25 @@ class Enemic {
|
|||||||
bool esta_;
|
bool esta_;
|
||||||
float brightness_; // Factor de brillantor (0.0-1.0)
|
float brightness_; // Factor de brillantor (0.0-1.0)
|
||||||
|
|
||||||
|
// [NEW] Enemy type and configuration
|
||||||
|
TipusEnemic tipus_;
|
||||||
|
|
||||||
|
// [NEW] Animation state
|
||||||
|
AnimacioEnemic animacio_;
|
||||||
|
|
||||||
|
// [NEW] Behavior state (type-specific)
|
||||||
|
float tracking_timer_; // For Quadrat: time since last angle update
|
||||||
|
const Punt* ship_position_; // Pointer to ship position (for tracking)
|
||||||
|
|
||||||
|
// [EXISTING] Private methods
|
||||||
void mou(float delta_time);
|
void mou(float delta_time);
|
||||||
|
|
||||||
|
// [NEW] Private methods
|
||||||
|
void actualitzar_animacio(float delta_time);
|
||||||
|
void actualitzar_palpitacio(float delta_time);
|
||||||
|
void actualitzar_rotacio_accelerada(float delta_time);
|
||||||
|
void comportament_pentagon(float delta_time);
|
||||||
|
void comportament_quadrat(float delta_time);
|
||||||
|
void comportament_molinillo(float delta_time);
|
||||||
|
float calcular_escala_actual() const; // Returns scale with palpitation applied
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ Nau::Nau(SDL_Renderer* renderer)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nau::inicialitzar() {
|
void Nau::inicialitzar(const Punt* spawn_point) {
|
||||||
// Inicialització de la nau (triangle)
|
// Inicialització de la nau (triangle)
|
||||||
// Basat en el codi Pascal original: lines 380-384
|
// Basat en el codi Pascal original: lines 380-384
|
||||||
// Copiat de joc_asteroides.cpp línies 30-44
|
// Copiat de joc_asteroides.cpp línies 30-44
|
||||||
@@ -37,11 +37,17 @@ void Nau::inicialitzar() {
|
|||||||
// [NUEVO] Ja no cal configurar punts polars - la geometria es carrega del
|
// [NUEVO] Ja no cal configurar punts polars - la geometria es carrega del
|
||||||
// fitxer Només inicialitzem l'estat de la instància
|
// fitxer Només inicialitzem l'estat de la instància
|
||||||
|
|
||||||
// Posició inicial al centre de l'àrea de joc
|
// Use custom spawn point if provided, otherwise use center
|
||||||
|
if (spawn_point) {
|
||||||
|
centre_.x = spawn_point->x;
|
||||||
|
centre_.y = spawn_point->y;
|
||||||
|
} else {
|
||||||
|
// Default: center of play area
|
||||||
float centre_x, centre_y;
|
float centre_x, centre_y;
|
||||||
Constants::obtenir_centre_zona(centre_x, centre_y);
|
Constants::obtenir_centre_zona(centre_x, centre_y);
|
||||||
centre_.x = centre_x; // 320
|
centre_.x = static_cast<int>(centre_x);
|
||||||
centre_.y = centre_y; // 213 (not 240!)
|
centre_.y = static_cast<int>(centre_y);
|
||||||
|
}
|
||||||
|
|
||||||
// Estat inicial
|
// Estat inicial
|
||||||
angle_ = 0.0f;
|
angle_ = 0.0f;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class Nau {
|
|||||||
: renderer_(nullptr) {}
|
: renderer_(nullptr) {}
|
||||||
Nau(SDL_Renderer* renderer);
|
Nau(SDL_Renderer* renderer);
|
||||||
|
|
||||||
void inicialitzar();
|
void inicialitzar(const Punt* spawn_point = nullptr);
|
||||||
void processar_input(float delta_time);
|
void processar_input(float delta_time);
|
||||||
void actualitzar(float delta_time);
|
void actualitzar(float delta_time);
|
||||||
void dibuixar() const;
|
void dibuixar() const;
|
||||||
@@ -25,6 +25,7 @@ class Nau {
|
|||||||
const Punt& get_centre() const { return centre_; }
|
const Punt& get_centre() const { return centre_; }
|
||||||
float get_angle() const { return angle_; }
|
float get_angle() const { return angle_; }
|
||||||
bool esta_viva() const { return !esta_tocada_; }
|
bool esta_viva() const { return !esta_tocada_; }
|
||||||
|
const std::shared_ptr<Graphics::Shape>& get_forma() const { return forma_; }
|
||||||
|
|
||||||
// Col·lisions (Fase 10)
|
// Col·lisions (Fase 10)
|
||||||
void marcar_tocada() { esta_tocada_ = true; }
|
void marcar_tocada() { esta_tocada_ = true; }
|
||||||
|
|||||||
@@ -108,67 +108,201 @@ void EscenaJoc::inicialitzar() {
|
|||||||
// Inicialitzar estat de col·lisió
|
// Inicialitzar estat de col·lisió
|
||||||
itocado_ = 0;
|
itocado_ = 0;
|
||||||
|
|
||||||
|
// Initialize lives and game over state
|
||||||
|
num_vides_ = Defaults::Game::STARTING_LIVES;
|
||||||
|
game_over_ = false;
|
||||||
|
game_over_timer_ = 0.0f;
|
||||||
|
|
||||||
|
// Set spawn point to center of play area
|
||||||
|
Constants::obtenir_centre_zona(punt_spawn_.x, punt_spawn_.y);
|
||||||
|
|
||||||
// Inicialitzar nau
|
// Inicialitzar nau
|
||||||
nau_.inicialitzar();
|
nau_.inicialitzar();
|
||||||
|
|
||||||
// Inicialitzar enemics (ORNIs)
|
// Inicialitzar enemics (ORNIs) amb tipus aleatoris
|
||||||
for (auto& enemy : orni_) {
|
for (auto& enemy : orni_) {
|
||||||
enemy.inicialitzar();
|
// Random type distribution: ~40% Pentagon, ~30% Quadrat, ~30% Molinillo
|
||||||
|
int rand_val = std::rand() % 10;
|
||||||
|
TipusEnemic tipus;
|
||||||
|
|
||||||
|
if (rand_val < 4) {
|
||||||
|
tipus = TipusEnemic::PENTAGON;
|
||||||
|
} else if (rand_val < 7) {
|
||||||
|
tipus = TipusEnemic::QUADRAT;
|
||||||
|
} else {
|
||||||
|
tipus = TipusEnemic::MOLINILLO;
|
||||||
|
}
|
||||||
|
|
||||||
|
enemy.inicialitzar(tipus);
|
||||||
|
enemy.set_ship_position(&nau_.get_centre()); // Set ship reference for tracking
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inicialitzar bales
|
// Inicialitzar bales
|
||||||
for (auto& bala : bales_) {
|
for (auto& bala : bales_) {
|
||||||
bala.inicialitzar();
|
bala.inicialitzar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Iniciar música de joc (sense stopMusic, ja s'ha parat en destructor de TITOL)
|
||||||
|
Audio::get()->playMusic("game.ogg");
|
||||||
}
|
}
|
||||||
|
|
||||||
void EscenaJoc::actualitzar(float delta_time) {
|
void EscenaJoc::actualitzar(float delta_time) {
|
||||||
// Actualitzar nau (input + física)
|
// Check game over state first
|
||||||
nau_.processar_input(delta_time);
|
if (game_over_) {
|
||||||
nau_.actualitzar(delta_time);
|
// Game over: only update timer, enemies, bullets, and debris
|
||||||
|
game_over_timer_ -= delta_time;
|
||||||
|
|
||||||
// Actualitzar moviment i rotació dels enemics (ORNIs)
|
if (game_over_timer_ <= 0.0f) {
|
||||||
|
// Aturar música de joc abans de tornar al títol
|
||||||
|
Audio::get()->stopMusic();
|
||||||
|
// Auto-transition to title screen
|
||||||
|
GestorEscenes::actual = GestorEscenes::Escena::TITOL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enemies and bullets continue moving during game over
|
||||||
for (auto& enemy : orni_) {
|
for (auto& enemy : orni_) {
|
||||||
enemy.actualitzar(delta_time);
|
enemy.actualitzar(delta_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualitzar moviment de bales (Fase 9)
|
|
||||||
for (auto& bala : bales_) {
|
for (auto& bala : bales_) {
|
||||||
bala.actualitzar(delta_time);
|
bala.actualitzar(delta_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detectar col·lisions bala-enemic (Fase 10)
|
debris_manager_.actualitzar(delta_time);
|
||||||
detectar_col·lisions_bales_enemics();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Actualitzar fragments d'explosions
|
// Check death sequence state
|
||||||
|
if (itocado_ > 0.0f) {
|
||||||
|
// Death sequence active: update timer
|
||||||
|
itocado_ += delta_time;
|
||||||
|
|
||||||
|
// Check if death duration completed
|
||||||
|
if (itocado_ >= Defaults::Game::DEATH_DURATION) {
|
||||||
|
// *** PHASE 3: RESPAWN OR GAME OVER ***
|
||||||
|
|
||||||
|
// Decrement lives
|
||||||
|
num_vides_--;
|
||||||
|
|
||||||
|
if (num_vides_ > 0) {
|
||||||
|
// Respawn ship
|
||||||
|
nau_.inicialitzar(&punt_spawn_);
|
||||||
|
itocado_ = 0.0f;
|
||||||
|
} else {
|
||||||
|
// Game over
|
||||||
|
game_over_ = true;
|
||||||
|
game_over_timer_ = Defaults::Game::GAME_OVER_DURATION;
|
||||||
|
itocado_ = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enemies and bullets continue moving during death sequence
|
||||||
|
for (auto& enemy : orni_) {
|
||||||
|
enemy.actualitzar(delta_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& bala : bales_) {
|
||||||
|
bala.actualitzar(delta_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
debris_manager_.actualitzar(delta_time);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// *** NORMAL GAMEPLAY ***
|
||||||
|
|
||||||
|
// Update ship (input + physics)
|
||||||
|
nau_.processar_input(delta_time);
|
||||||
|
nau_.actualitzar(delta_time);
|
||||||
|
|
||||||
|
// Update enemy movement and rotation
|
||||||
|
for (auto& enemy : orni_) {
|
||||||
|
enemy.actualitzar(delta_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update bullet movement
|
||||||
|
for (auto& bala : bales_) {
|
||||||
|
bala.actualitzar(delta_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect collisions
|
||||||
|
detectar_col·lisions_bales_enemics();
|
||||||
|
detectar_col·lisio_nau_enemics(); // New collision check
|
||||||
|
|
||||||
|
// Update debris
|
||||||
debris_manager_.actualitzar(delta_time);
|
debris_manager_.actualitzar(delta_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EscenaJoc::dibuixar() {
|
void EscenaJoc::dibuixar() {
|
||||||
// Dibuixar marges de la zona de joc
|
// Draw borders (always visible)
|
||||||
dibuixar_marges();
|
dibuixar_marges();
|
||||||
|
|
||||||
// Dibuixar nau
|
// Check game over state
|
||||||
nau_.dibuixar();
|
if (game_over_) {
|
||||||
|
// Game over: draw enemies, bullets, debris, and "GAME OVER" text
|
||||||
|
|
||||||
// Dibuixar ORNIs (enemics)
|
|
||||||
for (const auto& enemy : orni_) {
|
for (const auto& enemy : orni_) {
|
||||||
enemy.dibuixar();
|
enemy.dibuixar();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dibuixar bales (Fase 9)
|
|
||||||
for (const auto& bala : bales_) {
|
for (const auto& bala : bales_) {
|
||||||
bala.dibuixar();
|
bala.dibuixar();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dibuixar fragments d'explosions (després d'altres objectes)
|
|
||||||
debris_manager_.dibuixar();
|
debris_manager_.dibuixar();
|
||||||
|
|
||||||
// Dibuixar marcador
|
// Draw centered "GAME OVER" text
|
||||||
|
const std::string game_over_text = "GAME OVER";
|
||||||
|
constexpr float escala = 2.0f;
|
||||||
|
constexpr float spacing = 4.0f;
|
||||||
|
|
||||||
|
float text_width = text_.get_text_width(game_over_text, escala, spacing);
|
||||||
|
float text_height = text_.get_text_height(escala);
|
||||||
|
|
||||||
|
const SDL_FRect& play_area = Defaults::Zones::PLAYAREA;
|
||||||
|
float x = play_area.x + (play_area.w - text_width) / 2.0f;
|
||||||
|
float y = play_area.y + (play_area.h - text_height) / 2.0f;
|
||||||
|
|
||||||
|
text_.render(game_over_text, {x, y}, escala, spacing);
|
||||||
|
|
||||||
|
dibuixar_marcador();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// During death sequence, don't draw ship (debris draws automatically)
|
||||||
|
if (itocado_ == 0.0f) {
|
||||||
|
nau_.dibuixar();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw enemies (always)
|
||||||
|
for (const auto& enemy : orni_) {
|
||||||
|
enemy.dibuixar();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw bullets (always)
|
||||||
|
for (const auto& bala : bales_) {
|
||||||
|
bala.dibuixar();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw debris
|
||||||
|
debris_manager_.dibuixar();
|
||||||
|
|
||||||
|
// Draw scoreboard
|
||||||
dibuixar_marcador();
|
dibuixar_marcador();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EscenaJoc::processar_input(const SDL_Event& event) {
|
void EscenaJoc::processar_input(const SDL_Event& event) {
|
||||||
|
// Ignore ship controls during game over
|
||||||
|
if (game_over_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore ship controls during death sequence
|
||||||
|
if (itocado_ > 0.0f) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Processament d'input per events puntuals (no continus)
|
// Processament d'input per events puntuals (no continus)
|
||||||
// L'input continu (fletxes) es processa en actualitzar() amb
|
// L'input continu (fletxes) es processa en actualitzar() amb
|
||||||
// SDL_GetKeyboardState()
|
// SDL_GetKeyboardState()
|
||||||
@@ -217,7 +351,34 @@ void EscenaJoc::processar_input(const SDL_Event& event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EscenaJoc::tocado() {
|
void EscenaJoc::tocado() {
|
||||||
// TODO: Implementar seqüència de mort
|
// Death sequence: 3 phases
|
||||||
|
// Phase 1: First call (itocado_ == 0) - trigger explosion
|
||||||
|
// Phase 2: Animation (0 < itocado_ < 3.0s) - debris animation
|
||||||
|
// Phase 3: Respawn or game over (itocado_ >= 3.0s) - handled in actualitzar()
|
||||||
|
|
||||||
|
if (itocado_ == 0.0f) {
|
||||||
|
// *** PHASE 1: TRIGGER DEATH ***
|
||||||
|
|
||||||
|
// Mark ship as dead (stops rendering and input)
|
||||||
|
nau_.marcar_tocada();
|
||||||
|
|
||||||
|
// Create ship explosion
|
||||||
|
const Punt& ship_pos = nau_.get_centre();
|
||||||
|
float ship_angle = nau_.get_angle();
|
||||||
|
|
||||||
|
debris_manager_.explotar(
|
||||||
|
nau_.get_forma(), // Ship shape (3 lines)
|
||||||
|
ship_pos, // Center position
|
||||||
|
ship_angle, // Ship orientation
|
||||||
|
1.0f, // Normal scale
|
||||||
|
Defaults::Physics::Debris::VELOCITAT_BASE // 80 px/s
|
||||||
|
);
|
||||||
|
|
||||||
|
// Start death timer (non-zero to avoid re-triggering)
|
||||||
|
itocado_ = 0.001f;
|
||||||
|
}
|
||||||
|
// Phase 2 is automatic (debris updates in actualitzar())
|
||||||
|
// Phase 3 is handled in actualitzar() when itocado_ >= DEATH_DURATION
|
||||||
}
|
}
|
||||||
|
|
||||||
void EscenaJoc::dibuixar_marges() const {
|
void EscenaJoc::dibuixar_marges() const {
|
||||||
@@ -238,8 +399,8 @@ void EscenaJoc::dibuixar_marges() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EscenaJoc::dibuixar_marcador() {
|
void EscenaJoc::dibuixar_marcador() {
|
||||||
// Text estàtic (hardcoded)
|
// Display actual lives count (user requested "LIFES" plural English)
|
||||||
const std::string text = "SCORE: 01000 LIFE: 3 LEVEL: 01";
|
std::string text = "SCORE: 01000 LIFES: " + std::to_string(num_vides_) + " LEVEL: 01";
|
||||||
|
|
||||||
// Paràmetres de renderització
|
// Paràmetres de renderització
|
||||||
const float escala = 0.85f;
|
const float escala = 0.85f;
|
||||||
@@ -316,3 +477,39 @@ void EscenaJoc::detectar_col·lisions_bales_enemics() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EscenaJoc::detectar_col·lisio_nau_enemics() {
|
||||||
|
// Only check collisions if ship is alive
|
||||||
|
if (!nau_.esta_viva()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generous collision detection (80% hitbox)
|
||||||
|
constexpr float RADI_NAU = Defaults::Entities::SHIP_RADIUS;
|
||||||
|
constexpr float RADI_ENEMIC = Defaults::Entities::ENEMY_RADIUS;
|
||||||
|
constexpr float SUMA_RADIS =
|
||||||
|
(RADI_NAU + RADI_ENEMIC) * Defaults::Game::COLLISION_SHIP_ENEMY_AMPLIFIER;
|
||||||
|
constexpr float SUMA_RADIS_QUADRAT = SUMA_RADIS * SUMA_RADIS;
|
||||||
|
|
||||||
|
const Punt& pos_nau = nau_.get_centre();
|
||||||
|
|
||||||
|
// Check collision with all active enemies
|
||||||
|
for (const auto& enemic : orni_) {
|
||||||
|
if (!enemic.esta_actiu()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Punt& pos_enemic = enemic.get_centre();
|
||||||
|
|
||||||
|
// Calculate squared distance (avoid sqrt)
|
||||||
|
float dx = static_cast<float>(pos_nau.x - pos_enemic.x);
|
||||||
|
float dy = static_cast<float>(pos_nau.y - pos_enemic.y);
|
||||||
|
float distancia_quadrada = dx * dx + dy * dy;
|
||||||
|
|
||||||
|
// Check collision
|
||||||
|
if (distancia_quadrada <= SUMA_RADIS_QUADRAT) {
|
||||||
|
tocado(); // Trigger death sequence
|
||||||
|
return; // Only one collision per frame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -42,7 +42,13 @@ class EscenaJoc {
|
|||||||
std::array<Enemic, Constants::MAX_ORNIS> orni_;
|
std::array<Enemic, Constants::MAX_ORNIS> orni_;
|
||||||
std::array<Bala, Constants::MAX_BALES> bales_;
|
std::array<Bala, Constants::MAX_BALES> bales_;
|
||||||
Poligon chatarra_cosmica_;
|
Poligon chatarra_cosmica_;
|
||||||
uint16_t itocado_;
|
float itocado_; // Death timer (seconds)
|
||||||
|
|
||||||
|
// Lives and game over system
|
||||||
|
int num_vides_; // Current lives count
|
||||||
|
bool game_over_; // Game over state flag
|
||||||
|
float game_over_timer_; // Countdown timer for auto-return (seconds)
|
||||||
|
Punt punt_spawn_; // Configurable spawn point
|
||||||
|
|
||||||
// Text vectorial
|
// Text vectorial
|
||||||
Graphics::VectorText text_;
|
Graphics::VectorText text_;
|
||||||
@@ -50,6 +56,7 @@ class EscenaJoc {
|
|||||||
// Funcions privades
|
// Funcions privades
|
||||||
void tocado();
|
void tocado();
|
||||||
void detectar_col·lisions_bales_enemics(); // Col·lisions bala-enemic
|
void detectar_col·lisions_bales_enemics(); // Col·lisions bala-enemic
|
||||||
|
void detectar_col·lisio_nau_enemics(); // Ship-enemy collision detection
|
||||||
void dibuixar_marges() const; // Dibuixar vores de la zona de joc
|
void dibuixar_marges() const; // Dibuixar vores de la zona de joc
|
||||||
void dibuixar_marcador(); // Dibuixar marcador de puntuació
|
void dibuixar_marcador(); // Dibuixar marcador de puntuació
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -200,6 +200,10 @@ void EscenaLogo::canviar_estat(EstatAnimacio nou_estat) {
|
|||||||
std::mt19937 g(rd());
|
std::mt19937 g(rd());
|
||||||
std::shuffle(ordre_explosio_.begin(), ordre_explosio_.end(), g);
|
std::shuffle(ordre_explosio_.begin(), ordre_explosio_.end(), g);
|
||||||
}
|
}
|
||||||
|
else if (nou_estat == EstatAnimacio::POST_EXPLOSION)
|
||||||
|
{
|
||||||
|
Audio::get()->playMusic("title.ogg");
|
||||||
|
}
|
||||||
|
|
||||||
std::cout << "[EscenaLogo] Canvi a estat: " << static_cast<int>(nou_estat)
|
std::cout << "[EscenaLogo] Canvi a estat: " << static_cast<int>(nou_estat)
|
||||||
<< "\n";
|
<< "\n";
|
||||||
@@ -288,6 +292,7 @@ void EscenaLogo::actualitzar(float delta_time) {
|
|||||||
|
|
||||||
case EstatAnimacio::POST_EXPLOSION:
|
case EstatAnimacio::POST_EXPLOSION:
|
||||||
if (temps_estat_actual_ >= DURACIO_POST_EXPLOSION) {
|
if (temps_estat_actual_ >= DURACIO_POST_EXPLOSION) {
|
||||||
|
// Iniciar música de títol abans de la transició
|
||||||
GestorEscenes::actual = GestorEscenes::Escena::TITOL;
|
GestorEscenes::actual = GestorEscenes::Escena::TITOL;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -9,11 +9,11 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "game/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:
|
||||||
|
|||||||
@@ -3,15 +3,24 @@
|
|||||||
|
|
||||||
#include "escena_titol.hpp"
|
#include "escena_titol.hpp"
|
||||||
|
|
||||||
|
#include <cfloat>
|
||||||
|
#include <cmath>
|
||||||
#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"
|
||||||
|
|
||||||
|
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)
|
EscenaTitol::EscenaTitol(SDLManager& sdl)
|
||||||
: sdl_(sdl),
|
: sdl_(sdl),
|
||||||
text_(sdl.obte_renderer()),
|
text_(sdl.obte_renderer()),
|
||||||
@@ -25,7 +34,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 +45,159 @@ EscenaTitol::EscenaTitol(SDLManager& sdl)
|
|||||||
area_completa,
|
area_completa,
|
||||||
150 // densitat: 150 estrelles (50 per capa)
|
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() {
|
void EscenaTitol::executar() {
|
||||||
@@ -112,9 +275,18 @@ void EscenaTitol::actualitzar(float delta_time) {
|
|||||||
estat_actual_ = EstatTitol::MAIN;
|
estat_actual_ = EstatTitol::MAIN;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EstatTitol::MAIN:
|
case EstatTitol::MAIN:
|
||||||
// No hi ha lògica d'actualització en l'estat MAIN
|
// No hi ha lògica d'actualització en l'estat MAIN
|
||||||
break;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,22 +301,63 @@ void EscenaTitol::dibuixar() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Estat MAIN: Dibuixar text de títol i copyright (sobre el starfield)
|
// Estat MAIN i TRANSITION: Dibuixar títol i text (sobre el starfield)
|
||||||
if (estat_actual_ == EstatTitol::MAIN) {
|
if (estat_actual_ == EstatTitol::MAIN || estat_actual_ == EstatTitol::TRANSITION) {
|
||||||
// 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" ===
|
||||||
|
// 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 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;
|
|
||||||
|
|
||||||
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;
|
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);
|
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,15 +381,21 @@ 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
|
||||||
estat_actual_ = EstatTitol::MAIN;
|
estat_actual_ = EstatTitol::MAIN;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EstatTitol::MAIN:
|
case EstatTitol::MAIN:
|
||||||
// Anar al joc
|
// Iniciar transició amb fade-out de música
|
||||||
GestorEscenes::actual = GestorEscenes::Escena::JOC;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,22 +7,36 @@
|
|||||||
#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:
|
||||||
explicit EscenaTitol(SDLManager& sdl);
|
explicit EscenaTitol(SDLManager& sdl);
|
||||||
|
~EscenaTitol(); // Destructor per aturar música
|
||||||
void executar(); // Bucle principal de l'escena
|
void executar(); // Bucle principal de l'escena
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Màquina d'estats per la pantalla de títol
|
// Màquina d'estats per la pantalla de títol
|
||||||
enum class EstatTitol {
|
enum class EstatTitol {
|
||||||
INIT, // Pantalla negra inicial (2 segons)
|
INIT, // Pantalla negra inicial (2 segons)
|
||||||
MAIN // Pantalla de títol amb text
|
MAIN, // Pantalla de títol amb text
|
||||||
|
TRANSITION // Transició amb fade-out de música i text parpellejant
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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_;
|
||||||
@@ -31,11 +45,24 @@ class EscenaTitol {
|
|||||||
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 = 4.0f; // Duració de l'estat INIT (2 segons)
|
||||||
|
static constexpr float DURACIO_TRANSITION = 1.5f; // Duració de la transició (1.5 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)
|
||||||
|
static constexpr float BLINK_FREQUENCY = 3.0f; // Freqüència de parpelleig (3 Hz)
|
||||||
|
static constexpr int MUSIC_FADE = 1000; // Duracio del fade de la musica del titol al començar a jugar
|
||||||
|
|
||||||
// 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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user