Compare commits

..

20 Commits

Author SHA1 Message Date
JailDesigner 491992a4d7 bump version a 0.8.1 2026-05-26 19:40:08 +02:00
JailDesigner e5b727216c Merge branch 'refactor/move-gamecontrollerdb-to-root': gamecontrollerdb fora de data/ (al costat del binari) + logs uniformes 2026-05-26 19:39:11 +02:00
JailDesigner f03e337b9a refactor(input): gamecontrollerdb.txt a l'arrel + target controllerdb + logs estil [Input] 2026-05-26 19:38:31 +02:00
JailDesigner 99e99e7e08 Merge branch 'refactor/remove-dead-oscillator-code': neteja del ColorOscillator (ara via shader) 2026-05-26 19:23:45 +02:00
JailDesigner b93761eb1e refactor(render): eliminar restes del ColorOscillator (setLineColor/getLineColor/global mutable) i deixar DEFAULT_LINE_COLOR constexpr 2026-05-26 19:23:29 +02:00
JailDesigner 4f5421191d Merge branch 'feat/hud-palette': HUD amb colors per funció + diferenciació P1/P2 2026-05-26 19:18:07 +02:00
JailDesigner 71ed9dc24f feat(hud): paleta per segments (P1 blanc, vides ambre, nivell verd, P2 rosa) 2026-05-26 19:17:22 +02:00
JailDesigner 1a0cc504c4 Merge branch 'refactor/rename-explosion-sounds': sons d'explosió i bullet_zap amb noms descriptius + enemy_hit per a debris_partial 2026-05-26 19:06:00 +02:00
JailDesigner 86775d4642 refactor(audio): renombrar hit.wav a bullet_zap.wav (desintegració de bala, no HURT d'enemic) 2026-05-26 19:05:43 +02:00
JailDesigner b936f410ce feat(audio): so enemy_hit per a debris_partial (impacte parcial a enemic amb HP>1) 2026-05-26 19:03:19 +02:00
JailDesigner ddcd2076a1 refactor(audio): renombrar explosion/explosion2 a enemy_explosion/player_explosion 2026-05-26 18:57:26 +02:00
JailDesigner 9345facaed Merge branch 'feat/orb-counterattack': orb taronja rosat dispara bullet_double cap al jugador en cada hit 2026-05-26 18:54:27 +02:00
JailDesigner 885caa6bc3 feat(orb): contra-atac amb bullet_double dirigida al jugador en rebre impacte 2026-05-26 18:53:34 +02:00
JailDesigner a77bbe4420 Merge branch 'feat/reorganize-shapes': renombre big_pentagon→orb i reorganització de data/shapes per categoria 2026-05-26 18:27:11 +02:00
JailDesigner 61a4886e62 refactor(shapes): reorganitzar data/shapes en subcarpetes per categoria (enemy/bullet/ship/effect) 2026-05-26 18:25:15 +02:00
JailDesigner 164f58c883 refactor(enemies): renombrar big_pentagon a orb i enemy_big_orb a enemy_orb 2026-05-26 18:09:29 +02:00
JailDesigner fbfacb825b Merge branch 'refactor/revert-stl-loops': bucles for explícits en lloc de std::ranges::* on aplica 2026-05-26 13:50:46 +02:00
JailDesigner 5e4d2cf993 refactor(physics): tornar std::ranges::find a bucle for explícit 2026-05-26 13:49:16 +02:00
JailDesigner 97d3749269 refactor: tornar std::ranges::{any,all,find}_of a bucles for explícits 2026-05-26 13:45:54 +02:00
JailDesigner 0dcecf9a3c tune(lint): desactivar readability-use-anyofallof per coherència amb cppcheck 2026-05-26 13:41:06 +02:00
67 changed files with 747 additions and 373 deletions
+3
View File
@@ -9,6 +9,9 @@ Checks:
- -bugprone-easily-swappable-parameters - -bugprone-easily-swappable-parameters
- -bugprone-narrowing-conversions - -bugprone-narrowing-conversions
- -modernize-avoid-c-arrays - -modernize-avoid-c-arrays
# No forçar reemplaç de bucles "normals" per std::any_of/std::all_of.
# Equivalent a `--suppress=useStlAlgorithm` que ja tenim a cppcheck.
- -readability-use-anyofallof
# performance-noexcept-move-constructor crashea clang-tidy (LLVM 19.1) # performance-noexcept-move-constructor crashea clang-tidy (LLVM 19.1)
# con recursión infinita en ExceptionSpecAnalyzer::analyzeRecord cuando # con recursión infinita en ExceptionSpecAnalyzer::analyzeRecord cuando
# analiza ciertas instanciaciones de std::set. No es un falso positivo # analiza ciertas instanciaciones de std::set. No es un falso positivo
+1 -1
View File
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
project(orni VERSION 0.8.0 LANGUAGES CXX) project(orni VERSION 0.8.1 LANGUAGES CXX)
# Info del projecte (font de veritat per a project.h) # Info del projecte (font de veritat per a project.h)
set(PROJECT_LONG_NAME "Orni Attack") set(PROJECT_LONG_NAME "Orni Attack")
+28 -1
View File
@@ -84,9 +84,18 @@ else
endif endif
.PHONY: all debug release _windows-release _macos-release _linux-release \ .PHONY: all debug release _windows-release _macos-release _linux-release \
run run-debug clean rebuild show-version pack \ run run-debug clean rebuild show-version pack controllerdb \
format format-check tidy tidy-fix cppcheck hooks-install help format format-check tidy tidy-fix cppcheck hooks-install help
# Còpia del gamecontrollerdb.txt (si existeix) al directori de build, perquè
# director.cpp el resolgui via resource_base = directori de l'executable.
# Silenciós si el fitxer no existeix (l'usuari encara no ha fet `make controllerdb`).
ifeq ($(OS),Windows_NT)
CP_CONTROLLERDB = @powershell -Command "if (Test-Path 'gamecontrollerdb.txt') { Copy-Item 'gamecontrollerdb.txt' -Destination '$(BUILDDIR)' -Force }"
else
CP_CONTROLLERDB = @if [ -f gamecontrollerdb.txt ]; then cp gamecontrollerdb.txt $(BUILDDIR)/; fi
endif
# ============================================================================== # ==============================================================================
# COMPILACIÓ # COMPILACIÓ
# ============================================================================== # ==============================================================================
@@ -98,10 +107,12 @@ endif
all: all:
@cmake -S . -B $(BUILDDIR) $(CMAKE_GEN) -DCMAKE_BUILD_TYPE=Release $(CMAKE_DEFS) @cmake -S . -B $(BUILDDIR) $(CMAKE_GEN) -DCMAKE_BUILD_TYPE=Release $(CMAKE_DEFS)
@cmake --build $(BUILDDIR) -j$(JOBS) @cmake --build $(BUILDDIR) -j$(JOBS)
$(CP_CONTROLLERDB)
debug: debug:
@cmake -S . -B $(BUILDDIR) $(CMAKE_GEN) -DCMAKE_BUILD_TYPE=Debug $(CMAKE_DEFS) @cmake -S . -B $(BUILDDIR) $(CMAKE_GEN) -DCMAKE_BUILD_TYPE=Debug $(CMAKE_DEFS)
@cmake --build $(BUILDDIR) -j$(JOBS) @cmake --build $(BUILDDIR) -j$(JOBS)
$(CP_CONTROLLERDB)
run: all run: all
@./$(BUILDDIR)/$(PROJECT) @./$(BUILDDIR)/$(PROJECT)
@@ -138,6 +149,7 @@ _linux-release:
# Còpia de fitxers # Còpia de fitxers
cp $(BUILDDIR)/resources.pack "$(RELEASE_FOLDER)" cp $(BUILDDIR)/resources.pack "$(RELEASE_FOLDER)"
cp gamecontrollerdb.txt "$(RELEASE_FOLDER)"
cp README.md "$(RELEASE_FOLDER)" cp README.md "$(RELEASE_FOLDER)"
@[ -f LICENSE ] && cp LICENSE "$(RELEASE_FOLDER)" || true @[ -f LICENSE ] && cp LICENSE "$(RELEASE_FOLDER)" || true
cp "$(TARGET_FILE)" "$(RELEASE_FILE)" cp "$(TARGET_FILE)" "$(RELEASE_FILE)"
@@ -166,6 +178,7 @@ _windows-release:
@powershell -Command "if (-not (Test-Path '$(RELEASE_FOLDER)')) {New-Item '$(RELEASE_FOLDER)' -ItemType Directory}" @powershell -Command "if (-not (Test-Path '$(RELEASE_FOLDER)')) {New-Item '$(RELEASE_FOLDER)' -ItemType Directory}"
@powershell -Command "Copy-Item -Path '$(BUILDDIR)/resources.pack' -Destination '$(RELEASE_FOLDER)'" @powershell -Command "Copy-Item -Path '$(BUILDDIR)/resources.pack' -Destination '$(RELEASE_FOLDER)'"
@powershell -Command "Copy-Item 'gamecontrollerdb.txt' -Destination '$(RELEASE_FOLDER)'"
@powershell -Command "if (Test-Path 'LICENSE') { Copy-Item 'LICENSE' -Destination '$(RELEASE_FOLDER)' }" @powershell -Command "if (Test-Path 'LICENSE') { Copy-Item 'LICENSE' -Destination '$(RELEASE_FOLDER)' }"
@powershell -Command "Copy-Item 'README.md' -Destination '$(RELEASE_FOLDER)'" @powershell -Command "Copy-Item 'README.md' -Destination '$(RELEASE_FOLDER)'"
@powershell -Command "if (Test-Path 'release\windows\dll') { Copy-Item 'release\windows\dll\*.dll' -Destination '$(RELEASE_FOLDER)' }" @powershell -Command "if (Test-Path 'release\windows\dll') { Copy-Item 'release\windows\dll\*.dll' -Destination '$(RELEASE_FOLDER)' }"
@@ -206,6 +219,7 @@ _macos-release:
# Còpia de recursos i metadades del bundle # Còpia de recursos i metadades del bundle
cp $(BUILDDIR)/arm/resources.pack "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources" cp $(BUILDDIR)/arm/resources.pack "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
cp gamecontrollerdb.txt "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
cp -R release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64/SDL3.framework "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks" cp -R release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64/SDL3.framework "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks"
cp release/icons/icon.icns "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources" cp release/icons/icon.icns "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
cp release/macos/Info.plist "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents" cp release/macos/Info.plist "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents"
@@ -274,6 +288,19 @@ pack:
@cmake --build $(BUILDDIR) --target pack_resources @cmake --build $(BUILDDIR) --target pack_resources
@./$(BUILDDIR)/pack_resources data $(BUILDDIR)/resources.pack @./$(BUILDDIR)/pack_resources data $(BUILDDIR)/resources.pack
# ==============================================================================
# DESCÀRREGA DE GAMECONTROLLERDB
# ==============================================================================
# Descarrega l'última versió de gamecontrollerdb.txt (mappings de gamepads
# mantinguts per la comunitat) a l'arrel del projecte. SDL el carrega via
# filesystem real (no dins resources.pack) i s'ha de copiar al costat del binari
# en cada build (gestionat per CP_CONTROLLERDB a `all`/`debug` i pels release targets).
controllerdb:
@echo "Descargando gamecontrollerdb.txt..."
curl -fsSL https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/master/gamecontrollerdb.txt \
-o gamecontrollerdb.txt
@echo "gamecontrollerdb.txt actualizado"
# ============================================================================== # ==============================================================================
# CODE QUALITY (delegats a cmake) # CODE QUALITY (delegats a cmake)
# ============================================================================== # ==============================================================================
+1 -1
View File
@@ -3,7 +3,7 @@ name: bullet
# Shape de la bala. El bounding_radius del .shp dóna el hitbox base (~3 px); # Shape de la bala. El bounding_radius del .shp dóna el hitbox base (~3 px);
# scale el modula visualment i pel hitbox. # scale el modula visualment i pel hitbox.
shape: shape:
path: bullet.shp path: bullet/basic.shp
scale: 1.0 scale: 1.0
collision_factor: 1.0 collision_factor: 1.0
@@ -0,0 +1,21 @@
name: bullet_double
# Variant de bala "anular" (dos cercles concèntrics, aspecte d'aura de plasma).
# Pensada per a contra-atacs d'enemic (ex: orb dispara una bullet_double al
# jugador quan rep un impacte). Mateixa física que la bala bàsica del player;
# canvien la forma (cercle doble) i el color per llegir-se com a tret enemic
# distintiu (groc verdós vs. el verd laser del player o el roig de bullet_long).
shape:
path: bullet/double.shp
scale: 1.5
collision_factor: 1.0
physics:
mass: 0.5
restitution: 0.0
linear_damping: 0.0
angular_damping: 0.0
impact_momentum_factor: 4.0
colors:
normal: [200, 255, 80] # groc verdós (chartreuse) — contra-atac de l'orb
+1 -1
View File
@@ -4,7 +4,7 @@ name: bullet_long
# jugador i amb prou marge per reaccionar. La velocitat NO viu aquí: es passa # jugador i amb prou marge per reaccionar. La velocitat NO viu aquí: es passa
# a Bullet::fire() i la decideix qui dispara (l'AiTickAction). # a Bullet::fire() i la decideix qui dispara (l'AiTickAction).
shape: shape:
path: bullet_long.shp path: bullet/long.shp
scale: 1.0 scale: 1.0
collision_factor: 0.5 collision_factor: 0.5
@@ -1,12 +1,12 @@
name: big_pentagon name: orb
ai_type: big_pentagon # Validat contra el directori; mapeja a EnemyType::BIG_PENTAGON. ai_type: orb # Validat contra el directori; mapeja a EnemyType::ORB.
# Shape circular pròpia (anell exterior + anell interior + 6 radis + nucli), # Shape circular pròpia (anell exterior + anell interior + 6 radis + nucli),
# pensada per llegir-se com a "reactor / orb" amb més detall que els enemics # pensada per llegir-se com a "reactor / orb" amb més detall que els enemics
# petits. # petits.
shape: shape:
path: enemy_big_orb.shp path: enemy/orb.shp
scale: 1.5 scale: 1.0
collision_factor: 1.0 collision_factor: 1.0
physics: physics:
@@ -55,7 +55,7 @@ spawn:
safety_distance: 54.0 # 1.5× del normal (alineat amb scale 1.5). safety_distance: 54.0 # 1.5× del normal (alineat amb scale 1.5).
colors: colors:
normal: [66, 195, 208] # #42C3D0 — turquesa-cyan distintiu per al boss. normal: [255, 140, 110] # taronja rosat (coral) — distintiu del boss orb.
wounded: [255, 220, 60] wounded: [255, 220, 60]
score: 500 # 5x un enemic normal: aguanta 10x més. score: 500 # 5x un enemic normal: aguanta 10x més.
@@ -69,10 +69,14 @@ health: 10
events: events:
on_hit: on_hit:
- action: fire_bullet # contra-atac: dispara bullet_double dirigida al jugador
bullet: bullet_double
bullet_speed: 200.0
aim_mode: aimed
- action: decrease_health # primer: si arriba a 0 dispara on_no_health - action: decrease_health # primer: si arriba a 0 dispara on_no_health
#- action: flash # feedback visual de damage parcial #- action: flash # feedback visual de damage parcial
- action: create_debris_partial # xip a 0.3x mida (sense ser letal) - action: create_debris_partial # xip a 0.3x mida (sense ser letal)
- action: create_fireworks_small # espurna a cada hit (12 punts, lent) #- action: create_fireworks_small # espurna a cada hit (12 punts, lent)
- action: apply_impulse # empenta el cos (skip si will_die) - action: apply_impulse # empenta el cos (skip si will_die)
on_no_health: on_no_health:
- action: destroy # mort directa, sense wounded - action: destroy # mort directa, sense wounded
+1 -1
View File
@@ -2,7 +2,7 @@ name: pentagon
ai_type: pentagon # Validat contra el directori; mapeja a EnemyType::PENTAGON. ai_type: pentagon # Validat contra el directori; mapeja a EnemyType::PENTAGON.
shape: shape:
path: enemy_pentagon.shp path: enemy/pentagon.shp
scale: 1.0 # multiplicador visual + hitbox sobre la mida nativa del .shp scale: 1.0 # multiplicador visual + hitbox sobre la mida nativa del .shp
collision_factor: 1.0 # ajust opcional del hitbox (default 1.0) collision_factor: 1.0 # ajust opcional del hitbox (default 1.0)
+1 -1
View File
@@ -2,7 +2,7 @@ name: pinwheel
ai_type: pinwheel # Validat contra el directori; mapeja a EnemyType::PINWHEEL. ai_type: pinwheel # Validat contra el directori; mapeja a EnemyType::PINWHEEL.
shape: shape:
path: enemy_pinwheel.shp path: enemy/pinwheel.shp
scale: 1.0 # multiplicador visual + hitbox sobre la mida nativa del .shp scale: 1.0 # multiplicador visual + hitbox sobre la mida nativa del .shp
collision_factor: 1.0 # ajust opcional del hitbox (default 1.0) collision_factor: 1.0 # ajust opcional del hitbox (default 1.0)
+2 -2
View File
@@ -1,7 +1,7 @@
name: player_ship name: player_ship
# Shape de la nau. Resolt per ShapeLoader (busca a "shapes/<path>"). # Shape de la nau. Resolt per ShapeLoader (busca a "shapes/<path>").
# Nota: el segon jugador rep un override del shape ("ship2.shp") al ctor. # Nota: el segon jugador rep un override del shape ("ship/wedge.shp") al ctor.
# Quan s'introdueixin variants reals de nau, es crearà un YAML separat # Quan s'introdueixin variants reals de nau, es crearà un YAML separat
# per cada model. # per cada model.
# #
@@ -10,7 +10,7 @@ name: player_ship
# automàtic de la shape; tocar només si el feel del hitbox # automàtic de la shape; tocar només si el feel del hitbox
# no quadra amb la silueta visual (default 1.0). # no quadra amb la silueta visual (default 1.0).
shape: shape:
path: ship.shp path: ship/arrow.shp
scale: 1.0 scale: 1.0
collision_factor: 1.0 collision_factor: 1.0
+1 -1
View File
@@ -2,7 +2,7 @@ name: square
ai_type: square # Validat contra el directori; mapeja a EnemyType::SQUARE. ai_type: square # Validat contra el directori; mapeja a EnemyType::SQUARE.
shape: shape:
path: enemy_square.shp path: enemy/square.shp
scale: 1.0 # multiplicador visual + hitbox sobre la mida nativa del .shp scale: 1.0 # multiplicador visual + hitbox sobre la mida nativa del .shp
collision_factor: 1.0 # ajust opcional del hitbox (default 1.0) collision_factor: 1.0 # ajust opcional del hitbox (default 1.0)
+1 -1
View File
@@ -2,7 +2,7 @@ name: star
ai_type: star # Validat contra el directori; mapeja a EnemyType::STAR. ai_type: star # Validat contra el directori; mapeja a EnemyType::STAR.
shape: shape:
path: star_5.shp path: enemy/star.shp
scale: 0.7 # Lleugerament més petit que els altres enemics per diferenciar visualment. scale: 0.7 # Lleugerament més petit que els altres enemics per diferenciar visualment.
collision_factor: 1.0 collision_factor: 1.0
@@ -1,6 +1,6 @@
# bullet.shp - Projectil (octàgon, radi=3) # bullet/basic.shp - Projectil (octàgon, radi=3)
name: bullet name: basic
scale: 1.0 scale: 1.0
center: 0, 0 center: 0, 0
@@ -1,4 +1,4 @@
# bullet_double.shp - Bala anular (dos cercles concèntrics) # bullet/double.shp - Bala anular (dos cercles concèntrics)
# © 2026 JailDesigner # © 2026 JailDesigner
# #
# Dos octàgons concèntrics al centre (0,0): # Dos octàgons concèntrics al centre (0,0):
@@ -6,7 +6,7 @@
# - Interior: radi 2 (lleugerament més petit que la bala estàndard) # - Interior: radi 2 (lleugerament més petit que la bala estàndard)
# Aspecte d'anell / aura de plasma. Bounding radius natiu = 4. # Aspecte d'anell / aura de plasma. Bounding radius natiu = 4.
name: bullet_double name: double
scale: 1.0 scale: 1.0
center: 0, 0 center: 0, 0
@@ -1,4 +1,4 @@
# bullet_long.shp - Bala allargada vertical (dos mig-octàgons + dos costats) # bullet/long.shp - Bala allargada vertical (dos mig-octàgons + dos costats)
# © 2026 JailDesigner # © 2026 JailDesigner
# #
# Càpsula orientada al llarg de l'eix Y: la bala viatja segons el seu angle # Càpsula orientada al llarg de l'eix Y: la bala viatja segons el seu angle
@@ -15,7 +15,7 @@
# Bounding radius natiu = 6 (extrem vertical a y=±6). # Bounding radius natiu = 6 (extrem vertical a y=±6).
# collision_factor al YAML compensa el bounding doble (0.5 → hitbox ≈ 3). # collision_factor al YAML compensa el bounding doble (0.5 → hitbox ≈ 3).
name: bullet_long name: long
scale: 1.0 scale: 1.0
center: 0, 0 center: 0, 0
@@ -1,7 +1,7 @@
# star.shp - Estrella per a starfield # effect/starfield.shp - Estrella per a starfield
# © 2026 JailDesigner # © 2026 JailDesigner
name: star name: starfield
scale: 1.0 scale: 1.0
center: 0, 0 center: 0, 0
@@ -1,4 +1,4 @@
# title_flash.shp - Sparkle 4-puntes amb costats còncaus (Atari-style) # effect/title_flash.shp - Sparkle 4-puntes amb costats còncaus (Atari-style)
# 4 puntes als cardinals (radi 30) i valls còncaus als 45° (corba Bezier # 4 puntes als cardinals (radi 30) i valls còncaus als 45° (corba Bezier
# quadràtica amb control point ±8). 5 punts per arc subdividint la corba. # quadràtica amb control point ±8). 5 punts per arc subdividint la corba.
@@ -1,4 +1,4 @@
# enemy_big_orb.shp - ORNI enemic gegant (orb circular, doble anell amb radis) # enemy/orb.shp - ORNI enemic gegant (orb circular, doble anell amb radis)
# © 2026 JailDesigner # © 2026 JailDesigner
# #
# Forma "reactor / boss circular" — més detall que els enemics petits perquè # Forma "reactor / boss circular" — més detall que els enemics petits perquè
@@ -9,7 +9,7 @@
# - Petit "+" central com a nucli. # - Petit "+" central com a nucli.
# Bounding radius natiu = 20 (alineat amb la resta d'enemics). # Bounding radius natiu = 20 (alineat amb la resta d'enemics).
name: enemy_big_orb name: orb
scale: 1.0 scale: 1.0
center: 0, 0 center: 0, 0
@@ -1,6 +1,6 @@
# enemy_pentagon.shp - ORNI enemic (pentàgon doble concentric, radi exterior=20) # enemy/pentagon.shp - ORNI enemic (pentàgon doble concentric, radi exterior=20)
name: enemy_pentagon name: pentagon
scale: 1.0 scale: 1.0
center: 0, 0 center: 0, 0
@@ -1,7 +1,7 @@
# enemy_pinwheel.shp - ORNI enemic (molinillo de 4 triangles) # enemy/pinwheel.shp - ORNI enemic (molinillo de 4 triangles)
# © 2026 JailDesigner # © 2026 JailDesigner
name: enemy_pinwheel name: pinwheel
scale: 1.0 scale: 1.0
center: 0, 0 center: 0, 0
@@ -1,6 +1,6 @@
# enemy_square.shp - ORNI enemic (rombe, radi=20) + ull amb pupil·la al centre # enemy/square.shp - ORNI enemic (rombe, radi=20) + ull amb pupil·la al centre
name: enemy_square name: square
scale: 1.0 scale: 1.0
center: 0, 0 center: 0, 0
@@ -1,4 +1,4 @@
# star_5.shp - ORNI enemic (estrella de 5 puntes, només perímetre) # enemy/star.shp - ORNI enemic (estrella de 5 puntes, només perímetre)
# © 2026 JailDesigner # © 2026 JailDesigner
# #
# Pentagrama clàssic: 5 vèrtexs exteriors (radi 20) alternant amb 5 vèrtexs # Pentagrama clàssic: 5 vèrtexs exteriors (radi 20) alternant amb 5 vèrtexs
@@ -8,7 +8,7 @@
# Sense línies interiors: una única polyline que recorre el perímetre. # Sense línies interiors: una única polyline que recorre el perímetre.
# Bounding radius natiu ≈ 20 (alineat amb pentagon/square/pinwheel). # Bounding radius natiu ≈ 20 (alineat amb pentagon/square/pinwheel).
name: star_5 name: star
scale: 1.0 scale: 1.0
center: 0, 0 center: 0, 0
-8
View File
@@ -1,8 +0,0 @@
# ship.shp - Nau del jugador 1
# Triangle amb base còncava (punta de fletxa)
name: ship
scale: 1.0
center: 0, 0
polyline: 0,-12 8.49,8.49 0,4 -8.49,8.49 0,-12
+7
View File
@@ -0,0 +1,7 @@
# ship/arrow.shp - Nau del jugador 1 (triangle amb base còncava, punta de fletxa)
name: arrow
scale: 1.0
center: 0, 0
polyline: 0,-12 8.49,8.49 0,4 -8.49,8.49 0,-12
@@ -1,7 +1,7 @@
# ship2.shp - Nau del jugador 2 (interceptor amb ales) # ship/interceptor.shp - Interceptor amb ales laterals pronunciades
# © 2026 JailDesigner # © 2026 JailDesigner
name: ship2 name: interceptor
scale: 1.0 scale: 1.0
center: 0, 0 center: 0, 0
@@ -1,7 +1,6 @@
# ship2.shp - Nau del jugador 2 # ship/wedge.shp - Nau del jugador 2 (triangle amb cercle central)
# Triangle amb cercle central (distintiu visual)
name: ship2 name: wedge
scale: 1.0 scale: 1.0
center: 0, 0 center: 0, 0
Binary file not shown.
+14 -13
View File
@@ -9,7 +9,7 @@
# - { timeout: T } → quan han passat T segons des de l'inici de la wave. # - { timeout: T } → quan han passat T segons des de l'inici de la wave.
# - { all_dead: true, timeout: T } → el que arribe abans (amuntegament si vas lent). # - { all_dead: true, timeout: T } → el que arribe abans (amuntegament si vas lent).
# #
# Tipus d'enemic: pentagon, square (alias: cuadrado), pinwheel (alias: molinillo), star, big_pentagon. # Tipus d'enemic: pentagon, square (alias: cuadrado), pinwheel (alias: molinillo), star, orb.
metadata: metadata:
version: "2.0" version: "2.0"
@@ -18,10 +18,11 @@ metadata:
stages: stages:
# STAGE 1 — Tutorial: contacte amb pentagons i un cuadrado. # STAGE 1 — Tutorial: contacte amb pentagons i un cuadrado.
# (Test: també hi ha un orb a la primera onada per provar el contra-atac.)
- stage_id: 1 - stage_id: 1
multipliers: { velocity: 0.85, rotation: 0.9, tracking: 0.3 } multipliers: { velocity: 0.85, rotation: 0.9, tracking: 0.3 }
waves: waves:
- spawn: [pentagon, pentagon] - spawn: [pentagon, pentagon, orb]
spawn_interval: 0.6 spawn_interval: 0.6
next: all_dead next: all_dead
- spawn: [pentagon, pentagon, square] - spawn: [pentagon, pentagon, square]
@@ -47,14 +48,14 @@ stages:
spawn_interval: 0.5 spawn_interval: 0.5
next: end next: end
# STAGE 3 — Primer big_pentagon (HP=10). # STAGE 3 — Primer orb (HP=10).
- stage_id: 3 - stage_id: 3
multipliers: { velocity: 1.0, rotation: 1.0, tracking: 0.5 } multipliers: { velocity: 1.0, rotation: 1.0, tracking: 0.5 }
waves: waves:
- spawn: [pentagon, pentagon, square] - spawn: [pentagon, pentagon, square]
spawn_interval: 0.4 spawn_interval: 0.4
next: all_dead next: all_dead
- spawn: [big_pentagon] - spawn: [orb]
next: { all_dead: true, timeout: 12.0 } next: { all_dead: true, timeout: 12.0 }
- spawn: [pinwheel, pinwheel] - spawn: [pinwheel, pinwheel]
spawn_interval: 0.5 spawn_interval: 0.5
@@ -76,7 +77,7 @@ stages:
- spawn: [pinwheel, pinwheel, pinwheel] - spawn: [pinwheel, pinwheel, pinwheel]
spawn_interval: 0.4 spawn_interval: 0.4
next: all_dead next: all_dead
- spawn: [big_pentagon, pentagon, pentagon] - spawn: [orb, pentagon, pentagon]
spawn_interval: 0.5 spawn_interval: 0.5
next: end next: end
@@ -93,7 +94,7 @@ stages:
- spawn: [pinwheel, pinwheel, star, star] - spawn: [pinwheel, pinwheel, star, star]
spawn_interval: 0.4 spawn_interval: 0.4
next: all_dead next: all_dead
- spawn: [big_pentagon, square, square] - spawn: [orb, square, square]
spawn_interval: 0.5 spawn_interval: 0.5
next: end next: end
@@ -110,7 +111,7 @@ stages:
- spawn: [pinwheel, pinwheel, pinwheel] - spawn: [pinwheel, pinwheel, pinwheel]
spawn_interval: 0.3 spawn_interval: 0.3
next: all_dead next: all_dead
- spawn: [big_pentagon, pinwheel, pinwheel] - spawn: [orb, pinwheel, pinwheel]
spawn_interval: 0.4 spawn_interval: 0.4
next: end next: end
@@ -127,7 +128,7 @@ stages:
- spawn: [star, star, star] - spawn: [star, star, star]
spawn_interval: 0.4 spawn_interval: 0.4
next: all_dead next: all_dead
- spawn: [big_pentagon, pinwheel, pinwheel, square] - spawn: [orb, pinwheel, pinwheel, square]
spawn_interval: 0.5 spawn_interval: 0.5
next: end next: end
@@ -141,7 +142,7 @@ stages:
- spawn: [square, square, star, star] - spawn: [square, square, star, star]
spawn_interval: 0.3 spawn_interval: 0.3
next: { all_dead: true, timeout: 5.0 } next: { all_dead: true, timeout: 5.0 }
- spawn: [big_pentagon] - spawn: [orb]
next: { all_dead: true, timeout: 8.0 } next: { all_dead: true, timeout: 8.0 }
- spawn: [pinwheel, pinwheel, square, star, pentagon] - spawn: [pinwheel, pinwheel, square, star, pentagon]
spawn_interval: 0.3 spawn_interval: 0.3
@@ -154,13 +155,13 @@ stages:
- spawn: [pinwheel, pinwheel, star, star] - spawn: [pinwheel, pinwheel, star, star]
spawn_interval: 0.3 spawn_interval: 0.3
next: { all_dead: true, timeout: 4.0 } next: { all_dead: true, timeout: 4.0 }
- spawn: [big_pentagon, square, square] - spawn: [orb, square, square]
spawn_interval: 0.4 spawn_interval: 0.4
next: { all_dead: true, timeout: 8.0 } next: { all_dead: true, timeout: 8.0 }
- spawn: [pinwheel, pinwheel, pinwheel, pinwheel] - spawn: [pinwheel, pinwheel, pinwheel, pinwheel]
spawn_interval: 0.3 spawn_interval: 0.3
next: { all_dead: true, timeout: 5.0 } next: { all_dead: true, timeout: 5.0 }
- spawn: [big_pentagon, pinwheel, pinwheel, square, star] - spawn: [orb, pinwheel, pinwheel, square, star]
spawn_interval: 0.4 spawn_interval: 0.4
next: end next: end
@@ -171,12 +172,12 @@ stages:
- spawn: [pinwheel, pinwheel, pinwheel, pinwheel] - spawn: [pinwheel, pinwheel, pinwheel, pinwheel]
spawn_interval: 0.25 spawn_interval: 0.25
next: { all_dead: true, timeout: 4.0 } next: { all_dead: true, timeout: 4.0 }
- spawn: [big_pentagon, square, star] - spawn: [orb, square, star]
spawn_interval: 0.4 spawn_interval: 0.4
next: { all_dead: true, timeout: 6.0 } next: { all_dead: true, timeout: 6.0 }
- spawn: [pinwheel, pinwheel, star, star, square] - spawn: [pinwheel, pinwheel, star, star, square]
spawn_interval: 0.3 spawn_interval: 0.3
next: { all_dead: true, timeout: 5.0 } next: { all_dead: true, timeout: 5.0 }
- spawn: [big_pentagon, big_pentagon, pinwheel, pinwheel, star] - spawn: [orb, orb, pinwheel, pinwheel, star]
spawn_interval: 0.4 spawn_interval: 0.4
next: end next: end
File diff suppressed because it is too large Load Diff
+4 -3
View File
@@ -35,10 +35,11 @@ namespace Defaults::Music {
namespace Defaults::Sound { namespace Defaults::Sound {
constexpr const char* CONTINUE = "effects/continue.wav"; // Cuenta atras constexpr const char* CONTINUE = "effects/continue.wav"; // Cuenta atras
constexpr const char* EXPLOSION = "effects/explosion.wav"; // Explosión constexpr const char* ENEMY_EXPLOSION = "effects/enemy_explosion.wav"; // Explosió d'enemic (debris default)
constexpr const char* EXPLOSION2 = "effects/explosion2.wav"; // Explosión alternativa constexpr const char* ENEMY_HIT = "effects/enemy_hit.wav"; // Impacte parcial a enemic (debris_partial — HP > 1)
constexpr const char* PLAYER_EXPLOSION = "effects/player_explosion.wav"; // Explosió de la nau del jugador
constexpr const char* FRIENDLY_FIRE_HIT = "effects/friendly_fire.wav"; // Friendly fire hit constexpr const char* FRIENDLY_FIRE_HIT = "effects/friendly_fire.wav"; // Friendly fire hit
constexpr const char* HIT = "effects/hit.wav"; // Enemic ferit (primer impacte → HURT) constexpr const char* BULLET_ZAP = "effects/bullet_zap.wav"; // Bala desintegrant-se (qualsevol impacte o eixida de playarea)
constexpr const char* HURT = "effects/hurt.wav"; // Nau pròpia entra a HURT constexpr const char* HURT = "effects/hurt.wav"; // Nau pròpia entra a HURT
constexpr const char* INIT_HUD = "effects/init_hud.wav"; // Para la animación del HUD constexpr const char* INIT_HUD = "effects/init_hud.wav"; // Para la animación del HUD
constexpr const char* LASER = "effects/laser_shoot.wav"; // Disparo constexpr const char* LASER = "effects/laser_shoot.wav"; // Disparo
+11
View File
@@ -12,6 +12,17 @@ namespace Defaults::Hud {
constexpr float SCOREBOARD_TEXT_SCALE = 0.85F; constexpr float SCOREBOARD_TEXT_SCALE = 0.85F;
constexpr float SCOREBOARD_TEXT_SPACING = 0.0F; constexpr float SCOREBOARD_TEXT_SPACING = 0.0F;
// Colors per segment del marcador. Jerarquia per funció (score/vides/nivell)
// + diferenciació de jugador (P1 blanc vs P2 rosa) sense xocar amb els
// colors d'enemics (cyan/roig). Amb alpha=255 el line_renderer usa el color
// directament sense caure al fallback verd (Rendering::DEFAULT_LINE_COLOR).
namespace Colors {
constexpr SDL_Color SCORE_P1 = {.r = 255, .g = 255, .b = 255, .a = 255}; // blanc
constexpr SDL_Color SCORE_P2 = {.r = 255, .g = 130, .b = 200, .a = 255}; // rosa magenta
constexpr SDL_Color LIVES = {.r = 255, .g = 180, .b = 60, .a = 255}; // ambre / or
constexpr SDL_Color LEVEL = {.r = 155, .g = 255, .b = 175, .a = 255}; // verd sistema
} // namespace Colors
// Animación de entrada del HUD (init_hud_animator). // Animación de entrada del HUD (init_hud_animator).
namespace InitAnim { namespace InitAnim {
// Spawn vertical de la nave: 50 px bajo la PLAYAREA (sale desde fuera). // Spawn vertical de la nave: 50 px bajo la PLAYAREA (sale desde fuera).
+2 -2
View File
@@ -6,8 +6,8 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
// Paleta semántica por tipo de entidad. Si una entity declara color, lo // Paleta semántica por tipo de entidad. Si una entity declara color, lo
// pasa al pipeline con alpha=255 (sentinela "color válido"); si no, se // pasa al pipeline con alpha=255 (sentinela "color válido"); si no,
// usa el color global del oscilador (g_current_line_color). // line_renderer::linea() cau a DEFAULT_LINE_COLOR (verd fòsfor fallback).
namespace Defaults::Palette { namespace Defaults::Palette {
// Paleta neon: pujada lleugera dels canals secundaris per millorar la // Paleta neon: pujada lleugera dels canals secundaris per millorar la
+6 -3
View File
@@ -3,7 +3,6 @@
#pragma once #pragma once
#include <algorithm>
#include <array> #include <array>
namespace Defaults::Rendering { namespace Defaults::Rendering {
@@ -35,8 +34,12 @@ namespace Defaults::Rendering {
constexpr int RENDER_HEIGHT_DEFAULT = 720; constexpr int RENDER_HEIGHT_DEFAULT = 720;
constexpr auto isValidRenderResolution(int w, int h) -> bool { constexpr auto isValidRenderResolution(int w, int h) -> bool {
return std::ranges::any_of(RESOLUTION_PRESETS, for (const auto& preset : RESOLUTION_PRESETS) {
[w, h](const ResolutionPreset& preset) { return preset.w == w && preset.h == h; }); if (preset.w == w && preset.h == h) {
return true;
}
}
return false;
} }
} // namespace Defaults::Rendering } // namespace Defaults::Rendering
+4 -4
View File
@@ -51,12 +51,12 @@ namespace Graphics {
namespace { namespace {
// Lerp de l'oscil·lador (color base actual) cap a un color "flash" en // Lerp del color base verd fòsfor cap a un color "flash" en funció de
// funció de f ∈ [0, 1]. Retorna sempre amb alpha>0 perquè el line_renderer // f ∈ [0, 1]. Retorna sempre amb alpha>0 perquè el line_renderer l'usi
// l'use directament (sense barrejar amb el global). // directament (sense caure al fallback DEFAULT_LINE_COLOR).
auto lerpColor(SDL_Color flash, float f) -> SDL_Color { auto lerpColor(SDL_Color flash, float f) -> SDL_Color {
const float CLAMPED = std::clamp(f, 0.0F, 1.0F); const float CLAMPED = std::clamp(f, 0.0F, 1.0F);
const SDL_Color BASE = Rendering::getLineColor(); constexpr SDL_Color BASE = Rendering::DEFAULT_LINE_COLOR;
const auto LERP_U8 = [&](unsigned char a, unsigned char b) { const auto LERP_U8 = [&](unsigned char a, unsigned char b) {
const float OUT = (static_cast<float>(a) * (1.0F - CLAMPED)) + (static_cast<float>(b) * CLAMPED); const float OUT = (static_cast<float>(a) * (1.0F - CLAMPED)) + (static_cast<float>(b) * CLAMPED);
return static_cast<unsigned char>(OUT); return static_cast<unsigned char>(OUT);
+1 -1
View File
@@ -20,7 +20,7 @@ namespace Graphics {
return it->second; // Cache hit return it->second; // Cache hit
} }
// Normalize path: "ship.shp" → "shapes/ship.shp" // Normalize path: "ship/arrow.shp" → "shapes/ship/arrow.shp"
// "logo/letra_j.shp" → "shapes/logo/letra_j.shp" // "logo/letra_j.shp" → "shapes/logo/letra_j.shp"
std::string normalized = filename; std::string normalized = filename;
if (!normalized.starts_with("shapes/")) { if (!normalized.starts_with("shapes/")) {
+1 -1
View File
@@ -19,7 +19,7 @@ namespace Graphics {
// Carregar shape desde file (con caché) // Carregar shape desde file (con caché)
// Retorna punter compartit (nullptr si error) // Retorna punter compartit (nullptr si error)
// Exemple: load("ship.shp") → busca a "data/shapes/ship.shp" // Exemple: load("ship/arrow.shp") → busca a "data/shapes/ship/arrow.shp"
static auto load(const std::string& filename) -> std::shared_ptr<Shape>; static auto load(const std::string& filename) -> std::shared_ptr<Shape>;
// Netejar caché (útil per debug/recàrrega) // Netejar caché (útil per debug/recàrrega)
+2 -2
View File
@@ -26,7 +26,7 @@ namespace Graphics {
// - scale: factor de scale (1.0 = 20×40 px por carácter) // - scale: factor de scale (1.0 = 20×40 px por carácter)
// - spacing: espacio entre caracteres en píxeles (a scale 1.0) // - spacing: espacio entre caracteres en píxeles (a scale 1.0)
// - brightness: factor de brightness (0.0-1.0, default 1.0 = màxima brightness) // - brightness: factor de brightness (0.0-1.0, default 1.0 = màxima brightness)
// - color: color RGBA explícit; si alpha==0 (default) s'usa l'oscil·lador global // - color: color RGBA explícit; si alpha==0 (default) es fa fallback a Rendering::DEFAULT_LINE_COLOR
void render(const std::string& text, const Vec2& position, float scale = 1.0F, float spacing = 2.0F, float brightness = 1.0F, SDL_Color color = {0, 0, 0, 0}) const; void render(const std::string& text, const Vec2& position, float scale = 1.0F, float spacing = 2.0F, float brightness = 1.0F, SDL_Color color = {0, 0, 0, 0}) const;
// Renderizar string centrado en un punto // Renderizar string centrado en un punto
@@ -35,7 +35,7 @@ namespace Graphics {
// - scale: factor de scale (1.0 = 20×40 px por carácter) // - scale: factor de scale (1.0 = 20×40 px por carácter)
// - spacing: espacio entre caracteres en píxeles (a scale 1.0) // - spacing: espacio entre caracteres en píxeles (a scale 1.0)
// - brightness: factor de brightness (0.0-1.0, default 1.0 = màxima brightness) // - brightness: factor de brightness (0.0-1.0, default 1.0 = màxima brightness)
// - color: color RGBA explícit; si alpha==0 (default) s'usa l'oscil·lador global // - color: color RGBA explícit; si alpha==0 (default) es fa fallback a Rendering::DEFAULT_LINE_COLOR
void renderCentered(const std::string& text, const Vec2& centre_point, float scale = 1.0F, float spacing = 2.0F, float brightness = 1.0F, SDL_Color color = {0, 0, 0, 0}) const; void renderCentered(const std::string& text, const Vec2& centre_point, float scale = 1.0F, float spacing = 2.0F, float brightness = 1.0F, SDL_Color color = {0, 0, 0, 0}) const;
// Calcular ancho total de un string (útil para centrado). // Calcular ancho total de un string (útil para centrado).
+1 -1
View File
@@ -4,7 +4,7 @@
// Mesh3D = llista de vèrtexs Vec3 + llista d'arestes (parells d'índexs). // Mesh3D = llista de vèrtexs Vec3 + llista d'arestes (parells d'índexs).
// drawWireframe() aplica una Transform3D al mesh, projecta amb Camera3D i // drawWireframe() aplica una Transform3D al mesh, projecta amb Camera3D i
// emet cada aresta com una línia 2D pel pipeline `Rendering::linea` (mateix // emet cada aresta com una línia 2D pel pipeline `Rendering::linea` (mateix
// pipeline que la resta del joc: glow verd via ColorOscillator si color.a==0). // pipeline que la resta del joc: verd fòsfor via Rendering::DEFAULT_LINE_COLOR si color.a==0).
// //
// Sense depth buffer: el caller és responsable d'ordenar els meshos per // Sense depth buffer: el caller és responsable d'ordenar els meshos per
// profunditat decreixent si vol oclusió coherent (la pipeline és LINE_LIST // profunditat decreixent si vol oclusió coherent (la pipeline és LINE_LIST
+6 -4
View File
@@ -3,7 +3,6 @@
#include "core/input/define_inputs.hpp" #include "core/input/define_inputs.hpp"
#include <algorithm>
#include <format> #include <format>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -154,9 +153,12 @@ namespace System {
} }
auto DefineInputs::isInUse(int code) const -> bool { auto DefineInputs::isInUse(int code) const -> bool {
return std::ranges::any_of(sequence_, [code](const Step& s) { for (const auto& s : sequence_) {
return s.captured == code; if (s.captured == code) {
}); return true;
}
}
return false;
} }
void DefineInputs::captureAndAdvance(int code) { void DefineInputs::captureAndAdvance(int code) {
+19 -11
View File
@@ -2,7 +2,6 @@
#include <SDL3/SDL.h> // Para SDL_GetGamepadAxis, SDL_GamepadAxis, SDL_GamepadButton, SDL_GetError, SDL_JoystickID, SDL_AddGamepadMappingsFromFile, SDL_Event, SDL_EventType, SDL_GetGamepadButton, SDL_GetKeyboardState, SDL_INIT_GAMEPAD, SDL_InitSubSystem, SDL_LogError, SDL_OpenGamepad, SDL_PollEvent, SDL_WasInit, Sint16, SDL_Gamepad, SDL_LogCategory, SDL_Scancode #include <SDL3/SDL.h> // Para SDL_GetGamepadAxis, SDL_GamepadAxis, SDL_GamepadButton, SDL_GetError, SDL_JoystickID, SDL_AddGamepadMappingsFromFile, SDL_Event, SDL_EventType, SDL_GetGamepadButton, SDL_GetKeyboardState, SDL_INIT_GAMEPAD, SDL_InitSubSystem, SDL_LogError, SDL_OpenGamepad, SDL_PollEvent, SDL_WasInit, Sint16, SDL_Gamepad, SDL_LogCategory, SDL_Scancode
#include <algorithm> // Para std::ranges::any_of
#include <iostream> // Para basic_ostream, operator<<, cout, cerr #include <iostream> // Para basic_ostream, operator<<, cout, cerr
#include <memory> // Para shared_ptr, __shared_ptr_access, allocator, operator==, make_shared #include <memory> // Para shared_ptr, __shared_ptr_access, allocator, operator==, make_shared
#include <unordered_map> // Para unordered_map, _Node_iterator, operator==, _Node_iterator_base, _Node_const_iterator #include <unordered_map> // Para unordered_map, _Node_iterator, operator==, _Node_iterator_base, _Node_const_iterator
@@ -166,9 +165,12 @@ auto Input::checkAnyButton(bool repeat) -> bool {
// Comprueba si algún player (P1 o P2) presionó alguna acción de una lista // Comprueba si algún player (P1 o P2) presionó alguna acción de una lista
auto Input::checkAnyPlayerAction(const std::span<const InputAction>& actions, bool repeat) -> bool { auto Input::checkAnyPlayerAction(const std::span<const InputAction>& actions, bool repeat) -> bool {
return std::ranges::any_of(actions, [this, repeat](const InputAction& action) { for (const auto& action : actions) {
return checkActionPlayer1(action, repeat) || checkActionPlayer2(action, repeat); if (checkActionPlayer1(action, repeat) || checkActionPlayer2(action, repeat)) {
}); return true;
}
}
return false;
} }
// Comprueba si hay algun mando conectado // Comprueba si hay algun mando conectado
@@ -307,8 +309,11 @@ auto Input::checkTriggerInput(Action action, const std::shared_ptr<Gamepad>& gam
} }
void Input::addGamepadMappingsFromFile() { void Input::addGamepadMappingsFromFile() {
if (SDL_AddGamepadMappingsFromFile(gamepad_mappings_file_.c_str()) < 0) { const int COUNT = SDL_AddGamepadMappingsFromFile(gamepad_mappings_file_.c_str());
std::cout << "Error, could not load " << gamepad_mappings_file_.c_str() << " file: " << SDL_GetError() << '\n'; if (COUNT < 0) {
std::cerr << "[Input] Error carregant " << gamepad_mappings_file_ << ": " << SDL_GetError() << '\n';
} else {
std::cout << "[Input] " << gamepad_mappings_file_ << " carregat (" << COUNT << " mappings)\n";
} }
} }
@@ -326,8 +331,7 @@ void Input::initSDLGamePad() {
} else { } else {
addGamepadMappingsFromFile(); addGamepadMappingsFromFile();
discoverGamepads(); discoverGamepads();
std::cout << "\n** INPUT SYSTEM **\n"; std::cout << "[Input] inicialitzat\n";
std::cout << "Input System initialized successfully\n";
} }
} }
} }
@@ -441,9 +445,13 @@ auto Input::addGamepad(int device_index) -> std::string {
} }
auto Input::removeGamepad(SDL_JoystickID id) -> std::string { auto Input::removeGamepad(SDL_JoystickID id) -> std::string {
auto it = std::ranges::find_if(gamepads_, [id](const std::shared_ptr<Gamepad>& gamepad) { auto it = gamepads_.end();
return gamepad->instance_id == id; for (auto i = gamepads_.begin(); i != gamepads_.end(); ++i) {
}); if ((*i)->instance_id == id) {
it = i;
break;
}
}
if (it != gamepads_.end()) { if (it != gamepads_.end()) {
std::string name = (*it)->name; std::string name = (*it)->name;
+5 -3
View File
@@ -3,7 +3,6 @@
#include "core/physics/physics_world.hpp" #include "core/physics/physics_world.hpp"
#include <algorithm>
#include <cmath> #include <cmath>
#include "core/physics/rigid_body.hpp" #include "core/physics/rigid_body.hpp"
@@ -14,10 +13,13 @@ namespace Physics {
if (body == nullptr) { if (body == nullptr) {
return; return;
} }
if (std::ranges::find(bodies_, body) == bodies_.end()) { for (const auto* b : bodies_) {
bodies_.push_back(body); if (b == body) {
return;
} }
} }
bodies_.push_back(body);
}
void PhysicsWorld::removeBody(RigidBody* body) { void PhysicsWorld::removeBody(RigidBody* body) {
std::erase(bodies_, body); std::erase(bodies_, body);
+2 -11
View File
@@ -8,11 +8,6 @@
namespace Rendering { namespace Rendering {
// Color global compartido para líneas sin paleta propia (HUD, debug, texto
// genérico). Equivale al "color máximo" de la antigua oscilación CPU: verde
// fósforo CRT. El pulso de brillo lo aplica ahora el shader de postpro.
SDL_Color g_current_line_color = {100, 255, 100, 255};
// Grosor global por defecto. Configurable via setLineThickness. // Grosor global por defecto. Configurable via setLineThickness.
float g_current_line_thickness = Defaults::Rendering::LINE_THICKNESS_DEFAULT; float g_current_line_thickness = Defaults::Rendering::LINE_THICKNESS_DEFAULT;
@@ -36,8 +31,8 @@ namespace Rendering {
const auto FX2 = static_cast<float>(x2); const auto FX2 = static_cast<float>(x2);
const auto FY2 = static_cast<float>(y2); const auto FY2 = static_cast<float>(y2);
// color.alpha==0 → usar color global (verde fósforo). alpha>0 → color directo. // color.alpha==0 → fallback a DEFAULT_LINE_COLOR (verd fòsfor). alpha>0 → color directo.
const SDL_Color SOURCE = (color.a > 0) ? color : g_current_line_color; const SDL_Color SOURCE = (color.a > 0) ? color : DEFAULT_LINE_COLOR;
const float R = (static_cast<float>(SOURCE.r) * brightness) / 255.0F; const float R = (static_cast<float>(SOURCE.r) * brightness) / 255.0F;
const float G = (static_cast<float>(SOURCE.g) * brightness) / 255.0F; const float G = (static_cast<float>(SOURCE.g) * brightness) / 255.0F;
const float B = (static_cast<float>(SOURCE.b) * brightness) / 255.0F; const float B = (static_cast<float>(SOURCE.b) * brightness) / 255.0F;
@@ -68,10 +63,6 @@ namespace Rendering {
} }
} }
void setLineColor(SDL_Color color) { g_current_line_color = color; }
auto getLineColor() -> SDL_Color { return g_current_line_color; }
void setLineThickness(float thickness) { void setLineThickness(float thickness) {
if (thickness > 0.0F) { if (thickness > 0.0F) {
g_current_line_thickness = thickness; g_current_line_thickness = thickness;
+11 -9
View File
@@ -3,9 +3,10 @@
// //
// El dibujo de líneas pasa por el pipeline GPU. Las coordenadas (x1,y1,x2,y2) // El dibujo de líneas pasa por el pipeline GPU. Las coordenadas (x1,y1,x2,y2)
// son lógicas (1280×720); el shader las mapea a NDC y el viewport del SDLManager // son lógicas (1280×720); el shader las mapea a NDC y el viewport del SDLManager
// hace el letterbox a píxeles físicos. El brillo modula el color global de // hace el letterbox a píxeles físicos. El pulse de brillo lo aplica el shader
// línea (lo gestiona ColorOscillator). El grosor es configurable por línea // de postpro (ya no hi ha un ColorOscillator a CPU). El grosor es configurable
// (parámetro thickness>0) o global (g_current_line_thickness vía setLineThickness). // per línia (parámetro thickness>0) o global (g_current_line_thickness vía
// setLineThickness).
#pragma once #pragma once
@@ -15,12 +16,17 @@
namespace Rendering { namespace Rendering {
// Color verd fòsfor CRT per defecte: s'usa quan el caller passa color amb
// alpha==0 (sentinella "sense color propi"). Constant immutable: la
// semàntica de "color global" ja no existeix (era de l'antic ColorOscillator).
constexpr SDL_Color DEFAULT_LINE_COLOR = {.r = 100, .g = 255, .b = 100, .a = 255};
// Dibuja una línea entre dos puntos en coordenadas lógicas (1280×720). // Dibuja una línea entre dos puntos en coordenadas lógicas (1280×720).
// brightness: factor de brillo (0.0..1.0, default 1.0 = brillo máximo). // brightness: factor de brillo (0.0..1.0, default 1.0 = brillo máximo).
// Pre-multiplica el RGB del color (color dim sobre fons negre). // Pre-multiplica el RGB del color (color dim sobre fons negre).
// thickness: grosor en píxeles lógicos. Si <= 0 usa g_current_line_thickness. // thickness: grosor en píxeles lógicos. Si <= 0 usa g_current_line_thickness.
// color: si alpha==0, se usa el color global del oscilador; si alpha>0 se // color: si alpha==0, se usa DEFAULT_LINE_COLOR (verd fòsfor fallback);
// usa este color directo (paleta semántica por entidad). // si alpha>0 se usa este color directo (paleta semántica por entidad).
// alpha: alpha que arriba al GPU (default 1.0 = opac, behavior original). // alpha: alpha que arriba al GPU (default 1.0 = opac, behavior original).
// Valors <1.0 fan que la línia es barregi de veritat sobre el dest // Valors <1.0 fan que la línia es barregi de veritat sobre el dest
// en comptes de sobrepintar-lo (útil per halos translúcids). // en comptes de sobrepintar-lo (útil per halos translúcids).
@@ -49,10 +55,6 @@ namespace Rendering {
SDL_Color color = {0, 0, 0, 0}, SDL_Color color = {0, 0, 0, 0},
SDL_Color glow_color = {0, 0, 0, 0}); SDL_Color glow_color = {0, 0, 0, 0});
// Color global de las líneas (lo actualiza ColorOscillator vía SDLManager).
void setLineColor(SDL_Color color);
[[nodiscard]] auto getLineColor() -> SDL_Color;
// Grosor global por defecto (en píxeles lógicos). Default: 1.5. // Grosor global por defecto (en píxeles lógicos). Default: 1.5.
void setLineThickness(float thickness); void setLineThickness(float thickness);
[[nodiscard]] auto getLineThickness() -> float; [[nodiscard]] auto getLineThickness() -> float;
+5 -2
View File
@@ -106,8 +106,11 @@ Director::Director(int argc, char* argv[])
// falla, Locale::text() retorna la clau crua i el joc segueix funcionant. // falla, Locale::text() retorna la clau crua i el joc segueix funcionant.
Locale::get().load(std::string("locale/") + cfg_->locale + ".yaml"); Locale::get().load(std::string("locale/") + cfg_->locale + ".yaml");
// Inicialitzar sistema de input // Inicialitzar sistema de input. El gamecontrollerdb.txt viu al costat del
Input::init("data/gamecontrollerdb.txt"); // binari (no dins de resources.pack, perquè SDL_AddGamepadMappingsFromFile
// necessita una ruta real de filesystem). resource_base ja apunta al directori
// de l'executable (o a Contents/Resources en bundles de macOS).
Input::init(resource_base + "/gamecontrollerdb.txt");
// Autoassignacio de primer arranque: si cap dels dos jugadors te mando // Autoassignacio de primer arranque: si cap dels dos jugadors te mando
// assignat al config, repartim els que hi haja detectats (P1 = pad 0, // assignat al config, repartim els que hi haja detectats (P1 = pad 0,
+1 -1
View File
@@ -64,7 +64,7 @@ namespace Effects {
const Vec2& velocitat_objecte = {.x = 0.0F, .y = 0.0F}, const Vec2& velocitat_objecte = {.x = 0.0F, .y = 0.0F},
float velocitat_angular = 0.0F, float velocitat_angular = 0.0F,
float factor_herencia_visual = 0.0F, float factor_herencia_visual = 0.0F,
const std::string& sound = Defaults::Sound::EXPLOSION, const std::string& sound = Defaults::Sound::ENEMY_EXPLOSION,
SDL_Color color = {0, 0, 0, 0}, // alpha==0 → fragmentos usan oscilador global SDL_Color color = {0, 0, 0, 0}, // alpha==0 → fragmentos usan oscilador global
float lifetime = Defaults::Physics::Debris::TEMPS_VIDA, float lifetime = Defaults::Physics::Debris::TEMPS_VIDA,
float friction = Defaults::Physics::Debris::ACCELERACIO, float friction = Defaults::Physics::Debris::ACCELERACIO,
+2 -2
View File
@@ -33,9 +33,9 @@ namespace Effects {
TrailManager::TrailManager(Rendering::Renderer* renderer) TrailManager::TrailManager(Rendering::Renderer* renderer)
: renderer_(renderer), : renderer_(renderer),
star_shape_(Graphics::ShapeLoader::load("star.shp")) { star_shape_(Graphics::ShapeLoader::load("effect/starfield.shp")) {
if (!star_shape_ || !star_shape_->isValid()) { if (!star_shape_ || !star_shape_->isValid()) {
std::cerr << "[TrailManager] Warning: no s'ha pogut load star.shp\n"; std::cerr << "[TrailManager] Warning: no s'ha pogut load effect/starfield.shp\n";
} }
for (auto& particle : pool_) { for (auto& particle : pool_) {
particle.active = false; particle.active = false;
+1 -1
View File
@@ -114,6 +114,6 @@ void Bullet::desactivar() {
void Bullet::draw() const { void Bullet::draw() const {
if (is_active_ && shape_) { if (is_active_ && shape_) {
Rendering::renderShape(renderer_, shape_, center_, angle_, 1.0F, 1.0F, brightness_, config_->colors.normal); Rendering::renderShape(renderer_, shape_, center_, angle_, config_->shape.scale, 1.0F, brightness_, config_->colors.normal);
} }
} }
+1 -1
View File
@@ -21,7 +21,7 @@ enum class EnemyType : uint8_t {
SQUARE = 1, // Square perseguidor (tracks ship) SQUARE = 1, // Square perseguidor (tracks ship)
PINWHEEL = 2, // Molinillo agresivo (rápido, girando) PINWHEEL = 2, // Molinillo agresivo (rápido, girando)
STAR = 3, // Estrella de 5 puntes (clone visual de Pentagon, comportament zigzag) STAR = 3, // Estrella de 5 puntes (clone visual de Pentagon, comportament zigzag)
BIG_PENTAGON = 4, // Pentàgon gegant tough (HP=10, chase lent — primer enemic HP>1) ORB = 4, // Orb gegant tough (HP=10, chase lent — primer enemic HP>1)
}; };
// Forward declaration — EnemyConfig viu a enemy_config.hpp i s'inclou només a enemy.cpp. // Forward declaration — EnemyConfig viu a enemy_config.hpp i s'inclou només a enemy.cpp.
+46 -9
View File
@@ -30,7 +30,7 @@ namespace {
if (s == "square") { return EnemyType::SQUARE; } if (s == "square") { return EnemyType::SQUARE; }
if (s == "pinwheel") { return EnemyType::PINWHEEL; } if (s == "pinwheel") { return EnemyType::PINWHEEL; }
if (s == "star") { return EnemyType::STAR; } if (s == "star") { return EnemyType::STAR; }
if (s == "big_pentagon") { return EnemyType::BIG_PENTAGON; } if (s == "orb") { return EnemyType::ORB; }
return std::nullopt; return std::nullopt;
} }
@@ -186,6 +186,11 @@ namespace {
} }
} }
// Forward-decl: aimModeFromString viu més avall (junt amb la resta de
// helpers d'AI) però parseActionList el necessita per al payload de
// FIRE_BULLET. Evita reordenar tot el bloc.
auto aimModeFromString(const std::string& s) -> std::optional<AimMode>;
auto actionTypeFromString(const std::string& s) -> std::optional<EnemyActionType> { auto actionTypeFromString(const std::string& s) -> std::optional<EnemyActionType> {
if (s == "set_hurt") { return EnemyActionType::SET_HURT; } if (s == "set_hurt") { return EnemyActionType::SET_HURT; }
if (s == "destroy") { return EnemyActionType::DESTROY; } if (s == "destroy") { return EnemyActionType::DESTROY; }
@@ -197,6 +202,7 @@ namespace {
if (s == "apply_impulse") { return EnemyActionType::APPLY_IMPULSE; } if (s == "apply_impulse") { return EnemyActionType::APPLY_IMPULSE; }
if (s == "decrease_health") { return EnemyActionType::DECREASE_HEALTH; } if (s == "decrease_health") { return EnemyActionType::DECREASE_HEALTH; }
if (s == "flash") { return EnemyActionType::FLASH; } if (s == "flash") { return EnemyActionType::FLASH; }
if (s == "fire_bullet") { return EnemyActionType::FIRE_BULLET; }
return std::nullopt; return std::nullopt;
} }
@@ -219,19 +225,50 @@ namespace {
<< event_name << " (" << enemy_name << ")\n"; << event_name << " (" << enemy_name << ")\n";
return false; return false;
} }
out.push_back({*PARSED}); EnemyAction action;
action.type = *PARSED;
// Payload de FIRE_BULLET. Camps opcionals; els defaults són els del struct.
if (action.type == EnemyActionType::FIRE_BULLET) {
if (item.contains("bullet")) {
action.bullet_config_name = item["bullet"].get_value<std::string>();
}
if (item.contains("bullet_speed")) {
action.bullet_speed = item["bullet_speed"].get_value<float>();
}
if (item.contains("aim_mode")) {
const auto AIM_STR = item["aim_mode"].get_value<std::string>();
const auto AIM = aimModeFromString(AIM_STR);
if (!AIM) {
std::cerr << "[EnemyConfig] Error: aim_mode desconegut '" << AIM_STR
<< "' a " << event_name << " (" << enemy_name << ")\n";
return false;
}
action.aim_mode = *AIM;
}
if (item.contains("jitter_rad")) {
action.jitter_rad = item["jitter_rad"].get_value<float>();
}
}
out.push_back(action);
} }
return true; return true;
} }
// Defaults: replica el flux hardcoded actual (set_hurt → destroy → score+debris+fireworks). // Defaults: replica el flux hardcoded actual (set_hurt → destroy → score+debris+fireworks).
// Construïm via mutació per esquivar warnings de designated-init parcial sobre
// l'EnemyAction (que té payload de FIRE_BULLET no rellevant ací).
void fillLegacyDefaults(EnemyEventConfig& events) { void fillLegacyDefaults(EnemyEventConfig& events) {
events.on_hit = {{EnemyActionType::SET_HURT}}; const auto MAKE = [](EnemyActionType type) {
events.on_hurt_end = {{EnemyActionType::DESTROY}}; EnemyAction a;
a.type = type;
return a;
};
events.on_hit = {MAKE(EnemyActionType::SET_HURT)};
events.on_hurt_end = {MAKE(EnemyActionType::DESTROY)};
events.on_destroy = { events.on_destroy = {
{EnemyActionType::ADD_SCORE}, MAKE(EnemyActionType::ADD_SCORE),
{EnemyActionType::CREATE_DEBRIS}, MAKE(EnemyActionType::CREATE_DEBRIS),
{EnemyActionType::CREATE_FIREWORKS}, MAKE(EnemyActionType::CREATE_FIREWORKS),
}; };
} }
@@ -353,8 +390,8 @@ namespace {
out.movement.rotation_proximity_multiplier = legacy.rotation_proximity_multiplier; out.movement.rotation_proximity_multiplier = legacy.rotation_proximity_multiplier;
out.movement.proximity_distance = legacy.proximity_distance; out.movement.proximity_distance = legacy.proximity_distance;
break; break;
case EnemyType::BIG_PENTAGON: case EnemyType::ORB:
// Sense legacy fallback: el YAML del big_pentagon ha de definir // Sense legacy fallback: el YAML de l'orb ha de definir
// ai.movement explícitament. Default chase lent perquè el switch // ai.movement explícitament. Default chase lent perquè el switch
// siga exhaustiu i no falli si algú omet el bloc ai. // siga exhaustiu i no falli si algú omet el bloc ai.
out.movement.type = MovementType::CHASE; out.movement.type = MovementType::CHASE;
+11
View File
@@ -8,8 +8,11 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <string>
#include <vector> #include <vector>
#include "game/entities/enemy_ai.hpp" // AimMode
enum class EnemyEventType : uint8_t { enum class EnemyEventType : uint8_t {
ON_HIT, // Impactat per una bala ON_HIT, // Impactat per una bala
ON_NO_HEALTH, // health ha arribat a 0 o menys aquest frame (via DECREASE_HEALTH) ON_NO_HEALTH, // health ha arribat a 0 o menys aquest frame (via DECREASE_HEALTH)
@@ -28,10 +31,18 @@ enum class EnemyActionType : uint8_t {
APPLY_IMPULSE, // Aplica l'impuls de la bala impactant APPLY_IMPULSE, // Aplica l'impuls de la bala impactant
DECREASE_HEALTH, // Decrementa health_; si <=0, dispatcha ON_NO_HEALTH DECREASE_HEALTH, // Decrementa health_; si <=0, dispatcha ON_NO_HEALTH
FLASH, // Flash visual breu (feedback per impacte parcial) FLASH, // Flash visual breu (feedback per impacte parcial)
FIRE_BULLET, // Dispara una bala (config per nom) dirigida o aleatòria
}; };
struct EnemyAction { struct EnemyAction {
EnemyActionType type; EnemyActionType type;
// Payload de FIRE_BULLET (ignorat per altres tipus). Paral·lel a AiTickAction
// perquè un futur refactor pugui compartir doShoot/doFireBullet si val la pena.
std::string bullet_config_name;
float bullet_speed{200.0F};
AimMode aim_mode{AimMode::RANDOM};
float jitter_rad{0.0F};
}; };
struct EnemyEventConfig { struct EnemyEventConfig {
+4 -4
View File
@@ -13,7 +13,7 @@ EnemyConfig EnemyRegistry::pentagon_config;
EnemyConfig EnemyRegistry::square_config; EnemyConfig EnemyRegistry::square_config;
EnemyConfig EnemyRegistry::pinwheel_config; EnemyConfig EnemyRegistry::pinwheel_config;
EnemyConfig EnemyRegistry::star_config; EnemyConfig EnemyRegistry::star_config;
EnemyConfig EnemyRegistry::big_pentagon_config; EnemyConfig EnemyRegistry::orb_config;
bool EnemyRegistry::loaded = false; bool EnemyRegistry::loaded = false;
namespace { namespace {
@@ -40,7 +40,7 @@ auto EnemyRegistry::loadAll() -> bool {
loadOne("square", EnemyType::SQUARE, square_config) && loadOne("square", EnemyType::SQUARE, square_config) &&
loadOne("pinwheel", EnemyType::PINWHEEL, pinwheel_config) && loadOne("pinwheel", EnemyType::PINWHEEL, pinwheel_config) &&
loadOne("star", EnemyType::STAR, star_config) && loadOne("star", EnemyType::STAR, star_config) &&
loadOne("big_pentagon", EnemyType::BIG_PENTAGON, big_pentagon_config); loadOne("orb", EnemyType::ORB, orb_config);
loaded = OK; loaded = OK;
if (OK) { if (OK) {
std::cout << "[EnemyRegistry] 5 configuracions d'enemic carregades.\n"; std::cout << "[EnemyRegistry] 5 configuracions d'enemic carregades.\n";
@@ -62,8 +62,8 @@ auto EnemyRegistry::get(EnemyType type) -> const EnemyConfig& {
return pinwheel_config; return pinwheel_config;
case EnemyType::STAR: case EnemyType::STAR:
return star_config; return star_config;
case EnemyType::BIG_PENTAGON: case EnemyType::ORB:
return big_pentagon_config; return orb_config;
} }
std::cerr << "[EnemyRegistry] FATAL: tipus desconegut\n"; std::cerr << "[EnemyRegistry] FATAL: tipus desconegut\n";
std::exit(EXIT_FAILURE); std::exit(EXIT_FAILURE);
+1 -1
View File
@@ -27,6 +27,6 @@ class EnemyRegistry {
static EnemyConfig square_config; static EnemyConfig square_config;
static EnemyConfig pinwheel_config; static EnemyConfig pinwheel_config;
static EnemyConfig star_config; static EnemyConfig star_config;
static EnemyConfig big_pentagon_config; static EnemyConfig orb_config;
static bool loaded; static bool loaded;
}; };
+1 -1
View File
@@ -26,7 +26,7 @@ Ship::Ship(Rendering::Renderer* renderer, PlayerConfig config, const char* shape
config_(std::move(config)) { config_(std::move(config)) {
brightness_ = Defaults::Brightness::NAU; brightness_ = Defaults::Brightness::NAU;
// El shape pot venir del YAML o ser overridden (ex: P2 amb "ship2.shp"). // El shape pot venir del YAML o ser overridden (ex: P2 amb "ship/wedge.shp").
const std::string SHAPE_PATH = (shape_override != nullptr) ? shape_override : config_.shape.path; const std::string SHAPE_PATH = (shape_override != nullptr) ? shape_override : config_.shape.path;
shape_ = Graphics::ShapeLoader::load(SHAPE_PATH); shape_ = Graphics::ShapeLoader::load(SHAPE_PATH);
if (!shape_ || !shape_->isValid()) { if (!shape_ || !shape_->isValid()) {
+29 -39
View File
@@ -83,7 +83,7 @@ GameScene::GameScene(SDLManager& sdl, SceneContext& context)
// Inicialitzar naves: P1 amb el shape del YAML, P2 amb override visual. // Inicialitzar naves: P1 amb el shape del YAML, P2 amb override visual.
ships_[0] = Ship(sdl.getRenderer(), *player_config); // Jugador 1: nau estàndard ships_[0] = Ship(sdl.getRenderer(), *player_config); // Jugador 1: nau estàndard
ships_[1] = Ship(sdl.getRenderer(), *player_config, "ship2.shp"); // Jugador 2: interceptor amb ales ships_[1] = Ship(sdl.getRenderer(), *player_config, "ship/wedge.shp"); // Jugador 2: triangle amb cercle central
// Inicialitzar balas con renderer // Inicialitzar balas con renderer
std::ranges::fill(bullets_, Bullet(sdl.getRenderer())); std::ranges::fill(bullets_, Bullet(sdl.getRenderer()));
@@ -710,7 +710,7 @@ void GameScene::drawInitHudState() {
} }
if (score_progress > 0.0F) { if (score_progress > 0.0F) {
Systems::InitHud::drawScoreboardAnimated(text_, buildScoreboard(), score_progress); Systems::InitHud::drawScoreboardAnimated(text_, buildScoreboardSegments(), score_progress);
} }
if (ship1_progress > 0.0F && match_config_.player1_active && ships_[0].isActive()) { if (ship1_progress > 0.0F && match_config_.player1_active && ships_[0].isActive()) {
@@ -801,7 +801,7 @@ void GameScene::tocado(uint8_t player_id, const Vec2& bullet_velocity) {
INHERITED_VEL, INHERITED_VEL,
0.0F, // sense herència angular 0.0F, // sense herència angular
0.0F, // sin herencia visual 0.0F, // sin herencia visual
Defaults::Sound::EXPLOSION2, Defaults::Sound::PLAYER_EXPLOSION,
ships_[player_id].getConfig().colors.normal, ships_[player_id].getConfig().colors.normal,
Defaults::Physics::Debris::ENEMY_LIFETIME, Defaults::Physics::Debris::ENEMY_LIFETIME,
Defaults::Physics::Debris::ENEMY_FRICTION, Defaults::Physics::Debris::ENEMY_FRICTION,
@@ -816,59 +816,49 @@ void GameScene::tocado(uint8_t player_id, const Vec2& bullet_velocity) {
} }
void GameScene::drawScoreboard() { void GameScene::drawScoreboard() {
// Construir text del marcador
std::string text = buildScoreboard();
// Parámetros de renderització
const float SCALE = Defaults::Hud::SCOREBOARD_TEXT_SCALE; const float SCALE = Defaults::Hud::SCOREBOARD_TEXT_SCALE;
const float SPACING = Defaults::Hud::SCOREBOARD_TEXT_SPACING; const float SPACING = Defaults::Hud::SCOREBOARD_TEXT_SPACING;
// Calcular centro de la zone del marcador
const SDL_FRect& scoreboard_zone = Defaults::Zones::SCOREBOARD; const SDL_FRect& scoreboard_zone = Defaults::Zones::SCOREBOARD;
float center_x = scoreboard_zone.w / 2.0F; const Vec2 CENTER = {
float center_y = scoreboard_zone.y + (scoreboard_zone.h / 2.0F); .x = scoreboard_zone.w / 2.0F,
.y = scoreboard_zone.y + (scoreboard_zone.h / 2.0F),
// Renderizar centrat };
text_.renderCentered(text, {.x = center_x, .y = center_y}, SCALE, SPACING); Systems::InitHud::drawScoreboardSegmentsAt(text_, buildScoreboardSegments(), CENTER, SCALE, SPACING);
} }
auto GameScene::buildScoreboard() const -> std::string { auto GameScene::buildScoreboardSegments() const -> Systems::InitHud::ScoreboardSegments {
// Puntuación P1 (6 dígits) - mostrar zeros si inactiu Systems::InitHud::ScoreboardSegments out;
std::string score_p1;
std::string vides_p1; // Puntuació P1 (6 dígits) - zeros si inactiu
if (match_config_.player1_active) { if (match_config_.player1_active) {
score_p1 = std::to_string(score_per_player_[0]); std::string s = std::to_string(score_per_player_[0]);
score_p1 = std::string(6 - std::min(6, static_cast<int>(score_p1.length())), '0') + score_p1; out.score_p1 = std::string(6 - std::min(6, static_cast<int>(s.length())), '0') + s;
vides_p1 = (lives_per_player_[0] < 10) out.lives_p1 = (lives_per_player_[0] < 10)
? "0" + std::to_string(lives_per_player_[0]) ? "0" + std::to_string(lives_per_player_[0])
: std::to_string(lives_per_player_[0]); : std::to_string(lives_per_player_[0]);
} else { } else {
score_p1 = "000000"; out.score_p1 = "000000";
vides_p1 = "00"; out.lives_p1 = "00";
} }
// Nivel (2 dígits) // Nivell (2 dígits) amb label localitzat
uint8_t stage_num = stage_manager_->getCurrentStage(); const uint8_t STAGE_NUM = stage_manager_->getCurrentStage();
std::string stage_str = (stage_num < 10) ? "0" + std::to_string(stage_num) const std::string STAGE_STR = (STAGE_NUM < 10) ? "0" + std::to_string(STAGE_NUM)
: std::to_string(stage_num); : std::to_string(STAGE_NUM);
out.level = Locale::get().text("hud.level") + STAGE_STR;
// Puntuación P2 (6 dígits) - mostrar zeros si inactiu // Puntuació P2 (6 dígits) - zeros si inactiu
std::string score_p2;
std::string vides_p2;
if (match_config_.player2_active) { if (match_config_.player2_active) {
score_p2 = std::to_string(score_per_player_[1]); std::string s = std::to_string(score_per_player_[1]);
score_p2 = std::string(6 - std::min(6, static_cast<int>(score_p2.length())), '0') + score_p2; out.score_p2 = std::string(6 - std::min(6, static_cast<int>(s.length())), '0') + s;
vides_p2 = (lives_per_player_[1] < 10) out.lives_p2 = (lives_per_player_[1] < 10)
? "0" + std::to_string(lives_per_player_[1]) ? "0" + std::to_string(lives_per_player_[1])
: std::to_string(lives_per_player_[1]); : std::to_string(lives_per_player_[1]);
} else { } else {
score_p2 = "000000"; out.score_p2 = "000000";
vides_p2 = "00"; out.lives_p2 = "00";
} }
return out;
// Format: "123456 03 LEVEL 01 654321 02"
// Nota: dos espais entre seccions, mantenir ambdós slots siempre visibles
return score_p1 + " " + vides_p1 + " " + Locale::get().text("hud.level") + stage_str + " " + score_p2 + " " + vides_p2;
} }
// [NEW] Stage system helper methods // [NEW] Stage system helper methods
+4 -2
View File
@@ -29,6 +29,7 @@
#include "game/stage_system/stage_config.hpp" #include "game/stage_system/stage_config.hpp"
#include "game/stage_system/stage_manager.hpp" #include "game/stage_system/stage_manager.hpp"
#include "game/systems/collision_system.hpp" #include "game/systems/collision_system.hpp"
#include "game/systems/init_hud_animator.hpp"
// Game over state machine // Game over state machine
enum class GameOverState : uint8_t { enum class GameOverState : uint8_t {
@@ -128,8 +129,9 @@ class GameScene final : public Scene {
void drawPlayingState(); void drawPlayingState();
void drawLevelCompletedState(); void drawLevelCompletedState();
// [NEW] Función helper del marcador // [NEW] Helper del marcador: construeix els 5 segments (score_p1, vides_p1,
[[nodiscard]] auto buildScoreboard() const -> std::string; // level, score_p2, vides_p2) per a render colorit per segment.
[[nodiscard]] auto buildScoreboardSegments() const -> Systems::InitHud::ScoreboardSegments;
// Sub-pasos de update() (descompuestos en Fase 9d para reducir // Sub-pasos de update() (descompuestos en Fase 9d para reducir
// complejidad cognitiva; cada uno es responsable de una sección). // complejidad cognitiva; cada uno es responsable de una sección).
+1 -1
View File
@@ -78,7 +78,7 @@ TitleScene::TitleScene(SDLManager& sdl, SceneContext& context)
// Flash que tapa el "pop" final de la nau al VP. Es spawneja al centre // Flash que tapa el "pop" final de la nau al VP. Es spawneja al centre
// de pantalla (= projecció del VP) quan ship_animator avisa. // de pantalla (= projecció del VP) quan ship_animator avisa.
flash_shape_ = Graphics::ShapeLoader::load("title_flash.shp"); flash_shape_ = Graphics::ShapeLoader::load("effect/title_flash.shp");
ship_animator_->setOnShipDisappear([this](int /*player_id*/) { ship_animator_->setOnShipDisappear([this](int /*player_id*/) {
triggerFlash(Vec2{ triggerFlash(Vec2{
.x = static_cast<float>(Defaults::Window::WIDTH) / 2.0F, .x = static_cast<float>(Defaults::Window::WIDTH) / 2.0F,
+6 -4
View File
@@ -3,7 +3,6 @@
#pragma once #pragma once
#include <algorithm>
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -56,9 +55,12 @@ namespace StageSystem {
if (stage_id == 0 || waves.empty()) { if (stage_id == 0 || waves.empty()) {
return false; return false;
} }
return std::ranges::all_of(waves, [](const WaveConfig& w) { for (const auto& w : waves) {
return w.next.isValid() && !w.spawn.empty(); if (!w.next.isValid() || w.spawn.empty()) {
}); return false;
}
}
return true;
} }
}; };
+2 -2
View File
@@ -219,8 +219,8 @@ namespace StageSystem {
out = EnemyType::PINWHEEL; out = EnemyType::PINWHEEL;
} else if (type_str == "star") { } else if (type_str == "star") {
out = EnemyType::STAR; out = EnemyType::STAR;
} else if (type_str == "big_pentagon") { } else if (type_str == "orb") {
out = EnemyType::BIG_PENTAGON; out = EnemyType::ORB;
} else { } else {
return false; return false;
} }
+3 -3
View File
@@ -15,7 +15,7 @@ namespace Systems::Collision {
namespace { namespace {
// Trenca una bala en debris (8 fragments de l'octàgon) + so HIT + desactiva. // Trenca una bala en debris (8 fragments de l'octàgon) + so BULLET_ZAP + desactiva.
// S'invoca des de qualsevol desactivació de bala (impacte amb enemic, amb jugador, // S'invoca des de qualsevol desactivació de bala (impacte amb enemic, amb jugador,
// o sortida del PLAYAREA) per a un feedback visual i sonor consistent. // o sortida del PLAYAREA) per a un feedback visual i sonor consistent.
void breakBullet(Effects::DebrisManager& debris_manager, Bullet& bullet) { void breakBullet(Effects::DebrisManager& debris_manager, Bullet& bullet) {
@@ -30,7 +30,7 @@ namespace Systems::Collision {
Vec2{}, // sense herència de velocitat (fragments radials) Vec2{}, // sense herència de velocitat (fragments radials)
0.0F, // sense velocity angular heretada 0.0F, // sense velocity angular heretada
0.0F, // sense rotació visual heretada 0.0F, // sense rotació visual heretada
Defaults::Sound::HIT, Defaults::Sound::BULLET_ZAP,
bullet.getConfig().colors.normal, bullet.getConfig().colors.normal,
Defaults::Physics::Debris::TEMPS_VIDA, Defaults::Physics::Debris::TEMPS_VIDA,
Defaults::Physics::Debris::ACCELERACIO, Defaults::Physics::Debris::ACCELERACIO,
@@ -234,7 +234,7 @@ namespace Systems::Collision {
processWoundedDeaths(ctx); // expiran ANTES de ser tocadas por bala este frame processWoundedDeaths(ctx); // expiran ANTES de ser tocadas por bala este frame
detectBulletEnemy(ctx); detectBulletEnemy(ctx);
// Wounded chain desactivat: era massa fàcil que un enemic ferit topés // Wounded chain desactivat: era massa fàcil que un enemic ferit topés
// amb el big_pentagon (10 HP) i el matés instantàniament. La regla // amb l'orb (10 HP) i el matés instantàniament. La regla
// "ferit-toca-sa → ferit" queda permanentment fora. // "ferit-toca-sa → ferit" queda permanentment fora.
detectShipEnemy(ctx); detectShipEnemy(ctx);
detectBulletPlayer(ctx); detectBulletPlayer(ctx);
+100 -9
View File
@@ -3,13 +3,19 @@
#include "game/systems/enemy_event_dispatcher.hpp" #include "game/systems/enemy_event_dispatcher.hpp"
#include <cmath>
#include <cstdint> #include <cstdint>
#include <cstdlib>
#include "core/defaults.hpp" #include "core/defaults.hpp"
#include "core/types.hpp" #include "core/types.hpp"
#include "game/constants.hpp"
#include "game/entities/bullet.hpp" #include "game/entities/bullet.hpp"
#include "game/entities/bullet_config.hpp" #include "game/entities/bullet_config.hpp"
#include "game/entities/bullet_registry.hpp"
#include "game/entities/enemy_ai.hpp"
#include "game/entities/enemy_config.hpp" #include "game/entities/enemy_config.hpp"
#include "game/entities/ship.hpp"
namespace Systems::EnemyEvents { namespace Systems::EnemyEvents {
@@ -25,9 +31,10 @@ namespace Systems::EnemyEvents {
} }
// Helper compartit per CREATE_DEBRIS i CREATE_DEBRIS_PARTIAL: única // Helper compartit per CREATE_DEBRIS i CREATE_DEBRIS_PARTIAL: única
// crida a explode(), paràmetres alineats; només canvien piece_scale // crida a explode(), paràmetres alineats; canvien piece_scale (1.0
// (1.0 explosió, 0.3 xip) i el color (cos vs hit-feedback). // explosió, 0.3 xip), el color (cos vs hit-feedback) i el so
void spawnDebrisForEnemy(Systems::Collision::Context& ctx, const Enemy& enemy, const Bullet* bullet, float piece_scale, SDL_Color color) { // (ENEMY_EXPLOSION en la mort vs ENEMY_HIT en l'impacte parcial).
void spawnDebrisForEnemy(Systems::Collision::Context& ctx, const Enemy& enemy, const Bullet* bullet, float piece_scale, SDL_Color color, const char* sound) {
constexpr float SPEED_EXPLOSIO = 80.0F; constexpr float SPEED_EXPLOSIO = 80.0F;
const Vec2 INHERITED_VEL = enemy.getVelocityVector() * const Vec2 INHERITED_VEL = enemy.getVelocityVector() *
Defaults::Physics::Debris::ENEMY_VELOCITY_INHERITANCE; Defaults::Physics::Debris::ENEMY_VELOCITY_INHERITANCE;
@@ -42,7 +49,7 @@ namespace Systems::EnemyEvents {
INHERITED_VEL, INHERITED_VEL,
0.0F, 0.0F,
0.0F, 0.0F,
Defaults::Sound::EXPLOSION, sound,
color, color,
Defaults::Physics::Debris::ENEMY_LIFETIME, Defaults::Physics::Debris::ENEMY_LIFETIME,
Defaults::Physics::Debris::ENEMY_FRICTION, Defaults::Physics::Debris::ENEMY_FRICTION,
@@ -72,6 +79,87 @@ namespace Systems::EnemyEvents {
(bullet->getBody().mass * bullet->getConfig().physics.impact_momentum_factor); (bullet->getBody().mass * bullet->getConfig().physics.impact_momentum_factor);
enemy.applyImpulse(IMPULSE); enemy.applyImpulse(IMPULSE);
} }
auto randFloat01() -> float {
return static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX);
}
// Còpia local de la mateixa primitiva que viu a enemy_ai_system.cpp.
// No s'ha extret a un header compartit perquè és l'únic punt de
// duplicació; si apareix un tercer consumidor, refactoritzar.
auto findNearestShipPosition(const Enemy& enemy) -> const Vec2* {
const Vec2& self = enemy.getCenter();
const Vec2* best = nullptr;
float best_dist_sq = 0.0F;
for (const Ship* ship : enemy.getShips()) {
if (ship == nullptr || !ship->isActive()) {
continue;
}
const Vec2& pos = ship->getCenter();
const Vec2 DELTA = pos - self;
const float DIST_SQ = DELTA.lengthSquared();
if (best == nullptr || DIST_SQ < best_dist_sq) {
best = &pos;
best_dist_sq = DIST_SQ;
}
}
return best;
}
// FIRE_BULLET: paral·lel a doShoot() d'enemy_ai_system.cpp, però disparat
// per esdeveniment (típicament on_hit per a contra-atacs) en lloc de
// periòdicament. owner_id es deriva de l'índex dins ctx.enemies via
// aritmètica de punters (l'array és contigu).
void doFireBullet(Systems::Collision::Context& ctx, const Enemy& enemy, const EnemyAction& action) {
if (action.bullet_config_name.empty()) {
return;
}
const BulletConfig* cfg = BulletRegistry::get(action.bullet_config_name);
if (cfg == nullptr) {
return;
}
Bullet* slot = nullptr;
constexpr std::size_t START = Defaults::Entities::ENEMY_BULLET_START_IDX;
constexpr std::size_t END = START + Defaults::Entities::MAX_ENEMY_BULLETS;
for (std::size_t i = START; i < END; ++i) {
if (!ctx.bullets[i].isActive()) {
slot = &ctx.bullets[i];
break;
}
}
if (slot == nullptr) {
return; // pool d'enemic ple
}
float angle = 0.0F;
if (action.aim_mode == AimMode::AIMED) {
const Vec2* target = findNearestShipPosition(enemy);
if (target == nullptr) {
angle = randFloat01() * 2.0F * Constants::PI;
} else {
const Vec2 TO = *target - enemy.getCenter();
angle = std::atan2(TO.y, TO.x) + (Constants::PI / 2.0F);
}
} else {
angle = randFloat01() * 2.0F * Constants::PI;
}
if (action.jitter_rad > 0.0F) {
angle += (randFloat01() - 0.5F) * 2.0F * action.jitter_rad;
}
// Localitzem l'índex de l'enemic per construir l'owner_id. Evitem
// aritmètica de punters sobre Enemy (tipus polimòrfic — UB si la
// jerarquia canvia); cerca lineal a l'array (mida petita, no és hot path).
std::size_t enemy_index = 0;
for (std::size_t i = 0; i < ctx.enemies.size(); ++i) {
if (&ctx.enemies[i] == &enemy) {
enemy_index = i;
break;
}
}
const auto OWNER = static_cast<uint8_t>(Defaults::Entities::ENEMY_OWNER_BASE + enemy_index);
slot->fire(enemy.getCenter(), angle, OWNER, action.bullet_speed, cfg);
}
} // namespace } // namespace
void dispatchEvent(Systems::Collision::Context& ctx, Enemy& enemy, EnemyEventType event, uint8_t shooter_id, const Bullet* bullet) { void dispatchEvent(Systems::Collision::Context& ctx, Enemy& enemy, EnemyEventType event, uint8_t shooter_id, const Bullet* bullet) {
@@ -120,13 +208,13 @@ namespace Systems::EnemyEvents {
doAddScore(ctx, enemy, shooter_id); doAddScore(ctx, enemy, shooter_id);
break; break;
case EnemyActionType::CREATE_DEBRIS: case EnemyActionType::CREATE_DEBRIS:
// Explosió de mort: trossos en color cos (correcte físicament). // Explosió de mort: trossos en color cos + so ENEMY_EXPLOSION.
spawnDebrisForEnemy(ctx, enemy, bullet, 1.0F, enemy.getConfig().colors.normal); spawnDebrisForEnemy(ctx, enemy, bullet, 1.0F, enemy.getConfig().colors.normal, Defaults::Sound::ENEMY_EXPLOSION);
break; break;
case EnemyActionType::CREATE_DEBRIS_PARTIAL: case EnemyActionType::CREATE_DEBRIS_PARTIAL:
// Xip d'impacte: trossos en color wounded (daurat) per // Xip d'impacte: trossos en color wounded (daurat) + so
// diferenciar-los visualment del cos i marcar "damage". // ENEMY_HIT, diferent de l'explosió per marcar damage no letal.
spawnDebrisForEnemy(ctx, enemy, bullet, Defaults::Enemies::Debris::PARTIAL_PIECE_SCALE, enemy.getConfig().colors.wounded); spawnDebrisForEnemy(ctx, enemy, bullet, Defaults::Enemies::Debris::PARTIAL_PIECE_SCALE, enemy.getConfig().colors.wounded, Defaults::Sound::ENEMY_HIT);
break; break;
case EnemyActionType::CREATE_FIREWORKS: case EnemyActionType::CREATE_FIREWORKS:
// Burst de mort: línia blanca + glow wounded (daurat) per // Burst de mort: línia blanca + glow wounded (daurat) per
@@ -153,6 +241,9 @@ namespace Systems::EnemyEvents {
case EnemyActionType::FLASH: case EnemyActionType::FLASH:
enemy.triggerFlash(); enemy.triggerFlash();
break; break;
case EnemyActionType::FIRE_BULLET:
doFireBullet(ctx, enemy, action);
break;
} }
} }
} }
+35 -5
View File
@@ -8,6 +8,7 @@
#include <string> #include <string>
#include "core/defaults.hpp" #include "core/defaults.hpp"
#include "core/defaults/hud.hpp"
#include "core/math/easing.hpp" #include "core/math/easing.hpp"
#include "core/rendering/line_renderer.hpp" #include "core/rendering/line_renderer.hpp"
@@ -77,8 +78,40 @@ namespace Systems::InitHud {
} }
} }
void drawScoreboardSegmentsAt(const Graphics::VectorText& text,
const ScoreboardSegments& segments,
const Vec2& center,
float scale,
float spacing) {
// Separadors entre segments (preservant el layout legacy: " ", " ", " ", " ").
const float W_SEP1 = Graphics::VectorText::getTextWidth(" ", scale, spacing);
const float W_SEP2 = Graphics::VectorText::getTextWidth(" ", scale, spacing);
const float W_SP1 = Graphics::VectorText::getTextWidth(segments.score_p1, scale, spacing);
const float W_LP1 = Graphics::VectorText::getTextWidth(segments.lives_p1, scale, spacing);
const float W_LV = Graphics::VectorText::getTextWidth(segments.level, scale, spacing);
const float W_SP2 = Graphics::VectorText::getTextWidth(segments.score_p2, scale, spacing);
const float W_LP2 = Graphics::VectorText::getTextWidth(segments.lives_p2, scale, spacing);
const float TOTAL = W_SP1 + W_SEP1 + W_LP1 + W_SEP2 + W_LV + W_SEP2 + W_SP2 + W_SEP1 + W_LP2;
const float HEIGHT = Graphics::VectorText::getTextHeight(scale);
const float TOP_Y = center.y - (HEIGHT / 2.0F);
float x = center.x - (TOTAL / 2.0F);
text.render(segments.score_p1, {.x = x, .y = TOP_Y}, scale, spacing, 1.0F, Defaults::Hud::Colors::SCORE_P1);
x += W_SP1 + W_SEP1;
text.render(segments.lives_p1, {.x = x, .y = TOP_Y}, scale, spacing, 1.0F, Defaults::Hud::Colors::LIVES);
x += W_LP1 + W_SEP2;
text.render(segments.level, {.x = x, .y = TOP_Y}, scale, spacing, 1.0F, Defaults::Hud::Colors::LEVEL);
x += W_LV + W_SEP2;
text.render(segments.score_p2, {.x = x, .y = TOP_Y}, scale, spacing, 1.0F, Defaults::Hud::Colors::SCORE_P2);
x += W_SP2 + W_SEP1;
text.render(segments.lives_p2, {.x = x, .y = TOP_Y}, scale, spacing, 1.0F, Defaults::Hud::Colors::LIVES);
}
void drawScoreboardAnimated(const Graphics::VectorText& text, void drawScoreboardAnimated(const Graphics::VectorText& text,
const std::string& scoreboard_text, const ScoreboardSegments& segments,
float progress) { float progress) {
const float EASED = Easing::easeOutQuad(progress); const float EASED = Easing::easeOutQuad(progress);
@@ -91,10 +124,7 @@ namespace Systems::InitHud {
const auto Y_INI = static_cast<float>(Defaults::Game::HEIGHT); const auto Y_INI = static_cast<float>(Defaults::Game::HEIGHT);
const float Y_ANIM = Y_INI + ((Y_FINAL - Y_INI) * EASED); const float Y_ANIM = Y_INI + ((Y_FINAL - Y_INI) * EASED);
text.renderCentered(scoreboard_text, drawScoreboardSegmentsAt(text, segments, {.x = CENTRE_X, .y = Y_ANIM}, SCALE, SPACING);
Vec2{.x = CENTRE_X, .y = Y_ANIM},
SCALE,
SPACING);
} }
} // namespace Systems::InitHud } // namespace Systems::InitHud
+23 -3
View File
@@ -21,6 +21,17 @@
namespace Systems::InitHud { namespace Systems::InitHud {
// Segments del marcador. Cada segment es renderitza amb el seu propi color
// (vegeu Defaults::Hud::Colors). El layout final concatena en aquest ordre
// amb separadors d'1, 2, 2, 1 espais respectivament (igual que el legacy).
struct ScoreboardSegments {
std::string score_p1;
std::string lives_p1;
std::string level; // ex: "NIVELL 01"
std::string score_p2;
std::string lives_p2;
};
// Convierte un progreso global 0..1 al sub-progreso de un elemento que solo // Convierte un progreso global 0..1 al sub-progreso de un elemento que solo
// se anima en la ventana [ratio_init, ratio_end]. // se anima en la ventana [ratio_init, ratio_end].
// < ratio_init → 0.0 (no empezó) // < ratio_init → 0.0 (no empezó)
@@ -40,10 +51,19 @@ namespace Systems::InitHud {
// 66..100% → línea inferior crece desde los lados hacia el centro. // 66..100% → línea inferior crece desde los lados hacia el centro.
void drawBordersAnimated(Rendering::Renderer* renderer, float progress); void drawBordersAnimated(Rendering::Renderer* renderer, float progress);
// Dibuja el scoreboard centrado, subiendo desde fuera de la pantalla // Dibuixa els 5 segments del scoreboard centrats al voltant de `center`,
// hasta su posición final con easing. // cadascun amb el seu color (Defaults::Hud::Colors). Separadors de 1/2/2/1
// espais entre segments per preservar el layout legacy.
void drawScoreboardSegmentsAt(const Graphics::VectorText& text,
const ScoreboardSegments& segments,
const Vec2& center,
float scale,
float spacing);
// Dibuixa el scoreboard centrat, pujant des de fora de la pantalla fins a
// la seva posició final amb easing. Delega a drawScoreboardSegmentsAt.
void drawScoreboardAnimated(const Graphics::VectorText& text, void drawScoreboardAnimated(const Graphics::VectorText& text,
const std::string& scoreboard_text, const ScoreboardSegments& segments,
float progress); float progress);
} // namespace Systems::InitHud } // namespace Systems::InitHud
+14 -6
View File
@@ -114,8 +114,8 @@ namespace Title {
} }
void ShipAnimator::init() { void ShipAnimator::init() {
auto shape_p1 = Graphics::ShapeLoader::load("ship.shp"); auto shape_p1 = Graphics::ShapeLoader::load("ship/arrow.shp");
auto shape_p2 = Graphics::ShapeLoader::load("ship2.shp"); auto shape_p2 = Graphics::ShapeLoader::load("ship/wedge.shp");
ships_[0].player_id = 1; ships_[0].player_id = 1;
if (shape_p1 && shape_p1->isValid()) { if (shape_p1 && shape_p1->isValid()) {
@@ -225,13 +225,21 @@ namespace Title {
} }
auto ShipAnimator::isVisible() const -> bool { auto ShipAnimator::isVisible() const -> bool {
return std::ranges::any_of(ships_, for (const auto& s : ships_) {
[](const TitleShip& s) { return s.visible; }); if (s.visible) {
return true;
}
}
return false;
} }
auto ShipAnimator::isAnimationComplete() const -> bool { auto ShipAnimator::isAnimationComplete() const -> bool {
return std::ranges::all_of(ships_, for (const auto& s : ships_) {
[](const TitleShip& s) { return !s.visible; }); if (s.visible) {
return false;
}
}
return true;
} }
void ShipAnimator::updateEntering(TitleShip& ship, float delta_time) { void ShipAnimator::updateEntering(TitleShip& ship, float delta_time) {
+2 -2
View File
@@ -3,8 +3,8 @@
// //
// Manté la mateixa màquina d'estats // Manté la mateixa màquina d'estats
// (ENTERING → FLOATING → EXITING) però treballa amb posicions Vec3 i emet // (ENTERING → FLOATING → EXITING) però treballa amb posicions Vec3 i emet
// wireframes a través d'una `Camera3D`. La geometria s'extrau de `ship.shp` // wireframes a través d'una `Camera3D`. La geometria s'extrau de
// (P1) i `ship2.shp` (P2) per extrusió en Z. // `ship/arrow.shp` (P1) i `ship/wedge.shp` (P2) per extrusió en Z.
#pragma once #pragma once