Compare commits

2 Commits

Author SHA1 Message Date
b3f6e2fcf0 Metal post-processing pipeline implementation
- Add custom Metal render target creation
- Implement post-processing without GPU-CPU-GPU copies
- Create Metal texture extraction system
- Add Metal CRT shader pipeline integration
- Modify screen rendering to use Metal when available
- Enable shaders by default for testing

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-16 07:42:03 +02:00
7f00942517 treballant en metal 2025-09-10 20:44:10 +02:00
60 changed files with 1774 additions and 2259 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,4 @@
.vscode .vscode
.claude
build/ build/
data/config/config.txt data/config/config.txt
*.DS_Store *.DS_Store

View File

@@ -1,81 +0,0 @@
# Plan de Migración DeltaTime - Eliminación de frameFactor
## Problema Identificado
Se están usando `frameFactor` conversions en 7 archivos, lo que indica una migración incompleta a deltaTime.
El patrón `float frameFactor = deltaTime / (1000.0f / 60.0f)` simula frames de 60fps en lugar de usar tiempo real.
## Archivos Afectados y Estado
1. **balloon.cpp** - 9 ocurrencias en métodos: moveX(), moveY(), updateState(), updateCreation()
2. **balloon_manager.cpp** - 2 ocurrencias en updateBalloonDeployment()
3. **bullet.cpp** - 3 ocurrencias en move()
4. **item.cpp** - 6 ocurrencias en move()
5. **moving_sprite.cpp** - 5 ocurrencias en move()
6. **tabe.cpp** - 5 ocurrencias en update() y updateHitEffect()
7. **credits.cpp** - 3 ocurrencias en update() y handleFadeOut()
## Estrategia de Migración
### Opción A: Velocidades ya en pixels/segundo
Si las velocidades están definidas en pixels/segundo:
```cpp
// ANTES (incorrecto)
float frameFactor = deltaTime / (1000.0f / 60.0f);
pos_x_ += vel_x_ * frameFactor;
// DESPUÉS (correcto)
pos_x_ += vel_x_ * (deltaTime / 1000.0f); // deltaTime en ms -> segundos
```
### Opción B: Velocidades en pixels/frame (legacy)
Si las velocidades están en pixels/frame (sistema legacy):
```cpp
// ANTES (incorrecto con deltaTime)
float frameFactor = deltaTime / (1000.0f / 60.0f);
pos_x_ += vel_x_ * frameFactor;
// OPCIÓN 1: Convertir velocidades a pixels/segundo
static constexpr float VEL_X_PER_SECOND = VEL_X_PER_FRAME * 60.0f;
pos_x_ += VEL_X_PER_SECOND * (deltaTime / 1000.0f);
// OPCIÓN 2: Mantener frame-factor pero mejorar claridad
pos_x_ += vel_x_ * (deltaTime / FRAME_TIME_MS); // donde FRAME_TIME_MS = 16.67f
```
## Plan de Ejecución
### Fase 1: Análisis de Velocidades
- [ ] Revisar definiciones de velocidades en cada clase
- [ ] Determinar si están en pixels/frame o pixels/segundo
- [ ] Identificar constantes que necesitan conversión
### Fase 2: Migración por Archivo
- [x] **balloon.cpp**: Migrar velocidades x/y y contadores ✅
- [x] **balloon_manager.cpp**: Migrar balloon_deploy_counter_ ✅
- [x] **bullet.cpp**: Migrar velocidades de bala ✅ (VEL_Y: -3.0F→-0.18F, VEL_X: ±2.0F→±0.12F)
- [x] **item.cpp**: Migrar física de ítems ✅ (vel_x: ±1.0F→±0.06F, vel_y: -4.0F→-0.24F, accel_y: 0.2F→0.012F)
- [ ] **moving_sprite.cpp**: Migrar sistema base de movimiento
- [ ] **tabe.cpp**: Migrar movimiento y efectos
- [ ] **credits.cpp**: Migrar contadores de timing
### Fase 3: Verificación
- [ ] Compilar y probar cada archivo migrado
- [ ] Verificar que el comportamiento se mantiene consistente
- [ ] Eliminar todas las referencias a frameFactor
- [ ] Actualizar comentarios para reflejar unidades correctas
## Criterios de Éxito
1. ✅ Cero ocurrencias de "frameFactor" en el código
2. ✅ Todas las velocidades claramente documentadas (pixels/segundo vs pixels/frame)
3. ✅ Comportamiento del juego idéntico al anterior
4. ✅ Código más limpio y mantenible
## Notas Importantes
- El frameFactor actual simula 60fps: `deltaTime / 16.67ms`
- Esto significa que las velocidades actuales están en "pixels per 16.67ms"
- Para verdadero deltaTime, necesitamos convertir a "pixels per second" o usar factor de frame explícito
- Mantener constantes claras para evitar números mágicos
## Estado: En Progreso
- Análisis iniciado
- Plan documentado
- Próximo paso: Análisis de velocidades en cada archivo

View File

@@ -18,148 +18,148 @@ X3_75, 0, DEFAULT_POS_Y, LEFT, BALLOON, EXTRALARGE, 0
formation: 2 formation: 2
# Cuatro enemigos BALLOON1 uno detrás del otro. A la izquierda y hacia el centro # Cuatro enemigos BALLOON1 uno detrás del otro. A la izquierda y hacia el centro
X1_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 500 X1_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 30
X1_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 334 X1_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 20
X1_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 167 X1_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 10
X1_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0 X1_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0
formation: 3 formation: 3
# Cuatro enemigos BALLOON1 uno detrás del otro. A la derecha y hacia el centro # Cuatro enemigos BALLOON1 uno detrás del otro. A la derecha y hacia el centro
X1_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 500 X1_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 30
X1_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 334 X1_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 20
X1_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 167 X1_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 10
X1_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0 X1_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0
formation: 4 formation: 4
# Tres enemigos BALLOON2. 0, 417, 834. Hacia la derecha # Tres enemigos BALLOON2. 0, 25, 50. Hacia la derecha
X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 334 X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 20
X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 167 X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 10
X2_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0 X2_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0
formation: 5 formation: 5
# Tres enemigos BALLOON2. 50, 75, 1667. Hacia la izquierda # Tres enemigos BALLOON2. 50, 75, 100. Hacia la izquierda
X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 334 X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 20
X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 167 X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 10
X2_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0 X2_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0
formation: 6 formation: 6
# Tres enemigos BALLOON2. 0, 0, 0. Hacia la derecha # Tres enemigos BALLOON2. 0, 0, 0. Hacia la derecha
X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 334 X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 20
X2_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 167 X2_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 10
X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0 X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0
formation: 7 formation: 7
# Tres enemigos BALLOON2. 100, 1667, 1667. Hacia la izquierda # Tres enemigos BALLOON2. 100, 100, 100. Hacia la izquierda
X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 334 X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 20
X2_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 167 X2_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 10
X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0 X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0
formation: 8 formation: 8
# Seis enemigos BALLOON0. 0, 0, 0, 0, 0, 0. Hacia la derecha # Seis enemigos BALLOON0. 0, 0, 0, 0, 0, 0. Hacia la derecha
X0_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 834 X0_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 50
X0_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 667 X0_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 40
X0_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 500 X0_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 30
X0_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 334 X0_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 20
X0_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 167 X0_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 10
X0_0, 5, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0 X0_0, 5, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0
formation: 9 formation: 9
# Seis enemigos BALLOON0. 100, 1667, 1667, 1667, 1667, 1667. Hacia la izquierda # Seis enemigos BALLOON0. 100, 100, 100, 100, 100, 100. Hacia la izquierda
X0_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 834 X0_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 50
X0_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 667 X0_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 40
X0_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 500 X0_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 30
X0_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 334 X0_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 20
X0_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 167 X0_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 10
X0_100, -5, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0 X0_100, -5, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0
formation: 10 formation: 10
# Tres enemigos BALLOON3 seguidos desde la izquierda. Hacia la derecha # Tres enemigos BALLOON3 seguidos desde la izquierda. Hacia la derecha
X3_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, EXTRALARGE, 500 X3_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, EXTRALARGE, 30
X3_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, EXTRALARGE, 250 X3_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, EXTRALARGE, 15
X3_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, EXTRALARGE, 0 X3_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, EXTRALARGE, 0
formation: 11 formation: 11
# Tres enemigos BALLOON3 seguidos desde la derecha. Hacia la izquierda # Tres enemigos BALLOON3 seguidos desde la derecha. Hacia la izquierda
X3_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, EXTRALARGE, 500 X3_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, EXTRALARGE, 30
X3_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, EXTRALARGE, 250 X3_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, EXTRALARGE, 15
X3_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, EXTRALARGE, 0 X3_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, EXTRALARGE, 0
formation: 12 formation: 12
# Seis enemigos BALLOON1 uno detrás del otro. A la izquierda y hacia el centro # Seis enemigos BALLOON1 uno detrás del otro. A la izquierda y hacia el centro
X1_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 834 X1_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 50
X1_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 667 X1_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 40
X1_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 500 X1_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 30
X1_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 334 X1_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 20
X1_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 167 X1_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 10
X1_0, 5, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0 X1_0, 5, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0
formation: 13 formation: 13
# Seis enemigos BALLOON1 uno detrás del otro. A la derecha y hacia el centro # Seis enemigos BALLOON1 uno detrás del otro. A la derecha y hacia el centro
X1_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 834 X1_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 50
X1_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 667 X1_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 40
X1_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 500 X1_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 30
X1_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 334 X1_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 20
X1_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 167 X1_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 10
X1_100, -5, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0 X1_100, -5, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0
formation: 14 formation: 14
# Cinco enemigos BALLOON2. Hacia la derecha. Separados # Cinco enemigos BALLOON2. Hacia la derecha. Separados
X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 667 X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 40
X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 500 X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 30
X2_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 334 X2_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 20
X2_0, 6, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 167 X2_0, 6, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 10
X2_0, 8, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0 X2_0, 8, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0
formation: 15 formation: 15
# Cinco enemigos BALLOON2. Hacia la izquierda. Separados # Cinco enemigos BALLOON2. Hacia la izquierda. Separados
X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 667 X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 40
X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 500 X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 30
X2_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 334 X2_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 20
X2_100, -6, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 167 X2_100, -6, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 10
X2_100, -8, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0 X2_100, -8, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0
formation: 16 formation: 16
# Cinco enemigos BALLOON2. Hacia la derecha. Juntos # Cinco enemigos BALLOON2. Hacia la derecha. Juntos
X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 667 X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 40
X2_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 500 X2_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 30
X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 334 X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 20
X2_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 167 X2_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 10
X2_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0 X2_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0
formation: 17 formation: 17
# Cinco enemigos BALLOON2. Hacia la izquierda. Juntos # Cinco enemigos BALLOON2. Hacia la izquierda. Juntos
X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 667 X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 40
X2_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 500 X2_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 30
X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 334 X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 20
X2_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 167 X2_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 10
X2_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0 X2_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0
formation: 18 formation: 18
# Doce enemigos BALLOON0. Hacia la derecha. Juntos # Doce enemigos BALLOON0. Hacia la derecha. Juntos
X0_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 1834 X0_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 110
X0_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 1667 X0_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 100
X0_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 25000 X0_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 90
X0_0, 50, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 1334 X0_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 80
X0_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 70 X0_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 70
X0_0, 83, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 1000 X0_0, 5, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 60
X0_0, 100, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 830 X0_0, 6, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 50
X0_0, 7, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 40 X0_0, 7, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 40
X0_0, 8, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 500 X0_0, 8, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 30
X0_0, 150, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 20 X0_0, 9, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 20
X0_0, 167, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 10 X0_0, 10, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 10
X0_0, 11, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0 X0_0, 11, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0
formation: 19 formation: 19
# Doce enemigos BALLOON0. Hacia la izquierda. Juntos # Doce enemigos BALLOON0. Hacia la izquierda. Juntos
X0_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 1834 X0_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 110
X0_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 1667 X0_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 100
X0_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 25000 X0_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 90
X0_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 1334 X0_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 80
X0_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 70 X0_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 70
X0_100, -5, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 1000 X0_100, -5, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 60
X0_100, -6, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 830 X0_100, -6, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 50
X0_100, -7, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 40 X0_100, -7, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 40
X0_100, -8, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 500 X0_100, -8, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 30
X0_100, -9, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 20 X0_100, -9, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 20
X0_100, -10, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 10 X0_100, -10, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 10
X0_100, -11, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0 X0_100, -11, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0
@@ -173,105 +173,105 @@ X3_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, EXTRALARGE, 0
formation: 21 formation: 21
# Diez enemigos BALLOON1 uno detrás del otro. Izquierda/derecha. Simétricos # Diez enemigos BALLOON1 uno detrás del otro. Izquierda/derecha. Simétricos
X1_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 200 X1_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 12
X1_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 150 X1_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 9
X1_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 100 X1_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 6
X1_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 50 X1_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 3
X1_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0 X1_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, MEDIUM, 0
X1_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 200 X1_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 12
X1_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 150 X1_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 9
X1_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 100 X1_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 6
X1_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 50 X1_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 3
X1_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0 X1_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, MEDIUM, 0
formation: 22 formation: 22
# Diez enemigos BALLOON2. Hacia la derecha/izquierda. Separados. Simétricos # Diez enemigos BALLOON2. Hacia la derecha/izquierda. Separados. Simétricos
X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 667 X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 40
X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 500 X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 30
X2_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 334 X2_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 20
X2_0, 6, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 167 X2_0, 6, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 10
X2_0, 8, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0 X2_0, 8, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0
X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 667 X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 40
X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 500 X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 30
X2_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 334 X2_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 20
X2_100, -6, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 167 X2_100, -6, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 10
X2_100, -8, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0 X2_100, -8, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0
formation: 23 formation: 23
# Diez enemigos BALLOON2. Hacia la derecha. Juntos. Simétricos # Diez enemigos BALLOON2. Hacia la derecha. Juntos. Simétricos
X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 667 X2_0, 0, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 40
X2_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 500 X2_0, 1, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 30
X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 334 X2_0, 2, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 20
X2_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 167 X2_0, 3, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 10
X2_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0 X2_0, 4, DEFAULT_POS_Y, RIGHT, BALLOON, LARGE, 0
X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 667 X2_100, 0, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 40
X2_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 500 X2_100, -1, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 30
X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 334 X2_100, -2, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 20
X2_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 167 X2_100, -3, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 10
X2_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0 X2_100, -4, DEFAULT_POS_Y, LEFT, BALLOON, LARGE, 0
formation: 24 formation: 24
# Treinta enemigos BALLOON0. Del centro hacia los extremos. Juntos. Simétricos # Treinta enemigos BALLOON0. Del centro hacia los extremos. Juntos. Simétricos
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 83 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 5
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 167 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 10
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 250 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 15
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 334 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 20
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 417 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 25
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 500 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 30
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 584 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 35
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 667 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 40
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 750 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 45
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 834 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 50
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 917 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 55
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 16700 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 60
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 16784 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 65
X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 1167 X0_50, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 70
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 83 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 5
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 167 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 10
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 250 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 15
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 334 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 20
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 417 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 25
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 500 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 30
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 584 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 35
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 667 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 40
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 750 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 45
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 834 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 50
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 917 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 55
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 16700 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 60
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 16784 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 65
X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 1167 X0_50, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 70
formation: 25 formation: 25
# Treinta enemigos BALLOON0. Del centro hacia adentro. Juntos. Simétricos # Treinta enemigos BALLOON0. Del centro hacia adentro. Juntos. Simétricos
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 1167 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 70
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 16784 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 65
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 16700 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 60
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 917 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 55
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 834 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 50
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 750 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 45
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 667 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 40
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 584 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 35
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 500 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 30
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 417 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 25
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 334 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 20
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 250 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 15
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 167 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 10
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 83 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 5
X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0 X0_50 + 20, 0, DEFAULT_POS_Y, LEFT, BALLOON, SMALL, 0
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 1167 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 70
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 16784 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 65
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 16700 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 60
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 917 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 55
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 834 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 50
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 750 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 45
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 667 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 40
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 584 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 35
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 500 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 30
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 417 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 25
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 334 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 20
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 250 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 15
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 167 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 10
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 83 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 5
X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0 X0_50 - 20, 0, DEFAULT_POS_Y, RIGHT, BALLOON, SMALL, 0

View File

@@ -10,8 +10,8 @@ game.play_area.rect.x 0 # Posición X de la zona jugable
game.play_area.rect.y 0 # Posición Y de la zona jugable game.play_area.rect.y 0 # Posición Y de la zona jugable
game.play_area.rect.w 320 # Ancho de la zona jugable game.play_area.rect.w 320 # Ancho de la zona jugable
game.play_area.rect.h 200 # Alto de la zona jugable game.play_area.rect.h 200 # Alto de la zona jugable
game.name_entry_idle_time 10000 # Segundos para introducir el nombre al finalizar la partida si no se pulsa nada game.name_entry_idle_time 10 # Segundos para introducir el nombre al finalizar la partida si no se pulsa nada
game.name_entry_total_time 60000 # Segundos totales para introducir el nombre al finalizar la partida game.name_entry_total_time 60 # Segundos totales para introducir el nombre al finalizar la partida
game.hit_stop false # Indica si debe haber un paro cuando el jugador es golpeado por un globo game.hit_stop false # Indica si debe haber un paro cuando el jugador es golpeado por un globo
game.hit_stop_ms 500 # Cantidad de milisegundos que dura el hit_stop game.hit_stop_ms 500 # Cantidad de milisegundos que dura el hit_stop
@@ -39,24 +39,24 @@ scoreboard.text_color2 FFFFFF # Color secundario del texto del marca
scoreboard.skip_countdown_value 8 # Valor para saltar la cuenta atrás (segundos) scoreboard.skip_countdown_value 8 # Valor para saltar la cuenta atrás (segundos)
# --- TITLE --- # --- TITLE ---
title.press_start_position 180 # Posición Y del texto "Press Start" title.press_start_position 180 # Posición Y del texto "Press Start"
title.title_duration 14000 # Duración de la pantalla de título (milisegundos) title.title_duration 800 # Duración de la pantalla de título (frames)
title.arcade_edition_position 123 # Posición Y del subtítulo "Arcade Edition" title.arcade_edition_position 123 # Posición Y del subtítulo "Arcade Edition"
title.title_c_c_position 80 # Posición Y del título principal title.title_c_c_position 80 # Posición Y del título principal
title.bg_color 41526F # Color de fondo en la sección titulo title.bg_color 41526F # Color de fondo en la sección titulo
# --- BACKGROUND --- # --- BACKGROUND ---
background.attenuate_color FFFFFF00 # Color de atenuación del fondo (RGBA hexadecimal) background.attenuate_color FFFFFF00 # Color de atenuación del fondo (RGBA hexadecimal)
# --- BALLOONS --- (deltaTime puro: vel en pixels/ms, grav en pixels/ms²) # --- BALLOONS ---
balloon.settings[0].vel 0.165f # Velocidad inicial del globo 1 (pixels/ms) balloon.settings[0].vel 2.75f # Velocidad inicial del globo 1
balloon.settings[0].grav 0.00032f # Gravedad aplicada al globo 1 (pixels/ms²) balloon.settings[0].grav 0.09f # Gravedad aplicada al globo 1
balloon.settings[1].vel 0.222f # Velocidad inicial del globo 2 (pixels/ms) balloon.settings[1].vel 3.70f # Velocidad inicial del globo 2
balloon.settings[1].grav 0.00036f # Gravedad aplicada al globo 2 (pixels/ms²) balloon.settings[1].grav 0.10f # Gravedad aplicada al globo 2
balloon.settings[2].vel 0.282f # Velocidad inicial del globo 3 (pixels/ms) balloon.settings[2].vel 4.70f # Velocidad inicial del globo 3
balloon.settings[2].grav 0.00036f # Gravedad aplicada al globo 3 (pixels/ms²) balloon.settings[2].grav 0.10f # Gravedad aplicada al globo 3
balloon.settings[3].vel 0.327f # Velocidad inicial del globo 4 (pixels/ms) balloon.settings[3].vel 5.45f # Velocidad inicial del globo 4
balloon.settings[3].grav 0.00036f # Gravedad aplicada al globo 4 (pixels/ms²) balloon.settings[3].grav 0.10f # Gravedad aplicada al globo 4
balloon.color[0] blue # Color de creación del globo normal balloon.color[0] blue # Color de creación del globo normal
balloon.color[1] orange # Color del globo normal balloon.color[1] orange # Color del globo normal

View File

@@ -10,8 +10,8 @@ game.play_area.rect.x 0 # Posición X de la zona jugable
game.play_area.rect.y 0 # Posición Y de la zona jugable game.play_area.rect.y 0 # Posición Y de la zona jugable
game.play_area.rect.w 320 # Ancho de la zona jugable game.play_area.rect.w 320 # Ancho de la zona jugable
game.play_area.rect.h 216 # Alto de la zona jugable game.play_area.rect.h 216 # Alto de la zona jugable
game.name_entry_idle_time 10000 # Segundos para introducir el nombre al finalizar la partida si no se pulsa nada game.name_entry_idle_time 10 # Segundos para introducir el nombre al finalizar la partida si no se pulsa nada
game.name_entry_total_time 60000 # Segundos totales para introducir el nombre al finalizar la partida game.name_entry_total_time 60 # Segundos totales para introducir el nombre al finalizar la partida
game.hit_stop false # Indica si debe haber un paro cuando el jugador es golpeado por un globo game.hit_stop false # Indica si debe haber un paro cuando el jugador es golpeado por un globo
game.hit_stop_ms 500 # Cantidad de milisegundos que dura el hit_stop game.hit_stop_ms 500 # Cantidad de milisegundos que dura el hit_stop
@@ -39,24 +39,24 @@ scoreboard.text_color2 FFFFFF # Color secundario del texto del marca
scoreboard.skip_countdown_value 8 # Valor para saltar la cuenta atrás (segundos) scoreboard.skip_countdown_value 8 # Valor para saltar la cuenta atrás (segundos)
# --- TITLE --- # --- TITLE ---
title.press_start_position 180 # Posición Y del texto "Press Start" title.press_start_position 180 # Posición Y del texto "Press Start"
title.title_duration 14000 # Duración de la pantalla de título (milisegundos) title.title_duration 800 # Duración de la pantalla de título (frames)
title.arcade_edition_position 123 # Posición Y del subtítulo "Arcade Edition" title.arcade_edition_position 123 # Posición Y del subtítulo "Arcade Edition"
title.title_c_c_position 80 # Posición Y del título principal title.title_c_c_position 80 # Posición Y del título principal
title.bg_color 41526F # Color de fondo en la sección titulo title.bg_color 41526F # Color de fondo en la sección titulo
# --- BACKGROUND --- # --- BACKGROUND ---
background.attenuate_color FFFFFF00 # Color de atenuación del fondo (RGBA hexadecimal) background.attenuate_color FFFFFF00 # Color de atenuación del fondo (RGBA hexadecimal)
# --- BALLOONS --- (deltaTime puro: vel en pixels/ms, grav en pixels/ms²) # --- BALLOONS ---
balloon.settings[0].vel 0.165f # Velocidad inicial del globo 1 (pixels/ms) balloon.settings[0].vel 2.75f # Velocidad inicial del globo 1
balloon.settings[0].grav 0.00032f # Gravedad aplicada al globo 1 (pixels/ms²) balloon.settings[0].grav 0.09f # Gravedad aplicada al globo 1
balloon.settings[1].vel 0.222f # Velocidad inicial del globo 2 (pixels/ms) balloon.settings[1].vel 3.70f # Velocidad inicial del globo 2
balloon.settings[1].grav 0.00036f # Gravedad aplicada al globo 2 (pixels/ms²) balloon.settings[1].grav 0.10f # Gravedad aplicada al globo 2
balloon.settings[2].vel 0.282f # Velocidad inicial del globo 3 (pixels/ms) balloon.settings[2].vel 4.70f # Velocidad inicial del globo 3
balloon.settings[2].grav 0.00036f # Gravedad aplicada al globo 3 (pixels/ms²) balloon.settings[2].grav 0.10f # Gravedad aplicada al globo 3
balloon.settings[3].vel 0.327f # Velocidad inicial del globo 4 (pixels/ms) balloon.settings[3].vel 5.45f # Velocidad inicial del globo 4
balloon.settings[3].grav 0.00036f # Gravedad aplicada al globo 4 (pixels/ms²) balloon.settings[3].grav 0.10f # Gravedad aplicada al globo 4
balloon.color[0] blue # Color de creación del globo normal balloon.color[0] blue # Color de creación del globo normal
balloon.color[1] orange # Color del globo normal balloon.color[1] orange # Color del globo normal

View File

@@ -1,79 +0,0 @@
# Plan de Limpieza Post-Migración DeltaTime
## Estado Actual
✅ Migración básica completada: bullet.cpp, item.cpp, moving_sprite.cpp, game_logo.cpp
✅ Magic numbers convertidos a constantes en game_logo.cpp
## Tareas Pendientes
### 1. Eliminar Contadores Frame-Based
- [ ] Buscar todos los contadores que usen lógica frame-based
- [ ] Convertir a timers basados en deltaTime
- [ ] Eliminar variables como `counter_`, `frame_counter_`, etc.
- [ ] Patrón: `if (counter-- <= 0)``if (timer >= DURATION_MS)`
- [ ] **IMPORTANTE**: Todos los contadores han de ser crecientes, de cero hasta llegar a la constante que define su tope
### 2. Revisar Inicializaciones de Aceleraciones MovingSprite
- [ ] Buscar todas las llamadas a `setAccelX()`, `setAccelY()`
- [ ] Buscar asignaciones directas a `ax_`, `ay_`
- [ ] Convertir de `pixels/frame²` a `pixels/ms²`
- [ ] Factor de conversión: `valor_original / (16.67)²`
### 3. Problema Detectado: Demo - Creación Incorrecta de Globos
- [ ] Investigar cómo se crean los globos en modo demo
- [ ] Verificar si usan timing frame-based obsoleto
- [ ] Corregir la lógica de creación para deltaTime
### 4. Búsqueda de Patrones Problemáticos
- [ ] Buscar `frameFactor` residual
- [ ] Buscar `1000.0f / 60.0f` hardcodeado
- [ ] Buscar `16.67f` hardcodeado
- [ ] Buscar comentarios con "frame" o "60fps"
- [ ] Localizar magic numbers y convertirlos a constantes con nombres descriptivos
- [ ] **IMPORTANTE**: Modificar speed en ficheros .ani - está en frames, hay que pasarlo a milisegundos (multiplicar speed por 1000/60 = 16.67)
- [ ] **SmartSprite**: Revisar inicialización de SmartSprites en el código - cambiar setFinishedCounter() a setFinishedDelay() y convertir valores de frames a milisegundos
### 5. Cambio de Unidades de Tiempo en sections/*
- [ ] Cambiar el cálculo de deltatime en source/sections/* para que devuelva segundos (float) en lugar de milisegundos
- [ ] Cambiar velocidades de pixeles/ms a pixeles/segundos para evitar valores absurdamente pequeños
- [ ] Cambiar aceleraciones de pixeles/ms² a pixeles/segundos²
- [ ] Actualizar todas las constantes de tiempo en archivos de sections
### 6. Archivos Prioritarios a Revisar
- [ ] **player.cpp** - puede tener aceleraciones
- [ ] **balloon.cpp** - contadores de estado
- [ ] **stage.cpp** - timers de nivel
- [ ] **credits.cpp** - efectos de texto
- [ ] **tabe.cpp** - movimiento del protagonista
- [ ] **sections/*.cpp** - transiciones y efectos
### 7. Validación Final
- [ ] Compilar sin warnings
- [ ] Probar gameplay normal
- [ ] Probar modo demo
- [ ] Verificar que no hay saltos de velocidad
- [ ] Confirmar que el timing es consistente en diferentes framerates
## Comandos Útiles de Búsqueda
```bash
# Buscar contadores frame-based
rg "counter.*--|\+\+.*counter|counter.*\+\+|--.*counter"
# Buscar inicializaciones de aceleración
rg "setAccel|\.ax.*=|\.ay.*="
# Buscar hardcoded framerates
rg "60\.0|16\.67|1000\.0.*60"
```
## DECISIÓN IMPORTANTE: TODO EL CÓDIGO USA SEGUNDOS
- **CAMBIO DE PLAN**: Todo el código del juego debe usar deltaTime en segundos (float)
- **NO** debe haber soporte para frames, milisegundos y segundos simultáneamente
- **SOLO SEGUNDOS** en todo el codebase
- Velocidades en `pixels/segundos`, aceleraciones en `pixels/segundos²`
- Todos los contadores deben ser crecientes (0 → constante_tope)
- Eliminar todos los métodos duales (updateS, setSpeedS, etc.) - solo una versión
- Convertir completamente: path_sprite.cpp, writer.cpp, tiled_bg.cpp, etc.
- Documentar las conversiones en comentarios
- Crear constantes para valores repetidos
- Evitar números mágicos

View File

@@ -43,10 +43,6 @@ auto loadAnimationsFromFile(const std::string& file_path) -> AnimationsFileBuffe
std::vector<std::string> buffer; std::vector<std::string> buffer;
std::string line; std::string line;
while (std::getline(input_stream, line)) { while (std::getline(input_stream, line)) {
// Eliminar caracteres de retorno de carro (\r) al final de la línea
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
if (!line.empty()) { if (!line.empty()) {
buffer.push_back(line); buffer.push_back(line);
} }
@@ -86,33 +82,33 @@ auto AnimatedSprite::getAnimationIndex(const std::string& name) -> int {
return -1; return -1;
} }
// Calcula el frame correspondiente a la animación (time-based) // Calcula el frame correspondiente a la animación
void AnimatedSprite::animate(float deltaTime) { void AnimatedSprite::animate() {
if (animations_[current_animation_].speed == 0 || animations_[current_animation_].paused) { if (animations_[current_animation_].speed == 0 || animations_[current_animation_].paused) {
return; return;
} }
// Acumular tiempo transcurrido // Calcula el frame actual a partir del contador
animations_[current_animation_].time_accumulator += deltaTime; animations_[current_animation_].current_frame = animations_[current_animation_].counter / animations_[current_animation_].speed;
// Verificar si es momento de cambiar frame // Si alcanza el final de la animación, reinicia el contador de la animación
if (animations_[current_animation_].time_accumulator >= animations_[current_animation_].speed) { // en función de la variable loop y coloca el nuevo frame
animations_[current_animation_].time_accumulator -= animations_[current_animation_].speed; if (animations_[current_animation_].current_frame >= animations_[current_animation_].frames.size()) {
animations_[current_animation_].current_frame++; if (animations_[current_animation_].loop == -1) { // Si no hay loop, deja el último frame
animations_[current_animation_].current_frame = animations_[current_animation_].frames.size();
// Si alcanza el final de la animación animations_[current_animation_].completed = true;
if (animations_[current_animation_].current_frame >= animations_[current_animation_].frames.size()) { } else { // Si hay loop, vuelve al frame indicado
if (animations_[current_animation_].loop == -1) { // Si no hay loop, deja el último frame animations_[current_animation_].counter = 0;
animations_[current_animation_].current_frame = animations_[current_animation_].frames.size() - 1; animations_[current_animation_].current_frame = animations_[current_animation_].loop;
animations_[current_animation_].completed = true;
} else { // Si hay loop, vuelve al frame indicado
animations_[current_animation_].time_accumulator = 0.0f;
animations_[current_animation_].current_frame = animations_[current_animation_].loop;
}
} }
}
// Actualizar el sprite clip // En caso contrario
else {
// Escoge el frame correspondiente de la animación
updateSpriteClip(); updateSpriteClip();
// Incrementa el contador de la animacion
animations_[current_animation_].counter++;
} }
} }
@@ -129,11 +125,11 @@ void AnimatedSprite::setCurrentAnimation(const std::string& name, bool reset) {
current_animation_ = NEW_ANIMATION; current_animation_ = NEW_ANIMATION;
if (reset) { if (reset) {
animations_[current_animation_].current_frame = 0; animations_[current_animation_].current_frame = 0;
animations_[current_animation_].time_accumulator = 0.0f; animations_[current_animation_].counter = 0;
animations_[current_animation_].completed = false; animations_[current_animation_].completed = false;
} else { } else {
animations_[current_animation_].current_frame = std::min(animations_[OLD_ANIMATION].current_frame, animations_[current_animation_].frames.size() - 1); animations_[current_animation_].current_frame = std::min(animations_[OLD_ANIMATION].current_frame, animations_[current_animation_].frames.size() - 1);
animations_[current_animation_].time_accumulator = animations_[OLD_ANIMATION].time_accumulator; animations_[current_animation_].counter = animations_[OLD_ANIMATION].counter;
animations_[current_animation_].completed = animations_[OLD_ANIMATION].completed; animations_[current_animation_].completed = animations_[OLD_ANIMATION].completed;
} }
updateSpriteClip(); updateSpriteClip();
@@ -148,27 +144,27 @@ void AnimatedSprite::setCurrentAnimation(int index, bool reset) {
current_animation_ = NEW_ANIMATION; current_animation_ = NEW_ANIMATION;
if (reset) { if (reset) {
animations_[current_animation_].current_frame = 0; animations_[current_animation_].current_frame = 0;
animations_[current_animation_].time_accumulator = 0.0f; animations_[current_animation_].counter = 0;
animations_[current_animation_].completed = false; animations_[current_animation_].completed = false;
} else { } else {
animations_[current_animation_].current_frame = std::min(animations_[OLD_ANIMATION].current_frame, animations_[current_animation_].frames.size()); animations_[current_animation_].current_frame = std::min(animations_[OLD_ANIMATION].current_frame, animations_[current_animation_].frames.size());
animations_[current_animation_].time_accumulator = animations_[OLD_ANIMATION].time_accumulator; animations_[current_animation_].counter = animations_[OLD_ANIMATION].counter;
animations_[current_animation_].completed = animations_[OLD_ANIMATION].completed; animations_[current_animation_].completed = animations_[OLD_ANIMATION].completed;
} }
updateSpriteClip(); updateSpriteClip();
} }
} }
// Actualiza las variables del objeto (time-based) // Actualiza las variables del objeto
void AnimatedSprite::update(float deltaTime) { void AnimatedSprite::update() {
animate(deltaTime); animate();
MovingSprite::update(deltaTime); MovingSprite::update();
} }
// Reinicia la animación // Reinicia la animación
void AnimatedSprite::resetAnimation() { void AnimatedSprite::resetAnimation() {
animations_[current_animation_].current_frame = 0; animations_[current_animation_].current_frame = 0;
animations_[current_animation_].time_accumulator = 0.0f; animations_[current_animation_].counter = 0;
animations_[current_animation_].completed = false; animations_[current_animation_].completed = false;
animations_[current_animation_].paused = false; animations_[current_animation_].paused = false;
updateSpriteClip(); updateSpriteClip();
@@ -194,12 +190,6 @@ void AnimatedSprite::loadFromAnimationsFileBuffer(const AnimationsFileBuffer& so
// Pone un valor por defecto // Pone un valor por defecto
setWidth(config.frame_width); setWidth(config.frame_width);
setHeight(config.frame_height); setHeight(config.frame_height);
// Establece el primer frame inmediatamente si hay animaciones
if (!animations_.empty()) {
current_animation_ = 0;
updateSpriteClip();
}
} }
// Procesa una línea de configuración // Procesa una línea de configuración

View File

@@ -17,15 +17,15 @@ class Texture;
// --- Estructuras --- // --- Estructuras ---
struct Animation { struct Animation {
static constexpr float DEFAULT_SPEED = 80.0F; static constexpr int DEFAULT_SPEED = 5;
std::string name; // Nombre de la animación std::string name; // Nombre de la animación
std::vector<SDL_FRect> frames; // Frames que componen la animación std::vector<SDL_FRect> frames; // Frames que componen la animación
float speed{DEFAULT_SPEED}; // Velocidad de reproducción (ms entre frames) int speed{DEFAULT_SPEED}; // Velocidad de reproducción
int loop{0}; // Frame de vuelta al terminar (-1 para no repetir) int loop{0}; // Frame de vuelta al terminar (-1 para no repetir)
bool completed{false}; // Indica si la animación ha finalizado bool completed{false}; // Indica si la animación ha finalizado
size_t current_frame{0}; // Frame actual en reproducción size_t current_frame{0}; // Frame actual en reproducción
float time_accumulator{0.0f}; // Acumulador de tiempo para animaciones time-based int counter{0}; // Contador para la animación
bool paused{false}; // La animación no avanza bool paused{false}; // La animación no avanza
Animation() = default; Animation() = default;
@@ -55,7 +55,7 @@ class AnimatedSprite : public MovingSprite {
~AnimatedSprite() override = default; ~AnimatedSprite() override = default;
// --- Métodos principales --- // --- Métodos principales ---
void update(float deltaTime); // Actualiza la animación (time-based) void update() override; // Actualiza la animación
// --- Control de animaciones --- // --- Control de animaciones ---
void setCurrentAnimation(const std::string& name = "default", bool reset = true); // Establece la animación por nombre void setCurrentAnimation(const std::string& name = "default", bool reset = true); // Establece la animación por nombre
@@ -78,7 +78,7 @@ class AnimatedSprite : public MovingSprite {
int current_animation_ = 0; // Índice de la animación activa int current_animation_ = 0; // Índice de la animación activa
// --- Métodos internos --- // --- Métodos internos ---
void animate(float deltaTime); // Calcula el frame correspondiente a la animación (time-based) void animate(); // Calcula el frame correspondiente a la animación
void loadFromAnimationsFileBuffer(const AnimationsFileBuffer& source); // Carga la animación desde un vector de cadenas void loadFromAnimationsFileBuffer(const AnimationsFileBuffer& source); // Carga la animación desde un vector de cadenas
void processConfigLine(const std::string& line, AnimationConfig& config); // Procesa una línea de configuración void processConfigLine(const std::string& line, AnimationConfig& config); // Procesa una línea de configuración
void updateFrameCalculations(AnimationConfig& config); // Actualiza los cálculos basados en las dimensiones del frame void updateFrameCalculations(AnimationConfig& config); // Actualiza los cálculos basados en las dimensiones del frame

View File

@@ -126,7 +126,7 @@ void Background::initializeTextures() {
} }
// Actualiza la lógica del objeto // Actualiza la lógica del objeto
void Background::update(float delta_time) { void Background::update() {
// Actualiza la progresión y calcula transiciones // Actualiza la progresión y calcula transiciones
if (!manual_mode_) { if (!manual_mode_) {
updateProgression(); updateProgression();
@@ -136,14 +136,10 @@ void Background::update(float delta_time) {
updateAlphaColorTexture(); updateAlphaColorTexture();
// Actualiza las nubes // Actualiza las nubes
updateClouds(delta_time); updateClouds();
// Actualiza timer de hierba // Calcula el frame de la hierba
grass_timer_ += delta_time; grass_sprite_->setSpriteClip(0, (10 * (counter_ / 20 % 2)), 320, 10);
// Calcula el frame de la hierba (alterna cada GRASS_FRAME_DURATION ms)
int grass_frame = static_cast<int>(grass_timer_ / GRASS_FRAME_DURATION) % 2;
grass_sprite_->setSpriteClip(0, (10 * grass_frame), 320, 10);
// Calcula el valor de alpha // Calcula el valor de alpha
alpha_ = std::max((255 - (int)(255 * transition_)), 0); alpha_ = std::max((255 - (int)(255 * transition_)), 0);
@@ -152,6 +148,9 @@ void Background::update(float delta_time) {
sun_sprite_->setPosition(sun_path_.at(sun_index_)); sun_sprite_->setPosition(sun_path_.at(sun_index_));
moon_sprite_->setPosition(moon_path_.at(moon_index_)); moon_sprite_->setPosition(moon_path_.at(moon_index_));
// Incrementa el contador
++counter_;
// Compone todos los elementos del fondo en la textura // Compone todos los elementos del fondo en la textura
fillCanvas(); fillCanvas();
} }
@@ -315,12 +314,12 @@ void Background::updateCloudsSpeed() {
} }
// Actualiza las nubes // Actualiza las nubes
void Background::updateClouds(float deltaTime) { void Background::updateClouds() {
// Mueve las nubes // Mueve las nubes
top_clouds_sprite_a_->update(deltaTime); top_clouds_sprite_a_->update();
top_clouds_sprite_b_->update(deltaTime); top_clouds_sprite_b_->update();
bottom_clouds_sprite_a_->update(deltaTime); bottom_clouds_sprite_a_->update();
bottom_clouds_sprite_b_->update(deltaTime); bottom_clouds_sprite_b_->update();
// Calcula el offset de las nubes // Calcula el offset de las nubes
if (top_clouds_sprite_a_->getPosX() < -top_clouds_sprite_a_->getWidth()) { if (top_clouds_sprite_a_->getPosX() < -top_clouds_sprite_a_->getWidth()) {

View File

@@ -31,7 +31,7 @@ class Background {
~Background(); // Destructor ~Background(); // Destructor
// --- Métodos principales --- // --- Métodos principales ---
void update(float delta_time); // Actualiza la lógica del objeto void update(); // Actualiza la lógica del objeto
void render(); // Dibuja el objeto void render(); // Dibuja el objeto
void reset(); // Reinicia la progresión void reset(); // Reinicia la progresión
@@ -107,8 +107,7 @@ class Background {
float clouds_speed_ = 0; // Velocidad de las nubes float clouds_speed_ = 0; // Velocidad de las nubes
float transition_ = 0; // Porcentaje de transición float transition_ = 0; // Porcentaje de transición
size_t gradient_number_ = 0; // Índice de fondo degradado size_t gradient_number_ = 0; // Índice de fondo degradado
float grass_timer_ = 0.0f; // Timer para animación de hierba (ms) size_t counter_ = 0; // Contador interno
static constexpr float GRASS_FRAME_DURATION = 333.34f; // Duración por frame de hierba (20 frames * 16.67ms)
size_t alpha_color_texture_ = 0; // Transparencia de atenuación size_t alpha_color_texture_ = 0; // Transparencia de atenuación
size_t previous_alpha_color_texture_ = 0; // Transparencia anterior size_t previous_alpha_color_texture_ = 0; // Transparencia anterior
size_t sun_index_ = 0; // Índice del recorrido del sol size_t sun_index_ = 0; // Índice del recorrido del sol
@@ -130,7 +129,7 @@ class Background {
void renderBottomClouds(); // Dibuja las nubes inferiores void renderBottomClouds(); // Dibuja las nubes inferiores
void fillCanvas(); // Compone todos los elementos en la textura void fillCanvas(); // Compone todos los elementos en la textura
void updateAlphaColorTexture(); // Actualiza el alpha de la textura de atenuación void updateAlphaColorTexture(); // Actualiza el alpha de la textura de atenuación
void updateClouds(float deltaTime); // Actualiza el movimiento de las nubes (time-based) void updateClouds(); // Actualiza el movimiento de las nubes
void createSunPath(); // Precalcula el recorrido del sol void createSunPath(); // Precalcula el recorrido del sol
void createMoonPath(); // Precalcula el recorrido de la luna void createMoonPath(); // Precalcula el recorrido de la luna
}; };

View File

@@ -23,7 +23,7 @@ Balloon::Balloon(const Config& config)
creation_counter_ini_(config.creation_counter), creation_counter_ini_(config.creation_counter),
type_(config.type), type_(config.type),
size_(config.size), size_(config.size),
game_tempo_(config.game_tempo), speed_(config.speed),
play_area_(config.play_area), play_area_(config.play_area),
sound_(config.sound) { sound_(config.sound) {
switch (type_) { switch (type_) {
@@ -89,12 +89,6 @@ Balloon::Balloon(const Config& config)
// Establece la animación a usar // Establece la animación a usar
setAnimation(); setAnimation();
// Si no se está creando (creation_counter = 0), asegurar estado activo
if (!being_created_) {
start();
setInvulnerable(false);
}
} }
// Centra el globo en la posición X // Centra el globo en la posición X
@@ -141,20 +135,19 @@ void Balloon::render() {
} }
} }
// Actualiza la posición y estados del globo (time-based) // Actualiza la posición y estados del globo
void Balloon::move(float deltaTime) { void Balloon::move() {
if (isStopped()) { if (isStopped()) {
return; return;
} }
handleHorizontalMovement(deltaTime); handleHorizontalMovement();
handleVerticalMovement(deltaTime); handleVerticalMovement();
applyGravity(deltaTime); applyGravity();
} }
void Balloon::handleHorizontalMovement(float deltaTime) { void Balloon::handleHorizontalMovement() {
// DeltaTime puro: velocidad (pixels/ms) * tempo * tiempo (ms) x_ += vx_ * speed_;
x_ += vx_ * game_tempo_ * deltaTime;
const int CLIP = 2; const int CLIP = 2;
const float MIN_X = play_area_.x - CLIP; const float MIN_X = play_area_.x - CLIP;
@@ -165,9 +158,8 @@ void Balloon::handleHorizontalMovement(float deltaTime) {
} }
} }
void Balloon::handleVerticalMovement(float deltaTime) { void Balloon::handleVerticalMovement() {
// DeltaTime puro: velocidad (pixels/ms) * tempo * tiempo (ms) y_ += vy_ * speed_;
y_ += vy_ * game_tempo_ * deltaTime;
if (shouldCheckTopCollision()) { if (shouldCheckTopCollision()) {
handleTopCollision(); handleTopCollision();
@@ -222,9 +214,20 @@ void Balloon::handleBottomCollision() {
} }
} }
void Balloon::applyGravity(float deltaTime) { void Balloon::applyGravity() {
// DeltaTime puro: aceleración (pixels/ms²) * tempo * tiempo (ms) /*
vy_ += gravity_ * game_tempo_ * deltaTime; Para aplicar la gravedad, el diseño original la aplicaba en cada iteración del bucle
Al añadir el modificador de velocidad se reduce la distancia que recorre el objeto y por
tanto recibe mas gravedad. Para solucionarlo se va a aplicar la gravedad cuando se haya
recorrido una distancia igual a la velocidad en Y, que era el cálculo inicial
*/
travel_y_ += speed_;
if (travel_y_ >= 1.0F) {
travel_y_ -= 1.0F;
vy_ += gravity_;
}
} }
void Balloon::playBouncingSound() { void Balloon::playBouncingSound() {
@@ -239,20 +242,19 @@ void Balloon::playPoppingSound() {
} }
} }
// Actualiza al globo a su posicion, animación y controla los contadores (time-based) // Actualiza al globo a su posicion, animación y controla los contadores
void Balloon::update(float deltaTime) { void Balloon::update() {
move(deltaTime); move();
updateState(deltaTime); updateState();
updateBounceEffect(); updateBounceEffect();
shiftSprite(); shiftSprite();
shiftColliders(); shiftColliders();
sprite_->update(deltaTime); sprite_->update();
// Contador interno con deltaTime puro ++counter_;
counter_ += deltaTime;
} }
// Actualiza los estados del globo (time-based) // Actualiza los estados del globo
void Balloon::updateState(float deltaTime) { void Balloon::updateState() {
// Si se está creando // Si se está creando
if (isBeingCreated()) { if (isBeingCreated()) {
// Actualiza el valor de las variables // Actualiza el valor de las variables
@@ -261,13 +263,9 @@ void Balloon::updateState(float deltaTime) {
if (creation_counter_ > 0) { if (creation_counter_ > 0) {
// Desplaza lentamente el globo hacia abajo y hacia un lado // Desplaza lentamente el globo hacia abajo y hacia un lado
// Cada 166ms (equivalente a 10 frames a 60fps) if (creation_counter_ % 10 == 0) {
movement_accumulator_ += deltaTime;
if (movement_accumulator_ >= 166.0f) {
movement_accumulator_ -= 166.0f;
y_++; y_++;
x_ += vx_ * 10.0f; // Movimiento equivalente a 10 frames de velocidad horizontal x_ += vx_;
// Comprueba no se salga por los laterales // Comprueba no se salga por los laterales
const int MIN_X = play_area_.x; const int MIN_X = play_area_.x;
@@ -275,12 +273,11 @@ void Balloon::updateState(float deltaTime) {
if (x_ < MIN_X || x_ > MAX_X) { if (x_ < MIN_X || x_ > MAX_X) {
// Corrige y cambia el sentido de la velocidad // Corrige y cambia el sentido de la velocidad
x_ -= vx_ * 10.0f; x_ -= vx_;
vx_ = -vx_; vx_ = -vx_;
} }
} }
creation_counter_ -= deltaTime; --creation_counter_;
if (creation_counter_ < 0) creation_counter_ = 0;
} }
else { else {
@@ -314,14 +311,11 @@ void Balloon::setAnimation() {
} }
// Establece el frame de animación // Establece el frame de animación
std::string chosen_animation;
if (use_reversed_colors_) { if (use_reversed_colors_) {
chosen_animation = creating_animation; sprite_->setCurrentAnimation(creating_animation);
} else { } else {
chosen_animation = isBeingCreated() ? creating_animation : normal_animation; sprite_->setCurrentAnimation(isBeingCreated() ? creating_animation : normal_animation);
} }
sprite_->setCurrentAnimation(chosen_animation);
} }
// Detiene el globo // Detiene el globo

View File

@@ -36,12 +36,10 @@ class Balloon {
"balloon_pop2.wav", "balloon_pop2.wav",
"balloon_pop3.wav"}; "balloon_pop3.wav"};
// Velocidades horizontales en pixels/ms (convertidas desde 0.7 pixels/frame a 60fps) static constexpr float VELX_POSITIVE = 0.7F;
static constexpr float VELX_POSITIVE = 0.7F / (1000.0F / 60.0F); // ~0.042 pixels/ms static constexpr float VELX_NEGATIVE = -0.7F;
static constexpr float VELX_NEGATIVE = -0.7F / (1000.0F / 60.0F); // ~-0.042 pixels/ms
// Multiplicadores de tempo del juego (sin cambios, son puros multiplicadores) static constexpr std::array<float, 5> SPEED = {0.60F, 0.70F, 0.80F, 0.90F, 1.00F};
static constexpr std::array<float, 5> GAME_TEMPO = {0.60F, 0.70F, 0.80F, 0.90F, 1.00F};
static constexpr int POWERBALL_SCREENPOWER_MINIMUM = 10; static constexpr int POWERBALL_SCREENPOWER_MINIMUM = 10;
static constexpr int POWERBALL_COUNTER = 8; static constexpr int POWERBALL_COUNTER = 8;
@@ -76,7 +74,7 @@ class Balloon {
Type type = Type::BALLOON; Type type = Type::BALLOON;
Size size = Size::EXTRALARGE; Size size = Size::EXTRALARGE;
float vel_x = VELX_POSITIVE; float vel_x = VELX_POSITIVE;
float game_tempo = GAME_TEMPO.at(0); float speed = SPEED.at(0);
Uint16 creation_counter = 0; Uint16 creation_counter = 0;
SDL_FRect play_area = {.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F}; SDL_FRect play_area = {.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F};
std::shared_ptr<Texture> texture = nullptr; std::shared_ptr<Texture> texture = nullptr;
@@ -89,11 +87,11 @@ class Balloon {
~Balloon() = default; ~Balloon() = default;
// --- Métodos principales --- // --- Métodos principales ---
void alignTo(int x); // Centra el globo en la posición X void alignTo(int x); // Centra el globo en la posición X
void render(); // Pinta el globo en la pantalla void render(); // Pinta el globo en la pantalla
void move(float deltaTime); // Actualiza la posición y estados del globo (time-based) void move(); // Actualiza la posición y estados del globo
void update(float deltaTime); // Actualiza el globo (posición, animación, contadores) (time-based) void update(); // Actualiza el globo (posición, animación, contadores)
void stop(); // Detiene el globo void stop(); // Detiene el globo
void start(); // Pone el globo en movimiento void start(); // Pone el globo en movimiento
void pop(bool should_sound = false); // Explota el globo void pop(bool should_sound = false); // Explota el globo
@@ -122,7 +120,7 @@ class Balloon {
// --- Setters --- // --- Setters ---
void setVelY(float vel_y) { vy_ = vel_y; } void setVelY(float vel_y) { vy_ = vel_y; }
void setGameTempo(float tempo) { game_tempo_ = tempo; } void setSpeed(float speed) { speed_ = speed; }
void setInvulnerable(bool value) { invulnerable_ = value; } void setInvulnerable(bool value) { invulnerable_ = value; }
void setBouncingSound(bool value) { sound_.bouncing_enabled = value; } void setBouncingSound(bool value) { sound_.bouncing_enabled = value; }
void setPoppingSound(bool value) { sound_.poping_enabled = value; } void setPoppingSound(bool value) { sound_.poping_enabled = value; }
@@ -258,15 +256,15 @@ class Balloon {
bool stopped_; // Si el globo está parado bool stopped_; // Si el globo está parado
bool use_reversed_colors_ = false; // Si se usa el color alternativo bool use_reversed_colors_ = false; // Si se usa el color alternativo
Circle collider_; // Círculo de colisión Circle collider_; // Círculo de colisión
float creation_counter_; // Temporizador de creación Uint16 creation_counter_; // Temporizador de creación
float creation_counter_ini_; // Valor inicial del temporizador de creación Uint16 creation_counter_ini_; // Valor inicial del temporizador de creación
Uint16 score_; // Puntos al destruir el globo Uint16 score_; // Puntos al destruir el globo
Type type_; // Tipo de globo Type type_; // Tipo de globo
Size size_; // Tamaño de globo Size size_; // Tamaño de globo
Uint8 menace_; // Amenaza que genera el globo Uint8 menace_; // Amenaza que genera el globo
Uint32 counter_ = 0; // Contador interno Uint32 counter_ = 0; // Contador interno
float game_tempo_; // Multiplicador de tempo del juego float travel_y_ = 1.0F; // Distancia a recorrer en Y antes de aplicar gravedad
float movement_accumulator_ = 0.0f; // Acumulador para movimiento durante creación (deltaTime) float speed_; // Velocidad del globo
Uint8 power_; // Poder que alberga el globo Uint8 power_; // Poder que alberga el globo
SDL_FRect play_area_; // Zona de movimiento del globo SDL_FRect play_area_; // Zona de movimiento del globo
Sound sound_; // Configuración de sonido del globo Sound sound_; // Configuración de sonido del globo
@@ -282,9 +280,9 @@ class Balloon {
void playPoppingSound(); // Reproduce el sonido de reventar void playPoppingSound(); // Reproduce el sonido de reventar
// --- Movimiento y física --- // --- Movimiento y física ---
void handleHorizontalMovement(float deltaTime); // Maneja el movimiento horizontal (time-based) void handleHorizontalMovement(); // Maneja el movimiento horizontal
void handleVerticalMovement(float deltaTime); // Maneja el movimiento vertical (time-based) void handleVerticalMovement(); // Maneja el movimiento vertical
void applyGravity(float deltaTime); // Aplica la gravedad al objeto (time-based) void applyGravity(); // Aplica la gravedad al objeto
// --- Rebote --- // --- Rebote ---
void enableBounceEffect(); // Activa el efecto de rebote void enableBounceEffect(); // Activa el efecto de rebote
@@ -299,5 +297,5 @@ class Balloon {
void handleBottomCollision(); // Maneja la colisión inferior void handleBottomCollision(); // Maneja la colisión inferior
// --- Lógica de estado --- // --- Lógica de estado ---
void updateState(float deltaTime); // Actualiza los estados del globo (time-based) void updateState(); // Actualiza los estados del globo
}; };

View File

@@ -82,7 +82,7 @@ class BalloonFormations {
private: private:
// --- Constantes --- // --- Constantes ---
static constexpr int BALLOON_SPAWN_HEIGHT = 208; // Altura desde el suelo en la que aparecen los globos static constexpr int BALLOON_SPAWN_HEIGHT = 208; // Altura desde el suelo en la que aparecen los globos
static constexpr int DEFAULT_CREATION_TIME = 3334; // Tiempo base de creación de los globos en ms (200 frames × 16.67ms) static constexpr int DEFAULT_CREATION_TIME = 200; // Tiempo base de creación de los globos para las formaciones
// --- Variables --- // --- Variables ---
std::vector<Formation> formations_; // Vector con todas las formaciones disponibles std::vector<Formation> formations_; // Vector con todas las formaciones disponibles

View File

@@ -62,13 +62,13 @@ void BalloonManager::init() {
explosions_->addTexture(3, explosions_textures_.at(3), explosions_animations_.at(3)); explosions_->addTexture(3, explosions_textures_.at(3), explosions_animations_.at(3));
} }
// Actualiza (time-based) // Actualiza
void BalloonManager::update(float deltaTime) { void BalloonManager::update() {
for (const auto &balloon : balloons_) { for (const auto &balloon : balloons_) {
balloon->update(deltaTime); balloon->update();
} }
updateBalloonDeployCounter(deltaTime); updateBalloonDeployCounter();
explosions_->update(deltaTime); explosions_->update();
} }
// Renderiza los objetos // Renderiza los objetos
@@ -82,11 +82,11 @@ void BalloonManager::render() {
// Crea una formación de globos // Crea una formación de globos
void BalloonManager::deployRandomFormation(int stage) { void BalloonManager::deployRandomFormation(int stage) {
// Solo despliega una formación enemiga si ha pasado cierto tiempo desde la última // Solo despliega una formación enemiga si ha pasado cierto tiempo desde la última
if (balloon_deploy_counter_ >= DEFAULT_BALLOON_DEPLOY_COUNTER) { if (balloon_deploy_counter_ == 0) {
// En este punto se decide entre crear una powerball o una formación enemiga // En este punto se decide entre crear una powerball o una formación enemiga
if ((rand() % 100 < 15) && (canPowerBallBeCreated())) { if ((rand() % 100 < 15) && (canPowerBallBeCreated())) {
createPowerBall(); // Crea una powerball createPowerBall(); // Crea una powerball
balloon_deploy_counter_ = -167; // Resetea con pequeño retraso (10 frames = 167ms negativos) balloon_deploy_counter_ = 10; // Da un poco de margen para que se creen mas globos
} else { } else {
// Decrementa el contador de despliegues de globos necesarios para la siguiente PowerBall // Decrementa el contador de despliegues de globos necesarios para la siguiente PowerBall
if (power_ball_counter_ > 0) { if (power_ball_counter_ > 0) {
@@ -113,13 +113,13 @@ void BalloonManager::deployRandomFormation(int stage) {
.type = balloon.type, .type = balloon.type,
.size = balloon.size, .size = balloon.size,
.vel_x = balloon.vel_x, .vel_x = balloon.vel_x,
.game_tempo = balloon_speed_, .speed = balloon_speed_,
.creation_counter = static_cast<Uint16>(creation_time_enabled_ ? balloon.creation_counter : 0)}; .creation_counter = static_cast<Uint16>(creation_time_enabled_ ? balloon.creation_counter : 0)};
createBalloon(config); createBalloon(config);
} }
// Reinicia el contador para el próximo despliegue // Reinicia el contador para el próximo despliegue
balloon_deploy_counter_ = 0; balloon_deploy_counter_ = DEFAULT_BALLOON_DEPLOY_COUNTER;
} }
} }
} }
@@ -134,7 +134,7 @@ void BalloonManager::deployFormation(int formation_id) {
.type = balloon.type, .type = balloon.type,
.size = balloon.size, .size = balloon.size,
.vel_x = balloon.vel_x, .vel_x = balloon.vel_x,
.game_tempo = balloon_speed_, .speed = balloon_speed_,
.creation_counter = balloon.creation_counter}; .creation_counter = balloon.creation_counter};
createBalloon(config); createBalloon(config);
} }
@@ -150,7 +150,7 @@ void BalloonManager::deployFormation(int formation_id, float y) {
.type = balloon.type, .type = balloon.type,
.size = balloon.size, .size = balloon.size,
.vel_x = balloon.vel_x, .vel_x = balloon.vel_x,
.game_tempo = balloon_speed_, .speed = balloon_speed_,
.creation_counter = balloon.creation_counter}; .creation_counter = balloon.creation_counter};
createBalloon(config); createBalloon(config);
} }
@@ -162,10 +162,11 @@ void BalloonManager::freeBalloons() {
balloons_.erase(result.begin(), balloons_.end()); balloons_.erase(result.begin(), balloons_.end());
} }
// Actualiza la variable enemyDeployCounter (time-based) // Actualiza la variable enemyDeployCounter
void BalloonManager::updateBalloonDeployCounter(float deltaTime) { void BalloonManager::updateBalloonDeployCounter() {
// DeltaTime puro - contador incrementa hasta llegar al umbral if (balloon_deploy_counter_ > 0) {
balloon_deploy_counter_ += deltaTime; --balloon_deploy_counter_;
}
} }
// Indica si se puede crear una powerball // Indica si se puede crear una powerball
@@ -210,15 +211,14 @@ void BalloonManager::createChildBalloon(const std::shared_ptr<Balloon> &balloon,
.y = balloon->getPosY() + ((PARENT_HEIGHT - CHILD_HEIGHT) / 2), .y = balloon->getPosY() + ((PARENT_HEIGHT - CHILD_HEIGHT) / 2),
.size = static_cast<Balloon::Size>(static_cast<int>(balloon->getSize()) - 1), .size = static_cast<Balloon::Size>(static_cast<int>(balloon->getSize()) - 1),
.vel_x = direction == "LEFT" ? Balloon::VELX_NEGATIVE : Balloon::VELX_POSITIVE, .vel_x = direction == "LEFT" ? Balloon::VELX_NEGATIVE : Balloon::VELX_POSITIVE,
.game_tempo = balloon_speed_, .speed = balloon_speed_,
.creation_counter = 0}; .creation_counter = 0};
// Crea el globo // Crea el globo
auto b = createBalloon(config); auto b = createBalloon(config);
// Establece parametros (deltaTime puro - valores ya en pixels/ms) // Establece parametros
constexpr float VEL_Y_BALLOON_PER_MS = -0.15F; // -2.50F convertido a pixels/ms b->setVelY(b->getType() == Balloon::Type::BALLOON ? -2.50F : Balloon::VELX_NEGATIVE * 2.0F);
b->setVelY(b->getType() == Balloon::Type::BALLOON ? VEL_Y_BALLOON_PER_MS : Balloon::VELX_NEGATIVE * 2.0F);
// Herencia de estados // Herencia de estados
if (balloon->isStopped()) { b->stop(); } if (balloon->isStopped()) { b->stop(); }
@@ -245,7 +245,7 @@ void BalloonManager::createPowerBall() {
.type = Balloon::Type::POWERBALL, .type = Balloon::Type::POWERBALL,
.size = Balloon::Size::EXTRALARGE, .size = Balloon::Size::EXTRALARGE,
.vel_x = VEL_X.at(LUCK), .vel_x = VEL_X.at(LUCK),
.game_tempo = balloon_speed_, .speed = balloon_speed_,
.creation_counter = 0, .creation_counter = 0,
.play_area = play_area_, .play_area = play_area_,
.texture = balloon_textures_.at(4), .texture = balloon_textures_.at(4),
@@ -267,7 +267,7 @@ void BalloonManager::createPowerBall() {
void BalloonManager::setBalloonSpeed(float speed) { void BalloonManager::setBalloonSpeed(float speed) {
balloon_speed_ = speed; balloon_speed_ = speed;
for (auto &balloon : balloons_) { for (auto &balloon : balloons_) {
balloon->setGameTempo(speed); balloon->setSpeed(speed);
} }
} }
@@ -280,7 +280,7 @@ auto BalloonManager::popBalloon(const std::shared_ptr<Balloon> &balloon) -> int
balloon->pop(true); balloon->pop(true);
score = destroyAllBalloons(); score = destroyAllBalloons();
power_ball_enabled_ = false; power_ball_enabled_ = false;
balloon_deploy_counter_ = -334; // Resetea con retraso (20 frames = 334ms negativos) balloon_deploy_counter_ = 20;
} else { } else {
score = balloon->getScore(); score = balloon->getScore();
if (balloon->getSize() != Balloon::Size::SMALL) { if (balloon->getSize() != Balloon::Size::SMALL) {
@@ -336,7 +336,7 @@ auto BalloonManager::destroyAllBalloons() -> int {
score += destroyBalloon(balloon); score += destroyBalloon(balloon);
} }
balloon_deploy_counter_ = -5000; // Resetea con retraso grande (300 frames = 5000ms negativos) balloon_deploy_counter_ = 300;
Screen::get()->flash(Colors::FLASH, 3); Screen::get()->flash(Colors::FLASH, 3);
Screen::get()->shake(); Screen::get()->shake();
@@ -346,9 +346,7 @@ auto BalloonManager::destroyAllBalloons() -> int {
// Detiene todos los globos // Detiene todos los globos
void BalloonManager::stopAllBalloons() { void BalloonManager::stopAllBalloons() {
for (auto &balloon : balloons_) { for (auto &balloon : balloons_) {
if (!balloon->isBeingCreated()) { balloon->stop();
balloon->stop();
}
} }
} }

View File

@@ -28,8 +28,8 @@ class BalloonManager {
~BalloonManager() = default; ~BalloonManager() = default;
// --- Métodos principales --- // --- Métodos principales ---
void update(float deltaTime); // Actualiza el estado de los globos (time-based) void update(); // Actualiza el estado de los globos
void render(); // Renderiza los globos en pantalla void render(); // Renderiza los globos en pantalla
// --- Gestión de globos --- // --- Gestión de globos ---
void freeBalloons(); // Libera globos que ya no sirven void freeBalloons(); // Libera globos que ya no sirven
@@ -49,7 +49,7 @@ class BalloonManager {
void setBalloonSpeed(float speed); // Ajusta la velocidad de los globos void setBalloonSpeed(float speed); // Ajusta la velocidad de los globos
void setDefaultBalloonSpeed(float speed) { default_balloon_speed_ = speed; }; // Establece la velocidad base void setDefaultBalloonSpeed(float speed) { default_balloon_speed_ = speed; }; // Establece la velocidad base
void resetBalloonSpeed() { setBalloonSpeed(default_balloon_speed_); }; // Restablece la velocidad de los globos void resetBalloonSpeed() { setBalloonSpeed(default_balloon_speed_); }; // Restablece la velocidad de los globos
void updateBalloonDeployCounter(float deltaTime); // Actualiza el contador de despliegue (time-based) void updateBalloonDeployCounter(); // Actualiza el contador de despliegue
auto canPowerBallBeCreated() -> bool; // Indica si se puede crear una PowerBall auto canPowerBallBeCreated() -> bool; // Indica si se puede crear una PowerBall
auto calculateScreenPower() -> int; // Calcula el poder de los globos en pantalla auto calculateScreenPower() -> int; // Calcula el poder de los globos en pantalla
@@ -82,7 +82,7 @@ class BalloonManager {
private: private:
// --- Constantes --- // --- Constantes ---
static const int DEFAULT_BALLOON_DEPLOY_COUNTER = 5000; // 300 frames × 16.67ms = 5000ms static const int DEFAULT_BALLOON_DEPLOY_COUNTER = 300;
// --- Objetos y punteros --- // --- Objetos y punteros ---
Balloons balloons_; // Vector con los globos activos Balloons balloons_; // Vector con los globos activos
@@ -96,9 +96,9 @@ class BalloonManager {
// --- Variables de estado --- // --- Variables de estado ---
SDL_FRect play_area_ = param.game.play_area.rect; SDL_FRect play_area_ = param.game.play_area.rect;
float balloon_speed_ = Balloon::GAME_TEMPO.at(0); float balloon_speed_ = Balloon::SPEED.at(0);
float default_balloon_speed_ = Balloon::GAME_TEMPO.at(0); float default_balloon_speed_ = Balloon::SPEED.at(0);
float balloon_deploy_counter_ = 0; int balloon_deploy_counter_ = 0;
int power_ball_counter_ = 0; int power_ball_counter_ = 0;
int last_balloon_deploy_ = 0; int last_balloon_deploy_ = 0;
bool power_ball_enabled_ = false; bool power_ball_enabled_ = false;

View File

@@ -60,22 +60,21 @@ void Bullet::render() {
} }
} }
// Actualiza el estado del objeto (time-based) // Actualiza el estado del objeto
auto Bullet::update(float deltaTime) -> BulletMoveStatus { auto Bullet::update() -> BulletMoveStatus {
sprite_->update(deltaTime); sprite_->update();
return move(deltaTime); return move();
} }
// Implementación del movimiento usando BulletMoveStatus (time-based) // Implementación del movimiento usando BulletMoveStatus
auto Bullet::move(float deltaTime) -> BulletMoveStatus { auto Bullet::move() -> BulletMoveStatus {
// DeltaTime puro: velocidad (pixels/ms) * tiempo (ms) pos_x_ += vel_x_;
pos_x_ += vel_x_ * deltaTime;
if (pos_x_ < param.game.play_area.rect.x - WIDTH || pos_x_ > param.game.play_area.rect.w) { if (pos_x_ < param.game.play_area.rect.x - WIDTH || pos_x_ > param.game.play_area.rect.w) {
disable(); disable();
return BulletMoveStatus::OUT; return BulletMoveStatus::OUT;
} }
pos_y_ += VEL_Y * deltaTime; pos_y_ += VEL_Y;
if (pos_y_ < param.game.play_area.rect.y - HEIGHT) { if (pos_y_ < param.game.play_area.rect.y - HEIGHT) {
disable(); disable();
return BulletMoveStatus::OUT; return BulletMoveStatus::OUT;

View File

@@ -34,9 +34,9 @@ class Bullet {
~Bullet() = default; // Destructor ~Bullet() = default; // Destructor
// --- Métodos principales --- // --- Métodos principales ---
void render(); // Dibuja la bala en pantalla void render(); // Dibuja la bala en pantalla
auto update(float deltaTime) -> BulletMoveStatus; // Actualiza el estado del objeto (time-based) auto update() -> BulletMoveStatus; // Actualiza el estado del objeto
void disable(); // Desactiva la bala void disable(); // Desactiva la bala
// --- Getters --- // --- Getters ---
[[nodiscard]] auto isEnabled() const -> bool; // Comprueba si está activa [[nodiscard]] auto isEnabled() const -> bool; // Comprueba si está activa
@@ -45,9 +45,9 @@ class Bullet {
private: private:
// --- Constantes --- // --- Constantes ---
static constexpr float VEL_Y = -0.18F; // Velocidad vertical (pixels/ms) static constexpr float VEL_Y = -3.0F; // Velocidad vertical
static constexpr float VEL_X_LEFT = -0.12F; // Velocidad izquierda (pixels/ms) static constexpr float VEL_X_LEFT = -2.0F; // Velocidad izquierda
static constexpr float VEL_X_RIGHT = 0.12F; // Velocidad derecha (pixels/ms) static constexpr float VEL_X_RIGHT = 2.0F; // Velocidad derecha
static constexpr float VEL_X_CENTER = 0.0F; // Velocidad central static constexpr float VEL_X_CENTER = 0.0F; // Velocidad central
// --- Objetos y punteros --- // --- Objetos y punteros ---
@@ -64,7 +64,7 @@ class Bullet {
// --- Métodos internos --- // --- Métodos internos ---
void shiftColliders(); // Ajusta el círculo de colisión void shiftColliders(); // Ajusta el círculo de colisión
void shiftSprite(); // Ajusta el sprite void shiftSprite(); // Ajusta el sprite
auto move(float deltaTime) -> BulletMoveStatus; // Mueve la bala y devuelve su estado (time-based) auto move() -> BulletMoveStatus; // Mueve la bala y devuelve su estado
static auto calculateVelocity(BulletType bullet_type) -> float; // Calcula la velocidad horizontal de la bala static auto calculateVelocity(BulletType bullet_type) -> float; // Calcula la velocidad horizontal de la bala
static auto buildAnimationString(BulletType bullet_type, bool powered) -> std::string; // Construye el string de animación static auto buildAnimationString(BulletType bullet_type, bool powered) -> std::string; // Construye el string de animación
}; };

View File

@@ -15,8 +15,8 @@ namespace Game {
constexpr float WIDTH = 320.0F; constexpr float WIDTH = 320.0F;
constexpr float HEIGHT = 256.0F; constexpr float HEIGHT = 256.0F;
constexpr float ITEM_SIZE = 20.0F; constexpr float ITEM_SIZE = 20.0F;
constexpr int NAME_ENTRY_IDLE_TIME = 10000; // 10 segundos en milisegundos constexpr int NAME_ENTRY_IDLE_TIME = 10;
constexpr int NAME_ENTRY_TOTAL_TIME = 60000; // 60 segundos en milisegundos constexpr int NAME_ENTRY_TOTAL_TIME = 60;
constexpr bool HIT_STOP = false; constexpr bool HIT_STOP = false;
constexpr int HIT_STOP_MS = 500; constexpr int HIT_STOP_MS = 500;
constexpr const char* ITEM_TEXT_OUTLINE_COLOR = "FFFFFF00"; // 255, 255, 255, 0 constexpr const char* ITEM_TEXT_OUTLINE_COLOR = "FFFFFF00"; // 255, 255, 255, 0
@@ -58,7 +58,7 @@ constexpr int SKIP_COUNTDOWN_VALUE = 8;
// --- TITLE --- // --- TITLE ---
namespace Title { namespace Title {
constexpr int PRESS_START_POSITION = 180; constexpr int PRESS_START_POSITION = 180;
constexpr float DURATION = 14000; constexpr int DURATION = 800;
constexpr int ARCADE_EDITION_POSITION = 123; constexpr int ARCADE_EDITION_POSITION = 123;
constexpr int TITLE_C_C_POSITION = 80; constexpr int TITLE_C_C_POSITION = 80;
constexpr const char* BG_COLOR = "41526F"; constexpr const char* BG_COLOR = "41526F";
@@ -80,12 +80,11 @@ struct BalloonSettings {
grav(g) {} grav(g) {}
}; };
// Valores para deltaTime puro: vel en pixels/ms, grav en pixels/ms² (aceleración)
constexpr std::array<BalloonSettings, 4> SETTINGS = {{ constexpr std::array<BalloonSettings, 4> SETTINGS = {{
BalloonSettings(2.75F / 16.67F, 0.09F / (16.67F * 16.67F)), // Globo 0: vel=0.165 pixels/ms, grav=0.00032 pixels/ms² BalloonSettings(2.75F, 0.09F), // Globo 0
BalloonSettings(3.70F / 16.67F, 0.10F / (16.67F * 16.67F)), // Globo 1: vel=0.222 pixels/ms, grav=0.00036 pixels/ms² BalloonSettings(3.70F, 0.10F), // Globo 1
BalloonSettings(4.70F / 16.67F, 0.10F / (16.67F * 16.67F)), // Globo 2: vel=0.282 pixels/ms, grav=0.00036 pixels/ms² BalloonSettings(4.70F, 0.10F), // Globo 2
BalloonSettings(5.45F / 16.67F, 0.10F / (16.67F * 16.67F)) // Globo 3: vel=0.327 pixels/ms, grav=0.00036 pixels/ms² BalloonSettings(5.45F, 0.10F) // Globo 3
}}; }};
constexpr std::array<const char*, 4> COLORS = { constexpr std::array<const char*, 4> COLORS = {
@@ -220,7 +219,7 @@ constexpr SDL_ScaleMode VIDEO_SCALE_MODE = SDL_ScaleMode::SDL_SCALEMODE_NEAREST;
constexpr bool VIDEO_FULLSCREEN = false; constexpr bool VIDEO_FULLSCREEN = false;
constexpr bool VIDEO_VSYNC = true; constexpr bool VIDEO_VSYNC = true;
constexpr bool VIDEO_INTEGER_SCALE = true; constexpr bool VIDEO_INTEGER_SCALE = true;
constexpr bool VIDEO_SHADERS = false; constexpr bool VIDEO_SHADERS = true;
// Music // Music
constexpr bool MUSIC_ENABLED = true; constexpr bool MUSIC_ENABLED = true;

View File

@@ -42,7 +42,7 @@ Director::Director(int argc, std::span<char *> argv) {
Section::name = Section::Name::GAME; Section::name = Section::Name::GAME;
Section::options = Section::Options::GAME_PLAY_1P; Section::options = Section::Options::GAME_PLAY_1P;
#elif _DEBUG #elif _DEBUG
Section::name = Section::Name::INSTRUCTIONS; Section::name = Section::Name::HI_SCORE_TABLE;
Section::options = Section::Options::GAME_PLAY_1P; Section::options = Section::Options::GAME_PLAY_1P;
#else // NORMAL GAME #else // NORMAL GAME
Section::name = Section::Name::LOGO; Section::name = Section::Name::LOGO;

View File

@@ -6,10 +6,10 @@
class Texture; // lines 4-4 class Texture; // lines 4-4
// Actualiza la lógica de la clase (time-based) // Actualiza la lógica de la clase
void Explosions::update(float deltaTime) { void Explosions::update() {
for (auto &explosion : explosions_) { for (auto &explosion : explosions_) {
explosion->update(deltaTime); explosion->update();
} }
// Vacia el vector de elementos finalizados // Vacia el vector de elementos finalizados

View File

@@ -29,8 +29,8 @@ class Explosions {
~Explosions() = default; // Destructor por defecto ~Explosions() = default; // Destructor por defecto
// --- Métodos principales --- // --- Métodos principales ---
void update(float deltaTime); // Actualiza la lógica de la clase (time-based) void update(); // Actualiza la lógica de la clase
void render(); // Dibuja el objeto en pantalla void render(); // Dibuja el objeto en pantalla
// --- Configuración --- // --- Configuración ---
void addTexture(int size, const std::shared_ptr<Texture> &texture, const std::vector<std::string> &animation); // Añade texturas al objeto void addTexture(int size, const std::shared_ptr<Texture> &texture, const std::vector<std::string> &animation); // Añade texturas al objeto

View File

@@ -4,6 +4,20 @@
#include <string> // Para basic_string, string #include <string> // Para basic_string, string
namespace shader { namespace shader {
bool init(SDL_Window *ventana, SDL_Texture *texturaBackBuffer, const std::string &vertexShader, const std::string &fragmentShader = ""); bool init(SDL_Window *ventana, SDL_Texture *texturaBackBuffer, const std::string &shaderSource, const std::string &fragmentShader = "");
void render(); void render();
void cleanup();
bool isUsingOpenGL();
#ifdef __APPLE__
namespace metal {
bool initMetal(SDL_Window* window, SDL_Texture* backBuffer, const std::string& shaderFilename);
SDL_Texture* createMetalRenderTarget(SDL_Renderer* renderer, int width, int height);
void updateMetalTexture(SDL_Texture* backBuffer);
void renderMetal();
void renderWithPostProcessing(SDL_Renderer* renderer, SDL_Texture* sourceTexture);
void cleanupMetal();
}
#endif
} // namespace shader } // namespace shader

387
source/external/jail_shader_metal.mm vendored Normal file
View File

@@ -0,0 +1,387 @@
#include "jail_shader.h"
#ifdef __APPLE__
#include <SDL3/SDL.h>
#include <SDL3/SDL_metal.h>
#include <Metal/Metal.h>
#include <QuartzCore/CAMetalLayer.h>
#include <CoreFoundation/CoreFoundation.h>
#include <stdexcept>
#include <vector>
#include "../asset.h"
namespace shader {
namespace metal {
// Metal objects
id<MTLDevice> device = nullptr;
id<MTLRenderPipelineState> pipelineState = nullptr;
id<MTLBuffer> vertexBuffer = nullptr;
id<MTLTexture> backBufferTexture = nullptr;
id<MTLTexture> gameCanvasTexture = nullptr; // Our custom render target texture
id<MTLSamplerState> sampler = nullptr;
// SDL objects (references from main shader module)
SDL_Window* win = nullptr;
SDL_Renderer* renderer = nullptr;
SDL_Texture* backBuffer = nullptr;
// Vertex data for fullscreen quad
struct Vertex {
float position[4]; // x, y, z, w
float texcoord[2]; // u, v
};
const Vertex quadVertices[] = {
// Position (x, y, z, w) // TexCoord (u, v) - Standard OpenGL-style coordinates
{{-1.0f, -1.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}, // Bottom-left
{{ 1.0f, -1.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, // Bottom-right
{{-1.0f, 1.0f, 0.0f, 1.0f}, {0.0f, 0.0f}}, // Top-left
{{ 1.0f, 1.0f, 0.0f, 1.0f}, {1.0f, 0.0f}}, // Top-right
};
std::string loadMetalShader(const std::string& filename) {
// Try to load the .metal file from the same location as GLSL files
auto data = Asset::get()->loadData(filename);
if (!data.empty()) {
return std::string(data.begin(), data.end());
}
return "";
}
SDL_Texture* createMetalRenderTarget(SDL_Renderer* renderer, int width, int height) {
if (!renderer) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "createMetalRenderTarget: No renderer provided");
return nullptr;
}
// Crear textura Metal como render target
SDL_Texture* metalTexture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, width, height);
if (!metalTexture) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create Metal render target texture: %s", SDL_GetError());
return nullptr;
}
// Configurar filtrado nearest neighbor para look pixelado
SDL_SetTextureScaleMode(metalTexture, SDL_SCALEMODE_NEAREST);
// Try to extract and store the Metal texture directly
SDL_PropertiesID props = SDL_GetTextureProperties(metalTexture);
if (props != 0) {
const char* propertyNames[] = {
"SDL.texture.metal.texture",
"SDL.renderer.metal.texture",
"metal.texture",
"texture.metal",
"MTLTexture",
"texture"
};
for (const char* propName : propertyNames) {
gameCanvasTexture = (__bridge id<MTLTexture>)SDL_GetPointerProperty(props, propName, nullptr);
if (gameCanvasTexture) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Successfully extracted Metal texture via property '%s' (size: %lux%lu)",
propName, [gameCanvasTexture width], [gameCanvasTexture height]);
break;
}
}
}
if (!gameCanvasTexture) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Could not extract Metal texture from SDL texture - shaders may not work");
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Created Metal render target texture (%dx%d)", width, height);
return metalTexture;
}
bool initMetal(SDL_Window* window, SDL_Texture* backBufferTexture, const std::string& shaderFilename) {
// Store references
win = window;
backBuffer = backBufferTexture;
renderer = SDL_GetRenderer(window);
if (!renderer) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to get SDL renderer");
return false;
}
// Get Metal layer from SDL renderer and extract device from it
CAMetalLayer* metalLayer = (__bridge CAMetalLayer*)SDL_GetRenderMetalLayer(renderer);
if (!metalLayer) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to get Metal layer from SDL renderer");
return false;
}
device = metalLayer.device;
if (!device) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to get Metal device from layer");
return false;
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Got Metal device from SDL layer: %s", [[device name] UTF8String]);
// Note: We no longer need our own texture - we'll use the backBuffer directly
// Load and compile shaders
std::string metalShaderSource = loadMetalShader(shaderFilename);
if (metalShaderSource.empty()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load Metal shader: %s", shaderFilename.c_str());
return false;
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Loaded Metal shader %s (length: %zu)",
shaderFilename.c_str(), metalShaderSource.length());
NSString* shaderNSString = [NSString stringWithUTF8String:metalShaderSource.c_str()];
NSError* error = nil;
id<MTLLibrary> library = [device newLibraryWithSource:shaderNSString options:nil error:&error];
if (!library || error) {
if (error) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to compile Metal shader: %s",
[[error localizedDescription] UTF8String]);
}
return false;
}
id<MTLFunction> vertexFunction = [library newFunctionWithName:@"vertex_main"];
id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"fragment_main"];
if (!vertexFunction || !fragmentFunction) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load Metal shader functions");
return false;
}
// Create render pipeline
MTLRenderPipelineDescriptor* pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineDescriptor.vertexFunction = vertexFunction;
pipelineDescriptor.fragmentFunction = fragmentFunction;
pipelineDescriptor.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
// Set up vertex descriptor
MTLVertexDescriptor* vertexDescriptor = [[MTLVertexDescriptor alloc] init];
vertexDescriptor.attributes[0].format = MTLVertexFormatFloat4;
vertexDescriptor.attributes[0].offset = 0;
vertexDescriptor.attributes[0].bufferIndex = 0;
vertexDescriptor.attributes[1].format = MTLVertexFormatFloat2;
vertexDescriptor.attributes[1].offset = 4 * sizeof(float);
vertexDescriptor.attributes[1].bufferIndex = 0;
vertexDescriptor.layouts[0].stride = sizeof(Vertex);
vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
pipelineDescriptor.vertexDescriptor = vertexDescriptor;
pipelineState = [device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error];
if (!pipelineState || error) {
if (error) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create Metal render pipeline: %s",
[[error localizedDescription] UTF8String]);
}
return false;
}
// Create vertex buffer
vertexBuffer = [device newBufferWithBytes:quadVertices
length:sizeof(quadVertices)
options:MTLResourceOptionCPUCacheModeDefault];
// Create sampler state for nearest neighbor filtering (pixelated look)
MTLSamplerDescriptor* samplerDescriptor = [[MTLSamplerDescriptor alloc] init];
samplerDescriptor.minFilter = MTLSamplerMinMagFilterNearest;
samplerDescriptor.magFilter = MTLSamplerMinMagFilterNearest;
samplerDescriptor.sAddressMode = MTLSamplerAddressModeClampToEdge;
samplerDescriptor.tAddressMode = MTLSamplerAddressModeClampToEdge;
sampler = [device newSamplerStateWithDescriptor:samplerDescriptor];
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "** Metal shader system initialized successfully");
return true;
}
void updateMetalTexture(SDL_Texture* backBuffer) {
if (!device || !backBuffer) return;
// Only log this occasionally to avoid spam
static int attemptCount = 0;
static bool hasLogged = false;
// Try multiple property names that SDL3 might use
SDL_PropertiesID props = SDL_GetTextureProperties(backBuffer);
if (props != 0) {
const char* propertyNames[] = {
"SDL.texture.metal.texture",
"SDL.renderer.metal.texture",
"metal.texture",
"texture.metal",
"MTLTexture",
"texture"
};
for (const char* propName : propertyNames) {
id<MTLTexture> sdlMetalTexture = (__bridge id<MTLTexture>)SDL_GetPointerProperty(props, propName, nullptr);
if (sdlMetalTexture) {
backBufferTexture = sdlMetalTexture;
if (!hasLogged) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Got Metal texture via property '%s' (size: %lux%lu)",
propName, [backBufferTexture width], [backBufferTexture height]);
hasLogged = true;
}
return;
}
}
}
// If we can't get the texture after several attempts, log once and continue
if (!hasLogged && attemptCount++ > 10) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Could not access SDL Metal texture after %d attempts - shader will be skipped", attemptCount);
hasLogged = true;
}
}
void renderMetal() {
if (!renderer || !device || !pipelineState) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Metal render failed: missing components");
return;
}
// Correct pipeline: backBuffer → Metal shaders → screen (no double rendering)
// Try to get the Metal texture directly from the SDL backBuffer texture
updateMetalTexture(backBuffer);
if (!backBufferTexture) {
static int fallbackLogCount = 0;
if (fallbackLogCount++ < 3) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Metal texture not available - falling back to normal SDL rendering (attempt %d)", fallbackLogCount);
}
// Fallback: render without shaders using normal SDL path
SDL_SetRenderTarget(renderer, nullptr);
SDL_RenderTexture(renderer, backBuffer, nullptr, nullptr);
SDL_RenderPresent(renderer);
return;
}
// Apply Metal CRT shader directly: backBuffer texture → screen
SDL_SetRenderTarget(renderer, nullptr);
// Get Metal command encoder to render directly to screen
void* encoder_ptr = SDL_GetRenderMetalCommandEncoder(renderer);
if (encoder_ptr) {
id<MTLRenderCommandEncoder> encoder = (__bridge id<MTLRenderCommandEncoder>)encoder_ptr;
static int debugCount = 0;
if (debugCount++ < 5) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Metal render attempt %d: encoder=%p, pipeline=%p, texture=%p",
debugCount, encoder, pipelineState, backBufferTexture);
}
// Apply CRT shader effect directly to backBuffer texture
[encoder setRenderPipelineState:pipelineState];
[encoder setVertexBuffer:vertexBuffer offset:0 atIndex:0];
[encoder setFragmentTexture:backBufferTexture atIndex:0];
[encoder setFragmentSamplerState:sampler atIndex:0];
// Draw fullscreen quad with CRT effect directly to screen
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
static int successCount = 0;
if (successCount++ < 5) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Applied CRT shader to backBuffer (attempt %d) - texture size: %lux%lu",
successCount, [backBufferTexture width], [backBufferTexture height]);
}
} else {
// Fallback: render normally without shaders
SDL_RenderTexture(renderer, backBuffer, nullptr, nullptr);
static int fallbackCount = 0;
if (fallbackCount++ < 3) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Failed to get Metal command encoder - fallback rendering");
}
}
SDL_RenderPresent(renderer);
}
void renderWithPostProcessing(SDL_Renderer* renderer, SDL_Texture* sourceTexture) {
if (!renderer || !sourceTexture || !device || !pipelineState) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Metal post-processing failed: missing components");
// Fallback: render normally without shaders
SDL_SetRenderTarget(renderer, nullptr);
SDL_RenderTexture(renderer, sourceTexture, nullptr, nullptr);
SDL_RenderPresent(renderer);
return;
}
// Use our stored Metal texture if available
id<MTLTexture> metalTexture = gameCanvasTexture;
if (!metalTexture) {
static int fallbackLogCount = 0;
if (fallbackLogCount++ < 3) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "gameCanvasTexture not available - falling back to normal rendering (attempt %d)", fallbackLogCount);
}
// Fallback: render normally without shaders
SDL_SetRenderTarget(renderer, nullptr);
SDL_RenderTexture(renderer, sourceTexture, nullptr, nullptr);
SDL_RenderPresent(renderer);
return;
}
// Apply Metal CRT shader post-processing: sourceTexture → screen
SDL_SetRenderTarget(renderer, nullptr);
// Get Metal command encoder to render directly to screen
void* encoder_ptr = SDL_GetRenderMetalCommandEncoder(renderer);
if (encoder_ptr) {
id<MTLRenderCommandEncoder> encoder = (__bridge id<MTLRenderCommandEncoder>)encoder_ptr;
static int debugCount = 0;
if (debugCount++ < 3) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Metal post-processing attempt %d: encoder=%p, pipeline=%p, texture=%p",
debugCount, encoder, pipelineState, metalTexture);
}
// Apply CRT shader effect to sourceTexture
[encoder setRenderPipelineState:pipelineState];
[encoder setVertexBuffer:vertexBuffer offset:0 atIndex:0];
[encoder setFragmentTexture:metalTexture atIndex:0];
[encoder setFragmentSamplerState:sampler atIndex:0];
// Draw fullscreen quad with CRT effect directly to screen
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
static int successCount = 0;
if (successCount++ < 3) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Applied CRT post-processing (attempt %d) - texture size: %lux%lu",
successCount, [metalTexture width], [metalTexture height]);
}
} else {
// Fallback: render normally without shaders
SDL_RenderTexture(renderer, sourceTexture, nullptr, nullptr);
static int fallbackCount = 0;
if (fallbackCount++ < 3) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Failed to get Metal command encoder for post-processing - fallback rendering");
}
}
SDL_RenderPresent(renderer);
}
void cleanupMetal() {
// Release Metal objects (ARC handles most of this automatically)
pipelineState = nullptr;
backBufferTexture = nullptr;
gameCanvasTexture = nullptr;
vertexBuffer = nullptr;
sampler = nullptr;
device = nullptr;
renderer = nullptr;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Metal shader system cleaned up");
}
} // namespace metal
} // namespace shader
#endif // __APPLE__

View File

@@ -82,11 +82,6 @@ void Fade::update() {
} }
} }
// Compatibilidad delta-time (ignora el parámetro ya que usa SDL_GetTicks)
void Fade::update(float delta_time) {
update(); // Llama al método original
}
void Fade::updatePreState() { void Fade::updatePreState() {
// Sistema basado en tiempo únicamente // Sistema basado en tiempo únicamente
Uint32 elapsed_time = SDL_GetTicks() - pre_start_time_; Uint32 elapsed_time = SDL_GetTicks() - pre_start_time_;

View File

@@ -37,11 +37,10 @@ class Fade {
~Fade(); ~Fade();
// --- Métodos principales --- // --- Métodos principales ---
void reset(); // Resetea variables para reutilizar el fade void reset(); // Resetea variables para reutilizar el fade
void render(); // Dibuja la transición en pantalla void render(); // Dibuja la transición en pantalla
void update(); // Actualiza el estado interno (ya usa tiempo real) void update(); // Actualiza el estado interno
void update(float delta_time); // Compatibilidad delta-time (ignora el parámetro) void activate(); // Activa el fade
void activate(); // Activa el fade
// --- Configuración --- // --- Configuración ---
void setColor(Uint8 r, Uint8 g, Uint8 b); // Establece el color RGB del fade void setColor(Uint8 r, Uint8 g, Uint8 b); // Establece el color RGB del fade

View File

@@ -45,7 +45,6 @@ void GameLogo::init() {
arcade_edition_status_ = Status::DISABLED; arcade_edition_status_ = Status::DISABLED;
shake_.init(1, 2, 8, XP); shake_.init(1, 2, 8, XP);
zoom_ = 3.0F * ZOOM_FACTOR; zoom_ = 3.0F * ZOOM_FACTOR;
post_finished_timer_ = 0.0f;
// Inicializa el bitmap de 'Coffee' // Inicializa el bitmap de 'Coffee'
coffee_sprite_->setPosX(XP); coffee_sprite_->setPosX(XP);
@@ -53,44 +52,44 @@ void GameLogo::init() {
coffee_sprite_->setWidth(coffee_texture_->getWidth()); coffee_sprite_->setWidth(coffee_texture_->getWidth());
coffee_sprite_->setHeight(coffee_texture_->getHeight()); coffee_sprite_->setHeight(coffee_texture_->getHeight());
coffee_sprite_->setVelX(0.0F); coffee_sprite_->setVelX(0.0F);
coffee_sprite_->setVelY(COFFEE_VEL_Y); coffee_sprite_->setVelY(2.5F);
coffee_sprite_->setAccelX(0.0F); coffee_sprite_->setAccelX(0.0F);
coffee_sprite_->setAccelY(COFFEE_ACCEL_Y); coffee_sprite_->setAccelY(0.1F);
coffee_sprite_->setSpriteClip(0, 0, coffee_texture_->getWidth(), coffee_texture_->getHeight()); coffee_sprite_->setSpriteClip(0, 0, coffee_texture_->getWidth(), coffee_texture_->getHeight());
coffee_sprite_->setEnabled(true); coffee_sprite_->setEnabled(true);
coffee_sprite_->setFinishedDelay(0.0f); coffee_sprite_->setFinishedCounter(0);
coffee_sprite_->setDestX(XP); coffee_sprite_->setDestX(XP);
coffee_sprite_->setDestY(y_ - coffee_texture_->getHeight()); coffee_sprite_->setDestY(y_ - coffee_texture_->getHeight());
// Inicializa el bitmap de 'Crisis' // Inicializa el bitmap de 'Crisis'
crisis_sprite_->setPosX(XP + CRISIS_OFFSET_X); crisis_sprite_->setPosX(XP + 15);
crisis_sprite_->setPosY(y_ + DESP); crisis_sprite_->setPosY(y_ + DESP);
crisis_sprite_->setWidth(crisis_texture_->getWidth()); crisis_sprite_->setWidth(crisis_texture_->getWidth());
crisis_sprite_->setHeight(crisis_texture_->getHeight()); crisis_sprite_->setHeight(crisis_texture_->getHeight());
crisis_sprite_->setVelX(0.0F); crisis_sprite_->setVelX(0.0F);
crisis_sprite_->setVelY(CRISIS_VEL_Y); crisis_sprite_->setVelY(-2.5F);
crisis_sprite_->setAccelX(0.0F); crisis_sprite_->setAccelX(0.0F);
crisis_sprite_->setAccelY(CRISIS_ACCEL_Y); crisis_sprite_->setAccelY(-0.1F);
crisis_sprite_->setSpriteClip(0, 0, crisis_texture_->getWidth(), crisis_texture_->getHeight()); crisis_sprite_->setSpriteClip(0, 0, crisis_texture_->getWidth(), crisis_texture_->getHeight());
crisis_sprite_->setEnabled(true); crisis_sprite_->setEnabled(true);
crisis_sprite_->setFinishedDelay(0.0f); crisis_sprite_->setFinishedCounter(0);
crisis_sprite_->setDestX(XP + CRISIS_OFFSET_X); crisis_sprite_->setDestX(XP + 15);
crisis_sprite_->setDestY(y_); crisis_sprite_->setDestY(y_);
// Inicializa el bitmap de 'DustRight' // Inicializa el bitmap de 'DustRight'
dust_right_sprite_->resetAnimation(); dust_right_sprite_->resetAnimation();
dust_right_sprite_->setPosX(coffee_sprite_->getPosX() + coffee_sprite_->getWidth()); dust_right_sprite_->setPosX(coffee_sprite_->getPosX() + coffee_sprite_->getWidth());
dust_right_sprite_->setPosY(y_); dust_right_sprite_->setPosY(y_);
dust_right_sprite_->setWidth(DUST_SIZE); dust_right_sprite_->setWidth(16);
dust_right_sprite_->setHeight(DUST_SIZE); dust_right_sprite_->setHeight(16);
dust_right_sprite_->setFlip(SDL_FLIP_HORIZONTAL); dust_right_sprite_->setFlip(SDL_FLIP_HORIZONTAL);
// Inicializa el bitmap de 'DustLeft' // Inicializa el bitmap de 'DustLeft'
dust_left_sprite_->resetAnimation(); dust_left_sprite_->resetAnimation();
dust_left_sprite_->setPosX(coffee_sprite_->getPosX() - DUST_SIZE); dust_left_sprite_->setPosX(coffee_sprite_->getPosX() - 16);
dust_left_sprite_->setPosY(y_); dust_left_sprite_->setPosY(y_);
dust_left_sprite_->setWidth(DUST_SIZE); dust_left_sprite_->setWidth(16);
dust_left_sprite_->setHeight(DUST_SIZE); dust_left_sprite_->setHeight(16);
// Inicializa el bitmap de 'Arcade Edition' // Inicializa el bitmap de 'Arcade Edition'
arcade_edition_sprite_->setZoom(zoom_); arcade_edition_sprite_->setZoom(zoom_);
@@ -113,45 +112,45 @@ void GameLogo::render() {
} }
} }
// Actualiza la lógica de la clase (time-based) // Actualiza la lógica de la clase
void GameLogo::update(float deltaTime) { void GameLogo::update() {
updateCoffeeCrisis(deltaTime); updateCoffeeCrisis();
updateArcadeEdition(deltaTime); updateArcadeEdition();
updatePostFinishedCounter(deltaTime); updatePostFinishedCounter();
} }
void GameLogo::updateCoffeeCrisis(float deltaTime) { void GameLogo::updateCoffeeCrisis() {
switch (coffee_crisis_status_) { switch (coffee_crisis_status_) {
case Status::MOVING: case Status::MOVING:
handleCoffeeCrisisMoving(deltaTime); handleCoffeeCrisisMoving();
break; break;
case Status::SHAKING: case Status::SHAKING:
handleCoffeeCrisisShaking(deltaTime); handleCoffeeCrisisShaking();
break; break;
case Status::FINISHED: case Status::FINISHED:
handleCoffeeCrisisFinished(deltaTime); handleCoffeeCrisisFinished();
break; break;
default: default:
break; break;
} }
} }
void GameLogo::updateArcadeEdition(float deltaTime) { void GameLogo::updateArcadeEdition() {
switch (arcade_edition_status_) { switch (arcade_edition_status_) {
case Status::MOVING: case Status::MOVING:
handleArcadeEditionMoving(deltaTime); handleArcadeEditionMoving();
break; break;
case Status::SHAKING: case Status::SHAKING:
handleArcadeEditionShaking(deltaTime); handleArcadeEditionShaking();
break; break;
default: default:
break; break;
} }
} }
void GameLogo::handleCoffeeCrisisMoving(float deltaTime) { void GameLogo::handleCoffeeCrisisMoving() {
coffee_sprite_->update(deltaTime); coffee_sprite_->update();
crisis_sprite_->update(deltaTime); crisis_sprite_->update();
if (coffee_sprite_->hasFinished() && crisis_sprite_->hasFinished()) { if (coffee_sprite_->hasFinished() && crisis_sprite_->hasFinished()) {
coffee_crisis_status_ = Status::SHAKING; coffee_crisis_status_ = Status::SHAKING;
@@ -159,23 +158,22 @@ void GameLogo::handleCoffeeCrisisMoving(float deltaTime) {
} }
} }
void GameLogo::handleCoffeeCrisisShaking(float deltaTime) { void GameLogo::handleCoffeeCrisisShaking() {
if (shake_.remaining > 0) { if (shake_.remaining > 0) {
processShakeEffect(coffee_sprite_.get(), crisis_sprite_.get(), deltaTime); processShakeEffect(coffee_sprite_.get(), crisis_sprite_.get());
} else { } else {
finishCoffeeCrisisShaking(); finishCoffeeCrisisShaking();
} }
updateDustSprites(deltaTime); updateDustSprites();
} }
void GameLogo::handleCoffeeCrisisFinished(float deltaTime) { void GameLogo::handleCoffeeCrisisFinished() {
updateDustSprites(deltaTime); updateDustSprites();
} }
void GameLogo::handleArcadeEditionMoving(float deltaTime) { void GameLogo::handleArcadeEditionMoving() {
// DeltaTime puro: decremento por milisegundo zoom_ -= 0.1F * ZOOM_FACTOR;
zoom_ -= (ZOOM_DECREMENT_PER_MS * ZOOM_FACTOR) * deltaTime;
arcade_edition_sprite_->setZoom(zoom_); arcade_edition_sprite_->setZoom(zoom_);
if (zoom_ <= 1.0F) { if (zoom_ <= 1.0F) {
@@ -183,38 +181,34 @@ void GameLogo::handleArcadeEditionMoving(float deltaTime) {
} }
} }
void GameLogo::handleArcadeEditionShaking(float deltaTime) { void GameLogo::handleArcadeEditionShaking() {
if (shake_.remaining > 0) { if (shake_.remaining > 0) {
processArcadeEditionShake(deltaTime); processArcadeEditionShake();
} else { } else {
arcade_edition_sprite_->setX(shake_.origin); arcade_edition_sprite_->setX(shake_.origin);
arcade_edition_status_ = Status::FINISHED; arcade_edition_status_ = Status::FINISHED;
} }
} }
void GameLogo::processShakeEffect(SmartSprite* primary_sprite, SmartSprite* secondary_sprite) {
void GameLogo::processShakeEffect(SmartSprite* primary_sprite, SmartSprite* secondary_sprite, float deltaTime) { if (shake_.counter > 0) {
shake_.time_accumulator += deltaTime; shake_.counter--;
} else {
if (shake_.time_accumulator >= SHAKE_DELAY_MS) { shake_.counter = shake_.delay;
shake_.time_accumulator -= SHAKE_DELAY_MS;
const auto DISPLACEMENT = calculateShakeDisplacement(); const auto DISPLACEMENT = calculateShakeDisplacement();
primary_sprite->setPosX(shake_.origin + DISPLACEMENT); primary_sprite->setPosX(shake_.origin + DISPLACEMENT);
if (secondary_sprite != nullptr) { if (secondary_sprite != nullptr) {
secondary_sprite->setPosX(shake_.origin + DISPLACEMENT + CRISIS_OFFSET_X); secondary_sprite->setPosX(shake_.origin + DISPLACEMENT + 15);
} }
shake_.remaining--; shake_.remaining--;
} }
} }
void GameLogo::processArcadeEditionShake(float deltaTime) { void GameLogo::processArcadeEditionShake() {
// Delay fijo en milisegundos (shake_.delay era frames, ahora usamos constante) if (shake_.counter > 0) {
float delayTime = SHAKE_DELAY_MS; shake_.counter--;
} else {
shake_.time_accumulator += deltaTime; shake_.counter = shake_.delay;
if (shake_.time_accumulator >= delayTime) {
shake_.time_accumulator -= delayTime;
const auto DISPLACEMENT = calculateShakeDisplacement(); const auto DISPLACEMENT = calculateShakeDisplacement();
arcade_edition_sprite_->setX(shake_.origin + DISPLACEMENT); arcade_edition_sprite_->setX(shake_.origin + DISPLACEMENT);
shake_.remaining--; shake_.remaining--;
@@ -227,7 +221,7 @@ auto GameLogo::calculateShakeDisplacement() const -> int {
void GameLogo::finishCoffeeCrisisShaking() { void GameLogo::finishCoffeeCrisisShaking() {
coffee_sprite_->setPosX(shake_.origin); coffee_sprite_->setPosX(shake_.origin);
crisis_sprite_->setPosX(shake_.origin + CRISIS_OFFSET_X); crisis_sprite_->setPosX(shake_.origin + 15);
coffee_crisis_status_ = Status::FINISHED; coffee_crisis_status_ = Status::FINISHED;
arcade_edition_status_ = Status::MOVING; arcade_edition_status_ = Status::MOVING;
} }
@@ -246,16 +240,16 @@ void GameLogo::playTitleEffects() {
Screen::get()->shake(); Screen::get()->shake();
} }
void GameLogo::updateDustSprites(float deltaTime) { void GameLogo::updateDustSprites() {
dust_right_sprite_->update(deltaTime); dust_right_sprite_->update();
dust_left_sprite_->update(deltaTime); dust_left_sprite_->update();
} }
void GameLogo::updatePostFinishedCounter(float deltaTime) { void GameLogo::updatePostFinishedCounter() {
if (coffee_crisis_status_ == Status::FINISHED && if (coffee_crisis_status_ == Status::FINISHED &&
arcade_edition_status_ == Status::FINISHED) { arcade_edition_status_ == Status::FINISHED &&
post_finished_counter_ > 0) {
post_finished_timer_ += deltaTime; --post_finished_counter_;
} }
} }
@@ -267,7 +261,7 @@ void GameLogo::enable() {
// Indica si ha terminado la animación // Indica si ha terminado la animación
auto GameLogo::hasFinished() const -> bool { auto GameLogo::hasFinished() const -> bool {
return post_finished_timer_ >= post_finished_delay_ms_; return post_finished_counter_ == 0;
} }
// Calcula el desplazamiento vertical inicial // Calcula el desplazamiento vertical inicial

View File

@@ -11,25 +11,14 @@ class Texture;
// --- Clase GameLogo: gestor del logo del juego --- // --- Clase GameLogo: gestor del logo del juego ---
class GameLogo { class GameLogo {
public: public:
// --- Constantes ---
static constexpr float COFFEE_VEL_Y = 0.15F; // Velocidad Y de coffee sprite (pixels/ms) - 2.5F/16.67
static constexpr float COFFEE_ACCEL_Y = 0.00036F; // Aceleración Y de coffee sprite (pixels/ms²) - 0.1F/(16.67)²
static constexpr float CRISIS_VEL_Y = -0.15F; // Velocidad Y de crisis sprite (pixels/ms) - -2.5F/16.67
static constexpr float CRISIS_ACCEL_Y = -0.00036F; // Aceleración Y de crisis sprite (pixels/ms²) - -0.1F/(16.67)²
static constexpr int CRISIS_OFFSET_X = 15; // Desplazamiento X de crisis sprite
static constexpr int DUST_SIZE = 16; // Tamaño de dust sprites
static constexpr float ZOOM_DECREMENT_PER_MS = 0.006F; // Decremento de zoom por milisegundo (0.1F/16.67ms)
static constexpr float SHAKE_DELAY_MS = 33.34F; // Delay de shake en milisegundos (2 frames * 16.67ms)
static constexpr float POST_FINISHED_FRAME_TIME = 16.67F; // Tiempo entre decrementos del counter (1 frame)
// --- Constructores y destructor --- // --- Constructores y destructor ---
GameLogo(int x, int y); GameLogo(int x, int y);
~GameLogo() = default; ~GameLogo() = default;
// --- Métodos principales --- // --- Métodos principales ---
void render(); // Pinta la clase en pantalla void render(); // Pinta la clase en pantalla
void update(float deltaTime); // Actualiza la lógica de la clase (time-based) void update(); // Actualiza la lógica de la clase
void enable(); // Activa la clase void enable(); // Activa la clase
// --- Getters --- // --- Getters ---
[[nodiscard]] auto hasFinished() const -> bool; // Indica si ha terminado la animación [[nodiscard]] auto hasFinished() const -> bool; // Indica si ha terminado la animación
@@ -45,13 +34,12 @@ class GameLogo {
// --- Estructuras privadas --- // --- Estructuras privadas ---
struct Shake { struct Shake {
int desp = 1; // Pixels de desplazamiento para agitar la pantalla en el eje x int desp = 1; // Pixels de desplazamiento para agitar la pantalla en el eje x
int delay = 2; // Retraso entre cada desplazamiento de la pantalla al agitarse (frame-based) int delay = 2; // Retraso entre cada desplazamiento de la pantalla al agitarse
int length = 8; // Cantidad de desplazamientos a realizar int length = 8; // Cantidad de desplazamientos a realizar
int remaining = length; // Cantidad de desplazamientos pendientes a realizar int remaining = length; // Cantidad de desplazamientos pendientes a realizar
int counter = delay; // Contador para el retraso (frame-based) int counter = delay; // Contador para el retraso
float time_accumulator = 0.0f; // Acumulador de tiempo para deltaTime int origin = 0; // Valor inicial de la pantalla para dejarla igual tras el desplazamiento
int origin = 0; // Valor inicial de la pantalla para dejarla igual tras el desplazamiento
Shake() = default; Shake() = default;
Shake(int d, int de, int l, int o) Shake(int d, int de, int l, int o)
@@ -68,7 +56,6 @@ class GameLogo {
length = l; length = l;
remaining = l; remaining = l;
counter = de; counter = de;
time_accumulator = 0.0f;
origin = o; origin = o;
} }
}; };
@@ -92,34 +79,32 @@ class GameLogo {
float x_; // Posición X del logo float x_; // Posición X del logo
float y_; // Posición Y del logo float y_; // Posición Y del logo
float zoom_ = 1.0F; // Zoom aplicado al texto "ARCADE EDITION" float zoom_ = 1.0F; // Zoom aplicado al texto "ARCADE EDITION"
float post_finished_delay_ms_ = POST_FINISHED_FRAME_TIME; // Retraso final tras animaciones (ms) int post_finished_counter_ = 1; // Contador final tras animaciones
float post_finished_timer_ = 0.0f; // Timer acumulado para retraso final (ms)
// --- Inicialización --- // --- Inicialización ---
void init(); // Inicializa las variables void init(); // Inicializa las variables
[[nodiscard]] auto getInitialVerticalDesp() const -> int; // Calcula el desplazamiento vertical inicial [[nodiscard]] auto getInitialVerticalDesp() const -> int; // Calcula el desplazamiento vertical inicial
// --- Actualización de estados específicos --- // --- Actualización de estados específicos ---
void updateCoffeeCrisis(float deltaTime); // Actualiza el estado de "Coffee Crisis" (time-based) void updateCoffeeCrisis(); // Actualiza el estado de "Coffee Crisis"
void updateArcadeEdition(float deltaTime); // Actualiza el estado de "Arcade Edition" (time-based) void updateArcadeEdition(); // Actualiza el estado de "Arcade Edition"
void updatePostFinishedCounter(float deltaTime); // Actualiza el contador tras finalizar una animación (time-based) void updatePostFinishedCounter(); // Actualiza el contador tras finalizar una animación
// --- Efectos visuales: movimiento y sacudidas --- // --- Efectos visuales: movimiento y sacudidas ---
void handleCoffeeCrisisMoving(float deltaTime); // Maneja el movimiento de "Coffee Crisis" (time-based) void handleCoffeeCrisisMoving(); // Maneja el movimiento de "Coffee Crisis"
void handleCoffeeCrisisShaking(float deltaTime); // Maneja la sacudida de "Coffee Crisis" (time-based) void handleCoffeeCrisisShaking(); // Maneja la sacudida de "Coffee Crisis"
void handleArcadeEditionMoving(float deltaTime); // Maneja el movimiento de "Arcade Edition" (time-based) void handleArcadeEditionMoving(); // Maneja el movimiento de "Arcade Edition"
void handleArcadeEditionShaking(float deltaTime); // Maneja la sacudida de "Arcade Edition" (time-based) void handleArcadeEditionShaking(); // Maneja la sacudida de "Arcade Edition"
void processShakeEffect(SmartSprite* primary_sprite, SmartSprite* secondary_sprite = nullptr); // Procesa el efecto de sacudida en sprites (frame-based) void processShakeEffect(SmartSprite* primary_sprite, SmartSprite* secondary_sprite = nullptr); // Procesa el efecto de sacudida en sprites
void processShakeEffect(SmartSprite* primary_sprite, SmartSprite* secondary_sprite, float deltaTime); // Procesa el efecto de sacudida en sprites (time-based) void processArcadeEditionShake(); // Procesa la sacudida específica de "Arcade Edition"
void processArcadeEditionShake(float deltaTime); // Procesa la sacudida específica de "Arcade Edition" (time-based) [[nodiscard]] auto calculateShakeDisplacement() const -> int; // Calcula el desplazamiento de la sacudida
[[nodiscard]] auto calculateShakeDisplacement() const -> int; // Calcula el desplazamiento de la sacudida
// --- Gestión de finalización de efectos --- // --- Gestión de finalización de efectos ---
void handleCoffeeCrisisFinished(float deltaTime); // Maneja el final de la animación "Coffee Crisis" (time-based) void handleCoffeeCrisisFinished(); // Maneja el final de la animación "Coffee Crisis"
void finishCoffeeCrisisShaking(); // Finaliza la sacudida de "Coffee Crisis" void finishCoffeeCrisisShaking(); // Finaliza la sacudida de "Coffee Crisis"
void finishArcadeEditionMoving(); // Finaliza el movimiento de "Arcade Edition" void finishArcadeEditionMoving(); // Finaliza el movimiento de "Arcade Edition"
// --- Utilidades --- // --- Utilidades ---
static void playTitleEffects(); // Reproduce efectos visuales/sonoros del título static void playTitleEffects(); // Reproduce efectos visuales/sonoros del título
void updateDustSprites(float deltaTime); // Actualiza los sprites de polvo (time-based) void updateDustSprites(); // Actualiza los sprites de polvo
}; };

View File

@@ -1,7 +1,6 @@
#include "item.h" #include "item.h"
#include <algorithm> // Para clamp #include <algorithm> // Para clamp
#include <cmath> // Para fmod
#include <cstdlib> // Para rand #include <cstdlib> // Para rand
#include "animated_sprite.h" // Para AnimatedSprite #include "animated_sprite.h" // Para AnimatedSprite
@@ -19,9 +18,9 @@ Item::Item(ItemType type, float x, float y, SDL_FRect &play_area, const std::sha
height_ = COFFEE_MACHINE_HEIGHT; height_ = COFFEE_MACHINE_HEIGHT;
pos_x_ = getCoffeeMachineSpawn(x, width_, play_area_.w); pos_x_ = getCoffeeMachineSpawn(x, width_, play_area_.w);
pos_y_ = y; pos_y_ = y;
vel_x_ = ((rand() % 3) - 1) * COFFEE_MACHINE_VEL_X_FACTOR; vel_x_ = ((rand() % 3) - 1) * 0.5F;
vel_y_ = COFFEE_MACHINE_VEL_Y; vel_y_ = -0.1F;
accel_y_ = COFFEE_MACHINE_ACCEL_Y; accel_y_ = 0.1F;
collider_.r = 10; collider_.r = 10;
break; break;
} }
@@ -34,13 +33,13 @@ Item::Item(ItemType type, float x, float y, SDL_FRect &play_area, const std::sha
const int direction = rand() % 6; const int direction = rand() % 6;
if (direction < 3) { if (direction < 3) {
// Velocidades negativas: -1.0, -0.66, -0.33 // Velocidades negativas: -1.0, -0.66, -0.33
vel_x_ = -ITEM_VEL_X_BASE + (direction * ITEM_VEL_X_STEP); vel_x_ = -1.0F + (direction * 0.33F);
} else { } else {
// Velocidades positivas: 0.33, 0.66, 1.0 // Velocidades positivas: 0.33, 0.66, 1.0
vel_x_ = ITEM_VEL_X_STEP + ((direction - 3) * ITEM_VEL_X_STEP); vel_x_ = 0.33F + ((direction - 3) * 0.33F);
} }
vel_y_ = ITEM_VEL_Y; vel_y_ = -4.0F;
accel_y_ = ITEM_ACCEL_Y; accel_y_ = 0.2F;
collider_.r = width_ / 2; collider_.r = width_ / 2;
break; break;
} }
@@ -67,34 +66,24 @@ void Item::alignTo(int x) {
void Item::render() { void Item::render() {
if (enabled_) { if (enabled_) {
// Muestra normalmente hasta los últimos ~3.3 segundos (200 frames) if (time_to_live_ > 200) {
constexpr float BLINK_START_MS = LIFETIME_DURATION_MS - (200.0f * (1000.0f / 60.0f)); sprite_->render();
} else if (time_to_live_ % 20 > 10) {
if (lifetime_timer_ < BLINK_START_MS) {
sprite_->render(); sprite_->render();
} else {
// Efecto de parpadeo en los últimos segundos (cada ~333ms o 20 frames)
constexpr float BLINK_INTERVAL_MS = 20.0f * (1000.0f / 60.0f);
const float phase = fmod(lifetime_timer_, BLINK_INTERVAL_MS);
const float half_interval = BLINK_INTERVAL_MS / 2.0f;
if (phase < half_interval) {
sprite_->render();
}
} }
} }
} }
void Item::move(float deltaTime) { void Item::move() {
floor_collision_ = false; floor_collision_ = false;
// Calcula la nueva posición usando deltaTime puro (velocidad en pixels/ms) // Calcula la nueva posición
pos_x_ += vel_x_ * deltaTime; pos_x_ += vel_x_;
pos_y_ += vel_y_ * deltaTime; pos_y_ += vel_y_;
// Aplica las aceleraciones a la velocidad usando deltaTime puro (aceleración en pixels/ms²) // Aplica las aceleraciones a la velocidad
vel_x_ += accel_x_ * deltaTime; vel_x_ += accel_x_;
vel_y_ += accel_y_ * deltaTime; vel_y_ += accel_y_;
// Comprueba los laterales de la zona de juego // Comprueba los laterales de la zona de juego
const float MIN_X = param.game.play_area.rect.x; const float MIN_X = param.game.play_area.rect.x;
@@ -124,24 +113,24 @@ void Item::move(float deltaTime) {
case ItemType::COFFEE_MACHINE: case ItemType::COFFEE_MACHINE:
// La máquina de café es mas pesada y tiene una fisica diferente, ademas hace ruido // La máquina de café es mas pesada y tiene una fisica diferente, ademas hace ruido
floor_collision_ = true; floor_collision_ = true;
if (std::abs(vel_y_) < BOUNCE_VEL_THRESHOLD) { if (vel_y_ < 1.0F) {
// Si la velocidad vertical es baja, detiene el objeto // Si la velocidad vertical es baja, detiene el objeto
vel_y_ = vel_x_ = accel_x_ = accel_y_ = 0; vel_y_ = vel_x_ = accel_x_ = accel_y_ = 0;
} else { } else {
// Si la velocidad vertical es alta, el objeto rebota y pierde velocidad // Si la velocidad vertical es alta, el objeto rebota y pierde velocidad
vel_y_ *= COFFEE_BOUNCE_DAMPING; vel_y_ *= -0.20F;
vel_x_ *= HORIZONTAL_DAMPING; vel_x_ *= 0.75F;
} }
break; break;
default: default:
// Si no es una máquina de café // Si no es una máquina de café
if (std::abs(vel_y_) < BOUNCE_VEL_THRESHOLD) { if (vel_y_ < 1.0F) {
// Si la velocidad vertical es baja, detiene el objeto // Si la velocidad vertical es baja, detiene el objeto
vel_y_ = vel_x_ = accel_x_ = accel_y_ = 0; vel_y_ = vel_x_ = accel_x_ = accel_y_ = 0;
} else { } else {
// Si la velocidad vertical es alta, el objeto rebota y pierde velocidad // Si la velocidad vertical es alta, el objeto rebota y pierde velocidad
vel_y_ *= ITEM_BOUNCE_DAMPING; vel_y_ *= -0.5F;
vel_x_ *= HORIZONTAL_DAMPING; vel_x_ *= 0.75F;
} }
break; break;
} }
@@ -154,15 +143,16 @@ void Item::move(float deltaTime) {
void Item::disable() { enabled_ = false; } void Item::disable() { enabled_ = false; }
void Item::update(float deltaTime) { void Item::update() {
move(deltaTime); move();
sprite_->update(deltaTime); sprite_->update();
updateTimeToLive(deltaTime); updateTimeToLive();
} }
void Item::updateTimeToLive(float deltaTime) { void Item::updateTimeToLive() {
lifetime_timer_ += deltaTime; if (time_to_live_ > 0) {
if (lifetime_timer_ >= LIFETIME_DURATION_MS) { time_to_live_--;
} else {
disable(); disable();
} }
} }

View File

@@ -29,34 +29,16 @@ class Item {
// --- Constantes --- // --- Constantes ---
static constexpr int COFFEE_MACHINE_WIDTH = 30; // Anchura de la máquina de café static constexpr int COFFEE_MACHINE_WIDTH = 30; // Anchura de la máquina de café
static constexpr int COFFEE_MACHINE_HEIGHT = 39; // Altura de la máquina de café static constexpr int COFFEE_MACHINE_HEIGHT = 39; // Altura de la máquina de café
static constexpr float LIFETIME_DURATION_MS = 10000.0f; // Duración de vida del ítem (600 frames a 60fps)
// Velocidades base (pixels/ms) - Coffee Machine
static constexpr float COFFEE_MACHINE_VEL_X_FACTOR = 0.03F; // Factor para velocidad X de máquina de café
static constexpr float COFFEE_MACHINE_VEL_Y = -0.006F; // Velocidad Y inicial de máquina de café
static constexpr float COFFEE_MACHINE_ACCEL_Y = 0.00036F; // Aceleración Y de máquina de café (pixels/ms²)
// Velocidades base (pixels/ms) - Items normales (del plan: vel_x: ±1.0F→±0.06F, vel_y: -4.0F→-0.24F, accel_y: 0.2F→0.012F)
static constexpr float ITEM_VEL_X_BASE = 0.06F; // Velocidad X base para items (1.0F/16.67)
static constexpr float ITEM_VEL_X_STEP = 0.02F; // Incremento de velocidad X (0.33F/16.67)
static constexpr float ITEM_VEL_Y = -0.24F; // Velocidad Y inicial de items (-4.0F/16.67)
static constexpr float ITEM_ACCEL_Y = 0.00072F; // Aceleración Y de items (pixels/ms²) - 0.2F * (60/1000)²
// Constantes de física de rebote
static constexpr float BOUNCE_VEL_THRESHOLD = 0.06F; // Umbral de velocidad para parar (1.0F/16.67)
static constexpr float COFFEE_BOUNCE_DAMPING = -0.20F; // Factor de rebote Y para máquina de café
static constexpr float ITEM_BOUNCE_DAMPING = -0.5F; // Factor de rebote Y para items normales
static constexpr float HORIZONTAL_DAMPING = 0.75F; // Factor de amortiguación horizontal
// --- Constructor y destructor --- // --- Constructor y destructor ---
Item(ItemType type, float x, float y, SDL_FRect &play_area, const std::shared_ptr<Texture> &texture, const std::vector<std::string> &animation); // Constructor principal Item(ItemType type, float x, float y, SDL_FRect &play_area, const std::shared_ptr<Texture> &texture, const std::vector<std::string> &animation); // Constructor principal
~Item() = default; // Destructor ~Item() = default; // Destructor
// --- Métodos principales --- // --- Métodos principales ---
void alignTo(int x); // Centra el objeto en la posición X indicada void alignTo(int x); // Centra el objeto en la posición X indicada
void render(); // Renderiza el objeto en pantalla void render(); // Renderiza el objeto en pantalla
void disable(); // Desactiva el objeto void disable(); // Desactiva el objeto
void update(float deltaTime); // Actualiza la posición, animación y contadores (time-based) void update(); // Actualiza la posición, animación y contadores
// --- Getters --- // --- Getters ---
[[nodiscard]] auto getPosX() const -> float { return pos_x_; } // Obtiene la posición X [[nodiscard]] auto getPosX() const -> float { return pos_x_; } // Obtiene la posición X
@@ -84,14 +66,14 @@ class Item {
float accel_y_; // Aceleración en el eje Y float accel_y_; // Aceleración en el eje Y
int width_; // Ancho del objeto int width_; // Ancho del objeto
int height_; // Alto del objeto int height_; // Alto del objeto
float lifetime_timer_ = 0.0f; // Acumulador de tiempo de vida del ítem (milisegundos) Uint16 time_to_live_ = 600; // Tiempo que el objeto está presente
bool floor_collision_ = false; // Indica si el objeto colisiona con el suelo bool floor_collision_ = false; // Indica si el objeto colisiona con el suelo
bool enabled_ = true; // Indica si el objeto está habilitado bool enabled_ = true; // Indica si el objeto está habilitado
// --- Métodos internos --- // --- Métodos internos ---
void shiftColliders(); // Alinea el círculo de colisión con la posición del objeto void shiftColliders(); // Alinea el círculo de colisión con la posición del objeto
void shiftSprite(); // Coloca el sprite en la posición del objeto void shiftSprite(); // Coloca el sprite en la posición del objeto
void move(float deltaTime); // Actualiza la posición y estados del objeto (time-based) void move(); // Actualiza la posición y estados del objeto
void updateTimeToLive(float deltaTime); // Actualiza el contador de tiempo de vida (time-based) void updateTimeToLive(); // Actualiza el contador de tiempo de vida
static auto getCoffeeMachineSpawn(int player_x, int item_width, int area_width, int margin = 2) -> int; // Calcula la zona de aparición de la máquina de café static auto getCoffeeMachineSpawn(int player_x, int item_width, int area_width, int margin = 2) -> int; // Calcula la zona de aparición de la máquina de café
}; };

View File

@@ -53,41 +53,42 @@ void MovingSprite::stop() {
flip_ = SDL_FLIP_NONE; // Establece como se ha de voltear el sprite flip_ = SDL_FLIP_NONE; // Establece como se ha de voltear el sprite
} }
// Mueve el sprite (time-based) // Mueve el sprite
void MovingSprite::move(float deltaTime) { void MovingSprite::move() {
// DeltaTime puro: velocidad (pixels/ms) * tiempo (ms) x_ += vx_;
x_ += vx_ * deltaTime; y_ += vy_;
y_ += vy_ * deltaTime;
// Aceleración (pixels/ms²) * tiempo (ms) vx_ += ax_;
vx_ += ax_ * deltaTime; vy_ += ay_;
vy_ += ay_ * deltaTime;
pos_.x = static_cast<int>(x_); pos_.x = static_cast<int>(x_);
pos_.y = static_cast<int>(y_); pos_.y = static_cast<int>(y_);
} }
// Actualiza las variables internas del objeto (time-based) // Actualiza las variables internas del objeto
void MovingSprite::update(float deltaTime) { void MovingSprite::update() {
move(deltaTime); move();
rotate(deltaTime); rotate();
} }
// Muestra el sprite por pantalla // Muestra el sprite por pantalla
void MovingSprite::render() { void MovingSprite::render() { getTexture()->render(pos_.x, pos_.y, &sprite_clip_, horizontal_zoom_, vertical_zoom_, rotate_.angle, &rotate_.center, flip_); }
getTexture()->render(pos_.x, pos_.y, &sprite_clip_, horizontal_zoom_, vertical_zoom_, rotate_.angle, &rotate_.center, flip_);
}
// Establece la rotacion (time-based) // Establece la rotacion
void MovingSprite::rotate(float deltaTime) { void MovingSprite::rotate() {
if (rotate_.enabled) { if (rotate_.enabled) {
rotate_.angle += rotate_.amount * deltaTime; ++rotate_.counter;
if (rotate_.counter % rotate_.speed == 0) {
updateAngle();
rotate_.counter = 0;
}
} }
} }
// Activa o desactiva el efecto de rotación // Activa o desactiva el efecto de rotación
void MovingSprite::setRotate(bool enable) { void MovingSprite::setRotate(bool enable) {
rotate_.enabled = enable; rotate_.enabled = enable;
rotate_.counter = 0;
} }
// Establece la posición y_ el tamaño del objeto // Establece la posición y_ el tamaño del objeto

View File

@@ -15,6 +15,7 @@ class MovingSprite : public Sprite {
// --- Estructuras --- // --- Estructuras ---
struct Rotate { struct Rotate {
bool enabled{false}; // Indica si ha de rotar bool enabled{false}; // Indica si ha de rotar
int counter{0}; // Contador
int speed{1}; // Velocidad de giro int speed{1}; // Velocidad de giro
double angle{0.0}; // Ángulo para dibujarlo double angle{0.0}; // Ángulo para dibujarlo
float amount{0.0F}; // Cantidad de grados a girar en cada iteración float amount{0.0F}; // Cantidad de grados a girar en cada iteración
@@ -28,10 +29,10 @@ class MovingSprite : public Sprite {
~MovingSprite() override = default; ~MovingSprite() override = default;
// --- Métodos principales --- // --- Métodos principales ---
virtual void update(float deltaTime); // Actualiza las variables internas del objeto (time-based) virtual void update(); // Actualiza las variables internas del objeto
void clear() override; // Reinicia todas las variables a cero void clear() override; // Reinicia todas las variables a cero
void stop(); // Elimina el movimiento del sprite void stop(); // Elimina el movimiento del sprite
void render() override; // Muestra el sprite por pantalla void render() override; // Muestra el sprite por pantalla
// --- Configuración --- // --- Configuración ---
void setPos(SDL_FRect rect); // Establece la posición y el tamaño del objeto void setPos(SDL_FRect rect); // Establece la posición y el tamaño del objeto
@@ -78,6 +79,6 @@ class MovingSprite : public Sprite {
// --- Métodos internos --- // --- Métodos internos ---
void updateAngle() { rotate_.angle += rotate_.amount; } // Incrementa el valor del ángulo void updateAngle() { rotate_.angle += rotate_.amount; } // Incrementa el valor del ángulo
void move(float deltaTime); // Mueve el sprite según velocidad y aceleración (time-based) void move(); // Mueve el sprite según velocidad y aceleración
void rotate(float deltaTime); // Rota el sprite según los parámetros de rotación (time-based) void rotate(); // Rota el sprite según los parámetros de rotación
}; };

View File

@@ -106,6 +106,7 @@ auto setParams(const std::string& var, const std::string& value) -> bool {
{"scoreboard.rect.h", [](const std::string& v) { param.scoreboard.rect.h = std::stoi(v); }}, {"scoreboard.rect.h", [](const std::string& v) { param.scoreboard.rect.h = std::stoi(v); }},
{"scoreboard.skip_countdown_value", [](const std::string& v) { param.scoreboard.skip_countdown_value = std::stoi(v); }}, {"scoreboard.skip_countdown_value", [](const std::string& v) { param.scoreboard.skip_countdown_value = std::stoi(v); }},
{"title.press_start_position", [](const std::string& v) { param.title.press_start_position = std::stoi(v); }}, {"title.press_start_position", [](const std::string& v) { param.title.press_start_position = std::stoi(v); }},
{"title.title_duration", [](const std::string& v) { param.title.title_duration = std::stoi(v); }},
{"title.arcade_edition_position", [](const std::string& v) { param.title.arcade_edition_position = std::stoi(v); }}, {"title.arcade_edition_position", [](const std::string& v) { param.title.arcade_edition_position = std::stoi(v); }},
{"title.title_c_c_position", [](const std::string& v) { param.title.title_c_c_position = std::stoi(v); }}, {"title.title_c_c_position", [](const std::string& v) { param.title.title_c_c_position = std::stoi(v); }},
{"intro.text_distance_from_bottom", [](const std::string& v) { param.intro.text_distance_from_bottom = std::stoi(v); }}}; {"intro.text_distance_from_bottom", [](const std::string& v) { param.intro.text_distance_from_bottom = std::stoi(v); }}};
@@ -181,7 +182,6 @@ auto setParams(const std::string& var, const std::string& value) -> bool {
{"balloon.settings[3].grav", [](const std::string& v) { param.balloon.settings.at(3).grav = std::stof(v); }}, {"balloon.settings[3].grav", [](const std::string& v) { param.balloon.settings.at(3).grav = std::stof(v); }},
{"tabe.min_spawn_time", [](const std::string& v) { param.tabe.min_spawn_time = std::stof(v); }}, {"tabe.min_spawn_time", [](const std::string& v) { param.tabe.min_spawn_time = std::stof(v); }},
{"tabe.max_spawn_time", [](const std::string& v) { param.tabe.max_spawn_time = std::stof(v); }}, {"tabe.max_spawn_time", [](const std::string& v) { param.tabe.max_spawn_time = std::stof(v); }},
{"title.title_duration", [](const std::string& v) { param.title.title_duration = std::stof(v); }},
{"service_menu.window_message.padding", [](const std::string& v) { param.service_menu.window_message.padding = std::stof(v); }}, {"service_menu.window_message.padding", [](const std::string& v) { param.service_menu.window_message.padding = std::stof(v); }},
{"service_menu.window_message.line_spacing", [](const std::string& v) { param.service_menu.window_message.line_spacing = std::stof(v); }}, {"service_menu.window_message.line_spacing", [](const std::string& v) { param.service_menu.window_message.line_spacing = std::stof(v); }},
{"service_menu.window_message.title_separator_spacing", [](const std::string& v) { param.service_menu.window_message.title_separator_spacing = std::stof(v); }}, {"service_menu.window_message.title_separator_spacing", [](const std::string& v) { param.service_menu.window_message.title_separator_spacing = std::stof(v); }},

View File

@@ -38,7 +38,7 @@ struct ParamFade {
// --- Parámetros de la pantalla de título --- // --- Parámetros de la pantalla de título ---
struct ParamTitle { struct ParamTitle {
int press_start_position = GameDefaults::Title::PRESS_START_POSITION; int press_start_position = GameDefaults::Title::PRESS_START_POSITION;
float title_duration = GameDefaults::Title::DURATION; int title_duration = GameDefaults::Title::DURATION;
int arcade_edition_position = GameDefaults::Title::ARCADE_EDITION_POSITION; int arcade_edition_position = GameDefaults::Title::ARCADE_EDITION_POSITION;
int title_c_c_position = GameDefaults::Title::TITLE_C_C_POSITION; int title_c_c_position = GameDefaults::Title::TITLE_C_C_POSITION;
Color bg_color = Color::fromHex(GameDefaults::Title::BG_COLOR); Color bg_color = Color::fromHex(GameDefaults::Title::BG_COLOR);

View File

@@ -4,12 +4,6 @@
#include <functional> // Para function #include <functional> // Para function
#include <utility> // Para move #include <utility> // Para move
// Constructor para paths por puntos (convertido a segundos)
Path::Path(const std::vector<SDL_FPoint> &spots_init, float waiting_time_s_init)
: spots(spots_init), is_point_path(true) {
waiting_time_s = waiting_time_s_init;
}
// Devuelve un vector con los puntos que conforman la ruta // Devuelve un vector con los puntos que conforman la ruta
auto createPath(float start, float end, PathType type, float fixed_pos, int steps, const std::function<double(double)> &easing_function) -> std::vector<SDL_FPoint> { auto createPath(float start, float end, PathType type, float fixed_pos, int steps, const std::function<double(double)> &easing_function) -> std::vector<SDL_FPoint> {
std::vector<SDL_FPoint> v; std::vector<SDL_FPoint> v;
@@ -39,9 +33,9 @@ auto createPath(float start, float end, PathType type, float fixed_pos, int step
} }
// Actualiza la posición y comprueba si ha llegado a su destino // Actualiza la posición y comprueba si ha llegado a su destino
void PathSprite::update(float delta_time) { void PathSprite::update() {
if (enabled_ && !has_finished_) { if (enabled_ && !has_finished_) {
moveThroughCurrentPath(delta_time); moveThroughCurrentPath();
goToNextPathOrDie(); goToNextPathOrDie();
} }
} }
@@ -83,14 +77,14 @@ void PathSprite::addPath(Path path, bool centered) {
} }
} }
// Añade un recorrido generado (en segundos) // Añade un recorrido
void PathSprite::addPath(float start, float end, PathType type, float fixed_pos, float duration_s, const std::function<double(double)> &easing_function, float waiting_time_s) { void PathSprite::addPath(int start, int end, PathType type, int fixed_pos, int steps, const std::function<double(double)> &easing_function, int waiting_counter) {
paths_.emplace_back(start, end, type, fixed_pos, duration_s, waiting_time_s, easing_function); paths_.emplace_back(createPath(start, end, type, fixed_pos, steps, easing_function), waiting_counter);
} }
// Añade un recorrido por puntos (en segundos) // Añade un recorrido
void PathSprite::addPath(const std::vector<SDL_FPoint> &spots, float waiting_time_s) { void PathSprite::addPath(const std::vector<SDL_FPoint> &spots, int waiting_counter) {
paths_.emplace_back(spots, waiting_time_s); paths_.emplace_back(spots, waiting_counter);
} }
// Habilita el objeto // Habilita el objeto
@@ -101,78 +95,35 @@ void PathSprite::enable() {
enabled_ = true; enabled_ = true;
// Establece la posición inicial // Establece la posición
auto &path = paths_.at(current_path_); auto &path = paths_.at(current_path_);
if (path.is_point_path) { const auto &p = path.spots.at(path.counter);
const auto &p = path.spots.at(path.counter); setPosition(p);
setPosition(p);
} else {
// Para paths generados, establecer posición inicial
SDL_FPoint initial_pos;
if (path.type == PathType::HORIZONTAL) {
initial_pos = {path.start_pos, path.fixed_pos};
} else {
initial_pos = {path.fixed_pos, path.start_pos};
}
setPosition(initial_pos);
}
} }
// Coloca el sprite en los diferentes puntos del recorrido // Coloca el sprite en los diferentes puntos del recorrido
void PathSprite::moveThroughCurrentPath(float delta_time) { void PathSprite::moveThroughCurrentPath() {
auto &path = paths_.at(current_path_); auto &path = paths_.at(current_path_);
if (path.is_point_path) { // Establece la posición
// Lógica para paths por puntos (compatibilidad) const auto &p = path.spots.at(path.counter);
const auto &p = path.spots.at(path.counter); setPosition(p);
setPosition(p);
if (!path.on_destination) { // Comprobar si ha terminado el recorrido
++path.counter; if (!path.on_destination) {
if (path.counter >= static_cast<int>(path.spots.size())) { ++path.counter;
path.on_destination = true; if (path.counter >= static_cast<int>(path.spots.size())) {
path.counter = static_cast<int>(path.spots.size()) - 1; path.on_destination = true;
} path.counter = static_cast<int>(path.spots.size()) - 1;
} }
}
if (path.on_destination) { // Comprobar si ha terminado la espera
path.waiting_elapsed += delta_time; if (path.on_destination) {
if (path.waiting_elapsed >= path.waiting_time_s) { if (path.waiting_counter == 0) {
path.finished = true; path.finished = true;
}
}
} else {
// Lógica para paths generados en tiempo real
if (!path.on_destination) {
path.elapsed_time += delta_time;
// Calcular progreso (0.0 a 1.0)
float progress = path.elapsed_time / path.duration_s;
if (progress >= 1.0f) {
progress = 1.0f;
path.on_destination = true;
}
// Aplicar función de easing
double eased_progress = path.easing_function(progress);
// Calcular posición actual
float current_pos = path.start_pos + (path.end_pos - path.start_pos) * static_cast<float>(eased_progress);
// Establecer posición según el tipo
SDL_FPoint position;
if (path.type == PathType::HORIZONTAL) {
position = {current_pos, path.fixed_pos};
} else {
position = {path.fixed_pos, current_pos};
}
setPosition(position);
} else { } else {
// Esperar en destino --path.waiting_counter;
path.waiting_elapsed += delta_time;
if (path.waiting_elapsed >= path.waiting_time_s) {
path.finished = true;
}
} }
} }
} }

View File

@@ -24,31 +24,17 @@ enum class PathCentered { // Centrado del recorrido
}; };
// --- Estructuras --- // --- Estructuras ---
struct Path { // Define un recorrido para el sprite struct Path { // Define un recorrido para el sprite
float start_pos; // Posición inicial std::vector<SDL_FPoint> spots; // Puntos por los que se desplazará el sprite
float end_pos; // Posición final int waiting_counter; // Tiempo de espera una vez en el destino
PathType type; // Tipo de movimiento (horizontal/vertical) bool on_destination = false; // Indica si ha llegado al destino
float fixed_pos; // Posición fija en el eje contrario bool finished = false; // Indica si ha terminado de esperarse
float duration_s; // Duración de la animación en segundos int counter = 0; // Contador interno
float waiting_time_s; // Tiempo de espera una vez en el destino
std::function<double(double)> easing_function; // Función de easing
float elapsed_time = 0.0f; // Tiempo transcurrido
float waiting_elapsed = 0.0f; // Tiempo de espera transcurrido
bool on_destination = false; // Indica si ha llegado al destino
bool finished = false; // Indica si ha terminado de esperarse
// Constructor para paths generados // Constructor
Path(float start, float end, PathType path_type, float fixed, float duration, float waiting, std::function<double(double)> easing) Path(const std::vector<SDL_FPoint> &spots_init, int waiting_counter_init)
: start_pos(start), end_pos(end), type(path_type), fixed_pos(fixed), : spots(spots_init),
duration_s(duration), waiting_time_s(waiting), easing_function(std::move(easing)) {} waiting_counter(waiting_counter_init) {}
// Constructor para paths por puntos (convertido a segundos)
Path(const std::vector<SDL_FPoint> &spots_init, float waiting_time_s_init);
// Variables para paths por puntos
std::vector<SDL_FPoint> spots; // Solo para paths por puntos
int counter = 0; // Solo para paths por puntos
bool is_point_path = false; // Indica si es un path por puntos
}; };
// --- Funciones --- // --- Funciones ---
@@ -63,13 +49,13 @@ class PathSprite : public Sprite {
~PathSprite() override = default; ~PathSprite() override = default;
// --- Métodos principales --- // --- Métodos principales ---
void update(float delta_time); // Actualiza la posición del sprite según el recorrido (delta_time en segundos) void update(); // Actualiza la posición del sprite según el recorrido
void render() override; // Muestra el sprite por pantalla void render() override; // Muestra el sprite por pantalla
// --- Gestión de recorridos --- // --- Gestión de recorridos ---
void addPath(Path path, bool centered = false); // Añade un recorrido (Path) void addPath(Path path, bool centered = false); // Añade un recorrido (Path)
void addPath(const std::vector<SDL_FPoint> &spots, float waiting_time_s = 0.0f); // Añade un recorrido a partir de puntos void addPath(const std::vector<SDL_FPoint> &spots, int waiting_counter = 0); // Añade un recorrido a partir de puntos
void addPath(float start, float end, PathType type, float fixed_pos, float duration_s, const std::function<double(double)> &easing_function, float waiting_time_s = 0.0f); // Añade un recorrido generado void addPath(int start, int end, PathType type, int fixed_pos, int steps, const std::function<double(double)> &easing_function, int waiting_counter = 0); // Añade un recorrido generado
// --- Estado y control --- // --- Estado y control ---
void enable(); // Habilita el objeto void enable(); // Habilita el objeto
@@ -86,6 +72,6 @@ class PathSprite : public Sprite {
std::vector<Path> paths_; // Caminos a recorrer por el sprite std::vector<Path> paths_; // Caminos a recorrer por el sprite
// --- Métodos internos --- // --- Métodos internos ---
void moveThroughCurrentPath(float delta_time); // Coloca el sprite en los diferentes puntos del recorrido void moveThroughCurrentPath(); // Coloca el sprite en los diferentes puntos del recorrido
void goToNextPathOrDie(); // Cambia de recorrido o finaliza void goToNextPathOrDie(); // Cambia de recorrido o finaliza
}; };

View File

@@ -3,7 +3,6 @@
#include <SDL3/SDL.h> // Para SDL_GetTicks, SDL_FlipMode #include <SDL3/SDL.h> // Para SDL_GetTicks, SDL_FlipMode
#include <algorithm> // Para clamp, max, min #include <algorithm> // Para clamp, max, min
#include <cmath> // Para fmod
#include <cstdlib> // Para rand #include <cstdlib> // Para rand
#include "animated_sprite.h" // Para AnimatedSprite #include "animated_sprite.h" // Para AnimatedSprite
@@ -60,14 +59,17 @@ void Player::init() {
power_up_counter_ = POWERUP_COUNTER; power_up_counter_ = POWERUP_COUNTER;
extra_hit_ = false; extra_hit_ = false;
coffees_ = 0; coffees_ = 0;
continue_ticks_ = 0;
continue_counter_ = 10; continue_counter_ = 10;
name_entry_idle_time_accumulator_ = 0.0f; name_entry_ticks_ = 0;
name_entry_total_time_accumulator_ = 0.0f; name_entry_idle_counter_ = 0;
name_entry_total_counter_ = 0;
shiftColliders(); shiftColliders();
vel_x_ = 0; vel_x_ = 0;
vel_y_ = 0; vel_y_ = 0;
score_ = 0; score_ = 0;
score_multiplier_ = 1.0F; score_multiplier_ = 1.0F;
cant_fire_counter_ = 10;
enter_name_->init(last_enter_name_); enter_name_->init(last_enter_name_);
// Establece la posición del sprite // Establece la posición del sprite
@@ -146,38 +148,38 @@ void Player::setInputEnteringName(Input::Action action) {
default: default:
break; break;
} }
name_entry_idle_time_accumulator_ = 0.0f; name_entry_idle_counter_ = 0;
} }
// Fase 1: Sistema de movimiento time-based // Mueve el jugador a la posición y animación que le corresponde
void Player::move(float deltaTime) { void Player::move() {
switch (playing_state_) { switch (playing_state_) {
case State::PLAYING: case State::PLAYING:
handlePlayingMovement(deltaTime); handlePlayingMovement();
break; break;
case State::ROLLING: case State::ROLLING:
handleRollingMovement(); handleRollingMovement();
break; break;
case State::TITLE_ANIMATION: case State::TITLE_ANIMATION:
handleTitleAnimation(); handleTitleAnimation();
break; break;
case State::CONTINUE_TIME_OUT: case State::CONTINUE_TIME_OUT:
handleContinueTimeOut(); handleContinueTimeOut();
break; break;
case State::LEAVING_SCREEN: case State::LEAVING_SCREEN:
handleLeavingScreen(); handleLeavingScreen();
break; break;
case State::ENTERING_SCREEN: case State::ENTERING_SCREEN:
handleEnteringScreen(); handleEnteringScreen();
break; break;
case State::CREDITS: case State::CREDITS:
handleCreditsMovement(deltaTime); handleCreditsMovement();
break; break;
case State::WAITING: case State::WAITING:
handleWaitingMovement(deltaTime); handleWaitingMovement();
break; break;
case State::RECOVER: case State::RECOVER:
handleRecoverMovement(); handleRecoverMovement();
break; break;
default: default:
break; break;
@@ -196,19 +198,6 @@ void Player::handlePlayingMovement() {
shiftSprite(); shiftSprite();
} }
// Fase 1: Movimiento time-based durante el juego
void Player::handlePlayingMovement(float deltaTime) {
// Mueve el jugador a derecha o izquierda (time-based)
pos_x_ += vel_x_ * deltaTime / (1000.0f / 60.0f);
// Si el jugador abandona el area de juego por los laterales, restaura su posición
const float MIN_X = play_area_.x - 5;
const float MAX_X = play_area_.w + 5 - WIDTH;
pos_x_ = std::clamp(pos_x_, MIN_X, MAX_X);
shiftSprite();
}
void Player::handleRecoverMovement() { void Player::handleRecoverMovement() {
if (player_sprite_->getCurrentAnimationFrame() == 10) { playSound("voice_recover.wav"); } if (player_sprite_->getCurrentAnimationFrame() == 10) { playSound("voice_recover.wav"); }
if (player_sprite_->animationIsCompleted()) { setPlayingState(State::RESPAWNING); } if (player_sprite_->animationIsCompleted()) { setPlayingState(State::RESPAWNING); }
@@ -348,20 +337,6 @@ void Player::handleCreditsMovement() {
shiftSprite(); shiftSprite();
} }
// Fase 4: Movimiento general en la pantalla de créditos (time-based)
void Player::handleCreditsMovement(float deltaTime) {
pos_x_ += (vel_x_ / 2.0F) * deltaTime / (1000.0f / 60.0f); // Convert frame-based movement to time-based
if (vel_x_ > 0) {
handleCreditsRightMovement();
} else {
handleCreditsLeftMovement();
}
updateWalkingStateForCredits();
shiftSprite();
}
void Player::handleCreditsRightMovement() { void Player::handleCreditsRightMovement() {
if (pos_x_ > param.game.game_area.rect.w - WIDTH) { if (pos_x_ > param.game.game_area.rect.w - WIDTH) {
pos_x_ = param.game.game_area.rect.w - WIDTH; pos_x_ = param.game.game_area.rect.w - WIDTH;
@@ -384,16 +359,6 @@ void Player::handleWaitingMovement() {
} }
} }
// Fase 4: Controla la animación del jugador saludando (time-based)
void Player::handleWaitingMovement(float deltaTime) {
waiting_time_accumulator_ += deltaTime;
float waiting_duration = static_cast<float>(WAITING_COUNTER) * (1000.0f / 60.0f); // Convert frames to milliseconds
if (waiting_time_accumulator_ >= waiting_duration) {
waiting_time_accumulator_ = 0.0f;
player_sprite_->resetAnimation();
}
}
void Player::updateWalkingStateForCredits() { void Player::updateWalkingStateForCredits() {
if (pos_x_ > param.game.game_area.center_x - WIDTH / 2) { if (pos_x_ > param.game.game_area.center_x - WIDTH / 2) {
setWalkingState(State::WALKING_LEFT); setWalkingState(State::WALKING_LEFT);
@@ -422,35 +387,11 @@ void Player::updateStepCounter() {
} }
} }
// Fase 4: Incrementa o ajusta el contador de pasos (time-based)
void Player::updateStepCounter(float deltaTime) {
step_time_accumulator_ += deltaTime;
float step_interval = 10.0f / 60.0f; // 10 frames converted to seconds
if (step_time_accumulator_ >= step_interval) {
step_time_accumulator_ = 0.0f;
playSound("walk.wav");
}
}
// Pinta el jugador en pantalla // Pinta el jugador en pantalla
void Player::render() { void Player::render() {
if (power_up_ && isPlaying()) { if (power_up_ && isPlaying()) {
// Convertir lógica de parpadeo a deltaTime if (power_up_counter_ > (POWERUP_COUNTER / 4) || power_up_counter_ % 20 > 4) {
float total_powerup_time_ms = static_cast<float>(POWERUP_COUNTER) / 60.0f * 1000.0f; // Total time in ms
float quarter_time_ms = total_powerup_time_ms / 4.0f; // 25% del tiempo total
if (power_up_time_accumulator_ > quarter_time_ms) {
// En los primeros 75% del tiempo, siempre visible
power_sprite_->render(); power_sprite_->render();
} else {
// En el último 25%, parpadea cada 20 frames (333ms aprox)
constexpr float blink_period_ms = 20.0f / 60.0f * 1000.0f; // 20 frames in ms
constexpr float visible_proportion = 4.0f / 20.0f; // 4 frames visible de 20 total
float cycle_position = fmod(power_up_time_accumulator_, blink_period_ms) / blink_period_ms;
if (cycle_position >= visible_proportion) {
power_sprite_->render();
}
} }
} }
@@ -513,8 +454,8 @@ auto Player::computeAnimation() const -> std::pair<std::string, SDL_FlipMode> {
return {anim_name, flip_mode}; return {anim_name, flip_mode};
} }
// Fase 1: Establece la animación correspondiente al estado (time-based) // Establece la animación correspondiente al estado
void Player::setAnimation(float deltaTime) { void Player::setAnimation() {
switch (playing_state_) { switch (playing_state_) {
case State::PLAYING: case State::PLAYING:
case State::ENTERING_NAME_GAME_COMPLETED: case State::ENTERING_NAME_GAME_COMPLETED:
@@ -550,23 +491,108 @@ void Player::setAnimation(float deltaTime) {
break; break;
} }
// La diferencia clave: usa deltaTime para las animaciones player_sprite_->update();
player_sprite_->update(deltaTime); power_sprite_->update();
power_sprite_->update(deltaTime);
} }
// Actualiza al jugador con deltaTime (time-based) // Actualiza el valor de la variable
void Player::update(float deltaTime) { void Player::updateCooldown() {
move(deltaTime); // Sistema de movimiento time-based if (playing_state_ != State::PLAYING) {
setAnimation(deltaTime); // Animaciones time-based return;
shiftColliders(); // Sin cambios (posicional) }
updateFireSystem(deltaTime); // Sistema de disparo de dos líneas
updatePowerUp(deltaTime); // Sistema de power-up time-based if (cant_fire_counter_ > 0) {
updateInvulnerable(deltaTime); // Sistema de invulnerabilidad time-based handleFiringCooldown();
updateScoreboard(); // Sin cambios (no temporal) } else {
updateContinueCounter(deltaTime); // Sistema de continue time-based handleRecoilAndCooling();
updateEnterNameCounter(deltaTime); // Sistema de name entry time-based }
updateShowingName(deltaTime); // Sistema de showing name time-based }
void Player::handleFiringCooldown() {
cooling_state_counter_ = COOLING_DURATION;
// Transition to recoiling state at halfway point
if (cant_fire_counter_ == recoiling_state_duration_ / 2) {
transitionToRecoiling();
}
--cant_fire_counter_;
if (cant_fire_counter_ == 0) {
recoiling_state_counter_ = recoiling_state_duration_;
}
}
void Player::handleRecoilAndCooling() {
if (recoiling_state_counter_ > 0) {
--recoiling_state_counter_;
return;
}
handleCoolingState();
}
void Player::handleCoolingState() {
if (cooling_state_counter_ > COOLING_COMPLETE) {
if (cooling_state_counter_ == COOLING_DURATION) {
transitionToCooling();
}
--cooling_state_counter_;
}
if (cooling_state_counter_ == COOLING_COMPLETE) {
completeCooling();
}
}
void Player::transitionToRecoiling() {
switch (firing_state_) {
case State::FIRING_LEFT:
setFiringState(State::RECOILING_LEFT);
break;
case State::FIRING_RIGHT:
setFiringState(State::RECOILING_RIGHT);
break;
case State::FIRING_UP:
setFiringState(State::RECOILING_UP);
break;
default:
break;
}
}
void Player::transitionToCooling() {
switch (firing_state_) {
case State::RECOILING_LEFT:
setFiringState(State::COOLING_LEFT);
break;
case State::RECOILING_RIGHT:
setFiringState(State::COOLING_RIGHT);
break;
case State::RECOILING_UP:
setFiringState(State::COOLING_UP);
break;
default:
break;
}
}
void Player::completeCooling() {
setFiringState(State::FIRING_NONE);
cooling_state_counter_ = -1;
}
// Actualiza al jugador a su posicion, animación y controla los contadores
void Player::update() {
move();
setAnimation();
shiftColliders();
updateCooldown();
updatePowerUp();
updateInvulnerable();
updateScoreboard();
updateContinueCounter();
updateEnterNameCounter();
updateShowingName();
} }
void Player::passShowingName() { void Player::passShowingName() {
@@ -636,8 +662,8 @@ void Player::setPlayingState(State state) {
} }
case State::CONTINUE: { case State::CONTINUE: {
// Inicializa el contador de continuar // Inicializa el contador de continuar
continue_ticks_ = SDL_GetTicks();
continue_counter_ = 9; continue_counter_ = 9;
continue_time_accumulator_ = 0.0f; // Initialize time accumulator
playSound("continue_clock.wav"); playSound("continue_clock.wav");
setScoreboardMode(Scoreboard::Mode::CONTINUE); setScoreboardMode(Scoreboard::Mode::CONTINUE);
break; break;
@@ -656,7 +682,6 @@ void Player::setPlayingState(State state) {
} }
pos_y_ = default_pos_y_; pos_y_ = default_pos_y_;
waiting_counter_ = 0; waiting_counter_ = 0;
waiting_time_accumulator_ = 0.0f; // Initialize time accumulator
shiftSprite(); shiftSprite();
player_sprite_->setCurrentAnimation("hello"); player_sprite_->setCurrentAnimation("hello");
player_sprite_->animtionPause(); player_sprite_->animtionPause();
@@ -668,7 +693,7 @@ void Player::setPlayingState(State state) {
break; break;
} }
case State::SHOWING_NAME: { case State::SHOWING_NAME: {
showing_name_time_accumulator_ = 0.0f; // Inicializar acumulador time-based showing_name_ticks_ = SDL_GetTicks();
setScoreboardMode(Scoreboard::Mode::SHOW_NAME); setScoreboardMode(Scoreboard::Mode::SHOW_NAME);
Scoreboard::get()->setRecordName(scoreboard_panel_, last_enter_name_); Scoreboard::get()->setRecordName(scoreboard_panel_, last_enter_name_);
addScoreToScoreBoard(); addScoreToScoreBoard();
@@ -724,14 +749,12 @@ void Player::setPlayingState(State state) {
} }
case State::LEAVING_SCREEN: { case State::LEAVING_SCREEN: {
step_counter_ = 0; step_counter_ = 0;
step_time_accumulator_ = 0.0f; // Initialize time accumulator
setScoreboardMode(Scoreboard::Mode::GAME_COMPLETED); setScoreboardMode(Scoreboard::Mode::GAME_COMPLETED);
break; break;
} }
case State::ENTERING_SCREEN: { case State::ENTERING_SCREEN: {
init(); init();
step_counter_ = 0; step_counter_ = 0;
step_time_accumulator_ = 0.0f; // Initialize time accumulator
setScoreboardMode(Scoreboard::Mode::SCORE); setScoreboardMode(Scoreboard::Mode::SCORE);
switch (id_) { switch (id_) {
case Id::PLAYER1: case Id::PLAYER1:
@@ -772,26 +795,24 @@ void Player::decScoreMultiplier() {
void Player::setInvulnerable(bool value) { void Player::setInvulnerable(bool value) {
invulnerable_ = value; invulnerable_ = value;
invulnerable_counter_ = invulnerable_ ? INVULNERABLE_COUNTER : 0; invulnerable_counter_ = invulnerable_ ? INVULNERABLE_COUNTER : 0;
invulnerable_time_accumulator_ = invulnerable_ ? static_cast<float>(INVULNERABLE_COUNTER) / 60.0f * 1000.0f : 0.0f; // Convert frames to milliseconds
} }
// Monitoriza el estado (time-based) // Monitoriza el estado
void Player::updateInvulnerable(float deltaTime) { void Player::updateInvulnerable() {
if (playing_state_ == State::PLAYING && invulnerable_) { if (playing_state_ == State::PLAYING && invulnerable_) {
if (invulnerable_time_accumulator_ > 0) { if (invulnerable_counter_ > 0) {
invulnerable_time_accumulator_ -= deltaTime; --invulnerable_counter_;
// Frecuencia fija de parpadeo adaptada a deltaTime (en milisegundos) // Frecuencia fija de parpadeo (como el original)
constexpr float blink_period_ms = 8.0f / 60.0f * 1000.0f; // 8 frames convertidos a ms constexpr int blink_speed = 8;
// Calcula proporción decreciente basada en tiempo restante // Calcula proporción decreciente: menos textura blanca hacia el final
float total_invulnerable_time_ms = static_cast<float>(INVULNERABLE_COUNTER) / 60.0f * 1000.0f; // Al inicio: 50-50, hacia el final: 70-30 (menos blanco)
float progress = 1.0f - (invulnerable_time_accumulator_ / total_invulnerable_time_ms); float progress = 1.0f - (static_cast<float>(invulnerable_counter_) / INVULNERABLE_COUNTER);
float white_proportion = 0.5f - progress * 0.2f; // Menos blanco hacia el final int white_frames = static_cast<int>((0.5f - progress * 0.2f) * blink_speed);
// Calcula si debe mostrar textura de invulnerabilidad basado en el ciclo temporal // Alterna entre texturas con proporción variable
float cycle_position = fmod(invulnerable_time_accumulator_, blink_period_ms) / blink_period_ms; bool should_show_invulnerable = (invulnerable_counter_ % blink_speed) < white_frames;
bool should_show_invulnerable = cycle_position < white_proportion;
size_t target_texture = should_show_invulnerable ? INVULNERABLE_TEXTURE : coffees_; size_t target_texture = should_show_invulnerable ? INVULNERABLE_TEXTURE : coffees_;
// Solo cambia textura si es diferente (optimización) // Solo cambia textura si es diferente (optimización)
@@ -800,7 +821,6 @@ void Player::updateInvulnerable(float deltaTime) {
} }
} else { } else {
// Fin de invulnerabilidad // Fin de invulnerabilidad
invulnerable_time_accumulator_ = 0;
setInvulnerable(false); setInvulnerable(false);
player_sprite_->setActiveTexture(coffees_); player_sprite_->setActiveTexture(coffees_);
} }
@@ -811,18 +831,14 @@ void Player::updateInvulnerable(float deltaTime) {
void Player::setPowerUp() { void Player::setPowerUp() {
power_up_ = true; power_up_ = true;
power_up_counter_ = POWERUP_COUNTER; power_up_counter_ = POWERUP_COUNTER;
power_up_time_accumulator_ = static_cast<float>(POWERUP_COUNTER) / 60.0f * 1000.0f; // Convert frames to milliseconds
} }
// Actualiza el valor de la variable (time-based) // Actualiza el valor de la variable
void Player::updatePowerUp(float deltaTime) { void Player::updatePowerUp() {
if (playing_state_ == State::PLAYING) { if (playing_state_ == State::PLAYING) {
if (power_up_) { if (power_up_) {
power_up_time_accumulator_ -= deltaTime; --power_up_counter_;
power_up_ = power_up_time_accumulator_ > 0; power_up_ = power_up_counter_ > 0;
if (!power_up_) {
power_up_time_accumulator_ = 0;
}
} }
} }
} }
@@ -859,36 +875,31 @@ void Player::setPlayerTextures(const std::vector<std::shared_ptr<Texture>> &text
power_sprite_->setTexture(texture[1]); power_sprite_->setTexture(texture[1]);
} }
// Actualiza el contador de continue (time-based) // Actualiza el contador de continue
void Player::updateContinueCounter(float deltaTime) { void Player::updateContinueCounter() {
if (playing_state_ == State::CONTINUE) { if (playing_state_ == State::CONTINUE) {
continue_time_accumulator_ += deltaTime; constexpr int TICKS_SPEED = 1000;
constexpr float CONTINUE_INTERVAL = 1000.0f; // 1 segundo en milisegundos if (SDL_GetTicks() - continue_ticks_ > TICKS_SPEED) {
if (continue_time_accumulator_ >= CONTINUE_INTERVAL) {
continue_time_accumulator_ -= CONTINUE_INTERVAL;
decContinueCounter(); decContinueCounter();
} }
} }
} }
// Actualiza el contador de entrar nombre (time-based) // Actualiza el contador de entrar nombre
void Player::updateEnterNameCounter(float deltaTime) { void Player::updateEnterNameCounter() {
if (playing_state_ == State::ENTERING_NAME || playing_state_ == State::ENTERING_NAME_GAME_COMPLETED) { if (playing_state_ == State::ENTERING_NAME || playing_state_ == State::ENTERING_NAME_GAME_COMPLETED) {
name_entry_time_accumulator_ += deltaTime; constexpr int TICKS_SPEED = 1000;
constexpr float NAME_ENTRY_INTERVAL = 1000.0f; // 1 segundo en milisegundos if (SDL_GetTicks() - name_entry_ticks_ > TICKS_SPEED) {
if (name_entry_time_accumulator_ >= NAME_ENTRY_INTERVAL) {
name_entry_time_accumulator_ -= NAME_ENTRY_INTERVAL;
decNameEntryCounter(); decNameEntryCounter();
} }
} }
} }
// Actualiza el estado de SHOWING_NAME (time-based) // Actualiza el estado de SHOWING_NAME
void Player::updateShowingName(float deltaTime) { void Player::updateShowingName() {
if (playing_state_ == State::SHOWING_NAME) { if (playing_state_ == State::SHOWING_NAME) {
showing_name_time_accumulator_ += deltaTime; constexpr int TICKS_SPEED = 5000;
constexpr float SHOWING_NAME_DURATION = 5000.0f; // 5 segundos en milisegundos if (SDL_GetTicks() - name_entry_ticks_ > TICKS_SPEED) {
if (showing_name_time_accumulator_ >= SHOWING_NAME_DURATION) {
game_completed_ ? setPlayingState(State::LEAVING_SCREEN) : setPlayingState(State::CONTINUE); game_completed_ ? setPlayingState(State::LEAVING_SCREEN) : setPlayingState(State::CONTINUE);
} }
} }
@@ -896,7 +907,7 @@ void Player::updateShowingName(float deltaTime) {
// Decrementa el contador de continuar // Decrementa el contador de continuar
void Player::decContinueCounter() { void Player::decContinueCounter() {
continue_time_accumulator_ = 0.0f; // Reset time accumulator continue_ticks_ = SDL_GetTicks();
--continue_counter_; --continue_counter_;
if (continue_counter_ < 0) { if (continue_counter_ < 0) {
setPlayingState(State::CONTINUE_TIME_OUT); setPlayingState(State::CONTINUE_TIME_OUT);
@@ -907,17 +918,17 @@ void Player::decContinueCounter() {
// Decrementa el contador de entrar nombre // Decrementa el contador de entrar nombre
void Player::decNameEntryCounter() { void Player::decNameEntryCounter() {
name_entry_time_accumulator_ = 0.0f; // Reset time accumulator name_entry_ticks_ = SDL_GetTicks();
// Incrementa acumuladores de tiempo (1 segundo = 1000ms) // Actualiza contadores
name_entry_idle_time_accumulator_ += 1000.0f; ++name_entry_idle_counter_;
name_entry_total_time_accumulator_ += 1000.0f; ++name_entry_total_counter_;
// Comprueba los acumuladores directamente contra los límites en milisegundos // Comprueba los contadores
if ((name_entry_total_time_accumulator_ >= param.game.name_entry_total_time) || if ((name_entry_total_counter_ >= param.game.name_entry_total_time) ||
(name_entry_idle_time_accumulator_ >= param.game.name_entry_idle_time)) { (name_entry_idle_counter_ >= param.game.name_entry_idle_time)) {
name_entry_total_time_accumulator_ = 0.0f; name_entry_total_counter_ = 0;
name_entry_idle_time_accumulator_ = 0.0f; name_entry_idle_counter_ = 0;
if (playing_state_ == State::ENTERING_NAME) { if (playing_state_ == State::ENTERING_NAME) {
last_enter_name_ = getRecordName(); last_enter_name_ = getRecordName();
setPlayingState(State::SHOWING_NAME); setPlayingState(State::SHOWING_NAME);
@@ -977,145 +988,4 @@ void Player::addScoreToScoreBoard() const {
void Player::addCredit() { void Player::addCredit() {
++credits_used_; ++credits_used_;
playSound("credit.wav"); playSound("credit.wav");
}
// ========================================
// SISTEMA DE DISPARO DE DOS LÍNEAS
// ========================================
// Método principal del sistema de disparo
void Player::updateFireSystem(float deltaTime) {
updateFunctionalLine(deltaTime); // Línea 1: CanFire
updateVisualLine(deltaTime); // Línea 2: Animaciones
}
// LÍNEA 1: Sistema Funcional (CanFire)
void Player::updateFunctionalLine(float deltaTime) {
if (fire_cooldown_timer_ > 0) {
fire_cooldown_timer_ -= deltaTime;
can_fire_new_system_ = false;
} else {
fire_cooldown_timer_ = 0; // Evitar valores negativos
can_fire_new_system_ = true;
}
}
// LÍNEA 2: Sistema Visual (Animaciones)
void Player::updateVisualLine(float deltaTime) {
if (visual_fire_state_ == VisualFireState::NORMAL) {
return; // No hay temporizador activo en estado NORMAL
}
visual_state_timer_ -= deltaTime;
switch (visual_fire_state_) {
case VisualFireState::AIMING:
if (visual_state_timer_ <= 0) {
transitionToRecoilingNew();
}
break;
case VisualFireState::RECOILING:
if (visual_state_timer_ <= 0) {
transitionToThreatPose();
}
break;
case VisualFireState::THREAT_POSE:
if (visual_state_timer_ <= 0) {
transitionToNormalNew();
}
break;
case VisualFireState::NORMAL:
// Ya manejado arriba
break;
}
}
// Inicia un disparo en ambas líneas
void Player::startFiringSystem(int cooldown_frames) {
// LÍNEA 1: Inicia cooldown funcional
fire_cooldown_timer_ = static_cast<float>(cooldown_frames) / 60.0f * 1000.0f; // Convertir frames a ms
can_fire_new_system_ = false;
// LÍNEA 2: Resetea completamente el estado visual
aiming_duration_ = fire_cooldown_timer_ * AIMING_DURATION_FACTOR; // 50% del cooldown
recoiling_duration_ = aiming_duration_ * RECOILING_DURATION_MULTIPLIER; // 4 veces la duración de aiming
visual_fire_state_ = VisualFireState::AIMING;
visual_state_timer_ = aiming_duration_;
updateFiringStateFromVisual(); // Sincroniza firing_state_ para animaciones
}
// Sincroniza firing_state_ con visual_fire_state_
void Player::updateFiringStateFromVisual() {
// Mantener la dirección actual del disparo
State base_state = State::FIRING_NONE;
if (firing_state_ == State::FIRING_LEFT || firing_state_ == State::RECOILING_LEFT || firing_state_ == State::COOLING_LEFT) {
base_state = State::FIRING_LEFT;
} else if (firing_state_ == State::FIRING_RIGHT || firing_state_ == State::RECOILING_RIGHT || firing_state_ == State::COOLING_RIGHT) {
base_state = State::FIRING_RIGHT;
} else if (firing_state_ == State::FIRING_UP || firing_state_ == State::RECOILING_UP || firing_state_ == State::COOLING_UP) {
base_state = State::FIRING_UP;
}
switch (visual_fire_state_) {
case VisualFireState::NORMAL:
firing_state_ = State::FIRING_NONE;
break;
case VisualFireState::AIMING:
firing_state_ = base_state; // FIRING_LEFT/RIGHT/UP
break;
case VisualFireState::RECOILING:
switch (base_state) {
case State::FIRING_LEFT: firing_state_ = State::RECOILING_LEFT; break;
case State::FIRING_RIGHT: firing_state_ = State::RECOILING_RIGHT; break;
case State::FIRING_UP: firing_state_ = State::RECOILING_UP; break;
default: firing_state_ = State::RECOILING_UP; break;
}
break;
case VisualFireState::THREAT_POSE:
switch (base_state) {
case State::FIRING_LEFT: firing_state_ = State::COOLING_LEFT; break;
case State::FIRING_RIGHT: firing_state_ = State::COOLING_RIGHT; break;
case State::FIRING_UP: firing_state_ = State::COOLING_UP; break;
default: firing_state_ = State::COOLING_UP; break;
}
break;
}
}
// Transiciones del sistema visual
void Player::transitionToRecoilingNew() {
visual_fire_state_ = VisualFireState::RECOILING;
visual_state_timer_ = recoiling_duration_;
updateFiringStateFromVisual();
}
void Player::transitionToThreatPose() {
visual_fire_state_ = VisualFireState::THREAT_POSE;
// Calcular threat_pose_duration ajustada:
// Duración original (833ms) menos el tiempo extra que ahora dura recoiling
float original_recoiling_duration = fire_cooldown_timer_; // Era 100% del cooldown
float new_recoiling_duration = aiming_duration_ * RECOILING_DURATION_MULTIPLIER; // Ahora es más del cooldown
float extra_recoiling_time = new_recoiling_duration - original_recoiling_duration;
float adjusted_threat_duration = THREAT_POSE_DURATION - extra_recoiling_time;
// Asegurar que no sea negativo
visual_state_timer_ = std::max(adjusted_threat_duration, MIN_THREAT_POSE_DURATION);
updateFiringStateFromVisual();
}
void Player::transitionToNormalNew() {
visual_fire_state_ = VisualFireState::NORMAL;
visual_state_timer_ = 0;
updateFiringStateFromVisual();
} }

View File

@@ -17,21 +17,7 @@
class Texture; class Texture;
// --- Clase Player: jugador principal del juego --- // --- Clase Player ---
//
// Esta clase gestiona todos los aspectos de un jugador durante el juego,
// incluyendo movimiento, disparos, animaciones y estados especiales.
//
// Funcionalidades principales:
// • Sistema de disparo de dos líneas: funcional (cooldown) + visual (animaciones)
// • Estados de animación: normal → aiming → recoiling → threat_pose → normal
// • Movimiento time-based: compatibilidad con deltaTime para fluidez variable
// • Power-ups e invulnerabilidad: coffee machine, extra hits, parpadeos
// • Sistema de puntuación: multipliers, high scores, entrada de nombres
// • Estados de juego: playing, rolling, continue, entering_name, etc.
//
// El sistema de disparo utiliza duraciones configurables mediante constantes
// para facilitar el ajuste del gameplay y la sensación de disparo.
class Player { class Player {
public: public:
// --- Constantes --- // --- Constantes ---
@@ -108,9 +94,9 @@ class Player {
~Player() = default; ~Player() = default;
// --- Inicialización y ciclo de vida --- // --- Inicialización y ciclo de vida ---
void init(); // Inicializa el jugador void init(); // Inicializa el jugador
void update(float deltaTime); // Actualiza estado, animación y contadores (time-based) void update(); // Actualiza estado, animación y contadores
void render(); // Dibuja el jugador en pantalla void render(); // Dibuja el jugador en pantalla
// --- Entrada y control --- // --- Entrada y control ---
void setInput(Input::Action action); // Procesa entrada general void setInput(Input::Action action); // Procesa entrada general
@@ -118,13 +104,14 @@ class Player {
void setInputEnteringName(Input::Action action); // Procesa entrada al introducir nombre void setInputEnteringName(Input::Action action); // Procesa entrada al introducir nombre
// --- Movimiento y animación --- // --- Movimiento y animación ---
void move(float deltaTime); // Mueve el jugador (time-based) void move(); // Mueve el jugador
void setAnimation(float deltaTime); // Establece la animación según el estado (time-based) void setAnimation(); // Establece la animación según el estado
// --- Texturas y animaciones --- // --- Texturas y animaciones ---
void setPlayerTextures(const std::vector<std::shared_ptr<Texture>> &texture); // Cambia las texturas del jugador void setPlayerTextures(const std::vector<std::shared_ptr<Texture>> &texture); // Cambia las texturas del jugador
// --- Estados y contadores --- // --- Estados y contadores ---
void updateCooldown(); // Actualiza el cooldown de disparo
// --- Puntuación y marcador --- // --- Puntuación y marcador ---
void addScore(int score, int lowest_hi_score_entry); // Añade puntos void addScore(int score, int lowest_hi_score_entry); // Añade puntos
@@ -135,7 +122,7 @@ class Player {
void setPlayingState(State state); // Cambia el estado de juego void setPlayingState(State state); // Cambia el estado de juego
void setInvulnerable(bool value); // Establece el valor del estado de invulnerabilidad void setInvulnerable(bool value); // Establece el valor del estado de invulnerabilidad
void setPowerUp(); // Activa el modo PowerUp void setPowerUp(); // Activa el modo PowerUp
void updatePowerUp(float deltaTime); // Actualiza el valor de PowerUp (time-based) void updatePowerUp(); // Actualiza el valor de PowerUp
void giveExtraHit(); // Concede un toque extra al jugador void giveExtraHit(); // Concede un toque extra al jugador
void removeExtraHit(); // Quita el toque extra al jugador void removeExtraHit(); // Quita el toque extra al jugador
void decContinueCounter(); // Decrementa el contador de continuar void decContinueCounter(); // Decrementa el contador de continuar
@@ -158,7 +145,7 @@ class Player {
[[nodiscard]] auto isTitleHidden() const -> bool { return playing_state_ == State::TITLE_HIDDEN; } [[nodiscard]] auto isTitleHidden() const -> bool { return playing_state_ == State::TITLE_HIDDEN; }
// Getters // Getters
[[nodiscard]] auto canFire() const -> bool { return can_fire_new_system_; } // Usa nuevo sistema [[nodiscard]] auto canFire() const -> bool { return cant_fire_counter_ <= 0; }
[[nodiscard]] auto hasExtraHit() const -> bool { return extra_hit_; } [[nodiscard]] auto hasExtraHit() const -> bool { return extra_hit_; }
[[nodiscard]] auto isCooling() const -> bool { return firing_state_ == State::COOLING_LEFT || firing_state_ == State::COOLING_UP || firing_state_ == State::COOLING_RIGHT; } [[nodiscard]] auto isCooling() const -> bool { return firing_state_ == State::COOLING_LEFT || firing_state_ == State::COOLING_UP || firing_state_ == State::COOLING_RIGHT; }
[[nodiscard]] auto isRecoiling() const -> bool { return firing_state_ == State::RECOILING_LEFT || firing_state_ == State::RECOILING_UP || firing_state_ == State::RECOILING_RIGHT; } [[nodiscard]] auto isRecoiling() const -> bool { return firing_state_ == State::RECOILING_LEFT || firing_state_ == State::RECOILING_UP || firing_state_ == State::RECOILING_RIGHT; }
@@ -188,7 +175,7 @@ class Player {
// Setters inline // Setters inline
void setController(int index) { controller_index_ = index; } void setController(int index) { controller_index_ = index; }
void startFiringSystem(int cooldown_frames); // Método público para iniciar disparo void setCantFireCounter(int counter) { recoiling_state_duration_ = cant_fire_counter_ = counter; }
void setFiringState(State state) { firing_state_ = state; } void setFiringState(State state) { firing_state_ = state; }
void setInvulnerableCounter(int value) { invulnerable_counter_ = value; } void setInvulnerableCounter(int value) { invulnerable_counter_ = value; }
void setName(const std::string &name) { name_ = name; } void setName(const std::string &name) { name_ = name; }
@@ -206,27 +193,15 @@ class Player {
[[nodiscard]] auto getUsesKeyboard() const -> bool { return uses_keyboard_; } [[nodiscard]] auto getUsesKeyboard() const -> bool { return uses_keyboard_; }
private: private:
// --- Constantes de física y movimiento --- // --- Constantes ---
static constexpr float BASE_SPEED = 1.5F; // Velocidad base del jugador static constexpr int POWERUP_COUNTER = 1500; // Duración del estado PowerUp
static constexpr int INVULNERABLE_COUNTER = 200; // Duración del estado invulnerable
// --- Constantes de power-ups y estados especiales ---
static constexpr int POWERUP_COUNTER = 1500; // Duración del estado PowerUp (frames)
static constexpr int INVULNERABLE_COUNTER = 200; // Duración del estado invulnerable (frames)
static constexpr size_t INVULNERABLE_TEXTURE = 3; // Textura usada durante invulnerabilidad static constexpr size_t INVULNERABLE_TEXTURE = 3; // Textura usada durante invulnerabilidad
static constexpr float BASE_SPEED = 1.5F; // Velocidad base del jugador
// --- Constantes del sistema de disparo (obsoletas - usar nuevo sistema) ---
static constexpr int COOLING_DURATION = 50; // Duración del enfriamiento tras disparar static constexpr int COOLING_DURATION = 50; // Duración del enfriamiento tras disparar
static constexpr int COOLING_COMPLETE = 0; // Valor que indica enfriamiento completado static constexpr int COOLING_COMPLETE = 0; // Valor que indica enfriamiento completado
// --- Constantes de estados de espera ---
static constexpr int WAITING_COUNTER = 1000; // Tiempo de espera en estado de espera static constexpr int WAITING_COUNTER = 1000; // Tiempo de espera en estado de espera
// --- Constantes del nuevo sistema de disparo de dos líneas ---
static constexpr float AIMING_DURATION_FACTOR = 0.5f; // 50% del cooldown funcional
static constexpr float RECOILING_DURATION_MULTIPLIER = 4.0f; // 4 veces la duración de aiming
static constexpr float THREAT_POSE_DURATION = 833.33f; // 50 frames = ~833ms (duración base)
static constexpr float MIN_THREAT_POSE_DURATION = 100.0f; // Duración mínima para threat pose
// --- Objetos y punteros --- // --- Objetos y punteros ---
std::unique_ptr<AnimatedSprite> player_sprite_; // Sprite para dibujar el jugador std::unique_ptr<AnimatedSprite> player_sprite_; // Sprite para dibujar el jugador
std::unique_ptr<AnimatedSprite> power_sprite_; // Sprite para dibujar el aura del jugador con el poder a tope std::unique_ptr<AnimatedSprite> power_sprite_; // Sprite para dibujar el aura del jugador con el poder a tope
@@ -247,6 +222,9 @@ class Player {
State firing_state_ = State::FIRING_NONE; // Estado del jugador al disparar State firing_state_ = State::FIRING_NONE; // Estado del jugador al disparar
State playing_state_ = State::WAITING; // Estado del jugador en el juego State playing_state_ = State::WAITING; // Estado del jugador en el juego
Uint32 continue_ticks_ = 0; // Variable para poder cambiar el contador de continue en función del tiempo
Uint32 name_entry_ticks_ = 0; // Variable para poder cambiar el contador de poner nombre en función del tiempo
Uint32 showing_name_ticks_ = 0; // Tiempo en el que se entra al estado SHOWING_NAME
float pos_x_ = 0.0F; // Posición en el eje X float pos_x_ = 0.0F; // Posición en el eje X
float default_pos_x_; // Posición inicial para el jugador float default_pos_x_; // Posición inicial para el jugador
float vel_x_ = 0.0F; // Cantidad de píxeles a desplazarse en el eje X float vel_x_ = 0.0F; // Cantidad de píxeles a desplazarse en el eje X
@@ -254,36 +232,10 @@ class Player {
int pos_y_ = 0; // Posición en el eje Y int pos_y_ = 0; // Posición en el eje Y
int default_pos_y_; // Posición inicial para el jugador int default_pos_y_; // Posición inicial para el jugador
int vel_y_ = 0; // Cantidad de píxeles a desplazarse en el eje Y int vel_y_ = 0; // Cantidad de píxeles a desplazarse en el eje Y
float invulnerable_time_accumulator_ = 0.0f; // Acumulador de tiempo para invulnerabilidad (time-based) int cant_fire_counter_ = 0; // Contador durante el cual no puede disparar
float power_up_time_accumulator_ = 0.0f; // Acumulador de tiempo para power-up (time-based) int recoiling_state_counter_ = 0; // Contador para la animación del estado de retroceso
float continue_time_accumulator_ = 0.0f; // Acumulador de tiempo para continue counter (time-based) int recoiling_state_duration_ = 0; // Número de frames que dura el estado de retroceso
float name_entry_time_accumulator_ = 0.0f; // Acumulador de tiempo para name entry counter (time-based) int cooling_state_counter_ = 0; // Contador para la animación del estado cooling
float showing_name_time_accumulator_ = 0.0f; // Acumulador de tiempo para showing name (time-based)
float waiting_time_accumulator_ = 0.0f; // Acumulador de tiempo para waiting movement (time-based)
float step_time_accumulator_ = 0.0f; // Acumulador de tiempo para step counter (time-based)
// ========================================
// NUEVO SISTEMA DE DISPARO DE DOS LÍNEAS
// ========================================
// LÍNEA 1: SISTEMA FUNCIONAL (CanFire)
float fire_cooldown_timer_ = 0.0f; // Tiempo restante hasta poder disparar otra vez
bool can_fire_new_system_ = true; // true si puede disparar ahora mismo
// LÍNEA 2: SISTEMA VISUAL (Animaciones)
enum class VisualFireState {
NORMAL, // Brazo en posición neutral
AIMING, // Brazo alzado (disparando)
RECOILING, // Brazo en retroceso
THREAT_POSE // Posición amenazante
};
VisualFireState visual_fire_state_ = VisualFireState::NORMAL;
float visual_state_timer_ = 0.0f; // Tiempo en el estado visual actual
float aiming_duration_ = 0.0f; // Duración del estado AIMING
float recoiling_duration_ = 0.0f; // Duración del estado RECOILING
int invulnerable_counter_ = INVULNERABLE_COUNTER; // Contador para la invulnerabilidad int invulnerable_counter_ = INVULNERABLE_COUNTER; // Contador para la invulnerabilidad
int score_ = 0; // Puntos del jugador int score_ = 0; // Puntos del jugador
int coffees_ = 0; // Indica cuántos cafés lleva acumulados int coffees_ = 0; // Indica cuántos cafés lleva acumulados
@@ -291,8 +243,8 @@ class Player {
int power_up_x_offset_ = 0; // Desplazamiento del sprite de PowerUp respecto al sprite del jugador int power_up_x_offset_ = 0; // Desplazamiento del sprite de PowerUp respecto al sprite del jugador
int continue_counter_ = 10; // Contador para poder continuar int continue_counter_ = 10; // Contador para poder continuar
int controller_index_ = 0; // Índice del array de mandos que utilizará para moverse int controller_index_ = 0; // Índice del array de mandos que utilizará para moverse
float name_entry_idle_time_accumulator_ = 0.0f; // Tiempo idle acumulado para poner nombre (milisegundos) int name_entry_idle_counter_ = 0; // Contador para poner nombre
float name_entry_total_time_accumulator_ = 0.0f; // Tiempo total acumulado poniendo nombre (milisegundos) int name_entry_total_counter_ = 0; // Segundos totales que lleva acumulados poniendo nombre
int step_counter_ = 0; // Cuenta los pasos para los estados en los que camina automáticamente int step_counter_ = 0; // Cuenta los pasos para los estados en los que camina automáticamente
int credits_used_ = 0; // Indica el número de veces que ha continuado int credits_used_ = 0; // Indica el número de veces que ha continuado
int waiting_counter_ = 0; // Contador para el estado de espera int waiting_counter_ = 0; // Contador para el estado de espera
@@ -307,39 +259,23 @@ class Player {
// --- Métodos internos --- // --- Métodos internos ---
void shiftColliders(); // Actualiza el círculo de colisión a la posición del jugador void shiftColliders(); // Actualiza el círculo de colisión a la posición del jugador
void shiftSprite(); // Recoloca el sprite void shiftSprite(); // Recoloca el sprite
void updateInvulnerable(float deltaTime); // Monitoriza el estado de invulnerabilidad (time-based) void updateInvulnerable(); // Monitoriza el estado de invulnerabilidad
void updateContinueCounter(float deltaTime); // Actualiza el contador de continue (time-based) void updateContinueCounter(); // Actualiza el contador de continue
void updateEnterNameCounter(float deltaTime); // Actualiza el contador de entrar nombre (time-based) void updateEnterNameCounter(); // Actualiza el contador de entrar nombre
void updateShowingName(float deltaTime); // Actualiza el estado SHOWING_NAME (time-based) void updateShowingName(); // Actualiza el estado SHOWING_NAME
void decNameEntryCounter(); // Decrementa el contador de entrar nombre void decNameEntryCounter(); // Decrementa el contador de entrar nombre
void updateScoreboard(); // Actualiza el panel del marcador void updateScoreboard(); // Actualiza el panel del marcador
void setScoreboardMode(Scoreboard::Mode mode) const; // Cambia el modo del marcador void setScoreboardMode(Scoreboard::Mode mode) const; // Cambia el modo del marcador
void playSound(const std::string &name) const; // Hace sonar un sonido void playSound(const std::string &name) const; // Hace sonar un sonido
[[nodiscard]] auto isRenderable() const -> bool; // Indica si se puede dibujar el objeto [[nodiscard]] auto isRenderable() const -> bool; // Indica si se puede dibujar el objeto
void addScoreToScoreBoard() const; // Añade una puntuación a la tabla de records void addScoreToScoreBoard() const; // Añade una puntuación a la tabla de records
void handleFiringCooldown(); // Gestiona el tiempo de espera después de disparar antes de permitir otro disparo
// --- Métodos del sistema de disparo de dos líneas --- void handleRecoilAndCooling(); // Procesa simultáneamente el retroceso del arma y la transición al estado de enfriamiento si aplica
void updateFireSystem(float deltaTime); // Método principal del nuevo sistema de disparo void handleCoolingState(); // Actualiza la lógica interna mientras el sistema está en estado de enfriamiento
void updateFunctionalLine(float deltaTime); // Actualiza la línea funcional (CanFire) void transitionToRecoiling(); // Cambia el estado actual al de retroceso después de disparar
void updateVisualLine(float deltaTime); // Actualiza la línea visual (Animaciones) void transitionToCooling(); // Cambia el estado actual al de enfriamiento (por ejemplo, tras una ráfaga o sobrecalentamiento)
void startFiring(int cooldown_frames); // Inicia un nuevo disparo en ambas líneas void completeCooling(); // Finaliza el proceso de enfriamiento y restablece el estado listo para disparar
void updateFiringStateFromVisual(); // Sincroniza firing_state_ con visual_fire_state_ void handlePlayingMovement(); // Gestiona el movimiento del personaje u objeto durante el estado de juego activo
void transitionToRecoilingNew(); // Transición AIMING → RECOILING
void transitionToThreatPose(); // Transición RECOILING → THREAT_POSE
void transitionToNormalNew(); // Transición THREAT_POSE → NORMAL
// --- Métodos del sistema de disparo obsoleto ---
void handleFiringCooldown(); // Gestiona el tiempo de espera después de disparar (frame-based)
void handleFiringCooldown(float deltaTime); // Gestiona el tiempo de espera después de disparar (time-based)
void handleRecoilAndCooling(); // Procesa retroceso y enfriamiento (frame-based)
void handleRecoilAndCooling(float deltaTime); // Procesa retroceso y enfriamiento (time-based)
void handleCoolingState(); // Actualiza estado de enfriamiento (frame-based)
void handleCoolingState(float deltaTime); // Actualiza estado de enfriamiento (time-based)
void transitionToRecoiling(); // Transición a retroceso (sistema obsoleto)
void transitionToCooling(); // Transición a enfriamiento (sistema obsoleto)
void completeCooling(); // Finaliza enfriamiento (sistema obsoleto)
void handlePlayingMovement(); // Gestiona el movimiento del personaje u objeto durante el estado de juego activo (frame-based)
void handlePlayingMovement(float deltaTime); // Gestiona el movimiento del personaje u objeto durante el estado de juego activo (time-based)
void handleRecoverMovement(); // Comprueba si ha acabado la animación void handleRecoverMovement(); // Comprueba si ha acabado la animación
void handleRollingMovement(); // Actualiza la lógica de movimiento de "rodar" (posiblemente tras impacto o acción especial) void handleRollingMovement(); // Actualiza la lógica de movimiento de "rodar" (posiblemente tras impacto o acción especial)
void handleRollingBoundaryCollision(); // Detecta y maneja colisiones del objeto rodante con los límites de la pantalla void handleRollingBoundaryCollision(); // Detecta y maneja colisiones del objeto rodante con los límites de la pantalla
@@ -352,15 +288,12 @@ class Player {
void handleEnteringScreen(); // Lógica para entrar en una nueva pantalla, posiblemente con animación o retraso void handleEnteringScreen(); // Lógica para entrar en una nueva pantalla, posiblemente con animación o retraso
void handlePlayer1Entering(); // Controla la animación o posición de entrada del Jugador 1 en pantalla void handlePlayer1Entering(); // Controla la animación o posición de entrada del Jugador 1 en pantalla
void handlePlayer2Entering(); // Controla la animación o posición de entrada del Jugador 2 en pantalla void handlePlayer2Entering(); // Controla la animación o posición de entrada del Jugador 2 en pantalla
void handleCreditsMovement(); // Movimiento general en la pantalla de créditos (frame-based) void handleCreditsMovement(); // Movimiento general en la pantalla de créditos (desplazamiento vertical u horizontal)
void handleCreditsMovement(float deltaTime); // Movimiento general en la pantalla de créditos (time-based)
void handleCreditsRightMovement(); // Lógica específica para mover los créditos hacia la derecha void handleCreditsRightMovement(); // Lógica específica para mover los créditos hacia la derecha
void handleCreditsLeftMovement(); // Lógica específica para mover los créditos hacia la izquierda void handleCreditsLeftMovement(); // Lógica específica para mover los créditos hacia la izquierda
void handleWaitingMovement(); // Controla la animación del jugador saludando (frame-based) void handleWaitingMovement(); // Controla la animación del jugador saludando
void handleWaitingMovement(float deltaTime); // Controla la animación del jugador saludando (time-based)
void updateWalkingStateForCredits(); // Actualiza el estado de caminata de algún personaje u elemento animado en los créditos void updateWalkingStateForCredits(); // Actualiza el estado de caminata de algún personaje u elemento animado en los créditos
void setInputBasedOnPlayerId(); // Asocia las entradas de control en función del identificador del jugador (teclas, mando, etc.) void setInputBasedOnPlayerId(); // Asocia las entradas de control en función del identificador del jugador (teclas, mando, etc.)
void updateStepCounter(); // Incrementa o ajusta el contador de pasos (frame-based) void updateStepCounter(); // Incrementa o ajusta el contador de pasos para animaciones o mecánicas relacionadas con movimiento
void updateStepCounter(float deltaTime); // Incrementa o ajusta el contador de pasos (time-based)
[[nodiscard]] auto computeAnimation() const -> std::pair<std::string, SDL_FlipMode>; // Calcula la animacion de moverse y disparar del jugador [[nodiscard]] auto computeAnimation() const -> std::pair<std::string, SDL_FlipMode>; // Calcula la animacion de moverse y disparar del jugador
}; };

View File

@@ -44,8 +44,21 @@ Screen::Screen()
initSDLVideo(); initSDLVideo();
// Crea la textura de destino // Crea la textura de destino
#ifdef __APPLE__
const auto render_name = SDL_GetRendererName(renderer_);
if (render_name && !strncmp(render_name, "metal", 5)) {
// Usar nuestra propia Metal texture como render target
game_canvas_ = shader::metal::createMetalRenderTarget(renderer_, param.game.width, param.game.height);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Using custom Metal render target for game_canvas_");
} else {
// Fallback para otros renderers
game_canvas_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height);
SDL_SetTextureScaleMode(game_canvas_, SDL_SCALEMODE_NEAREST);
}
#else
game_canvas_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height); game_canvas_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height);
SDL_SetTextureScaleMode(game_canvas_, SDL_SCALEMODE_NEAREST); SDL_SetTextureScaleMode(game_canvas_, SDL_SCALEMODE_NEAREST);
#endif
// Crea el objeto de texto // Crea el objeto de texto
createText(); createText();
@@ -99,7 +112,18 @@ void Screen::renderPresent() {
clean(); clean();
if (Options::video.shaders) { if (Options::video.shaders) {
#ifdef __APPLE__
const auto render_name = SDL_GetRendererName(renderer_);
if (render_name && !strncmp(render_name, "metal", 5)) {
// Use Metal post-processing with our custom render target
shader::metal::renderWithPostProcessing(renderer_, game_canvas_);
} else {
// Fallback to standard shader system for non-Metal renderers
shader::render();
}
#else
shader::render(); shader::render();
#endif
} else { } else {
SDL_RenderTexture(renderer_, game_canvas_, nullptr, nullptr); SDL_RenderTexture(renderer_, game_canvas_, nullptr, nullptr);
SDL_RenderPresent(renderer_); SDL_RenderPresent(renderer_);
@@ -296,17 +320,29 @@ auto Screen::initSDLVideo() -> bool {
// Obtener información de la pantalla // Obtener información de la pantalla
getDisplayInfo(); getDisplayInfo();
#ifdef __APPLE__
// Configurar hint para Metal
if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal")) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Warning: Failed to set Metal hint!");
}
// Configurar flags para la creación de la ventana
SDL_WindowFlags window_flags = SDL_WINDOW_METAL;
#else // NOT APPLE
// Configurar hint para OpenGL // Configurar hint para OpenGL
if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl")) { if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl")) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Warning: Failed to set OpenGL hint!"); "Warning: Failed to set OpenGL hint!");
} }
// Configurar flags para la creación de la ventana
// Crear ventana
SDL_WindowFlags window_flags = SDL_WINDOW_OPENGL; SDL_WindowFlags window_flags = SDL_WINDOW_OPENGL;
#endif
// Configurar flags para la creación de la ventana
if (Options::video.fullscreen) { if (Options::video.fullscreen) {
window_flags |= SDL_WINDOW_FULLSCREEN; window_flags |= SDL_WINDOW_FULLSCREEN;
} }
// Crear ventana
window_ = SDL_CreateWindow( window_ = SDL_CreateWindow(
Options::window.caption.c_str(), Options::window.caption.c_str(),
param.game.width * Options::window.zoom, param.game.width * Options::window.zoom,

View File

@@ -78,46 +78,36 @@ Credits::~Credits() {
Options::gamepad_manager.clearPlayers(); Options::gamepad_manager.clearPlayers();
} }
// Calcula el deltatime
auto Credits::calculateDeltaTime() -> float {
const Uint64 current_time = SDL_GetTicks();
const float delta_time = static_cast<float>(current_time - last_time_);
last_time_ = current_time;
return delta_time;
}
// Bucle principal // Bucle principal
void Credits::run() { void Credits::run() {
last_time_ = SDL_GetTicks();
while (Section::name == Section::Name::CREDITS) { while (Section::name == Section::Name::CREDITS) {
checkInput(); checkInput();
const float delta_time = calculateDeltaTime(); update();
update(delta_time);
checkEvents(); // Tiene que ir antes del render checkEvents(); // Tiene que ir antes del render
render(); render();
} }
} }
// Actualiza las variables (time-based) // Actualiza las variables
void Credits::update(float deltaTime) { void Credits::update() {
const float multiplier = want_to_pass_ ? 4.0f : 1.0f; if (SDL_GetTicks() - ticks_ > param.game.speed) {
const float adjusted_delta_time = deltaTime * multiplier; ticks_ = SDL_GetTicks();
const int REPEAT = want_to_pass_ ? 4 : 1;
for (int i = 0; i < REPEAT; ++i) {
tiled_bg_->update();
cycleColors();
balloon_manager_->update();
updateTextureDstRects();
throwBalloons();
updatePlayers();
updateAllFades();
++counter_;
}
tiled_bg_->update(adjusted_delta_time); Screen::get()->update();
cycleColors();
balloon_manager_->update(adjusted_delta_time);
updateTextureDstRects(adjusted_delta_time);
throwBalloons(adjusted_delta_time);
updatePlayers(adjusted_delta_time);
updateAllFades(adjusted_delta_time);
// Convertir deltaTime a factor de frame (asumiendo 60fps) fillCanvas();
const float frameFactor = adjusted_delta_time / (1000.0f / 60.0f); }
counter_ += frameFactor;
Screen::get()->update();
fillCanvas();
Audio::update(); Audio::update();
} }
@@ -287,9 +277,9 @@ void Credits::fillCanvas() {
SDL_SetRenderTarget(Screen::get()->getRenderer(), temp); SDL_SetRenderTarget(Screen::get()->getRenderer(), temp);
} }
// Actualiza el destino de los rectangulos de las texturas (frame-based) // Actualiza el destino de los rectangulos de las texturas
void Credits::updateTextureDstRects() { void Credits::updateTextureDstRects() {
if (static_cast<int>(counter_) % 10 == 0) { if (counter_ % 10 == 0) {
// Comprueba la posición de la textura con los titulos de credito // Comprueba la posición de la textura con los titulos de credito
if (credits_rect_dst_.y + credits_rect_dst_.h > play_area_.y) { if (credits_rect_dst_.y + credits_rect_dst_.h > play_area_.y) {
--credits_rect_dst_.y; --credits_rect_dst_.y;
@@ -316,42 +306,7 @@ void Credits::updateTextureDstRects() {
} }
} }
// Actualiza el destino de los rectangulos de las texturas (time-based) // Tira globos al escenario
void Credits::updateTextureDstRects(float deltaTime) {
constexpr float TEXTURE_UPDATE_INTERVAL = 10 * (1000.0f / 60.0f); // 166.67ms (cada 10 frames)
static float texture_accumulator = 0.0f;
texture_accumulator += deltaTime;
if (texture_accumulator >= TEXTURE_UPDATE_INTERVAL) {
texture_accumulator -= TEXTURE_UPDATE_INTERVAL;
// Comprueba la posición de la textura con los titulos de credito
if (credits_rect_dst_.y + credits_rect_dst_.h > play_area_.y) {
--credits_rect_dst_.y;
}
// Comprueba la posición de la textura con el mini_logo
if (mini_logo_rect_dst_.y == mini_logo_final_pos_) {
mini_logo_on_position_ = true;
// Si el jugador quiere pasar los titulos de credito, el fade se inicia solo
if (want_to_pass_) {
fading_ = true;
}
// Se activa el contador para evitar que la sección sea infinita
if (counter_prevent_endless_ == 1000) {
fading_ = true;
} else {
++counter_prevent_endless_;
}
} else {
--mini_logo_rect_dst_.y;
}
}
}
// Tira globos al escenario (frame-based)
void Credits::throwBalloons() { void Credits::throwBalloons() {
constexpr int SPEED = 200; constexpr int SPEED = 200;
const std::vector<int> SETS = {0, 63, 25, 67, 17, 75, 13, 50}; const std::vector<int> SETS = {0, 63, 25, 67, 17, 75, 13, 50};
@@ -360,41 +315,12 @@ void Credits::throwBalloons() {
return; return;
} }
if (static_cast<int>(counter_) % SPEED == 0) { if (counter_ % SPEED == 0) {
const int INDEX = (static_cast<int>(counter_) / SPEED) % SETS.size(); const int INDEX = (counter_ / SPEED) % SETS.size();
balloon_manager_->deployFormation(SETS.at(INDEX), -60); balloon_manager_->deployFormation(SETS.at(INDEX), -60);
} }
if (static_cast<int>(counter_) % (SPEED * 4) == 0 && counter_ > 0) { if (counter_ % (SPEED * 4) == 0 && counter_ > 0) {
balloon_manager_->createPowerBall();
}
}
// Tira globos al escenario (time-based)
void Credits::throwBalloons(float deltaTime) {
constexpr int SPEED = 200;
const std::vector<int> SETS = {0, 63, 25, 67, 17, 75, 13, 50};
constexpr float BALLOON_INTERVAL = SPEED * (1000.0f / 60.0f); // 3333.33ms (cada 200 frames)
constexpr float POWERBALL_INTERVAL = (SPEED * 4) * (1000.0f / 60.0f); // 13333.33ms (cada 800 frames)
if (counter_ > ((SETS.size() - 1) * SPEED) * 3) {
return;
}
static float balloon_accumulator = 0.0f;
static float powerball_accumulator = 0.0f;
balloon_accumulator += deltaTime;
powerball_accumulator += deltaTime;
if (balloon_accumulator >= BALLOON_INTERVAL) {
balloon_accumulator -= BALLOON_INTERVAL;
const int INDEX = (static_cast<int>(counter_ / SPEED)) % SETS.size();
balloon_manager_->deployFormation(SETS.at(INDEX), -60);
}
if (powerball_accumulator >= POWERBALL_INTERVAL && counter_ > 0) {
powerball_accumulator -= POWERBALL_INTERVAL;
balloon_manager_->createPowerBall(); balloon_manager_->createPowerBall();
} }
} }
@@ -466,12 +392,12 @@ void Credits::initPlayers() {
} }
} }
// Actualiza los rectangulos negros (frame-based) // Actualiza los rectangulos negros
void Credits::updateBlackRects() { void Credits::updateBlackRects() {
static int current_step_ = steps_; static int current_step_ = steps_;
if (top_black_rect_.h != param.game.game_area.center_y - 1 && bottom_black_rect_.y != param.game.game_area.center_y + 1) { if (top_black_rect_.h != param.game.game_area.center_y - 1 && bottom_black_rect_.y != param.game.game_area.center_y + 1) {
// Si los rectangulos superior e inferior no han llegado al centro // Si los rectangulos superior e inferior no han llegado al centro
if (static_cast<int>(counter_) % 4 == 0) { if (counter_ % 4 == 0) {
// Incrementa la altura del rectangulo superior // Incrementa la altura del rectangulo superior
top_black_rect_.h = std::min(top_black_rect_.h + 1, param.game.game_area.center_y - 1); top_black_rect_.h = std::min(top_black_rect_.h + 1, param.game.game_area.center_y - 1);
@@ -509,57 +435,6 @@ void Credits::updateBlackRects() {
} }
} }
// Actualiza los rectangulos negros (time-based)
void Credits::updateBlackRects(float deltaTime) {
static float current_step_ = static_cast<float>(steps_);
constexpr float BLACK_RECT_INTERVAL = 4 * (1000.0f / 60.0f); // 66.67ms (cada 4 frames)
static float black_rect_accumulator = 0.0f;
if (top_black_rect_.h != param.game.game_area.center_y - 1 && bottom_black_rect_.y != param.game.game_area.center_y + 1) {
// Si los rectangulos superior e inferior no han llegado al centro
black_rect_accumulator += deltaTime;
if (black_rect_accumulator >= BLACK_RECT_INTERVAL) {
black_rect_accumulator -= BLACK_RECT_INTERVAL;
// Incrementa la altura del rectangulo superior
top_black_rect_.h = std::min(top_black_rect_.h + 1, param.game.game_area.center_y - 1);
// Incrementa la altura y modifica la posición del rectangulo inferior
++bottom_black_rect_.h;
bottom_black_rect_.y = std::max(bottom_black_rect_.y - 1, param.game.game_area.center_y + 1);
--current_step_;
setVolume(static_cast<int>(initial_volume_ * current_step_ / steps_));
}
} else {
// Si los rectangulos superior e inferior han llegado al centro
if (left_black_rect_.w != param.game.game_area.center_x && right_black_rect_.x != param.game.game_area.center_x) {
constexpr int SPEED = 2;
// Si los rectangulos izquierdo y derecho no han llegado al centro
// Incrementa la anchura del rectangulo situado a la izquierda
left_black_rect_.w = std::min(left_black_rect_.w + SPEED, param.game.game_area.center_x);
// Incrementa la anchura y modifica la posición del rectangulo situado a la derecha
right_black_rect_.w += SPEED;
right_black_rect_.x = std::max(right_black_rect_.x - SPEED, param.game.game_area.center_x);
--current_step_;
setVolume(static_cast<int>(initial_volume_ * current_step_ / steps_));
} else {
// Si los rectangulos izquierdo y derecho han llegado al centro
setVolume(0);
Audio::get()->stopMusic();
if (counter_pre_fade_ == 400) {
fade_out_->activate();
} else {
// Convertir deltaTime a factor de frame
const float frameFactor = deltaTime / (1000.0f / 60.0f);
counter_pre_fade_ += frameFactor;
}
}
}
}
// Actualiza el rectangulo rojo // Actualiza el rectangulo rojo
void Credits::updateRedRect() { void Credits::updateRedRect() {
border_rect_.x = left_black_rect_.x + left_black_rect_.w; border_rect_.x = left_black_rect_.x + left_black_rect_.w;
@@ -568,7 +443,7 @@ void Credits::updateRedRect() {
border_rect_.h = bottom_black_rect_.y - border_rect_.y + 1; border_rect_.h = bottom_black_rect_.y - border_rect_.y + 1;
} }
// Actualiza el estado de fade (frame-based) // Actualiza el estado de fade
void Credits::updateAllFades() { void Credits::updateAllFades() {
if (fading_) { if (fading_) {
updateBlackRects(); updateBlackRects();
@@ -586,24 +461,6 @@ void Credits::updateAllFades() {
} }
} }
// Actualiza el estado de fade (time-based)
void Credits::updateAllFades(float deltaTime) {
if (fading_) {
updateBlackRects(deltaTime);
updateRedRect();
}
fade_in_->update(); // Fade ya usa tiempo interno
if (fade_in_->hasEnded()) {
Audio::get()->playMusic("credits.ogg");
}
fade_out_->update(); // Fade ya usa tiempo interno
if (fade_out_->hasEnded()) {
Section::name = Section::Name::HI_SCORE_TABLE;
}
}
// Establece el nivel de volumen // Establece el nivel de volumen
void Credits::setVolume(int amount) { void Credits::setVolume(int amount) {
Options::audio.music.volume = std::clamp(amount, 0, 100); Options::audio.music.volume = std::clamp(amount, 0, 100);
@@ -651,10 +508,10 @@ void Credits::cycleColors() {
tiled_bg_->setColor(color_); tiled_bg_->setColor(color_);
} }
// Actualza los jugadores (time-based) // Actualza los jugadores
void Credits::updatePlayers(float deltaTime) { void Credits::updatePlayers() {
for (auto &player : players_) { for (auto &player : players_) {
player->update(deltaTime); player->update();
} }
} }

View File

@@ -25,11 +25,6 @@ class Credits {
// --- Bucle principal --- // --- Bucle principal ---
void run(); void run();
private:
// --- Métodos del bucle principal ---
void update(float deltaTime); // Actualización principal de la lógica (time-based)
auto calculateDeltaTime() -> float; // Calcula el deltatime
private: private:
// --- Constantes de clase --- // --- Constantes de clase ---
static constexpr int PLAY_AREA_HEIGHT = 200; static constexpr int PLAY_AREA_HEIGHT = 200;
@@ -46,10 +41,10 @@ class Credits {
SDL_Texture *canvas_; // Textura donde se dibuja todo SDL_Texture *canvas_; // Textura donde se dibuja todo
// --- Temporización y contadores --- // --- Temporización y contadores ---
Uint64 last_time_ = 0; // Último tiempo registrado para deltaTime Uint64 ticks_ = 0; // Control de velocidad del programa
float counter_ = 0; // Contador principal de lógica Uint32 counter_ = 0; // Contador principal de lógica
float counter_pre_fade_ = 0; // Activación del fundido final Uint32 counter_pre_fade_ = 0; // Activación del fundido final
float counter_prevent_endless_ = 0; // Prevención de bucle infinito Uint32 counter_prevent_endless_ = 0; // Prevención de bucle infinito
// --- Variables de estado --- // --- Variables de estado ---
bool fading_ = false; // Estado del fade final bool fading_ = false; // Estado del fade final
@@ -106,6 +101,8 @@ class Credits {
// Borde para la ventana // Borde para la ventana
SDL_FRect border_rect_ = play_area_; // Delimitador de ventana SDL_FRect border_rect_ = play_area_; // Delimitador de ventana
// --- Métodos del bucle principal ---
void update(); // Actualización principal de la lógica
void render(); // Renderizado de la escena void render(); // Renderizado de la escena
static void checkEvents(); // Manejo de eventos static void checkEvents(); // Manejo de eventos
void checkInput(); // Procesamiento de entrada void checkInput(); // Procesamiento de entrada
@@ -113,23 +110,19 @@ class Credits {
// --- Métodos de renderizado --- // --- Métodos de renderizado ---
void fillTextTexture(); // Crear textura de texto de créditos void fillTextTexture(); // Crear textura de texto de créditos
void fillCanvas(); // Renderizar todos los sprites y fondos void fillCanvas(); // Renderizar todos los sprites y fondos
void updateTextureDstRects(); // Actualizar destinos de texturas
void renderPlayers(); // Renderiza los jugadores void renderPlayers(); // Renderiza los jugadores
// --- Métodos de lógica del juego --- // --- Métodos de lógica del juego ---
void throwBalloons(); // Lanzar globos al escenario (frame-based) void throwBalloons(); // Lanzar globos al escenario
void throwBalloons(float deltaTime); // Lanzar globos al escenario (time-based) void initPlayers(); // Inicializar jugadores
void initPlayers(); // Inicializar jugadores void updateAllFades(); // Actualizar estados de fade
void updateAllFades(); // Actualizar estados de fade (frame-based) void cycleColors(); // Cambiar colores de fondo
void updateAllFades(float deltaTime); // Actualizar estados de fade (time-based) void updatePlayers(); // Actualza los jugadores
void cycleColors(); // Cambiar colores de fondo
void updatePlayers(float deltaTime); // Actualza los jugadores (time-based)
// --- Métodos de interfaz --- // --- Métodos de interfaz ---
void updateBlackRects(); // Actualizar rectángulos negros (letterbox) (frame-based) void updateBlackRects(); // Actualizar rectángulos negros (letterbox)
void updateBlackRects(float deltaTime); // Actualizar rectángulos negros (letterbox) (time-based) void updateRedRect(); // Actualizar rectángulo rojo (borde)
void updateRedRect(); // Actualizar rectángulo rojo (borde)
void updateTextureDstRects(); // Actualizar destinos de texturas (frame-based)
void updateTextureDstRects(float deltaTime); // Actualizar destinos de texturas (time-based)
// --- Métodos de audio --- // --- Métodos de audio ---
static void setVolume(int amount); // Establecer volumen static void setVolume(int amount); // Establecer volumen

View File

@@ -79,7 +79,7 @@ Game::Game(Player::Id player_id, int current_stage, bool demo)
scoreboard_ = Scoreboard::get(); scoreboard_ = Scoreboard::get();
fade_in_->setColor(param.fade.color); fade_in_->setColor(param.fade.color);
fade_in_->setPreDuration(demo_.enabled ? DEMO_FADE_PRE_DURATION_MS : 0); fade_in_->setPreDuration(demo_.enabled ? 500 : 0);
fade_in_->setPostDuration(0); fade_in_->setPostDuration(0);
fade_in_->setType(Fade::Type::RANDOM_SQUARE2); fade_in_->setType(Fade::Type::RANDOM_SQUARE2);
fade_in_->setMode(Fade::Mode::IN); fade_in_->setMode(Fade::Mode::IN);
@@ -211,9 +211,9 @@ void Game::updateHiScore() {
} }
// Actualiza las variables del jugador // Actualiza las variables del jugador
void Game::updatePlayers(float deltaTime) { void Game::updatePlayers() {
for (auto &player : players_) { for (auto &player : players_) {
player->update(deltaTime); player->update();
if (player->isPlaying()) { if (player->isPlaying()) {
// Comprueba la colisión entre el jugador y los globos // Comprueba la colisión entre el jugador y los globos
@@ -222,7 +222,7 @@ void Game::updatePlayers(float deltaTime) {
// Si hay colisión // Si hay colisión
if (balloon) { if (balloon) {
// Si el globo está parado y el temporizador activo, lo explota // Si el globo está parado y el temporizador activo, lo explota
if (balloon->isStopped() && time_stopped_timer_ > 0) { if (balloon->isStopped() && time_stopped_counter_ > 0) {
balloon_manager_->popBalloon(balloon); balloon_manager_->popBalloon(balloon);
} }
// En caso contrario, el jugador ha sido golpeado por un globo activo // En caso contrario, el jugador ha sido golpeado por un globo activo
@@ -312,28 +312,31 @@ void Game::updateStage() {
} }
// Actualiza el estado de fin de la partida // Actualiza el estado de fin de la partida
void Game::updateGameStateGameOver(float deltaTime) { void Game::updateGameStateGameOver() {
fade_out_->update(); fade_out_->update();
updatePlayers(deltaTime); updatePlayers();
updateScoreboard(); updateScoreboard();
updateBackground(deltaTime); updateBackground();
balloon_manager_->update(deltaTime); balloon_manager_->update();
tabe_->update(deltaTime); tabe_->update();
updateBullets(deltaTime); updateBullets();
updateItems(deltaTime); updateItems();
updateSmartSprites(deltaTime); updateSmartSprites();
updatePathSprites(deltaTime); updatePathSprites();
updateTimeStopped(deltaTime); updateTimeStopped();
checkBulletCollision(); checkBulletCollision();
cleanVectors(); cleanVectors();
if (game_over_timer_ < GAME_OVER_DURATION_MS) { if (game_over_counter_ > 0) {
handleGameOverEvents(); // Maneja eventos al inicio if (game_over_counter_ == GAME_OVER_COUNTER) {
createMessage({paths_.at(2), paths_.at(3)}, Resource::get()->getTexture("game_text_game_over"));
Audio::get()->fadeOutMusic(1000);
balloon_manager_->setBouncingSounds(true);
}
game_over_timer_ += deltaTime; // Incremento time-based game_over_counter_--;
constexpr float FADE_TRIGGER_MS = GAME_OVER_DURATION_MS - (150.0f * (1000.0f / 60.0f)); // 2500ms antes del final if (game_over_counter_ == 150) {
if (game_over_timer_ >= FADE_TRIGGER_MS && !fade_out_->isEnabled()) {
fade_out_->activate(); fade_out_->activate();
} }
} }
@@ -346,7 +349,7 @@ void Game::updateGameStateGameOver(float deltaTime) {
} }
if (fade_out_->hasEnded()) { if (fade_out_->hasEnded()) {
if (game_completed_timer_ > 0) { if (game_completed_counter_ > 0) {
Section::name = Section::Name::CREDITS; // Los jugadores han completado el juego Section::name = Section::Name::CREDITS; // Los jugadores han completado el juego
} else { } else {
Section::name = Section::Name::HI_SCORE_TABLE; // La partida ha terminado con la derrota de los jugadores Section::name = Section::Name::HI_SCORE_TABLE; // La partida ha terminado con la derrota de los jugadores
@@ -360,28 +363,55 @@ void Game::updateGameStateGameOver(float deltaTime) {
} }
// Gestiona eventos para el estado del final del juego // Gestiona eventos para el estado del final del juego
void Game::updateGameStateCompleted(float deltaTime) { void Game::updateGameStateCompleted() {
updatePlayers(deltaTime); constexpr int START_CELEBRATIONS = 400;
constexpr int END_CELEBRATIONS = START_CELEBRATIONS + 300;
updatePlayers();
updateScoreboard(); updateScoreboard();
updateBackground(deltaTime); updateBackground();
balloon_manager_->update(deltaTime); balloon_manager_->update();
tabe_->update(deltaTime); tabe_->update();
updateBullets(deltaTime); updateBullets();
updateItems(deltaTime); updateItems();
updateSmartSprites(deltaTime); updateSmartSprites();
updatePathSprites(deltaTime); updatePathSprites();
cleanVectors(); cleanVectors();
// Maneja eventos del juego completado // Comienza las celebraciones
handleGameCompletedEvents(); // Muestra el mensaje de felicitación y da los puntos a los jugadores
if (game_completed_counter_ == START_CELEBRATIONS) {
createMessage({paths_.at(4), paths_.at(5)}, Resource::get()->getTexture("game_text_congratulations"));
createMessage({paths_.at(6), paths_.at(7)}, Resource::get()->getTexture("game_text_1000000_points"));
for (auto &player : players_) {
if (player->isPlaying()) {
player->addScore(1000000, Options::settings.hi_score_table.back().score);
player->setPlayingState(Player::State::CELEBRATING);
} else {
player->setPlayingState(Player::State::GAME_OVER);
}
}
updateHiScore();
}
// Termina las celebraciones
if (game_completed_counter_ == END_CELEBRATIONS) {
for (auto &player : players_) {
if (player->isCelebrating()) {
player->setPlayingState(player->qualifiesForHighScore() ? Player::State::ENTERING_NAME_GAME_COMPLETED : Player::State::LEAVING_SCREEN);
}
}
}
// Si los jugadores ya no estan y no quedan mensajes en pantalla // Si los jugadores ya no estan y no quedan mensajes en pantalla
if (allPlayersAreGameOver() && path_sprites_.empty()) { if (allPlayersAreGameOver() && path_sprites_.empty()) {
setState(State::GAME_OVER); setState(State::GAME_OVER);
} }
// Incrementa el acumulador al final // Incrementa el contador al final
game_completed_timer_ += deltaTime; ++game_completed_counter_;
} }
// Comprueba el estado del juego // Comprueba el estado del juego
@@ -597,9 +627,9 @@ void Game::handleBalloonDestruction(std::shared_ptr<Balloon> balloon, const std:
} }
// Mueve las balas activas // Mueve las balas activas
void Game::updateBullets(float deltaTime) { void Game::updateBullets() {
for (auto &bullet : bullets_) { for (auto &bullet : bullets_) {
if (bullet->update(deltaTime) == BulletMoveStatus::OUT) { if (bullet->update() == BulletMoveStatus::OUT) {
getPlayer(bullet->getOwner())->decScoreMultiplier(); getPlayer(bullet->getOwner())->decScoreMultiplier();
} }
} }
@@ -629,10 +659,10 @@ void Game::freeBullets() {
} }
// Actualiza los items // Actualiza los items
void Game::updateItems(float deltaTime) { void Game::updateItems() {
for (auto &item : items_) { for (auto &item : items_) {
if (item->isEnabled()) { if (item->isEnabled()) {
item->update(deltaTime); item->update();
if (item->isOnFloor()) { if (item->isOnFloor()) {
playSound("title.wav"); playSound("title.wav");
screen_->shake(1, 2, 4); screen_->shake(1, 2, 4);
@@ -790,7 +820,7 @@ void Game::throwCoffee(int x, int y) {
smart_sprites_.back()->setDestX(x + (smart_sprites_.back()->getVelX() * 50)); smart_sprites_.back()->setDestX(x + (smart_sprites_.back()->getVelX() * 50));
smart_sprites_.back()->setDestY(param.game.height + 1); smart_sprites_.back()->setDestY(param.game.height + 1);
smart_sprites_.back()->setEnabled(true); smart_sprites_.back()->setEnabled(true);
smart_sprites_.back()->setFinishedDelay(0.0F); smart_sprites_.back()->setFinishedCounter(1);
smart_sprites_.back()->setSpriteClip(0, param.game.item_size, param.game.item_size, param.game.item_size); smart_sprites_.back()->setSpriteClip(0, param.game.item_size, param.game.item_size, param.game.item_size);
smart_sprites_.back()->setRotatingCenter({param.game.item_size / 2, param.game.item_size / 2}); smart_sprites_.back()->setRotatingCenter({param.game.item_size / 2, param.game.item_size / 2});
smart_sprites_.back()->setRotate(true); smart_sprites_.back()->setRotate(true);
@@ -799,9 +829,9 @@ void Game::throwCoffee(int x, int y) {
} }
// Actualiza los SmartSprites // Actualiza los SmartSprites
void Game::updateSmartSprites(float deltaTime) { void Game::updateSmartSprites() {
for (auto &sprite : smart_sprites_) { for (auto &sprite : smart_sprites_) {
sprite->update(deltaTime); sprite->update();
} }
} }
@@ -813,9 +843,9 @@ void Game::renderSmartSprites() {
} }
// Actualiza los PathSprites // Actualiza los PathSprites
void Game::updatePathSprites(float deltaTime) { void Game::updatePathSprites() {
for (auto &sprite : path_sprites_) { for (auto &sprite : path_sprites_) {
sprite->update(deltaTime); sprite->update();
} }
} }
@@ -857,35 +887,21 @@ void Game::handlePlayerCollision(std::shared_ptr<Player> &player, std::shared_pt
} }
} }
// Actualiza el estado del tiempo detenido // Actualiza y comprueba el valor de la variable
void Game::updateTimeStopped(float deltaTime) { void Game::updateTimeStopped() {
static constexpr float WARNING_THRESHOLD_MS = 2000.0f; // 120 frames a 60fps if (time_stopped_counter_ > 0) {
static constexpr float CLOCK_SOUND_INTERVAL_MS = 500.0f; // 30 frames a 60fps time_stopped_counter_--;
static constexpr float COLOR_FLASH_INTERVAL_MS = 250.0f; // 15 frames a 60fps if (time_stopped_counter_ > 120) {
if (time_stopped_counter_ % 30 == 0) {
if (time_stopped_timer_ > 0) {
time_stopped_timer_ -= deltaTime;
// Fase de advertencia (últimos 2 segundos)
if (time_stopped_timer_ <= WARNING_THRESHOLD_MS) {
static float last_sound_time = 0.0f;
last_sound_time += deltaTime;
if (last_sound_time >= CLOCK_SOUND_INTERVAL_MS) {
balloon_manager_->normalColorsToAllBalloons();
playSound("clock.wav");
last_sound_time = 0.0f;
} else if (last_sound_time >= COLOR_FLASH_INTERVAL_MS) {
balloon_manager_->reverseColorsToAllBalloons();
playSound("clock.wav"); playSound("clock.wav");
} }
} else { } else {
// Fase normal - solo sonido ocasional if (time_stopped_counter_ % 30 == 0) {
static float sound_timer = 0.0f; balloon_manager_->normalColorsToAllBalloons();
sound_timer += deltaTime; playSound("clock.wav");
if (sound_timer >= CLOCK_SOUND_INTERVAL_MS) { } else if (time_stopped_counter_ % 30 == 15) {
balloon_manager_->reverseColorsToAllBalloons();
playSound("clock.wav"); playSound("clock.wav");
sound_timer = 0.0f;
} }
} }
} else { } else {
@@ -893,16 +909,18 @@ void Game::updateTimeStopped(float deltaTime) {
} }
} }
// Actualiza toda la lógica del juego void Game::update() {
void Game::update(float deltaTime) { if (SDL_GetTicks() - ticks_ > param.game.speed) {
screen_->update(); ticks_ = SDL_GetTicks();
screen_->update();
updateDemo(); updateDemo();
#ifdef RECORDING #ifdef RECORDING
updateRecording(); updateRecording();
#endif #endif
updateGameStates(deltaTime); updateGameStates();
fillCanvas(); fillCanvas();
}
Audio::update(); Audio::update();
} }
@@ -920,26 +938,26 @@ void Game::render() {
} }
// Actualiza los estados del juego // Actualiza los estados del juego
void Game::updateGameStates(float deltaTime) { void Game::updateGameStates() {
if (!pause_manager_->isPaused()) { if (!pause_manager_->isPaused()) {
switch (state_) { switch (state_) {
case State::FADE_IN: case State::FADE_IN:
updateGameStateFadeIn(deltaTime); updateGameStateFadeIn();
break; break;
case State::ENTERING_PLAYER: case State::ENTERING_PLAYER:
updateGameStateEnteringPlayer(deltaTime); updateGameStateEnteringPlayer();
break; break;
case State::SHOWING_GET_READY_MESSAGE: case State::SHOWING_GET_READY_MESSAGE:
updateGameStateShowingGetReadyMessage(deltaTime); updateGameStateShowingGetReadyMessage();
break; break;
case State::PLAYING: case State::PLAYING:
updateGameStatePlaying(deltaTime); updateGameStatePlaying();
break; break;
case State::COMPLETED: case State::COMPLETED:
updateGameStateCompleted(deltaTime); updateGameStateCompleted();
break; break;
case State::GAME_OVER: case State::GAME_OVER:
updateGameStateGameOver(deltaTime); updateGameStateGameOver();
break; break;
default: default:
break; break;
@@ -948,8 +966,8 @@ void Game::updateGameStates(float deltaTime) {
} }
// Actualiza el fondo // Actualiza el fondo
void Game::updateBackground(float deltaTime) { void Game::updateBackground() {
background_->update(deltaTime); background_->update();
} }
// Dibuja los elementos de la zona de juego en su textura // Dibuja los elementos de la zona de juego en su textura
@@ -978,34 +996,23 @@ void Game::fillCanvas() {
void Game::enableTimeStopItem() { void Game::enableTimeStopItem() {
balloon_manager_->stopAllBalloons(); balloon_manager_->stopAllBalloons();
balloon_manager_->reverseColorsToAllBalloons(); balloon_manager_->reverseColorsToAllBalloons();
time_stopped_timer_ = TIME_STOPPED_DURATION_MS; time_stopped_counter_ = TIME_STOPPED_COUNTER;
} }
// Deshabilita el efecto del item de detener el tiempo // Deshabilita el efecto del item de detener el tiempo
void Game::disableTimeStopItem() { void Game::disableTimeStopItem() {
time_stopped_timer_ = 0; time_stopped_counter_ = 0;
balloon_manager_->startAllBalloons(); balloon_manager_->startAllBalloons();
balloon_manager_->normalColorsToAllBalloons(); balloon_manager_->normalColorsToAllBalloons();
} }
// Calcula el deltatime
auto Game::calculateDeltaTime() -> float {
const Uint64 current_time = SDL_GetTicks();
const float delta_time = static_cast<float>(current_time - last_time_);
last_time_ = current_time;
return delta_time;
}
// Bucle para el juego // Bucle para el juego
void Game::run() { void Game::run() {
last_time_ = SDL_GetTicks();
while (Section::name == Section::Name::GAME) { while (Section::name == Section::Name::GAME) {
#ifndef RECORDING #ifndef RECORDING
checkInput(); checkInput();
#endif #endif
const float delta_time = calculateDeltaTime(); update();
update(delta_time);
handleEvents(); // Tiene que ir antes del render handleEvents(); // Tiene que ir antes del render
render(); render();
} }
@@ -1322,7 +1329,7 @@ void Game::handleFireInput(const std::shared_ptr<Player> &player, BulletType bul
cant_fire_counter = NORMAL_COOLDOWN; cant_fire_counter = NORMAL_COOLDOWN;
} }
player->startFiringSystem(cant_fire_counter); // Sistema de disparo de dos líneas player->setCantFireCounter(cant_fire_counter);
} }
} }
@@ -1546,21 +1553,21 @@ void Game::initDifficultyVars() {
// Variables relacionadas con la dificultad // Variables relacionadas con la dificultad
switch (difficulty_) { switch (difficulty_) {
case Difficulty::Code::EASY: { case Difficulty::Code::EASY: {
balloon_manager_->setDefaultBalloonSpeed(Balloon::GAME_TEMPO.at(0)); balloon_manager_->setDefaultBalloonSpeed(Balloon::SPEED.at(0));
difficulty_score_multiplier_ = 0.5F; difficulty_score_multiplier_ = 0.5F;
scoreboard_->setColor(param.scoreboard.easy_color); scoreboard_->setColor(param.scoreboard.easy_color);
break; break;
} }
case Difficulty::Code::NORMAL: { case Difficulty::Code::NORMAL: {
balloon_manager_->setDefaultBalloonSpeed(Balloon::GAME_TEMPO.at(0)); balloon_manager_->setDefaultBalloonSpeed(Balloon::SPEED.at(0));
difficulty_score_multiplier_ = 1.0F; difficulty_score_multiplier_ = 1.0F;
scoreboard_->setColor(param.scoreboard.normal_color); scoreboard_->setColor(param.scoreboard.normal_color);
break; break;
} }
case Difficulty::Code::HARD: { case Difficulty::Code::HARD: {
balloon_manager_->setDefaultBalloonSpeed(Balloon::GAME_TEMPO.at(4)); balloon_manager_->setDefaultBalloonSpeed(Balloon::SPEED.at(4));
difficulty_score_multiplier_ = 1.5F; difficulty_score_multiplier_ = 1.5F;
scoreboard_->setColor(param.scoreboard.hard_color); scoreboard_->setColor(param.scoreboard.hard_color);
break; break;
@@ -1699,10 +1706,10 @@ void Game::updateRecording() {
#endif #endif
// Actualiza las variables durante dicho estado // Actualiza las variables durante dicho estado
void Game::updateGameStateFadeIn(float deltaTime) { void Game::updateGameStateFadeIn() {
fade_in_->update(); fade_in_->update();
updateScoreboard(); updateScoreboard();
updateBackground(deltaTime); updateBackground();
if (fade_in_->hasEnded()) { if (fade_in_->hasEnded()) {
setState(State::ENTERING_PLAYER); setState(State::ENTERING_PLAYER);
balloon_manager_->createTwoBigBalloons(); balloon_manager_->createTwoBigBalloons();
@@ -1711,11 +1718,11 @@ void Game::updateGameStateFadeIn(float deltaTime) {
} }
// Actualiza las variables durante dicho estado // Actualiza las variables durante dicho estado
void Game::updateGameStateEnteringPlayer(float deltaTime) { void Game::updateGameStateEnteringPlayer() {
balloon_manager_->update(deltaTime); balloon_manager_->update();
updatePlayers(deltaTime); updatePlayers();
updateScoreboard(); updateScoreboard();
updateBackground(deltaTime); updateBackground();
for (const auto &player : players_) { for (const auto &player : players_) {
if (player->isPlaying()) { if (player->isPlaying()) {
setState(State::SHOWING_GET_READY_MESSAGE); setState(State::SHOWING_GET_READY_MESSAGE);
@@ -1726,8 +1733,8 @@ void Game::updateGameStateEnteringPlayer(float deltaTime) {
} }
// Actualiza las variables durante dicho estado // Actualiza las variables durante dicho estado
void Game::updateGameStateShowingGetReadyMessage(float deltaTime) { void Game::updateGameStateShowingGetReadyMessage() {
updateGameStatePlaying(deltaTime); updateGameStatePlaying();
if (path_sprites_.empty()) { if (path_sprites_.empty()) {
setState(State::PLAYING); setState(State::PLAYING);
} }
@@ -1738,24 +1745,24 @@ void Game::updateGameStateShowingGetReadyMessage(float deltaTime) {
} }
// Actualiza las variables durante el transcurso normal del juego // Actualiza las variables durante el transcurso normal del juego
void Game::updateGameStatePlaying(float deltaTime) { void Game::updateGameStatePlaying() {
#ifdef _DEBUG #ifdef _DEBUG
if (auto_pop_balloons_) { if (auto_pop_balloons_) {
stage_manager_->addPower(1); stage_manager_->addPower(5);
} }
#endif #endif
updatePlayers(deltaTime); updatePlayers();
checkPlayersStatusPlaying(); checkPlayersStatusPlaying();
updateScoreboard(); updateScoreboard();
updateBackground(deltaTime); updateBackground();
balloon_manager_->update(deltaTime); balloon_manager_->update();
tabe_->update(deltaTime); tabe_->update();
updateBullets(deltaTime); updateBullets();
updateItems(deltaTime); updateItems();
updateStage(); updateStage();
updateSmartSprites(deltaTime); updateSmartSprites();
updatePathSprites(deltaTime); updatePathSprites();
updateTimeStopped(deltaTime); updateTimeStopped();
updateHelper(); updateHelper();
checkBulletCollision(); checkBulletCollision();
updateMenace(); updateMenace();
@@ -1813,9 +1820,9 @@ void Game::checkAndUpdateBalloonSpeed() {
for (size_t i = 0; i < std::size(THRESHOLDS); ++i) { for (size_t i = 0; i < std::size(THRESHOLDS); ++i) {
// Si la velocidad actual del globo es la correspondiente al umbral "i" y el porcentaje de progreso ha superado ese umbral // Si la velocidad actual del globo es la correspondiente al umbral "i" y el porcentaje de progreso ha superado ese umbral
if (balloon_manager_->getBalloonSpeed() == Balloon::GAME_TEMPO.at(i) && PERCENT > THRESHOLDS.at(i)) { if (balloon_manager_->getBalloonSpeed() == Balloon::SPEED.at(i) && PERCENT > THRESHOLDS.at(i)) {
// Sube la velocidad al siguiente nivel (i + 1) // Sube la velocidad al siguiente nivel (i + 1)
balloon_manager_->setBalloonSpeed(Balloon::GAME_TEMPO.at(i + 1)); balloon_manager_->setBalloonSpeed(Balloon::SPEED.at(i + 1));
return; return;
} }
} }
@@ -1893,55 +1900,6 @@ void Game::onPauseStateChanged(bool is_paused) {
tabe_->pauseTimer(is_paused); tabe_->pauseTimer(is_paused);
} }
// Maneja eventos del juego completado usando flags para triggers únicos
void Game::handleGameCompletedEvents() {
constexpr float START_CELEBRATIONS_MS = 6667.0f; // 400 frames a 60fps
constexpr float END_CELEBRATIONS_MS = 11667.0f; // 700 frames a 60fps
// Inicio de celebraciones
static bool start_celebrations_triggered = false;
if (!start_celebrations_triggered && game_completed_timer_ >= START_CELEBRATIONS_MS) {
createMessage({paths_.at(4), paths_.at(5)}, Resource::get()->getTexture("game_text_congratulations"));
createMessage({paths_.at(6), paths_.at(7)}, Resource::get()->getTexture("game_text_1000000_points"));
for (auto &player : players_) {
if (player->isPlaying()) {
player->addScore(1000000, Options::settings.hi_score_table.back().score);
player->setPlayingState(Player::State::CELEBRATING);
} else {
player->setPlayingState(Player::State::GAME_OVER);
}
}
updateHiScore();
start_celebrations_triggered = true;
}
// Fin de celebraciones
static bool end_celebrations_triggered = false;
if (!end_celebrations_triggered && game_completed_timer_ >= END_CELEBRATIONS_MS) {
for (auto &player : players_) {
if (player->isCelebrating()) {
player->setPlayingState(player->qualifiesForHighScore() ? Player::State::ENTERING_NAME_GAME_COMPLETED : Player::State::LEAVING_SCREEN);
}
}
fade_out_->activate();
end_celebrations_triggered = true;
}
}
// Maneja eventos de game over usando flag para trigger único
void Game::handleGameOverEvents() {
static bool game_over_triggered = false;
if (!game_over_triggered && game_over_timer_ == 0.0f) {
createMessage({paths_.at(2), paths_.at(3)}, Resource::get()->getTexture("game_text_game_over"));
Audio::get()->fadeOutMusic(1000);
balloon_manager_->setBouncingSounds(true);
game_over_triggered = true;
}
}
#ifdef _DEBUG #ifdef _DEBUG
// Comprueba los eventos en el modo DEBUG // Comprueba los eventos en el modo DEBUG
void Game::handleDebugEvents(const SDL_Event &event) { void Game::handleDebugEvents(const SDL_Event &event) {

View File

@@ -33,22 +33,7 @@ namespace Difficulty {
enum class Code; enum class Code;
} // namespace Difficulty } // namespace Difficulty
// --- Clase Game: núcleo principal del gameplay --- // --- Clase Game: gestor principal del juego ---
//
// Esta clase gestiona toda la lógica del juego durante las partidas activas,
// incluyendo mecánicas de juego, estados, objetos y sistemas de puntuación.
//
// Funcionalidades principales:
// • Gestión de jugadores: soporte para 1 o 2 jugadores simultáneos
// • Sistema de estados: fade-in, entrada, jugando, completado, game-over
// • Mecánicas de juego: globos, balas, ítems, power-ups y efectos especiales
// • Sistema de puntuación: scoreboard y tabla de récords
// • Efectos temporales: tiempo detenido, ayudas automáticas
// • Modo demo: reproducción automática para attract mode
// • Gestión de fases: progresión entre niveles y dificultad
//
// Utiliza un sistema de tiempo basado en milisegundos para garantizar
// comportamiento consistente independientemente del framerate.
class Game { class Game {
public: public:
// --- Constantes --- // --- Constantes ---
@@ -73,13 +58,12 @@ class Game {
GAME_OVER, // Fin del juego GAME_OVER, // Fin del juego
}; };
// --- Constantes de tiempo (en milisegundos) --- // --- Constantes internas ---
static constexpr float HELP_COUNTER_MS = 16667.0f; // Contador de ayuda (1000 frames a 60fps) static constexpr int HELP_COUNTER = 1000;
static constexpr float GAME_COMPLETED_START_FADE_MS = 8333.0f; // Inicio del fade al completar (500 frames) static constexpr int GAME_COMPLETED_START_FADE = 500;
static constexpr float GAME_COMPLETED_END_MS = 11667.0f; // Fin del juego completado (700 frames) static constexpr int GAME_COMPLETED_END = 700;
static constexpr float GAME_OVER_DURATION_MS = 5833.0f; // Duración game over (350 frames) static constexpr int GAME_OVER_COUNTER = 350;
static constexpr float TIME_STOPPED_DURATION_MS = 6000.0f; // Duración del tiempo detenido (360 frames) static constexpr int TIME_STOPPED_COUNTER = 360;
static constexpr int DEMO_FADE_PRE_DURATION_MS = 500; // Pre-duración del fade en modo demo
static constexpr int ITEM_POINTS_1_DISK_ODDS = 10; static constexpr int ITEM_POINTS_1_DISK_ODDS = 10;
static constexpr int ITEM_POINTS_2_GAVINA_ODDS = 6; static constexpr int ITEM_POINTS_2_GAVINA_ODDS = 6;
static constexpr int ITEM_POINTS_3_PACMAR_ODDS = 3; static constexpr int ITEM_POINTS_3_PACMAR_ODDS = 3;
@@ -93,7 +77,7 @@ class Game {
bool need_coffee{false}; // Indica si se necesitan cafes bool need_coffee{false}; // Indica si se necesitan cafes
bool need_coffee_machine{false}; // Indica si se necesita PowerUp bool need_coffee_machine{false}; // Indica si se necesita PowerUp
bool need_power_ball{false}; // Indica si se necesita una PowerBall bool need_power_ball{false}; // Indica si se necesita una PowerBall
float counter; // Contador para no dar ayudas consecutivas int counter; // Contador para no dar ayudas consecutivas
int item_disk_odds; // Probabilidad de aparición del objeto int item_disk_odds; // Probabilidad de aparición del objeto
int item_gavina_odds; // Probabilidad de aparición del objeto int item_gavina_odds; // Probabilidad de aparición del objeto
int item_pacmar_odds; // Probabilidad de aparición del objeto int item_pacmar_odds; // Probabilidad de aparición del objeto
@@ -102,7 +86,7 @@ class Game {
int item_coffee_machine_odds; // Probabilidad de aparición del objeto int item_coffee_machine_odds; // Probabilidad de aparición del objeto
Helper() Helper()
: counter(HELP_COUNTER_MS), : counter(HELP_COUNTER),
item_disk_odds(ITEM_POINTS_1_DISK_ODDS), item_disk_odds(ITEM_POINTS_1_DISK_ODDS),
item_gavina_odds(ITEM_POINTS_2_GAVINA_ODDS), item_gavina_odds(ITEM_POINTS_2_GAVINA_ODDS),
item_pacmar_odds(ITEM_POINTS_3_PACMAR_ODDS), item_pacmar_odds(ITEM_POINTS_3_PACMAR_ODDS),
@@ -150,14 +134,14 @@ class Game {
Demo demo_; // Variable con todas las variables relacionadas con el modo demo Demo demo_; // Variable con todas las variables relacionadas con el modo demo
Difficulty::Code difficulty_ = Options::settings.difficulty; // Dificultad del juego Difficulty::Code difficulty_ = Options::settings.difficulty; // Dificultad del juego
Helper helper_; // Variable para gestionar las ayudas Helper helper_; // Variable para gestionar las ayudas
Uint64 last_time_ = 0; // Último tiempo registrado para deltaTime Uint64 ticks_ = 0; // Contador de ticks para ajustar la velocidad del programa
bool coffee_machine_enabled_ = false; // Indica si hay una máquina de café en el terreno de juego bool coffee_machine_enabled_ = false; // Indica si hay una máquina de café en el terreno de juego
bool hi_score_achieved_ = false; // Indica si se ha superado la puntuación máxima bool hi_score_achieved_ = false; // Indica si se ha superado la puntuación máxima
float difficulty_score_multiplier_ = 1.0f; // Multiplicador de puntos en función de la dificultad float difficulty_score_multiplier_; // Multiplicador de puntos en función de la dificultad
float counter_ = 0.0f; // Contador para el juego int counter_ = 0; // Contador para el juego
float game_completed_timer_ = 0.0f; // Acumulador de tiempo para el tramo final (milisegundos) int game_completed_counter_ = 0; // Contador para el tramo final, cuando se ha completado la partida y ya no aparecen más globos
float game_over_timer_ = 0.0f; // Timer para el estado de fin de partida (milisegundos) int game_over_counter_ = GAME_OVER_COUNTER; // Contador para el estado de fin de partida
float time_stopped_timer_ = 0.0f; // Temporizador para llevar la cuenta del tiempo detenido int time_stopped_counter_ = 0; // Temporizador para llevar la cuenta del tiempo detenido
int menace_ = 0; // Nivel de amenaza actual int menace_ = 0; // Nivel de amenaza actual
int menace_threshold_ = 0; // Umbral del nivel de amenaza. Si el nivel de amenaza cae por debajo del umbral, se generan más globos. Si el umbral aumenta, aumenta el número de globos int menace_threshold_ = 0; // Umbral del nivel de amenaza. Si el nivel de amenaza cae por debajo del umbral, se generan más globos. Si el umbral aumenta, aumenta el número de globos
State state_ = State::FADE_IN; // Estado State state_ = State::FADE_IN; // Estado
@@ -170,26 +154,25 @@ class Game {
#endif #endif
// --- Ciclo principal del juego --- // --- Ciclo principal del juego ---
void update(float deltaTime); // Actualiza la lógica principal del juego void update(); // Actualiza la lógica principal del juego
auto calculateDeltaTime() -> float; // Calcula el deltatime void render(); // Renderiza todos los elementos del juego
void render(); // Renderiza todos los elementos del juego void handleEvents(); // Procesa los eventos del sistema en cola
void handleEvents(); // Procesa los eventos del sistema en cola void checkState(); // Verifica y actualiza el estado actual del juego
void checkState(); // Verifica y actualiza el estado actual del juego void setState(State state); // Cambia el estado del juego
void setState(State state); // Cambia el estado del juego void cleanVectors(); // Limpia vectores de elementos deshabilitados
void cleanVectors(); // Limpia vectores de elementos deshabilitados
// --- Gestión de estados del juego --- // --- Gestión de estados del juego ---
void updateGameStates(float deltaTime); // Actualiza todos los estados del juego void updateGameStates(); // Actualiza todos los estados del juego
void updateGameStateFadeIn(float deltaTime); // Gestiona el estado de transición de entrada (time-based) void updateGameStateFadeIn(); // Gestiona el estado de transición de entrada
void updateGameStateEnteringPlayer(float deltaTime); // Gestiona el estado de entrada de jugador void updateGameStateEnteringPlayer(); // Gestiona el estado de entrada de jugador
void updateGameStateShowingGetReadyMessage(float deltaTime); // Gestiona el estado de mensaje "preparado" void updateGameStateShowingGetReadyMessage(); // Gestiona el estado de mensaje "preparado"
void updateGameStatePlaying(float deltaTime); // Gestiona el estado de juego activo void updateGameStatePlaying(); // Gestiona el estado de juego activo
void updateGameStateCompleted(float deltaTime); // Gestiona el estado de juego completado void updateGameStateCompleted(); // Gestiona el estado de juego completado
void updateGameStateGameOver(float deltaTime); // Gestiona el estado de fin de partida void updateGameStateGameOver(); // Gestiona el estado de fin de partida
// --- Gestión de jugadores --- // --- Gestión de jugadores ---
void initPlayers(Player::Id player_id); // Inicializa los datos de los jugadores void initPlayers(Player::Id player_id); // Inicializa los datos de los jugadores
void updatePlayers(float deltaTime); // Actualiza las variables y estados de los jugadores void updatePlayers(); // Actualiza las variables y estados de los jugadores
void renderPlayers(); // Renderiza todos los jugadores en pantalla void renderPlayers(); // Renderiza todos los jugadores en pantalla
void sortPlayersByZOrder(); // Reorganiza el orden de dibujado de jugadores void sortPlayersByZOrder(); // Reorganiza el orden de dibujado de jugadores
auto getPlayer(Player::Id id) -> std::shared_ptr<Player>; // Obtiene un jugador por su identificador auto getPlayer(Player::Id id) -> std::shared_ptr<Player>; // Obtiene un jugador por su identificador
@@ -226,7 +209,7 @@ class Game {
void demoHandlePlayerInput(const std::shared_ptr<Player> &player, int index); // Procesa entrada de jugador en demo void demoHandlePlayerInput(const std::shared_ptr<Player> &player, int index); // Procesa entrada de jugador en demo
// --- Sistema de balas y proyectiles --- // --- Sistema de balas y proyectiles ---
void updateBullets(float deltaTime); // Actualiza posición y estado de todas las balas (time-based) void updateBullets(); // Actualiza posición y estado de todas las balas
void renderBullets(); // Renderiza todas las balas activas void renderBullets(); // Renderiza todas las balas activas
void createBullet(int x, int y, BulletType kind, bool powered_up, Player::Id owner); // Crea una nueva bala void createBullet(int x, int y, BulletType kind, bool powered_up, Player::Id owner); // Crea una nueva bala
void checkBulletCollision(); // Verifica colisiones de todas las balas void checkBulletCollision(); // Verifica colisiones de todas las balas
@@ -238,7 +221,7 @@ class Game {
void processBalloonHit(const std::shared_ptr<Bullet> &bullet, const std::shared_ptr<Balloon> &balloon); // Procesa impacto en globo void processBalloonHit(const std::shared_ptr<Bullet> &bullet, const std::shared_ptr<Balloon> &balloon); // Procesa impacto en globo
// --- Sistema de ítems y power-ups --- // --- Sistema de ítems y power-ups ---
void updateItems(float deltaTime); // Actualiza posición y estado de todos los ítems void updateItems(); // Actualiza posición y estado de todos los ítems
void renderItems(); // Renderiza todos los ítems activos void renderItems(); // Renderiza todos los ítems activos
auto dropItem() -> ItemType; // Determina aleatoriamente qué ítem soltar auto dropItem() -> ItemType; // Determina aleatoriamente qué ítem soltar
void createItem(ItemType type, float x, float y); // Crea un nuevo ítem en posición específica void createItem(ItemType type, float x, float y); // Crea un nuevo ítem en posición específica
@@ -246,23 +229,21 @@ class Game {
void destroyAllItems(); // Elimina todos los ítems activos de la pantalla void destroyAllItems(); // Elimina todos los ítems activos de la pantalla
// --- ítems especiales --- // --- ítems especiales ---
void enableTimeStopItem(); // Activa el efecto de detener el tiempo void enableTimeStopItem(); // Activa el efecto de detener el tiempo
void disableTimeStopItem(); // Desactiva el efecto de detener el tiempo void disableTimeStopItem(); // Desactiva el efecto de detener el tiempo
void updateTimeStopped(float deltaTime); // Actualiza el estado del tiempo detenido void updateTimeStopped(); // Actualiza el estado del tiempo detenido
void handleGameCompletedEvents(); // Maneja eventos del juego completado void throwCoffee(int x, int y); // Crea efecto de café arrojado al ser golpeado
void handleGameOverEvents(); // Maneja eventos de game over
void throwCoffee(int x, int y); // Crea efecto de café arrojado al ser golpeado
// --- Gestión de caída de ítems --- // --- Gestión de caída de ítems ---
void handleItemDrop(const std::shared_ptr<Balloon> &balloon, const std::shared_ptr<Player> &player); // Gestiona caída de ítem desde globo void handleItemDrop(const std::shared_ptr<Balloon> &balloon, const std::shared_ptr<Player> &player); // Gestiona caída de ítem desde globo
// --- Sprites inteligentes (smartsprites) --- // --- Sprites inteligentes (smartsprites) ---
void updateSmartSprites(float deltaTime); // Actualiza todos los sprites con lógica propia (time-based) void updateSmartSprites(); // Actualiza todos los sprites con lógica propia
void renderSmartSprites(); // Renderiza todos los sprites inteligentes void renderSmartSprites(); // Renderiza todos los sprites inteligentes
void freeSmartSprites(); // Libera memoria de sprites inteligentes void freeSmartSprites(); // Libera memoria de sprites inteligentes
// --- Sprites por ruta (pathsprites) --- // --- Sprites por ruta (pathsprites) ---
void updatePathSprites(float deltaTime); // Actualiza sprites que siguen rutas predefinidas void updatePathSprites(); // Actualiza sprites que siguen rutas predefinidas
void renderPathSprites(); // Renderiza sprites animados por ruta void renderPathSprites(); // Renderiza sprites animados por ruta
void freePathSprites(); // Libera memoria de sprites por ruta void freePathSprites(); // Libera memoria de sprites por ruta
void initPaths(); // Inicializa rutas predefinidas para animaciones void initPaths(); // Inicializa rutas predefinidas para animaciones
@@ -296,7 +277,7 @@ class Game {
// --- Recursos y renderizado --- // --- Recursos y renderizado ---
void setResources(); // Asigna texturas y animaciones a los objetos void setResources(); // Asigna texturas y animaciones a los objetos
void updateBackground(float deltaTime); // Actualiza elementos del fondo (time-based) void updateBackground(); // Actualiza elementos del fondo
void fillCanvas(); // Renderiza elementos del área de juego en su textura void fillCanvas(); // Renderiza elementos del área de juego en su textura
void updateHelper(); // Actualiza variables auxiliares de renderizado void updateHelper(); // Actualiza variables auxiliares de renderizado

View File

@@ -33,7 +33,7 @@ HiScoreTable::HiScoreTable()
backbuffer_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height)), backbuffer_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height)),
fade_(std::make_unique<Fade>()), fade_(std::make_unique<Fade>()),
background_(std::make_unique<Background>()), background_(std::make_unique<Background>()),
last_time_(0), ticks_(0),
view_area_(SDL_FRect{0, 0, param.game.width, param.game.height}), view_area_(SDL_FRect{0, 0, param.game.width, param.game.height}),
fade_mode_(Fade::Mode::IN), fade_mode_(Fade::Mode::IN),
background_fade_color_(Color(0, 0, 0)) { background_fade_color_(Color(0, 0, 0)) {
@@ -53,15 +53,17 @@ HiScoreTable::~HiScoreTable() {
} }
// Actualiza las variables // Actualiza las variables
void HiScoreTable::update(float delta_time) { void HiScoreTable::update() {
elapsed_time_ += delta_time; // Incrementa el tiempo transcurrido if (SDL_GetTicks() - ticks_ > param.game.speed) {
Screen::get()->update(); // Actualiza el objeto screen ticks_ = SDL_GetTicks(); // Actualiza el contador de ticks
Screen::get()->update(); // Actualiza el objeto screen
updateSprites(delta_time); // Actualiza las posiciones de los sprites de texto updateSprites(); // Actualiza las posiciones de los sprites de texto
background_->update(delta_time); // Actualiza el fondo background_->update(); // Actualiza el fondo
updateFade(delta_time); // Gestiona el fade updateFade(); // Gestiona el fade
updateCounter(); // Gestiona el contador y sus eventos updateCounter(); // Gestiona el contador y sus eventos
fillTexture(); // Dibuja los sprites en la textura fillTexture(); // Dibuja los sprites en la textura
}
Audio::update(); Audio::update();
} }
@@ -73,10 +75,9 @@ void HiScoreTable::render() {
SCREEN->start(); // Prepara para empezar a dibujar en la textura de juego SCREEN->start(); // Prepara para empezar a dibujar en la textura de juego
SCREEN->clean(); // Limpia la pantalla SCREEN->clean(); // Limpia la pantalla
background_->render(); // Pinta el fondo background_->render(); // Pinta el fondo
float counter_equivalent = elapsed_time_ * 60.0f; // Convertir tiempo a equivalente frame para UI view_area_.y = std::max(0.0F, param.game.height - counter_ + 100); // Establece la ventana del backbuffer
view_area_.y = std::max(0.0F, param.game.height - counter_equivalent + 100); // Establece la ventana del backbuffer SDL_RenderTexture(renderer_, backbuffer_, nullptr, &view_area_); // Copia el backbuffer al renderizador
SDL_RenderTexture(renderer_, backbuffer_, nullptr, &view_area_); // Copia el backbuffer al renderizador
fade_->render(); // Renderiza el fade fade_->render(); // Renderiza el fade
SCREEN->render(); // Vuelca el contenido del renderizador en pantalla SCREEN->render(); // Vuelca el contenido del renderizador en pantalla
@@ -116,32 +117,20 @@ void HiScoreTable::checkInput() {
GlobalInputs::check(); GlobalInputs::check();
} }
// Calcula el tiempo transcurrido desde el último frame
float HiScoreTable::calculateDeltaTime() {
const Uint64 current_time = SDL_GetTicks();
const float delta_time = static_cast<float>(current_time - last_time_) / 1000.0f; // Convertir ms a segundos
last_time_ = current_time;
return delta_time;
}
// Bucle para la pantalla de instrucciones // Bucle para la pantalla de instrucciones
void HiScoreTable::run() { void HiScoreTable::run() {
last_time_ = SDL_GetTicks();
Audio::get()->playMusic("title.ogg"); Audio::get()->playMusic("title.ogg");
while (Section::name == Section::Name::HI_SCORE_TABLE) { while (Section::name == Section::Name::HI_SCORE_TABLE) {
const float delta_time = calculateDeltaTime();
checkInput(); checkInput();
update(delta_time); update();
checkEvents(); // Tiene que ir antes del render checkEvents(); // Tiene que ir antes del render
render(); render();
} }
} }
// Gestiona el fade // Gestiona el fade
void HiScoreTable::updateFade(float delta_time) { void HiScoreTable::updateFade() {
fade_->update(delta_time); fade_->update();
if (fade_->hasEnded() && fade_mode_ == Fade::Mode::IN) { if (fade_->hasEnded() && fade_mode_ == Fade::Mode::IN) {
fade_->reset(); fade_->reset();
@@ -219,14 +208,15 @@ void HiScoreTable::createSprites() {
const int DEFAULT_POS_X = (backbuffer_width - ENTRY_WIDTH) / 2; const int DEFAULT_POS_X = (backbuffer_width - ENTRY_WIDTH) / 2;
const int POS_X = (i < 9) ? DEFAULT_POS_X : DEFAULT_POS_X - entry_text->getCharacterSize(); const int POS_X = (i < 9) ? DEFAULT_POS_X : DEFAULT_POS_X - entry_text->getCharacterSize();
const int POS_Y = (i * SPACE_BETWEEN_LINES) + FIRST_LINE + SPACE_BETWEEN_HEADER; const int POS_Y = (i * SPACE_BETWEEN_LINES) + FIRST_LINE + SPACE_BETWEEN_HEADER;
constexpr int STEPS = 80;
switch (ANIMATION) { switch (ANIMATION) {
case 0: // Ambos lados alternativamente case 0: // Ambos lados alternativamente
{ {
if (i % 2 == 0) { if (i % 2 == 0) {
entry_names_.back()->addPath(-entry_names_.back()->getWidth(), POS_X, PathType::HORIZONTAL, POS_Y, ANIM_DURATION_S, easeOutQuint); entry_names_.back()->addPath(-entry_names_.back()->getWidth(), POS_X, PathType::HORIZONTAL, POS_Y, STEPS, easeOutQuint);
entry_names_.back()->setPosition(-entry_names_.back()->getWidth(), 0); entry_names_.back()->setPosition(-entry_names_.back()->getWidth(), 0);
} else { } else {
entry_names_.back()->addPath(backbuffer_width, POS_X, PathType::HORIZONTAL, POS_Y, ANIM_DURATION_S, easeOutQuint); entry_names_.back()->addPath(backbuffer_width, POS_X, PathType::HORIZONTAL, POS_Y, STEPS, easeOutQuint);
entry_names_.back()->setPosition(backbuffer_width, 0); entry_names_.back()->setPosition(backbuffer_width, 0);
} }
break; break;
@@ -234,21 +224,21 @@ void HiScoreTable::createSprites() {
case 1: // Entran por la izquierda case 1: // Entran por la izquierda
{ {
entry_names_.back()->addPath(-entry_names_.back()->getWidth(), POS_X, PathType::HORIZONTAL, POS_Y, ANIM_DURATION_S, easeOutQuint); entry_names_.back()->addPath(-entry_names_.back()->getWidth(), POS_X, PathType::HORIZONTAL, POS_Y, STEPS, easeOutQuint);
entry_names_.back()->setPosition(-entry_names_.back()->getWidth(), 0); entry_names_.back()->setPosition(-entry_names_.back()->getWidth(), 0);
break; break;
} }
case 2: // Entran por la derecha case 2: // Entran por la derecha
{ {
entry_names_.back()->addPath(backbuffer_width, POS_X, PathType::HORIZONTAL, POS_Y, ANIM_DURATION_S, easeOutQuint); entry_names_.back()->addPath(backbuffer_width, POS_X, PathType::HORIZONTAL, POS_Y, STEPS, easeOutQuint);
entry_names_.back()->setPosition(backbuffer_width, 0); entry_names_.back()->setPosition(backbuffer_width, 0);
break; break;
} }
case 3: // Entran desde la parte inferior case 3: // Entran desde la parte inferior
{ {
entry_names_.back()->addPath(backbuffer_height, POS_Y, PathType::VERTICAL, POS_X, ANIM_DURATION_S, easeOutQuint); entry_names_.back()->addPath(backbuffer_height, POS_Y, PathType::VERTICAL, POS_X, STEPS, easeOutQuint);
entry_names_.back()->setPosition(0, backbuffer_height); entry_names_.back()->setPosition(0, backbuffer_height);
} }
@@ -259,20 +249,20 @@ void HiScoreTable::createSprites() {
} }
// Actualiza las posiciones de los sprites de texto // Actualiza las posiciones de los sprites de texto
void HiScoreTable::updateSprites(float delta_time) { void HiScoreTable::updateSprites() {
if (elapsed_time_ >= INIT_DELAY_S) { constexpr int INIT_COUNTER = 190;
const float elapsed_since_init = elapsed_time_ - INIT_DELAY_S; const int COUNTER_BETWEEN_ENTRIES = 16;
int index = static_cast<int>(elapsed_since_init / ENTRY_DELAY_S); if (counter_ >= INIT_COUNTER) {
if (index < static_cast<int>(entry_names_.size()) && index >= 0) { const int COUNTER2 = counter_ - INIT_COUNTER;
// Verificar si este índice debe activarse ahora if (COUNTER2 % COUNTER_BETWEEN_ENTRIES == 0) {
float expected_time = index * ENTRY_DELAY_S; int index = COUNTER2 / COUNTER_BETWEEN_ENTRIES;
if (elapsed_since_init >= expected_time && elapsed_since_init < expected_time + delta_time) { if (index < static_cast<int>(entry_names_.size())) {
entry_names_.at(index)->enable(); entry_names_.at(index)->enable();
} }
} }
} }
for (auto const &entry : entry_names_) { for (auto const &entry : entry_names_) {
entry->update(delta_time); entry->update();
} }
glowEntryNames(); glowEntryNames();
@@ -291,7 +281,7 @@ void HiScoreTable::initFade() {
void HiScoreTable::initBackground() { void HiScoreTable::initBackground() {
background_->setManualMode(true); background_->setManualMode(true);
background_->setPos(param.game.game_area.rect); background_->setPos(param.game.game_area.rect);
background_->setCloudsSpeed(CLOUDS_SPEED); background_->setCloudsSpeed(-0.1F);
const int LUCKY = rand() % 3; const int LUCKY = rand() % 3;
switch (LUCKY) { switch (LUCKY) {
@@ -356,8 +346,7 @@ void HiScoreTable::iniEntryColors() {
// Hace brillar los nombres de la tabla de records // Hace brillar los nombres de la tabla de records
void HiScoreTable::glowEntryNames() { void HiScoreTable::glowEntryNames() {
int color_counter = static_cast<int>(elapsed_time_ * 60.0f / 5.0f); // Convertir tiempo a equivalente frame const Color ENTRY_COLOR = getEntryColor(counter_ / 5);
const Color ENTRY_COLOR = getEntryColor(color_counter);
for (const auto &entry_index : Options::settings.glowing_entries) { for (const auto &entry_index : Options::settings.glowing_entries) {
if (entry_index != -1) { if (entry_index != -1) {
entry_names_.at(entry_index)->getTexture()->setColor(ENTRY_COLOR); entry_names_.at(entry_index)->getTexture()->setColor(ENTRY_COLOR);
@@ -367,17 +356,14 @@ void HiScoreTable::glowEntryNames() {
// Gestiona el contador // Gestiona el contador
void HiScoreTable::updateCounter() { void HiScoreTable::updateCounter() {
static bool background_changed = false; ++counter_;
static bool fade_activated = false;
if (elapsed_time_ >= BACKGROUND_CHANGE_S && !background_changed) { if (counter_ == 150) {
background_->setColor(background_fade_color_.DARKEN()); background_->setColor(background_fade_color_.DARKEN());
background_->setAlpha(96); background_->setAlpha(96);
background_changed = true;
} }
if (elapsed_time_ >= COUNTER_END_S && !fade_activated) { if (counter_ == COUNTER_END) {
fade_->activate(); fade_->activate();
fade_activated = true;
} }
} }

View File

@@ -30,13 +30,8 @@ class HiScoreTable {
void run(); void run();
private: private:
// --- Constantes (en segundos) --- // --- Constantes ---
static constexpr float COUNTER_END_S = 800.0f / 60.0f; // Tiempo final (≈13.33s) static constexpr Uint16 COUNTER_END = 800; // Valor final para el contador
static constexpr float INIT_DELAY_S = 190.0f / 60.0f; // Retraso inicial (≈3.17s)
static constexpr float ENTRY_DELAY_S = 16.0f / 60.0f; // Retraso entre entradas (≈0.27s)
static constexpr float BACKGROUND_CHANGE_S = 150.0f / 60.0f; // Tiempo cambio fondo (≈2.5s)
static constexpr float ANIM_DURATION_S = 80.0f / 60.0f; // Duración animación (≈1.33s)
static constexpr float CLOUDS_SPEED = -6.0f; // Velocidad nubes (pixels/s)
// --- Objetos y punteros --- // --- Objetos y punteros ---
SDL_Renderer *renderer_; // El renderizador de la ventana SDL_Renderer *renderer_; // El renderizador de la ventana
@@ -49,28 +44,27 @@ class HiScoreTable {
std::vector<Path> paths_; // Vector con los recorridos precalculados std::vector<Path> paths_; // Vector con los recorridos precalculados
// --- Variables --- // --- Variables ---
float elapsed_time_ = 0.0f; // Tiempo transcurrido (segundos) Uint16 counter_ = 0; // Contador
Uint64 last_time_ = 0; // Último timestamp para calcular delta-time Uint64 ticks_; // Contador de ticks para ajustar la velocidad del programa
SDL_FRect view_area_; // Parte de la textura que se muestra en pantalla SDL_FRect view_area_; // Parte de la textura que se muestra en pantalla
Fade::Mode fade_mode_; // Modo de fade a utilizar Fade::Mode fade_mode_; // Modo de fade a utilizar
Color background_fade_color_; // Color de atenuación del fondo Color background_fade_color_; // Color de atenuación del fondo
std::vector<Color> entry_colors_; // Colores para destacar las entradas en la tabla std::vector<Color> entry_colors_; // Colores para destacar las entradas en la tabla
// --- Métodos internos --- // --- Métodos internos ---
void update(float delta_time); // Actualiza las variables void update(); // Actualiza las variables
void render(); // Pinta en pantalla void render(); // Pinta en pantalla
static void checkEvents(); // Comprueba los eventos static void checkEvents(); // Comprueba los eventos
static void checkInput(); // Comprueba las entradas static void checkInput(); // Comprueba las entradas
static auto format(int number) -> std::string; // Convierte un entero a un string con separadores de miles static auto format(int number) -> std::string; // Convierte un entero a un string con separadores de miles
void fillTexture(); // Dibuja los sprites en la textura void fillTexture(); // Dibuja los sprites en la textura
void updateFade(float delta_time); // Gestiona el fade void updateFade(); // Gestiona el fade
void createSprites(); // Crea los sprites con los textos void createSprites(); // Crea los sprites con los textos
void updateSprites(float delta_time); // Actualiza las posiciones de los sprites de texto void updateSprites(); // Actualiza las posiciones de los sprites de texto
void initFade(); // Inicializa el fade void initFade(); // Inicializa el fade
void initBackground(); // Inicializa el fondo void initBackground(); // Inicializa el fondo
auto getEntryColor(int counter) -> Color; // Obtiene un color del vector de colores de entradas auto getEntryColor(int counter) -> Color; // Obtiene un color del vector de colores de entradas
void iniEntryColors(); // Inicializa los colores de las entradas void iniEntryColors(); // Inicializa los colores de las entradas
void glowEntryNames(); // Hace brillar los nombres de la tabla de records void glowEntryNames(); // Hace brillar los nombres de la tabla de records
void updateCounter(); // Gestiona el contador void updateCounter(); // Gestiona el contador
float calculateDeltaTime(); // Calcula el tiempo transcurrido desde el último frame
}; };

View File

@@ -88,24 +88,24 @@ void Instructions::iniSprites() {
void Instructions::updateSprites() { void Instructions::updateSprites() {
SDL_FRect src_rect = {0, 0, param.game.item_size, param.game.item_size}; SDL_FRect src_rect = {0, 0, param.game.item_size, param.game.item_size};
// Disquito (desplazamiento 12/60 = 0.2s) // Disquito
src_rect.y = param.game.item_size * (static_cast<int>((elapsed_time_ + 0.2f) / SPRITE_ANIMATION_CYCLE_S) % 2); src_rect.y = param.game.item_size * (((counter_ + 12) / 36) % 2);
sprites_[0]->setSpriteClip(src_rect); sprites_[0]->setSpriteClip(src_rect);
// Gavina (desplazamiento 9/60 = 0.15s) // Gavina
src_rect.y = param.game.item_size * (static_cast<int>((elapsed_time_ + 0.15f) / SPRITE_ANIMATION_CYCLE_S) % 2); src_rect.y = param.game.item_size * (((counter_ + 9) / 36) % 2);
sprites_[1]->setSpriteClip(src_rect); sprites_[1]->setSpriteClip(src_rect);
// Pacmar (desplazamiento 6/60 = 0.1s) // Pacmar
src_rect.y = param.game.item_size * (static_cast<int>((elapsed_time_ + 0.1f) / SPRITE_ANIMATION_CYCLE_S) % 2); src_rect.y = param.game.item_size * (((counter_ + 6) / 36) % 2);
sprites_[2]->setSpriteClip(src_rect); sprites_[2]->setSpriteClip(src_rect);
// Time Stopper (desplazamiento 3/60 = 0.05s) // Time Stopper
src_rect.y = param.game.item_size * (static_cast<int>((elapsed_time_ + 0.05f) / SPRITE_ANIMATION_CYCLE_S) % 2); src_rect.y = param.game.item_size * (((counter_ + 3) / 36) % 2);
sprites_[3]->setSpriteClip(src_rect); sprites_[3]->setSpriteClip(src_rect);
// Coffee (sin desplazamiento) // Coffee
src_rect.y = param.game.item_size * (static_cast<int>(elapsed_time_ / SPRITE_ANIMATION_CYCLE_S) % 2); src_rect.y = param.game.item_size * (((counter_ + 0) / 36) % 2);
sprites_[4]->setSpriteClip(src_rect); sprites_[4]->setSpriteClip(src_rect);
} }
@@ -204,15 +204,18 @@ void Instructions::fillBackbuffer() {
} }
// Actualiza las variables // Actualiza las variables
void Instructions::update(float delta_time) { void Instructions::update() {
elapsed_time_ += delta_time; // Incrementa el tiempo transcurrido if (SDL_GetTicks() - ticks_ > param.game.speed) {
Screen::get()->update(); // Actualiza el objeto screen ticks_ = SDL_GetTicks(); // Actualiza el contador de ticks
Screen::get()->update(); // Actualiza el objeto screen
updateSprites(); // Actualiza los sprites counter_++; // Incrementa el contador
updateBackbuffer(delta_time); // Gestiona la textura con los graficos updateSprites(); // Actualiza los sprites
tiled_bg_->update(delta_time); // Actualiza el mosaico de fondo updateBackbuffer(); // Gestiona la textura con los graficos
fade_->update(delta_time); // Actualiza el objeto "fade" tiled_bg_->update(); // Actualiza el mosaico de fondo
fillBackbuffer(); // Rellena el backbuffer fade_->update(); // Actualiza el objeto "fade"
fillBackbuffer(); // Rellena el backbuffer
}
Audio::update(); Audio::update();
} }
@@ -252,24 +255,12 @@ void Instructions::checkInput() {
GlobalInputs::check(); GlobalInputs::check();
} }
// Calcula el tiempo transcurrido desde el último frame
float Instructions::calculateDeltaTime() {
const Uint64 current_time = SDL_GetTicks();
const float delta_time = static_cast<float>(current_time - last_time_) / 1000.0f; // Convertir ms a segundos
last_time_ = current_time;
return delta_time;
}
// Bucle para la pantalla de instrucciones // Bucle para la pantalla de instrucciones
void Instructions::run() { void Instructions::run() {
last_time_ = SDL_GetTicks();
Audio::get()->playMusic("title.ogg"); Audio::get()->playMusic("title.ogg");
while (Section::name == Section::Name::INSTRUCTIONS) { while (Section::name == Section::Name::INSTRUCTIONS) {
const float delta_time = calculateDeltaTime();
checkInput(); checkInput();
update(delta_time); update();
checkEvents(); // Tiene que ir antes del render checkEvents(); // Tiene que ir antes del render
render(); render();
} }
@@ -324,27 +315,23 @@ void Instructions::renderLines(SDL_Renderer *renderer, SDL_Texture *texture, con
} }
// Gestiona la textura con los graficos // Gestiona la textura con los graficos
void Instructions::updateBackbuffer(float delta_time) { void Instructions::updateBackbuffer() {
// Establece la ventana del backbuffer (convertir elapsed_time_ a equivalente de counter) // Establece la ventana del backbuffer
float counter_equivalent = elapsed_time_ * 60.0f; // Convertir segundos a equivalente frame para UI view_.y = std::max(0.0F, param.game.height - counter_ + 100);
view_.y = std::max(0.0F, param.game.height - counter_equivalent + 100);
// Verifica si view_.y == 0 y gestiona el temporizador // Verifica si view_.y == 0 y gestiona el temporizador
if (view_.y == 0) { if (view_.y == 0) {
if (!start_delay_triggered_) { if (!start_delay_triggered_) {
// Activa el temporizador si no ha sido activado // Activa el temporizador si no ha sido activado
start_delay_triggered_ = true; start_delay_triggered_ = true;
start_delay_timer_ = 0.0f; start_delay_time_ = SDL_GetTicks();
} else { } else if (SDL_GetTicks() - start_delay_time_ >= 4000) {
start_delay_timer_ += delta_time; // Han pasado tres segundos, mover líneas
if (start_delay_timer_ >= START_DELAY_S) { all_lines_off_screen_ = moveLines(lines_, 320, 1.0F, 5);
// Han pasado los segundos de retraso, mover líneas
all_lines_off_screen_ = moveLines(lines_, 320, LINE_MOVE_DURATION_S, static_cast<Uint32>(LINE_START_DELAY_MS));
}
} }
} }
// Comprueba si todas las líneas han terminado // Comprueba si el contador ha llegado al final
if (all_lines_off_screen_) { if (all_lines_off_screen_) {
Section::name = Section::Name::TITLE; Section::name = Section::Name::TITLE;
Section::options = Section::Options::TITLE_1; Section::options = Section::Options::TITLE_1;

View File

@@ -50,12 +50,6 @@ class Instructions {
void run(); void run();
private: private:
// --- Constantes de tiempo (en segundos) ---
static constexpr float SPRITE_ANIMATION_CYCLE_S = 36.0f / 60.0f; // Ciclo de animación sprites (≈0.6s)
static constexpr float START_DELAY_S = 4.0f; // Retraso antes de mover líneas (4s)
static constexpr float LINE_MOVE_DURATION_S = 1.0f; // Duración movimiento líneas (1s)
static constexpr float LINE_START_DELAY_MS = 5.0f; // Retraso entre líneas (5ms)
// --- Objetos y punteros --- // --- Objetos y punteros ---
SDL_Renderer *renderer_; // El renderizador de la ventana SDL_Renderer *renderer_; // El renderizador de la ventana
SDL_Texture *texture_; // Textura fija con el texto SDL_Texture *texture_; // Textura fija con el texto
@@ -68,18 +62,18 @@ class Instructions {
std::unique_ptr<Fade> fade_; // Objeto para renderizar fades std::unique_ptr<Fade> fade_; // Objeto para renderizar fades
// --- Variables --- // --- Variables ---
float elapsed_time_ = 0.0f; // Tiempo transcurrido (segundos) int counter_ = 0; // Contador para manejar el progreso en la pantalla de instrucciones
Uint64 last_time_ = 0; // Último timestamp para calcular delta-time Uint64 ticks_ = 0; // Contador de ticks para ajustar la velocidad del programa
SDL_FRect view_; // Vista del backbuffer que se va a mostrar por pantalla SDL_FRect view_; // Vista del backbuffer que se va a mostrar por pantalla
SDL_FPoint sprite_pos_ = {0, 0}; // Posición del primer sprite en la lista SDL_FPoint sprite_pos_ = {0, 0}; // Posición del primer sprite en la lista
float item_space_ = 2.0; // Espacio entre los items en pantalla float item_space_ = 2.0; // Espacio entre los items en pantalla
std::vector<Line> lines_; // Vector que contiene las líneas animadas en la pantalla std::vector<Line> lines_; // Vector que contiene las líneas animadas en la pantalla
bool all_lines_off_screen_ = false; // Indica si todas las líneas han salido de la pantalla bool all_lines_off_screen_ = false; // Indica si todas las líneas han salido de la pantalla
float start_delay_timer_ = 0.0f; // Timer para retraso antes de mover líneas (segundos) Uint32 start_delay_time_ = 0; // Tiempo de inicio del retraso para mover las líneas
bool start_delay_triggered_ = false; // Bandera para determinar si el retraso ha comenzado bool start_delay_triggered_ = false; // Bandera para determinar si el retraso ha comenzado
// --- Métodos internos --- // --- Métodos internos ---
void update(float delta_time); // Actualiza las variables void update(); // Actualiza las variables
void render(); // Pinta en pantalla void render(); // Pinta en pantalla
static void checkEvents(); // Comprueba los eventos static void checkEvents(); // Comprueba los eventos
static void checkInput(); // Comprueba las entradas static void checkInput(); // Comprueba las entradas
@@ -88,8 +82,7 @@ class Instructions {
void iniSprites(); // Inicializa los sprites de los items void iniSprites(); // Inicializa los sprites de los items
void updateSprites(); // Actualiza los sprites void updateSprites(); // Actualiza los sprites
static auto initializeLines(int height) -> std::vector<Line>; // Inicializa las líneas animadas static auto initializeLines(int height) -> std::vector<Line>; // Inicializa las líneas animadas
static auto moveLines(std::vector<Line> &lines, int width, float duration, Uint32 start_delay) -> bool; // Mueve las líneas (ya usa tiempo real) static auto moveLines(std::vector<Line> &lines, int width, float duration, Uint32 start_delay) -> bool; // Mueve las líneas
static void renderLines(SDL_Renderer *renderer, SDL_Texture *texture, const std::vector<Line> &lines); // Renderiza las líneas static void renderLines(SDL_Renderer *renderer, SDL_Texture *texture, const std::vector<Line> &lines); // Renderiza las líneas
void updateBackbuffer(float delta_time); // Gestiona la textura con los gráficos void updateBackbuffer(); // Gestiona la textura con los gráficos
float calculateDeltaTime(); // Calcula el tiempo transcurrido desde el último frame
}; };

View File

@@ -39,7 +39,7 @@ Intro::Intro()
initTexts(); initTexts();
// Configura el fondo // Configura el fondo
tiled_bg_->setSpeed(TILED_BG_SPEED); tiled_bg_->setSpeed(0.3F);
tiled_bg_->setColor(bg_color_); tiled_bg_->setColor(bg_color_);
} }
@@ -191,7 +191,7 @@ void Intro::updateScene5() {
// Acaba la ultima imagen // Acaba la ultima imagen
if (card_sprites_.at(5)->hasFinished() && texts_.at(8)->hasFinished()) { if (card_sprites_.at(5)->hasFinished() && texts_.at(8)->hasFinished()) {
state_ = State::POST; state_ = State::POST;
state_start_time_ = SDL_GetTicks() / 1000.0f; state_start_time_ = SDL_GetTicks();
} }
} }
@@ -207,21 +207,24 @@ void Intro::switchText(int from_index, int to_index) {
} }
// Actualiza las variables del objeto // Actualiza las variables del objeto
void Intro::update(float delta_time) { void Intro::update() {
Screen::get()->update(); // Actualiza el objeto screen if (SDL_GetTicks() - ticks_ > param.game.speed) {
ticks_ = SDL_GetTicks(); // Actualiza el contador de ticks
Screen::get()->update(); // Actualiza el objeto screen
tiled_bg_->update(delta_time); // Actualiza el fondo tiled_bg_->update(); // Actualiza el fondo
switch (state_) { switch (state_) {
case State::SCENES: case State::SCENES:
updateSprites(delta_time); updateSprites();
updateTexts(delta_time); updateTexts();
updateScenes(); updateScenes();
break; break;
case State::POST: case State::POST:
updatePostState(); updatePostState();
break; break;
}
} }
Audio::update(); Audio::update();
@@ -250,24 +253,12 @@ void Intro::render() {
SCREEN->render(); // Vuelca el contenido del renderizador en pantalla SCREEN->render(); // Vuelca el contenido del renderizador en pantalla
} }
// Calcula el tiempo transcurrido desde el último frame
float Intro::calculateDeltaTime() {
const Uint64 current_time = SDL_GetTicks();
const float delta_time = static_cast<float>(current_time - last_time_) / 1000.0f; // Convertir ms a segundos
last_time_ = current_time;
return delta_time;
}
// Bucle principal // Bucle principal
void Intro::run() { void Intro::run() {
last_time_ = SDL_GetTicks();
Audio::get()->playMusic("intro.ogg", 0); Audio::get()->playMusic("intro.ogg", 0);
while (Section::name == Section::Name::INTRO) { while (Section::name == Section::Name::INTRO) {
const float delta_time = calculateDeltaTime();
checkInput(); checkInput();
update(delta_time); update();
checkEvents(); // Tiene que ir antes del render checkEvents(); // Tiene que ir antes del render
render(); render();
} }
@@ -286,7 +277,7 @@ void Intro::initSprites() {
// Constantes // Constantes
constexpr int TOTAL_SPRITES = TEXTURE_LIST.size(); constexpr int TOTAL_SPRITES = TEXTURE_LIST.size();
const float BORDER = CARD_BORDER_SIZE; const float BORDER = 2.0F;
auto texture = Resource::get()->getTexture(TEXTURE_LIST.front()); auto texture = Resource::get()->getTexture(TEXTURE_LIST.front());
const float CARD_WIDTH = texture->getWidth() + (BORDER * 2); const float CARD_WIDTH = texture->getWidth() + (BORDER * 2);
@@ -338,16 +329,16 @@ void Intro::initSprites() {
const float X_DEST = param.game.game_area.center_x - (CARD_WIDTH / 2); const float X_DEST = param.game.game_area.center_x - (CARD_WIDTH / 2);
const float Y_DEST = param.game.game_area.first_quarter_y - (CARD_HEIGHT / 4); const float Y_DEST = param.game.game_area.first_quarter_y - (CARD_HEIGHT / 4);
card_sprites_.at(0)->addPath(-CARD_WIDTH - CARD_OFFSET_MARGIN, X_DEST, PathType::HORIZONTAL, Y_DEST, CARD_ANIM_DURATION_NORMAL, easeInOutExpo, 0.0f); card_sprites_.at(0)->addPath(-CARD_WIDTH - 10, X_DEST, PathType::HORIZONTAL, Y_DEST, 100, easeInOutExpo, 0);
card_sprites_.at(1)->addPath(param.game.width, X_DEST, PathType::HORIZONTAL, Y_DEST, CARD_ANIM_DURATION_NORMAL, easeOutBounce, 0.0f); card_sprites_.at(1)->addPath(param.game.width, X_DEST, PathType::HORIZONTAL, Y_DEST, 100, easeOutBounce, 0);
card_sprites_.at(2)->addPath(-CARD_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, CARD_ANIM_DURATION_FAST, easeOutQuint, 0.0f); card_sprites_.at(2)->addPath(-CARD_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, 40, easeOutQuint, 0);
card_sprites_.at(3)->addPath(param.game.height, Y_DEST, PathType::VERTICAL, X_DEST, CARD_ANIM_DURATION_VERY_SLOW, easeInOutExpo, 0.0f); card_sprites_.at(3)->addPath(param.game.height, Y_DEST, PathType::VERTICAL, X_DEST, 300, easeInOutExpo, 0);
card_sprites_.at(4)->addPath(-CARD_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, CARD_ANIM_DURATION_MEDIUM, easeOutElastic, 0.0f); card_sprites_.at(4)->addPath(-CARD_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, 70, easeOutElastic, 0);
card_sprites_.at(5)->addPath(-CARD_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, CARD_ANIM_DURATION_SLOW, easeOutQuad, CARD_ANIM_DELAY_LONG); card_sprites_.at(5)->addPath(-CARD_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, 250, easeOutQuad, 450);
card_sprites_.at(5)->addPath(X_DEST, -CARD_WIDTH, PathType::HORIZONTAL, Y_DEST, CARD_ANIM_DURATION_SHORT, easeInElastic, 0.0f); card_sprites_.at(5)->addPath(X_DEST, -CARD_WIDTH, PathType::HORIZONTAL, Y_DEST, 80, easeInElastic, 0);
// Constantes // Constantes
const float DESP = SHADOW_OFFSET; const float DESP = 8;
const float SHADOW_SPRITE_WIDTH = CARD_WIDTH; const float SHADOW_SPRITE_WIDTH = CARD_WIDTH;
const float SHADOW_SPRITE_HEIGHT = CARD_HEIGHT; const float SHADOW_SPRITE_HEIGHT = CARD_HEIGHT;
@@ -389,13 +380,13 @@ void Intro::initSprites() {
const float S_X_DEST = X_DEST + DESP; const float S_X_DEST = X_DEST + DESP;
const float S_Y_DEST = Y_DEST + DESP; const float S_Y_DEST = Y_DEST + DESP;
shadow_sprites_.at(0)->addPath(param.game.height + CARD_OFFSET_MARGIN, S_Y_DEST, PathType::VERTICAL, S_X_DEST, CARD_ANIM_DURATION_NORMAL, easeInOutExpo, 0.0f); shadow_sprites_.at(0)->addPath(param.game.height + 10, S_Y_DEST, PathType::VERTICAL, S_X_DEST, 100, easeInOutExpo, 0);
shadow_sprites_.at(1)->addPath(-SHADOW_SPRITE_HEIGHT, S_Y_DEST, PathType::VERTICAL, S_X_DEST, CARD_ANIM_DURATION_NORMAL, easeOutBounce, 0.0f); shadow_sprites_.at(1)->addPath(-SHADOW_SPRITE_HEIGHT, S_Y_DEST, PathType::VERTICAL, S_X_DEST, 100, easeOutBounce, 0);
shadow_sprites_.at(2)->addPath(-SHADOW_SPRITE_WIDTH, S_X_DEST, PathType::HORIZONTAL, S_Y_DEST, CARD_ANIM_DURATION_FAST, easeOutQuint, 0.0f); shadow_sprites_.at(2)->addPath(-SHADOW_SPRITE_WIDTH, S_X_DEST, PathType::HORIZONTAL, S_Y_DEST, 40, easeOutQuint, 0);
shadow_sprites_.at(3)->addPath(-SHADOW_SPRITE_HEIGHT, S_Y_DEST, PathType::VERTICAL, S_X_DEST, CARD_ANIM_DURATION_VERY_SLOW, easeInOutExpo, 0.0f); shadow_sprites_.at(3)->addPath(-SHADOW_SPRITE_HEIGHT, S_Y_DEST, PathType::VERTICAL, S_X_DEST, 300, easeInOutExpo, 0);
shadow_sprites_.at(4)->addPath(param.game.height, S_Y_DEST, PathType::VERTICAL, S_X_DEST, CARD_ANIM_DURATION_MEDIUM, easeOutElastic, 0.0f); shadow_sprites_.at(4)->addPath(param.game.height, S_Y_DEST, PathType::VERTICAL, S_X_DEST, 70, easeOutElastic, 0);
shadow_sprites_.at(5)->addPath(param.game.width, S_X_DEST, PathType::HORIZONTAL, S_Y_DEST, CARD_ANIM_DURATION_SLOW, easeOutQuad, CARD_ANIM_DELAY_LONG); shadow_sprites_.at(5)->addPath(param.game.width, S_X_DEST, PathType::HORIZONTAL, S_Y_DEST, 250, easeOutQuad, 450);
shadow_sprites_.at(5)->addPath(S_X_DEST, param.game.width, PathType::HORIZONTAL, S_Y_DEST, CARD_ANIM_DURATION_SHORT, easeInElastic, 0.0f); shadow_sprites_.at(5)->addPath(S_X_DEST, param.game.width, PathType::HORIZONTAL, S_Y_DEST, 80, easeInElastic, 0);
} }
// Inicializa los textos // Inicializa los textos
@@ -405,47 +396,47 @@ void Intro::initTexts() {
auto writer = std::make_unique<Writer>(Resource::get()->getText("04b_25_metal")); auto writer = std::make_unique<Writer>(Resource::get()->getText("04b_25_metal"));
writer->setPosX(0); writer->setPosX(0);
writer->setPosY(param.game.height - param.intro.text_distance_from_bottom); writer->setPosY(param.game.height - param.intro.text_distance_from_bottom);
writer->setKerning(TEXT_KERNING); writer->setKerning(-2);
writer->setEnabled(false); writer->setEnabled(false);
writer->setFinishedTimerS(TEXT_DISPLAY_DURATION_S); writer->setFinishedCounter(180);
texts_.push_back(std::move(writer)); texts_.push_back(std::move(writer));
} }
// Un dia qualsevol de l'any 2000 // Un dia qualsevol de l'any 2000
texts_.at(0)->setCaption(Lang::getText("[INTRO] 1")); texts_.at(0)->setCaption(Lang::getText("[INTRO] 1"));
texts_.at(0)->setSpeedS(TEXT_SPEED_NORMAL); texts_.at(0)->setSpeed(8);
// Tot esta tranquil a la UPV // Tot esta tranquil a la UPV
texts_.at(1)->setCaption(Lang::getText("[INTRO] 2")); texts_.at(1)->setCaption(Lang::getText("[INTRO] 2"));
texts_.at(1)->setSpeedS(TEXT_SPEED_NORMAL); texts_.at(1)->setSpeed(8);
// Fins que un desaprensiu... // Fins que un desaprensiu...
texts_.at(2)->setCaption(Lang::getText("[INTRO] 3")); texts_.at(2)->setCaption(Lang::getText("[INTRO] 3"));
texts_.at(2)->setSpeedS(TEXT_SPEED_FAST); texts_.at(2)->setSpeed(12);
// HEY! ME ANE A FERME UN CORTAET... // HEY! ME ANE A FERME UN CORTAET...
texts_.at(3)->setCaption(Lang::getText("[INTRO] 4")); texts_.at(3)->setCaption(Lang::getText("[INTRO] 4"));
texts_.at(3)->setSpeedS(TEXT_SPEED_NORMAL); texts_.at(3)->setSpeed(8);
// UAAAAAAAAAAAAA!!! // UAAAAAAAAAAAAA!!!
texts_.at(4)->setCaption(Lang::getText("[INTRO] 5")); texts_.at(4)->setCaption(Lang::getText("[INTRO] 5"));
texts_.at(4)->setSpeedS(TEXT_SPEED_VERY_SLOW); texts_.at(4)->setSpeed(1);
// Espera un moment... // Espera un moment...
texts_.at(5)->setCaption(Lang::getText("[INTRO] 6")); texts_.at(5)->setCaption(Lang::getText("[INTRO] 6"));
texts_.at(5)->setSpeedS(TEXT_SPEED_VERY_FAST); texts_.at(5)->setSpeed(16);
// Si resulta que no tinc solt! // Si resulta que no tinc solt!
texts_.at(6)->setCaption(Lang::getText("[INTRO] 7")); texts_.at(6)->setCaption(Lang::getText("[INTRO] 7"));
texts_.at(6)->setSpeedS(TEXT_SPEED_SLOW); texts_.at(6)->setSpeed(2);
// MERDA DE MAQUINA! // MERDA DE MAQUINA!
texts_.at(7)->setCaption(Lang::getText("[INTRO] 8")); texts_.at(7)->setCaption(Lang::getText("[INTRO] 8"));
texts_.at(7)->setSpeedS(TEXT_SPEED_MEDIUM_SLOW); texts_.at(7)->setSpeed(3);
// Blop... blop... blop... // Blop... blop... blop...
texts_.at(8)->setCaption(Lang::getText("[INTRO] 9")); texts_.at(8)->setCaption(Lang::getText("[INTRO] 9"));
texts_.at(8)->setSpeedS(TEXT_SPEED_ULTRA_FAST); texts_.at(8)->setSpeed(20);
for (auto &text : texts_) { for (auto &text : texts_) {
text->center(param.game.game_area.center_x); text->center(param.game.game_area.center_x);
@@ -453,20 +444,20 @@ void Intro::initTexts() {
} }
// Actualiza los sprites // Actualiza los sprites
void Intro::updateSprites(float delta_time) { void Intro::updateSprites() {
for (auto &sprite : card_sprites_) { for (auto &sprite : card_sprites_) {
sprite->update(delta_time); sprite->update();
} }
for (auto &sprite : shadow_sprites_) { for (auto &sprite : shadow_sprites_) {
sprite->update(delta_time); sprite->update();
} }
} }
// Actualiza los textos // Actualiza los textos
void Intro::updateTexts(float delta_time) { void Intro::updateTexts() {
for (auto &text : texts_) { for (auto &text : texts_) {
text->updateS(delta_time); // Usar updateS para delta_time en segundos text->update();
} }
} }
@@ -485,12 +476,12 @@ void Intro::renderTexts() {
// Actualiza el estado POST // Actualiza el estado POST
void Intro::updatePostState() { void Intro::updatePostState() {
const float ELAPSED_TIME = (SDL_GetTicks() / 1000.0f) - state_start_time_; const Uint32 ELAPSED_TIME = SDL_GetTicks() - state_start_time_;
switch (post_state_) { switch (post_state_) {
case PostState::STOP_BG: case PostState::STOP_BG:
// EVENTO: Detiene el fondo después del tiempo especificado // EVENTO: Detiene el fondo después de 1 segundo
if (ELAPSED_TIME >= POST_BG_STOP_DELAY_S) { if (ELAPSED_TIME >= 1000) {
tiled_bg_->stopGracefully(); tiled_bg_->stopGracefully();
if (!bg_color_.IS_EQUAL_TO(param.title.bg_color)) { if (!bg_color_.IS_EQUAL_TO(param.title.bg_color)) {
@@ -503,13 +494,13 @@ void Intro::updatePostState() {
// Cambia de estado si el fondo se ha detenido y recuperado el color // Cambia de estado si el fondo se ha detenido y recuperado el color
if (tiled_bg_->isStopped() && bg_color_.IS_EQUAL_TO(param.title.bg_color)) { if (tiled_bg_->isStopped() && bg_color_.IS_EQUAL_TO(param.title.bg_color)) {
post_state_ = PostState::END; post_state_ = PostState::END;
state_start_time_ = SDL_GetTicks() / 1000.0f; state_start_time_ = SDL_GetTicks();
} }
break; break;
case PostState::END: case PostState::END:
// Finaliza la intro después del tiempo especificado // Finaliza la intro después de 1 segundo
if (ELAPSED_TIME >= POST_END_DELAY_S) { if (ELAPSED_TIME >= 1000) {
Audio::get()->stopMusic(); Audio::get()->stopMusic();
Section::name = Section::Name::TITLE; Section::name = Section::Name::TITLE;
Section::options = Section::Options::TITLE_1; Section::options = Section::Options::TITLE_1;

View File

@@ -11,22 +11,9 @@
#include "tiled_bg.h" // Para TiledBG #include "tiled_bg.h" // Para TiledBG
#include "writer.h" // Para Writer #include "writer.h" // Para Writer
// --- Clase Intro: secuencia cinemática de introducción del juego --- // --- Clase Intro: muestra la secuencia de introducción ---
// // Esta clase gestiona un estado del programa. Se encarga de mostrar la secuencia
// Esta clase gestiona la secuencia de introducción narrativa del juego, mostrando // de introducción.
// una serie de escenas con imágenes, texto y efectos visuales sincronizados.
//
// Funcionalidades principales:
// • Sistema de escenas secuencial: 6 escenas con transiciones automáticas
// • Animaciones de tarjetas: efectos de entrada con diferentes tipos de easing
// • Texto narrativo: velocidades de escritura configurables por escena
// • Efectos visuales: sombras, bordes y transiciones de color
// • Audio sincronizado: música de fondo durante toda la secuencia
// • Estado POST: transición suave hacia el menú principal
//
// Todas las duraciones y velocidades están configuradas mediante constantes
// para facilitar el ajuste fino de la experiencia cinematográfica.
class Intro { class Intro {
public: public:
// --- Constructor y destructor --- // --- Constructor y destructor ---
@@ -37,37 +24,6 @@ class Intro {
void run(); void run();
private: private:
// --- Constantes de tiempo (en segundos) ---
static constexpr float TEXT_DISPLAY_DURATION_S = 3.0f; // Duración de visualización de texto (180 frames a 60fps)
static constexpr float POST_BG_STOP_DELAY_S = 1.0f; // Retraso antes de detener el fondo
static constexpr float POST_END_DELAY_S = 1.0f; // Retraso antes de finalizar intro
// --- Constantes de layout ---
static constexpr float CARD_BORDER_SIZE = 2.0f; // Tamaño del borde de tarjetas
static constexpr float SHADOW_OFFSET = 8.0f; // Desplazamiento de sombra
static constexpr float TILED_BG_SPEED = 18.0f; // Velocidad del fondo mosaico (pixels/segundo)
static constexpr int TEXT_KERNING = -2; // Espaciado entre caracteres
// --- Constantes de velocidades de texto (segundos entre caracteres) ---
static constexpr float TEXT_SPEED_NORMAL = 0.133f; // Velocidad normal (8 frames * 16.67ms = 133ms)
static constexpr float TEXT_SPEED_FAST = 0.2f; // Velocidad rápida (12 frames * 16.67ms = 200ms)
static constexpr float TEXT_SPEED_VERY_SLOW = 0.0167f; // Velocidad muy lenta (1 frame * 16.67ms = 16.7ms)
static constexpr float TEXT_SPEED_VERY_FAST = 0.267f; // Velocidad muy rápida (16 frames * 16.67ms = 267ms)
static constexpr float TEXT_SPEED_SLOW = 0.033f; // Velocidad lenta (2 frames * 16.67ms = 33ms)
static constexpr float TEXT_SPEED_MEDIUM_SLOW = 0.05f; // Velocidad medio-lenta (3 frames * 16.67ms = 50ms)
static constexpr float TEXT_SPEED_ULTRA_FAST = 0.333f; // Velocidad ultra rápida (20 frames * 16.67ms = 333ms)
// --- Constantes de animaciones de tarjetas (duraciones en segundos) ---
static constexpr float CARD_ANIM_DURATION_NORMAL = 100.0f / 60.0f; // ≈ 1.6667 s
static constexpr float CARD_ANIM_DURATION_FAST = 40.0f / 60.0f; // ≈ 0.6667 s
static constexpr float CARD_ANIM_DURATION_MEDIUM = 70.0f / 60.0f; // ≈ 1.1667 s
static constexpr float CARD_ANIM_DURATION_SHORT = 80.0f / 60.0f; // ≈ 1.3333 s
static constexpr float CARD_ANIM_DURATION_SLOW = 250.0f / 60.0f; // ≈ 4.1667 s
static constexpr float CARD_ANIM_DURATION_VERY_SLOW = 300.0f / 60.0f; // ≈ 5.0000 s
static constexpr float CARD_ANIM_DELAY_LONG = 0.45f; // Retraso largo antes de animación
static constexpr float CARD_OFFSET_MARGIN = 10.0f; // Margen fuera de pantalla
// --- Estados internos --- // --- Estados internos ---
enum class State { enum class State {
SCENES, SCENES,
@@ -86,28 +42,27 @@ class Intro {
std::unique_ptr<TiledBG> tiled_bg_; // Fondo en mosaico std::unique_ptr<TiledBG> tiled_bg_; // Fondo en mosaico
// --- Variables --- // --- Variables ---
Uint64 last_time_ = 0; // Último timestamp para calcular delta-time Uint64 ticks_ = 0; // Contador de ticks para ajustar la velocidad del programa
int scene_ = 0; // Indica qué escena está activa int scene_ = 0; // Indica qué escena está activa
State state_ = State::SCENES; // Estado principal de la intro State state_ = State::SCENES; // Estado principal de la intro
PostState post_state_ = PostState::STOP_BG; // Estado POST PostState post_state_ = PostState::STOP_BG; // Estado POST
float state_start_time_; // Tiempo de inicio del estado actual (segundos) Uint32 state_start_time_; // Tiempo de inicio del estado actual
Color bg_color_ = param.intro.bg_color; // Color de fondo Color bg_color_ = param.intro.bg_color; // Color de fondo
// --- Métodos internos --- // --- Métodos internos ---
void update(float delta_time); // Actualiza las variables del objeto void update(); // Actualiza las variables del objeto
void render(); // Dibuja el objeto en pantalla void render(); // Dibuja el objeto en pantalla
static void checkInput(); // Comprueba las entradas static void checkInput(); // Comprueba las entradas
static void checkEvents(); // Comprueba los eventos static void checkEvents(); // Comprueba los eventos
void updateScenes(); // Actualiza las escenas de la intro void updateScenes(); // Actualiza las escenas de la intro
void initSprites(); // Inicializa las imágenes void initSprites(); // Inicializa las imágenes
void initTexts(); // Inicializa los textos void initTexts(); // Inicializa los textos
void updateSprites(float delta_time); // Actualiza los sprites void updateSprites(); // Actualiza los sprites
void updateTexts(float delta_time); // Actualiza los textos void updateTexts(); // Actualiza los textos
void renderSprites(); // Dibuja los sprites void renderSprites(); // Dibuja los sprites
void renderTexts(); // Dibuja los textos void renderTexts(); // Dibuja los textos
static void renderTextRect(); // Dibuja el rectangulo de fondo del texto; static void renderTextRect(); // Dibuja el rectangulo de fondo del texto;
void updatePostState(); // Actualiza el estado POST void updatePostState(); // Actualiza el estado POST
float calculateDeltaTime(); // Calcula el tiempo transcurrido desde el último frame
// --- Métodos para manejar cada escena individualmente --- // --- Métodos para manejar cada escena individualmente ---
void updateScene0(); void updateScene0();

View File

@@ -28,38 +28,38 @@ Logo::Logo()
dest_.y = param.game.game_area.center_y - jail_texture_->getHeight() / 2; dest_.y = param.game.game_area.center_y - jail_texture_->getHeight() / 2;
since_sprite_->setPosition(SDL_FRect{ since_sprite_->setPosition(SDL_FRect{
static_cast<float>((param.game.width - since_texture_->getWidth()) / 2), static_cast<float>((param.game.width - since_texture_->getWidth()) / 2),
static_cast<float>(SINCE_SPRITE_Y_OFFSET + jail_texture_->getHeight() + LOGO_SPACING), static_cast<float>(83 + jail_texture_->getHeight() + 5),
static_cast<float>(since_texture_->getWidth()), static_cast<float>(since_texture_->getWidth()),
static_cast<float>(since_texture_->getHeight())}); static_cast<float>(since_texture_->getHeight())});
since_sprite_->setY(dest_.y + jail_texture_->getHeight() + LOGO_SPACING); since_sprite_->setY(dest_.y + jail_texture_->getHeight() + 5);
since_sprite_->setSpriteClip(0, 0, since_texture_->getWidth(), since_texture_->getHeight()); since_sprite_->setSpriteClip(0, 0, since_texture_->getWidth(), since_texture_->getHeight());
since_texture_->setColor(SPECTRUM_BLACK.r, SPECTRUM_BLACK.g, SPECTRUM_BLACK.b); since_texture_->setColor(0x00, 0x00, 0x00);
// Crea los sprites de cada linea // Crea los sprites de cada linea
for (int i = 0; i < jail_texture_->getHeight(); ++i) { for (int i = 0; i < jail_texture_->getHeight(); ++i) {
auto temp = std::make_unique<Sprite>(jail_texture_, 0, i, jail_texture_->getWidth(), SPRITE_LINE_HEIGHT); auto temp = std::make_unique<Sprite>(jail_texture_, 0, i, jail_texture_->getWidth(), 1);
temp->setSpriteClip(0, i, jail_texture_->getWidth(), SPRITE_LINE_HEIGHT); temp->setSpriteClip(0, i, jail_texture_->getWidth(), 1);
const int POS_X = (i % 2 == 0) ? param.game.width + (i * LINE_OFFSET_FACTOR) : -jail_texture_->getWidth() - (i * LINE_OFFSET_FACTOR); const int POS_X = (i % 2 == 0) ? param.game.width + (i * 3) : -jail_texture_->getWidth() - (i * 3);
temp->setX(POS_X); temp->setX(POS_X);
temp->setY(dest_.y + i); temp->setY(dest_.y + i);
jail_sprite_.push_back(std::move(temp)); jail_sprite_.push_back(std::move(temp));
} }
// Inicializa el vector de colores con la paleta ZX Spectrum // Inicializa el vector de colores
color_.emplace_back(SPECTRUM_BLACK); color_.emplace_back(0x00, 0x00, 0x00); // Black
color_.emplace_back(SPECTRUM_BLUE); color_.emplace_back(0x00, 0x00, 0xd8); // Blue
color_.emplace_back(SPECTRUM_RED); color_.emplace_back(0xd8, 0x00, 0x00); // Red
color_.emplace_back(SPECTRUM_MAGENTA); color_.emplace_back(0xd8, 0x00, 0xd8); // Magenta
color_.emplace_back(SPECTRUM_GREEN); color_.emplace_back(0x00, 0xd8, 0x00); // Green
color_.emplace_back(SPECTRUM_CYAN); color_.emplace_back(0x00, 0xd8, 0xd8); // Cyan
color_.emplace_back(SPECTRUM_YELLOW); color_.emplace_back(0xd8, 0xd8, 0x00); // Yellow
color_.emplace_back(SPECTRUM_WHITE); color_.emplace_back(0xFF, 0xFF, 0xFF); // Bright white
} }
// Destructor // Destructor
Logo::~Logo() { Logo::~Logo() {
jail_texture_->setColor(RESET_COLOR.r, RESET_COLOR.g, RESET_COLOR.b); jail_texture_->setColor(255, 255, 255);
since_texture_->setColor(RESET_COLOR.r, RESET_COLOR.g, RESET_COLOR.b); since_texture_->setColor(255, 255, 255);
Audio::get()->stopAllSounds(); Audio::get()->stopAllSounds();
Audio::get()->stopMusic(); Audio::get()->stopMusic();
} }
@@ -78,30 +78,22 @@ void Logo::checkInput() {
GlobalInputs::check(); GlobalInputs::check();
} }
// Maneja la reproducción del sonido del logo
void Logo::handleSound() {
static bool sound_triggered = false;
if (!sound_triggered && elapsed_time_s_ >= SOUND_TRIGGER_TIME_S) {
Audio::get()->playSound("logo.wav");
sound_triggered = true;
}
}
// Gestiona el logo de JAILGAMES // Gestiona el logo de JAILGAMES
void Logo::updateJAILGAMES(float delta_time) { void Logo::updateJAILGAMES() {
if (elapsed_time_s_ > SOUND_TRIGGER_TIME_S) { if (counter_ == 30) {
const float PIXELS_TO_MOVE = LOGO_SPEED_PX_PER_S * delta_time; Audio::get()->playSound("logo.wav");
}
for (size_t i = 0; i < jail_sprite_.size(); ++i) { if (counter_ > 30) {
for (int i = 0; i < (int)jail_sprite_.size(); ++i) {
if (jail_sprite_[i]->getX() != dest_.x) { if (jail_sprite_[i]->getX() != dest_.x) {
if (i % 2 == 0) { if (i % 2 == 0) {
jail_sprite_[i]->incX(-PIXELS_TO_MOVE); jail_sprite_[i]->incX(-SPEED);
if (jail_sprite_[i]->getX() < dest_.x) { if (jail_sprite_[i]->getX() < dest_.x) {
jail_sprite_[i]->setX(dest_.x); jail_sprite_[i]->setX(dest_.x);
} }
} else { } else {
jail_sprite_[i]->incX(PIXELS_TO_MOVE); jail_sprite_[i]->incX(SPEED);
if (jail_sprite_[i]->getX() > dest_.x) { if (jail_sprite_[i]->getX() > dest_.x) {
jail_sprite_[i]->setX(dest_.x); jail_sprite_[i]->setX(dest_.x);
} }
@@ -111,41 +103,43 @@ void Logo::updateJAILGAMES(float delta_time) {
} }
// Comprueba si ha terminado el logo // Comprueba si ha terminado el logo
if (elapsed_time_s_ >= END_LOGO_TIME_S + POST_LOGO_DURATION_S) { if (counter_ == END_LOGO_COUNTER_MARK + POST_LOGO_DURATION) {
Section::name = Section::Name::INTRO; Section::name = Section::Name::INTRO;
} }
} }
// Gestiona el color de las texturas // Gestiona el color de las texturas
void Logo::updateTextureColors(float delta_time) { void Logo::updateTextureColors() {
constexpr int INC = 4;
// Manejo de 'sinceTexture' // Manejo de 'sinceTexture'
for (int i = 0; i <= MAX_SINCE_COLOR_INDEX; ++i) { for (int i = 0; i <= 7; ++i) {
const float target_time = SHOW_SINCE_SPRITE_TIME_S + COLOR_CHANGE_INTERVAL_S * i; if (counter_ == SHOW_SINCE_SPRITE_COUNTER_MARK + INC * i) {
if (elapsed_time_s_ >= target_time && elapsed_time_s_ - delta_time < target_time) {
since_texture_->setColor(color_[i].r, color_[i].g, color_[i].b); since_texture_->setColor(color_[i].r, color_[i].g, color_[i].b);
} }
} }
// Manejo de 'jailTexture' y 'sinceTexture' en el fade // Manejo de 'jailTexture' y 'sinceTexture' en el fade
for (int i = 0; i <= MAX_FADE_COLOR_INDEX; ++i) { for (int i = 0; i <= 6; ++i) {
const float target_time = INIT_FADE_TIME_S + COLOR_CHANGE_INTERVAL_S * i; if (counter_ == INIT_FADE_COUNTER_MARK + INC * i) {
if (elapsed_time_s_ >= target_time && elapsed_time_s_ - delta_time < target_time) { jail_texture_->setColor(color_[6 - i].r, color_[6 - i].g, color_[6 - i].b);
jail_texture_->setColor(color_[MAX_FADE_COLOR_INDEX - i].r, color_[MAX_FADE_COLOR_INDEX - i].g, color_[MAX_FADE_COLOR_INDEX - i].b); since_texture_->setColor(color_[6 - i].r, color_[6 - i].g, color_[6 - i].b);
since_texture_->setColor(color_[MAX_FADE_COLOR_INDEX - i].r, color_[MAX_FADE_COLOR_INDEX - i].g, color_[MAX_FADE_COLOR_INDEX - i].b);
} }
} }
} }
// Actualiza las variables // Actualiza las variables
void Logo::update(float delta_time) { void Logo::update() {
elapsed_time_s_ += delta_time; // Acumula el tiempo transcurrido if (SDL_GetTicks() - ticks_ > param.game.speed) {
ticks_ = SDL_GetTicks(); // Actualiza el contador de ticks
Screen::get()->update(); // Actualiza el objeto screen
Screen::get()->update(); // Actualiza el objeto screen updateJAILGAMES(); // Actualiza el logo de JAILGAMES
Audio::update(); // Actualiza el objeto audio updateTextureColors(); // Actualiza los colores de las texturas
++counter_; // Gestiona el contador
}
handleSound(); // Maneja la reproducción del sonido Audio::update();
updateTextureColors(delta_time); // Actualiza los colores de las texturas
updateJAILGAMES(delta_time); // Actualiza el logo de JAILGAMES
} }
// Dibuja en pantalla // Dibuja en pantalla
@@ -160,23 +154,11 @@ void Logo::render() {
SCREEN->render(); SCREEN->render();
} }
// Calcula el tiempo transcurrido desde el último frame
float Logo::calculateDeltaTime() {
const Uint64 current_time = SDL_GetTicks();
const float delta_time = static_cast<float>(current_time - last_time_) / 1000.0f; // Convertir ms a segundos
last_time_ = current_time;
return delta_time;
}
// Bucle para el logo del juego // Bucle para el logo del juego
void Logo::run() { void Logo::run() {
last_time_ = SDL_GetTicks();
while (Section::name == Section::Name::LOGO) { while (Section::name == Section::Name::LOGO) {
const float delta_time = calculateDeltaTime();
checkInput(); checkInput();
update(delta_time); update();
checkEvents(); // Tiene que ir antes del render checkEvents(); // Tiene que ir antes del render
render(); render();
} }
@@ -189,7 +171,7 @@ void Logo::renderJAILGAMES() {
sprite->render(); sprite->render();
} }
if (elapsed_time_s_ >= SHOW_SINCE_SPRITE_TIME_S) { if (counter_ >= SHOW_SINCE_SPRITE_COUNTER_MARK) {
since_sprite_->render(); since_sprite_->render();
} }
} }

View File

@@ -10,21 +10,12 @@
class Texture; class Texture;
// --- Clase Logo: pantalla de presentación de JAILGAMES con efectos retro --- // --- Clase Logo: dibuja el logo de JAILGAMES con efectos visuales ---
// // Esta clase gestiona un estado del programa. Se encarga de dibujar por pantalla el
// Esta clase gestiona el estado inicial del programa, mostrando el logo corporativo // logo de "JAILGAMES" utilizando un sencillo efecto consistente en generar un sprite por
// de JAILGAMES con efectos visuales inspirados en el ZX Spectrum. // cada línea del bitmap que forma la palabra "JAILGAMES". Posteriormente realiza una
// // modulación de color sobre la textura para simular un fade to black al estilo
// Funcionalidades principales: // ZX Spectrum.
// • Animación de convergencia: cada línea del logo entra desde los laterales
// • Efectos de color: transiciones automáticas usando la paleta ZX Spectrum
// • Audio sincronizado: reproduce sonido del logo en momento específico
// • Transición temporal: duración controlada con paso automático al siguiente estado
// • Sistema delta-time: animaciones suaves independientes del framerate
//
// La clase utiliza un sistema de tiempo basado en segundos para garantizar
// consistencia visual en diferentes velocidades de procesamiento.
class Logo { class Logo {
public: public:
// --- Constructor y destructor --- // --- Constructor y destructor ---
@@ -35,35 +26,12 @@ class Logo {
void run(); void run();
private: private:
// --- Constantes de tiempo (en segundos) --- // --- Constantes ---
static constexpr float SOUND_TRIGGER_TIME_S = 0.5f; // Tiempo para activar el sonido del logo static constexpr int SHOW_SINCE_SPRITE_COUNTER_MARK = 70; // Tiempo del contador en el que empieza a verse el sprite de "SINCE 1998"
static constexpr float SHOW_SINCE_SPRITE_TIME_S = 1.167f; // Tiempo para mostrar el sprite "SINCE 1998" static constexpr int INIT_FADE_COUNTER_MARK = 300; // Tiempo del contador cuando inicia el fade a negro
static constexpr float INIT_FADE_TIME_S = 5.0f; // Tiempo de inicio del fade a negro static constexpr int END_LOGO_COUNTER_MARK = 400; // Tiempo del contador para terminar el logo
static constexpr float END_LOGO_TIME_S = 6.668f; // Tiempo de finalización del logo static constexpr int POST_LOGO_DURATION = 20; // Tiempo que dura el logo con el fade al máximo
static constexpr float POST_LOGO_DURATION_S = 0.333f; // Duración adicional después del fade static constexpr int SPEED = 8; // Velocidad de desplazamiento de cada línea
static constexpr float LOGO_SPEED_PX_PER_S = 480.0f; // Velocidad de desplazamiento (píxeles por segundo) - 8.0f/16.67f*1000
static constexpr float COLOR_CHANGE_INTERVAL_S = 0.0667f; // Intervalo entre cambios de color (~4 frames a 60fps)
// --- Constantes de layout ---
static constexpr int SINCE_SPRITE_Y_OFFSET = 83; // Posición Y base del sprite "Since 1998"
static constexpr int LOGO_SPACING = 5; // Espaciado entre elementos del logo
static constexpr int LINE_OFFSET_FACTOR = 3; // Factor de desplazamiento inicial por línea
static constexpr int SPRITE_LINE_HEIGHT = 1; // Altura de cada línea sprite
// --- Constantes de colores ---
static constexpr int MAX_SINCE_COLOR_INDEX = 7; // Índice máximo para colores del sprite "Since"
static constexpr int MAX_FADE_COLOR_INDEX = 6; // Índice máximo para colores del fade
// --- Paleta ZX Spectrum para efectos de logo ---
static constexpr Color SPECTRUM_BLACK = Color(0x00, 0x00, 0x00); // Negro
static constexpr Color SPECTRUM_BLUE = Color(0x00, 0x00, 0xd8); // Azul
static constexpr Color SPECTRUM_RED = Color(0xd8, 0x00, 0x00); // Rojo
static constexpr Color SPECTRUM_MAGENTA = Color(0xd8, 0x00, 0xd8); // Magenta
static constexpr Color SPECTRUM_GREEN = Color(0x00, 0xd8, 0x00); // Verde
static constexpr Color SPECTRUM_CYAN = Color(0x00, 0xd8, 0xd8); // Cian
static constexpr Color SPECTRUM_YELLOW = Color(0xd8, 0xd8, 0x00); // Amarillo
static constexpr Color SPECTRUM_WHITE = Color(0xFF, 0xFF, 0xFF); // Blanco brillante
static constexpr Color RESET_COLOR = Color(255, 255, 255); // Color de reset
// --- Objetos y punteros --- // --- Objetos y punteros ---
std::shared_ptr<Texture> since_texture_; // Textura con los gráficos "Since 1998" std::shared_ptr<Texture> since_texture_; // Textura con los gráficos "Since 1998"
@@ -72,19 +40,17 @@ class Logo {
std::vector<std::unique_ptr<Sprite>> jail_sprite_; // Vector con los sprites de cada línea que forman el bitmap JAILGAMES std::vector<std::unique_ptr<Sprite>> jail_sprite_; // Vector con los sprites de cada línea que forman el bitmap JAILGAMES
// --- Variables --- // --- Variables ---
std::vector<Color> color_; // Vector con los colores para el fade std::vector<Color> color_; // Vector con los colores para el fade
float elapsed_time_s_ = 0.0f; // Tiempo transcurrido en segundos int counter_ = 0; // Contador
Uint64 last_time_ = 0; // Último timestamp para calcular delta-time Uint64 ticks_ = 0; // Contador de ticks para ajustar la velocidad del programa
SDL_FPoint dest_; // Posición donde dibujar el logo SDL_FPoint dest_; // Posición donde dibujar el logo
// --- Métodos internos --- // --- Métodos internos ---
void update(float delta_time); // Actualiza las variables void update(); // Actualiza las variables
void render(); // Dibuja en pantalla void render(); // Dibuja en pantalla
static void checkEvents(); // Comprueba el manejador de eventos static void checkEvents(); // Comprueba el manejador de eventos
static void checkInput(); // Comprueba las entradas static void checkInput(); // Comprueba las entradas
void updateJAILGAMES(float delta_time); // Gestiona el logo de JAILGAMES void updateJAILGAMES(); // Gestiona el logo de JAILGAMES
void renderJAILGAMES(); // Renderiza el logo de JAILGAMES void renderJAILGAMES(); // Renderiza el logo de JAILGAMES
void updateTextureColors(float delta_time); // Gestiona el color de las texturas void updateTextureColors(); // Gestiona el color de las texturas
void handleSound(); // Maneja la reproducción del sonido del logo
float calculateDeltaTime(); // Calcula el tiempo transcurrido desde el último frame
}; };

View File

@@ -42,7 +42,7 @@ Title::Title()
tiled_bg_(std::make_unique<TiledBG>(param.game.game_area.rect, TiledBGMode::RANDOM)), tiled_bg_(std::make_unique<TiledBG>(param.game.game_area.rect, TiledBGMode::RANDOM)),
game_logo_(std::make_unique<GameLogo>(param.game.game_area.center_x, param.title.title_c_c_position)), game_logo_(std::make_unique<GameLogo>(param.game.game_area.center_x, param.title.title_c_c_position)),
mini_logo_sprite_(std::make_unique<Sprite>(Resource::get()->getTexture("logo_jailgames_mini.png"))), mini_logo_sprite_(std::make_unique<Sprite>(Resource::get()->getTexture("logo_jailgames_mini.png"))),
state_(State::LOGO_ANIMATING), state_(TitleState::LOGO_ANIMATING),
num_controllers_(Input::get()->getNumGamepads()) { num_controllers_(Input::get()->getNumGamepads()) {
// Configura objetos // Configura objetos
tiled_bg_->setColor(param.title.bg_color); tiled_bg_->setColor(param.title.bg_color);
@@ -60,16 +60,16 @@ Title::Title()
Section::attract_mode = IS_TITLE_TO_DEMO ? Section::AttractMode::TITLE_TO_LOGO : Section::AttractMode::TITLE_TO_DEMO; Section::attract_mode = IS_TITLE_TO_DEMO ? Section::AttractMode::TITLE_TO_LOGO : Section::AttractMode::TITLE_TO_DEMO;
// Define los anclajes de los elementos // Define los anclajes de los elementos
anchor_.mini_logo = (param.game.height / MINI_LOGO_Y_DIVISOR * MINI_LOGO_Y_FACTOR) + BLOCK; anchor_.mini_logo = (param.game.height / 5 * 4) + BLOCK;
mini_logo_sprite_->setY(anchor_.mini_logo); mini_logo_sprite_->setY(anchor_.mini_logo);
anchor_.copyright_text = anchor_.mini_logo + mini_logo_sprite_->getHeight() + COPYRIGHT_TEXT_SPACING; anchor_.copyright_text = anchor_.mini_logo + mini_logo_sprite_->getHeight() + 3;
} }
// Destructor // Destructor
Title::~Title() { Title::~Title() {
Audio::get()->stopAllSounds(); Audio::get()->stopAllSounds();
if (Section::name == Section::Name::LOGO) { if (Section::name == Section::Name::LOGO) {
Audio::get()->fadeOutMusic(MUSIC_FADE_OUT_SHORT_MS); Audio::get()->fadeOutMusic(300);
} }
// Desregistra los jugadores de Options // Desregistra los jugadores de Options
@@ -78,27 +78,20 @@ Title::~Title() {
} }
// Actualiza las variables del objeto // Actualiza las variables del objeto
void Title::update(float deltaTime) { void Title::update() {
Screen::get()->update(); if (SDL_GetTicks() - ticks_ > param.game.speed) {
updateFade(); ticks_ = SDL_GetTicks();
updateState(deltaTime); Screen::get()->update();
updateStartPrompt();
for (auto& player : players_) { updateFade();
player->update(deltaTime); updateState();
updateStartPrompt();
updatePlayers();
} }
Audio::update(); Audio::update();
} }
// Calcula el tiempo transcurrido desde el último frame
float Title::calculateDeltaTime() {
const Uint64 current_time = SDL_GetTicks();
const float delta_time = static_cast<float>(current_time - last_time_);
last_time_ = current_time;
return delta_time;
}
// Dibuja el objeto en pantalla // Dibuja el objeto en pantalla
void Title::render() { void Title::render() {
static auto* const SCREEN = Screen::get(); static auto* const SCREEN = Screen::get();
@@ -143,7 +136,7 @@ void Title::handleDebugColorKeys(SDL_Keycode key) {
adjustColorComponent(key, color_); adjustColorComponent(key, color_);
counter_time_ = 0.0f; counter_ = 0;
tiled_bg_->setColor(color_); tiled_bg_->setColor(color_);
printColorValue(color_); printColorValue(color_);
} }
@@ -277,7 +270,7 @@ void Title::handleStartButtonPress(const Options::Gamepad* controller) {
} }
auto Title::canProcessStartButton() const -> bool { auto Title::canProcessStartButton() const -> bool {
return (state_ != State::LOGO_ANIMATING || ALLOW_TITLE_ANIMATION_SKIP); return (state_ != TitleState::LOGO_ANIMATING || ALLOW_TITLE_ANIMATION_SKIP);
} }
void Title::processPlayer1Start() { void Title::processPlayer1Start() {
@@ -296,26 +289,22 @@ void Title::processPlayer2Start() {
void Title::activatePlayerAndSetState(Player::Id player_id) { void Title::activatePlayerAndSetState(Player::Id player_id) {
getPlayer(player_id)->setPlayingState(Player::State::TITLE_ANIMATION); getPlayer(player_id)->setPlayingState(Player::State::TITLE_ANIMATION);
setState(State::START_HAS_BEEN_PRESSED); setState(TitleState::START_HAS_BEEN_PRESSED);
counter_time_ = 0.0f; counter_ = 0;
} }
// Bucle para el titulo del juego // Bucle para el titulo del juego
void Title::run() { void Title::run() {
last_time_ = SDL_GetTicks();
while (Section::name == Section::Name::TITLE) { while (Section::name == Section::Name::TITLE) {
const float delta_time = calculateDeltaTime();
checkInput(); checkInput();
update(delta_time); update();
checkEvents(); // Tiene que ir antes del render checkEvents(); // Tiene que ir antes del render
render(); render();
} }
} }
// Reinicia el contador interno // Reinicia el contador interno
void Title::resetCounter() { counter_time_ = 0.0f; } void Title::resetCounter() { counter_ = 0; }
// Intercambia la asignación de mandos a los jugadores // Intercambia la asignación de mandos a los jugadores
void Title::swapControllers() { void Title::swapControllers() {
@@ -377,22 +366,22 @@ void Title::updateFade() {
} }
// Actualiza el estado // Actualiza el estado
void Title::updateState(float deltaTime) { void Title::updateState() {
game_logo_->update(deltaTime);
tiled_bg_->update(deltaTime);
// Establece la lógica según el estado // Establece la lógica según el estado
switch (state_) { switch (state_) {
case State::LOGO_ANIMATING: { case TitleState::LOGO_ANIMATING: {
game_logo_->update();
if (game_logo_->hasFinished()) { if (game_logo_->hasFinished()) {
setState(State::LOGO_FINISHED); setState(TitleState::LOGO_FINISHED);
} }
break; break;
} }
case State::LOGO_FINISHED: { case TitleState::LOGO_FINISHED: {
counter_time_ += deltaTime; ++counter_; // Incrementa el contador
game_logo_->update(); // Actualiza el logo con el título del juego
tiled_bg_->update(); // Actualiza el mosaico de fondo
if (counter_time_ >= param.title.title_duration) { if (counter_ == param.title.title_duration) {
// El menu ha hecho time out // El menu ha hecho time out
fade_->setPostDuration(0); fade_->setPostDuration(0);
fade_->activate(); fade_->activate();
@@ -400,10 +389,12 @@ void Title::updateState(float deltaTime) {
} }
break; break;
} }
case State::START_HAS_BEEN_PRESSED: { case TitleState::START_HAS_BEEN_PRESSED: {
counter_time_ += deltaTime; ++counter_; // Incrementa el contador
game_logo_->update(); // Actualiza el logo con el título del juego
tiled_bg_->update(); // Actualiza el mosaico de fondo
if (counter_time_ >= START_PRESSED_DELAY_MS) { if (counter_ == 100) {
fade_->activate(); fade_->activate();
} }
break; break;
@@ -415,16 +406,22 @@ void Title::updateState(float deltaTime) {
} }
void Title::updateStartPrompt() { void Title::updateStartPrompt() {
constexpr Uint32 LOGO_BLINK_PERIOD = 833; // milisegundos
constexpr Uint32 LOGO_BLINK_ON_TIME = 583; // 833 - 250
constexpr Uint32 START_BLINK_PERIOD = 167;
constexpr Uint32 START_BLINK_ON_TIME = 83; // 167 - 83
Uint32 time_ms = SDL_GetTicks(); Uint32 time_ms = SDL_GetTicks();
bool condition_met = false; bool condition_met = false;
switch (state_) { switch (state_) {
case State::LOGO_FINISHED: case TitleState::LOGO_FINISHED:
condition_met = (time_ms % LOGO_BLINK_PERIOD_MS) >= (LOGO_BLINK_PERIOD_MS - LOGO_BLINK_ON_TIME_MS); condition_met = (time_ms % LOGO_BLINK_PERIOD) >= (LOGO_BLINK_PERIOD - LOGO_BLINK_ON_TIME);
break; break;
case State::START_HAS_BEEN_PRESSED: case TitleState::START_HAS_BEEN_PRESSED:
condition_met = (time_ms % START_BLINK_PERIOD_MS) >= (START_BLINK_PERIOD_MS - START_BLINK_ON_TIME_MS); condition_met = (time_ms % START_BLINK_PERIOD) >= (START_BLINK_PERIOD - START_BLINK_ON_TIME);
break; break;
default: default:
@@ -448,7 +445,7 @@ void Title::renderStartPrompt() {
} }
void Title::renderCopyright() { void Title::renderCopyright() {
if (state_ != State::LOGO_ANIMATING) { if (state_ != TitleState::LOGO_ANIMATING) {
// Mini logo // Mini logo
mini_logo_sprite_->render(); mini_logo_sprite_->render();
@@ -465,20 +462,20 @@ void Title::renderCopyright() {
} }
// Cambia el estado // Cambia el estado
void Title::setState(State state) { void Title::setState(TitleState state) {
if (state_ == state) { if (state_ == state) {
return; return;
} }
state_ = state; state_ = state;
switch (state_) { switch (state_) {
case State::LOGO_ANIMATING: case TitleState::LOGO_ANIMATING:
break; break;
case State::LOGO_FINISHED: case TitleState::LOGO_FINISHED:
Audio::get()->playMusic("title.ogg"); Audio::get()->playMusic("title.ogg");
break; break;
case State::START_HAS_BEEN_PRESSED: case TitleState::START_HAS_BEEN_PRESSED:
Audio::get()->fadeOutMusic(MUSIC_FADE_OUT_LONG_MS); Audio::get()->fadeOutMusic(1500);
break; break;
} }
} }
@@ -547,6 +544,13 @@ void Title::initPlayers() {
} }
} }
// Actualza los jugadores
void Title::updatePlayers() {
for (auto& player : players_) {
player->update();
}
}
// Renderiza los jugadores // Renderiza los jugadores
void Title::renderPlayers() { void Title::renderPlayers() {
for (auto const& player : players_) { for (auto const& player : players_) {

View File

@@ -19,22 +19,11 @@ namespace Options {
struct Gamepad; struct Gamepad;
} // namespace Options } // namespace Options
// --- Clase Title: pantalla de título y menú principal del juego --- // --- Constantes ---
// constexpr std::string_view TEXT_COPYRIGHT = "@2020,2025 JailDesigner"; // Texto de copyright
// Esta clase gestiona la pantalla de título del juego, incluyendo el menú principal constexpr bool ALLOW_TITLE_ANIMATION_SKIP = false; // Permite saltar la animación del título
// y la transición entre diferentes modos de juego.
// // --- Clase Title: gestiona el estado de título/menú principal del juego ---
// Funcionalidades principales:
// • Logo animado: muestra y anima el logotipo principal del juego
// • Selección de jugadores: permite iniciar partidas de 1 o 2 jugadores
// • Modo attract: cicla automáticamente entre título y demo
// • Efectos visuales: parpadeos, transiciones y efectos de fondo
// • Gestión de controles: soporte para teclado y múltiples gamepads
// • Timeouts automáticos: transición automática si no hay interacción
// • Debug de colores: herramientas de depuración para ajustes visuales
//
// La clase utiliza un sistema de tiempo basado en milisegundos para garantizar
// comportamiento consistente independientemente del framerate.
class Title { class Title {
public: public:
// --- Constructor y destructor --- // --- Constructor y destructor ---
@@ -45,28 +34,8 @@ class Title {
void run(); void run();
private: private:
// --- Constantes de tiempo (en milisegundos) ---
static constexpr float START_PRESSED_DELAY_MS = 1666.67f; // Tiempo antes de fade tras pulsar start (100 frames a 60fps)
static constexpr int MUSIC_FADE_OUT_LONG_MS = 1500; // Fade out largo de música
static constexpr int MUSIC_FADE_OUT_SHORT_MS = 300; // Fade out corto de música
// --- Constantes de parpadeo ---
static constexpr Uint32 LOGO_BLINK_PERIOD_MS = 833; // Período de parpadeo del logo
static constexpr Uint32 LOGO_BLINK_ON_TIME_MS = 583; // Tiempo encendido del logo (833-250)
static constexpr Uint32 START_BLINK_PERIOD_MS = 167; // Período de parpadeo del start
static constexpr Uint32 START_BLINK_ON_TIME_MS = 83; // Tiempo encendido del start (167-83)
// --- Constantes de layout ---
static constexpr int MINI_LOGO_Y_DIVISOR = 5; // Divisor para posición Y del mini logo
static constexpr int MINI_LOGO_Y_FACTOR = 4; // Factor para posición Y del mini logo
static constexpr int COPYRIGHT_TEXT_SPACING = 3; // Espaciado del texto de copyright
// --- Constantes de texto y configuración ---
static constexpr std::string_view TEXT_COPYRIGHT = "@2020,2025 JailDesigner"; // Texto de copyright
static constexpr bool ALLOW_TITLE_ANIMATION_SKIP = false; // Permite saltar la animación del título
// --- Enums --- // --- Enums ---
enum class State { enum class TitleState {
LOGO_ANIMATING, // El logo está animándose LOGO_ANIMATING, // El logo está animándose
LOGO_FINISHED, // El logo ha terminado de animarse LOGO_FINISHED, // El logo ha terminado de animarse
START_HAS_BEEN_PRESSED, // Se ha pulsado el botón de start START_HAS_BEEN_PRESSED, // Se ha pulsado el botón de start
@@ -90,20 +59,19 @@ class Title {
Anchor anchor_; // Anclas para definir la posición de los elementos del título Anchor anchor_; // Anclas para definir la posición de los elementos del título
Section::Name next_section_; // Siguiente sección a cargar Section::Name next_section_; // Siguiente sección a cargar
Section::Options selection_ = Section::Options::TITLE_TIME_OUT; // Opción elegida en el título Section::Options selection_ = Section::Options::TITLE_TIME_OUT; // Opción elegida en el título
State state_; // Estado actual de la sección TitleState state_; // Estado actual de la sección
Uint64 last_time_ = 0; // Último timestamp para calcular delta-time Uint64 ticks_ = 0; // Contador de ticks para ajustar la velocidad
float counter_time_ = 0.0f; // Temporizador para la pantalla de título (en milisegundos) int counter_ = 0; // Temporizador para la pantalla de título
int num_controllers_; // Número de mandos conectados int num_controllers_; // Número de mandos conectados
bool should_render_start_prompt_ = false; // Indica si se muestra el texto de PRESS START BUTTON TO PLAY bool should_render_start_prompt_ = false; // Indica si se muestra el texto de PRESS START BUTTON TO PLAY
bool player1_start_pressed_ = false; // Indica si se ha pulsado el botón de empezar para el jugador 1 bool player1_start_pressed_ = false; // Indica si se ha pulsado el botón de empezar para el jugador 1
bool player2_start_pressed_ = false; // Indica si se ha pulsado el botón de empezar para el jugador 2 bool player2_start_pressed_ = false; // Indica si se ha pulsado el botón de empezar para el jugador 2
// --- Ciclo de vida del título --- // --- Ciclo de vida del título ---
void update(float deltaTime); // Actualiza las variables del objeto void update(); // Actualiza las variables del objeto
float calculateDeltaTime(); // Calcula el tiempo transcurrido desde el último frame void updateState(); // Actualiza el estado actual del título
void updateState(float deltaTime); // Actualiza el estado actual del título void setState(TitleState state); // Cambia el estado del título
void setState(State state); // Cambia el estado del título void resetCounter(); // Reinicia el contador interno
void resetCounter(); // Reinicia el contador interno
// --- Entrada de usuario --- // --- Entrada de usuario ---
void checkEvents(); // Comprueba los eventos void checkEvents(); // Comprueba los eventos

View File

@@ -2,12 +2,12 @@
#include "moving_sprite.h" // Para MovingSprite #include "moving_sprite.h" // Para MovingSprite
// Actualiza la posición y comprueba si ha llegado a su destino (time-based) // Actualiza la posición y comprueba si ha llegado a su destino
void SmartSprite::update(float deltaTime) { void SmartSprite::update() {
if (enabled_) { if (enabled_) {
MovingSprite::update(deltaTime); MovingSprite::update();
checkMove(); checkMove();
checkFinished(deltaTime); checkFinished();
} }
} }
@@ -71,19 +71,16 @@ void SmartSprite::checkMove() {
} }
} }
// Comprueba si ha terminado (time-based) // Comprueba si ha terminado
void SmartSprite::checkFinished(float deltaTime) { void SmartSprite::checkFinished() {
// Comprueba si ha llegado a su destino // Comprueba si ha llegado a su destino
on_destination_ = (getPosX() == dest_x_ && getPosY() == dest_y_); on_destination_ = (getPosX() == dest_x_ && getPosY() == dest_y_);
if (on_destination_) { if (on_destination_) {
if (finished_delay_ms_ == 0.0f) { if (finished_counter_ == 0) {
finished_ = true; finished_ = true;
} else { } else {
finished_timer_ += deltaTime; --finished_counter_;
if (finished_timer_ >= finished_delay_ms_) {
finished_ = true;
}
} }
} }
} }

View File

@@ -16,8 +16,8 @@ class SmartSprite : public AnimatedSprite {
~SmartSprite() override = default; ~SmartSprite() override = default;
// --- Métodos principales --- // --- Métodos principales ---
void update(float deltaTime) override; // Actualiza la posición y comprueba si ha llegado a su destino (time-based) void update() override; // Actualiza la posición y comprueba si ha llegado a su destino
void render() override; // Dibuja el sprite void render() override; // Dibuja el sprite
// --- Getters --- // --- Getters ---
auto getDestX() const -> int { return dest_x_; } // Obtiene la posición de destino en X auto getDestX() const -> int { return dest_x_; } // Obtiene la posición de destino en X
@@ -26,22 +26,21 @@ class SmartSprite : public AnimatedSprite {
auto hasFinished() const -> bool { return finished_; } // Indica si ya ha terminado auto hasFinished() const -> bool { return finished_; } // Indica si ya ha terminado
// --- Setters --- // --- Setters ---
void setFinishedDelay(float value) { finished_delay_ms_ = value; } // Establece el retraso para deshabilitarlo (ms) void setFinishedCounter(int value) { finished_counter_ = value; } // Establece el contador para deshabilitarlo
void setDestX(int x) { dest_x_ = x; } // Establece la posición de destino en X void setDestX(int x) { dest_x_ = x; } // Establece la posición de destino en X
void setDestY(int y) { dest_y_ = y; } // Establece la posición de destino en Y void setDestY(int y) { dest_y_ = y; } // Establece la posición de destino en Y
void setEnabled(bool value) { enabled_ = value; } // Habilita o deshabilita el objeto void setEnabled(bool value) { enabled_ = value; } // Habilita o deshabilita el objeto
private: private:
// --- Variables de estado --- // --- Variables de estado ---
int dest_x_ = 0; // Posición de destino en el eje X int dest_x_ = 0; // Posición de destino en el eje X
int dest_y_ = 0; // Posición de destino en el eje Y int dest_y_ = 0; // Posición de destino en el eje Y
float finished_delay_ms_ = 0.0f; // Retraso para deshabilitarlo (ms) int finished_counter_ = 0; // Contador para deshabilitarlo
float finished_timer_ = 0.0f; // Timer acumulado (ms)
bool on_destination_ = false; // Indica si está en el destino bool on_destination_ = false; // Indica si está en el destino
bool finished_ = false; // Indica si ya ha terminado bool finished_ = false; // Indica si ya ha terminado
bool enabled_ = false; // Indica si el objeto está habilitado bool enabled_ = false; // Indica si el objeto está habilitado
// --- Métodos internos --- // --- Métodos internos ---
void checkFinished(float deltaTime); // Comprueba si ha terminado (time-based) void checkFinished(); // Comprueba si ha terminado
void checkMove(); // Comprueba el movimiento void checkMove(); // Comprueba el movimiento
}; };

View File

@@ -17,12 +17,12 @@ Tabe::Tabe()
: sprite_(std::make_unique<AnimatedSprite>(Resource::get()->getTexture("tabe.png"), Resource::get()->getAnimation("tabe.ani"))), : sprite_(std::make_unique<AnimatedSprite>(Resource::get()->getTexture("tabe.png"), Resource::get()->getAnimation("tabe.ani"))),
timer_(Timer(param.tabe.min_spawn_time, param.tabe.max_spawn_time)) {} timer_(Timer(param.tabe.min_spawn_time, param.tabe.max_spawn_time)) {}
// Actualiza la lógica (time-based) // Actualiza la lógica
void Tabe::update(float deltaTime) { void Tabe::update() {
if (enabled_ && !timer_.is_paused) { if (enabled_ && !timer_.is_paused) {
sprite_->update(deltaTime); sprite_->update();
move(deltaTime); move();
updateState(deltaTime); updateState();
} }
timer_.update(); timer_.update();
@@ -38,14 +38,11 @@ void Tabe::render() {
} }
} }
// Mueve el objeto (time-based) // Mueve el objeto
void Tabe::move(float deltaTime) { void Tabe::move() {
// Convertir deltaTime (milisegundos) a factor de frame (asumiendo 60fps)
float frameFactor = deltaTime / (1000.0f / 60.0f);
const int X = static_cast<int>(x_); const int X = static_cast<int>(x_);
speed_ += accel_ * frameFactor; speed_ += accel_;
x_ += speed_ * frameFactor; x_ += speed_;
fly_distance_ -= std::abs(X - static_cast<int>(x_)); fly_distance_ -= std::abs(X - static_cast<int>(x_));
// Comprueba si sale por los bordes // Comprueba si sale por los bordes
@@ -80,8 +77,7 @@ void Tabe::move(float deltaTime) {
if (fly_distance_ <= 0) { if (fly_distance_ <= 0) {
if (waiting_counter_ > 0) { if (waiting_counter_ > 0) {
accel_ = speed_ = 0.0F; accel_ = speed_ = 0.0F;
waiting_counter_ -= frameFactor; --waiting_counter_;
if (waiting_counter_ < 0) waiting_counter_ = 0;
} else { } else {
constexpr int CHOICES = 4; constexpr int CHOICES = 4;
const std::array<Direction, CHOICES> LEFT = { const std::array<Direction, CHOICES> LEFT = {
@@ -179,13 +175,11 @@ void Tabe::setState(State state) {
} }
} }
// Actualiza el estado (time-based) // Actualiza el estado
void Tabe::updateState(float deltaTime) { void Tabe::updateState() {
if (state_ == State::HIT) { if (state_ == State::HIT) {
// Convertir deltaTime (milisegundos) a factor de frame (asumiendo 60fps) --hit_counter_;
float frameFactor = deltaTime / (1000.0f / 60.0f); if (hit_counter_ == 0) {
hit_counter_ -= frameFactor;
if (hit_counter_ <= 0) {
setState(State::FLY); setState(State::FLY);
} }
} }

View File

@@ -26,8 +26,8 @@ class Tabe {
~Tabe() = default; ~Tabe() = default;
// --- Métodos principales --- // --- Métodos principales ---
void update(float deltaTime); // Actualiza la lógica (time-based) void update(); // Actualiza la lógica
void render(); // Dibuja el objeto void render(); // Dibuja el objeto
void enable(); // Habilita el objeto void enable(); // Habilita el objeto
void setState(State state); // Establece el estado void setState(State state); // Establece el estado
auto tryToGetBonus() -> bool; // Intenta obtener el bonus auto tryToGetBonus() -> bool; // Intenta obtener el bonus
@@ -130,21 +130,21 @@ class Tabe {
float speed_ = 0.0F; // Velocidad de movimiento float speed_ = 0.0F; // Velocidad de movimiento
float accel_ = 0.0F; // Aceleración float accel_ = 0.0F; // Aceleración
int fly_distance_ = 0; // Distancia de vuelo int fly_distance_ = 0; // Distancia de vuelo
float waiting_counter_ = 0; // Tiempo que pasa quieto int waiting_counter_ = 0; // Tiempo que pasa quieto
bool enabled_ = false; // Indica si el objeto está activo bool enabled_ = false; // Indica si el objeto está activo
Direction direction_ = Direction::TO_THE_LEFT; // Dirección actual Direction direction_ = Direction::TO_THE_LEFT; // Dirección actual
Direction destiny_ = Direction::TO_THE_LEFT; // Destino Direction destiny_ = Direction::TO_THE_LEFT; // Destino
State state_ = State::FLY; // Estado actual State state_ = State::FLY; // Estado actual
float hit_counter_ = 0; // Contador para el estado HIT int hit_counter_ = 0; // Contador para el estado HIT
int number_of_hits_ = 0; // Cantidad de disparos recibidos int number_of_hits_ = 0; // Cantidad de disparos recibidos
bool has_bonus_ = true; // Indica si aún tiene el bonus para soltar bool has_bonus_ = true; // Indica si aún tiene el bonus para soltar
Timer timer_; // Temporizador para gestionar la aparición Timer timer_; // Temporizador para gestionar la aparición
// --- Métodos internos --- // --- Métodos internos ---
void move(float deltaTime); // Mueve el objeto (time-based) void move(); // Mueve el objeto
void shiftSprite() { sprite_->setPos(x_, y_); } // Actualiza la posición del sprite void shiftSprite() { sprite_->setPos(x_, y_); } // Actualiza la posición del sprite
void setRandomFlyPath(Direction direction, int length); // Establece un vuelo aleatorio void setRandomFlyPath(Direction direction, int length); // Establece un vuelo aleatorio
void updateState(float deltaTime); // Actualiza el estado (time-based) void updateState(); // Actualiza el estado
void updateTimer(); // Actualiza el temporizador void updateTimer(); // Actualiza el temporizador
void disable(); // Deshabilita el objeto void disable(); // Deshabilita el objeto
}; };

View File

@@ -3,7 +3,7 @@
#include <SDL3/SDL.h> // Para SDL_SetRenderTarget, SDL_CreateTexture, SDL_DestroyTexture, SDL_FRect, SDL_GetRenderTarget, SDL_RenderTexture, SDL_PixelFormat, SDL_TextureAccess #include <SDL3/SDL.h> // Para SDL_SetRenderTarget, SDL_CreateTexture, SDL_DestroyTexture, SDL_FRect, SDL_GetRenderTarget, SDL_RenderTexture, SDL_PixelFormat, SDL_TextureAccess
#include <algorithm> #include <algorithm>
#include <cmath> // Para sin, pow #include <cmath> // Para sin
#include <cstdlib> // Para rand #include <cstdlib> // Para rand
#include <memory> // Para allocator, unique_ptr, make_unique #include <memory> // Para allocator, unique_ptr, make_unique
#include <numbers> // Para pi #include <numbers> // Para pi
@@ -81,10 +81,10 @@ void TiledBG::render() {
SDL_RenderTexture(renderer_, canvas_, &window_, &pos_); SDL_RenderTexture(renderer_, canvas_, &window_, &pos_);
} }
// Actualiza la lógica de la clase (time-based) // Actualiza la lógica de la clase
void TiledBG::update(float delta_time) { void TiledBG::update() {
updateDesp(delta_time); updateDesp();
updateStop(delta_time); updateStop();
switch (mode_) { switch (mode_) {
case TiledBGMode::DIAGONAL: { case TiledBGMode::DIAGONAL: {
@@ -107,20 +107,17 @@ void TiledBG::update(float delta_time) {
} }
} }
// Detiene el desplazamiento de forma ordenada (time-based) // Detiene el desplazamiento de forma ordenada
void TiledBG::updateStop(float delta_time) { void TiledBG::updateStop() {
if (stopping_) { if (stopping_) {
const int UMBRAL = STOP_THRESHOLD_FACTOR * speed_; const int UMBRAL = 20 * speed_; // Ajusta este valor según la precisión deseada
// Desacelerar si estamos cerca de completar el ciclo (ventana a punto de regresar a 0) // Desacelerar si estamos cerca de completar el ciclo (ventana a punto de regresar a 0)
if (window_.x >= TILE_WIDTH - UMBRAL) { if (window_.x >= TILE_WIDTH - UMBRAL) {
// Aplicar desaceleración time-based speed_ /= 1.05F; // Reduce gradualmente la velocidad
float frame_rate = 60.0F;
float deceleration_per_ms = std::pow(DECELERATION_FACTOR, frame_rate * delta_time / 1000.0F);
speed_ /= deceleration_per_ms;
// Asegura que no baje demasiado // Asegura que no baje demasiado
speed_ = std::max(speed_, MIN_SPEED); speed_ = std::max(speed_, 0.1F);
} }
// Si estamos en 0, detener // Si estamos en 0, detener

View File

@@ -24,8 +24,8 @@ class TiledBG {
~TiledBG(); ~TiledBG();
// --- Métodos principales --- // --- Métodos principales ---
void render(); // Pinta la clase en pantalla void render(); // Pinta la clase en pantalla
void update(float delta_time); // Actualiza la lógica de la clase void update(); // Actualiza la lógica de la clase
// --- Configuración --- // --- Configuración ---
void setSpeed(float speed) { speed_ = speed; } // Establece la velocidad void setSpeed(float speed) { speed_ = speed; } // Establece la velocidad
@@ -37,11 +37,8 @@ class TiledBG {
private: private:
// --- Constantes --- // --- Constantes ---
static constexpr int TILE_WIDTH = 64; // Ancho del tile static constexpr int TILE_WIDTH = 64; // Ancho del tile
static constexpr int TILE_HEIGHT = 64; // Alto del tile static constexpr int TILE_HEIGHT = 64; // Alto del tile
static constexpr float STOP_THRESHOLD_FACTOR = 20.0f; // Factor para umbral de parada
static constexpr float DECELERATION_FACTOR = 1.05f; // Factor de desaceleración
static constexpr float MIN_SPEED = 0.1f; // Velocidad mínima
// --- Objetos y punteros --- // --- Objetos y punteros ---
SDL_Renderer *renderer_; // El renderizador de la ventana SDL_Renderer *renderer_; // El renderizador de la ventana
@@ -57,7 +54,7 @@ class TiledBG {
bool stopping_ = false; // Indica si se está deteniendo bool stopping_ = false; // Indica si se está deteniendo
// --- Métodos internos --- // --- Métodos internos ---
void fillTexture(); // Rellena la textura con el contenido void fillTexture(); // Rellena la textura con el contenido
void updateDesp(float delta_time) { desp_ += speed_ * delta_time; } // Actualiza el desplazamiento (time-based) void updateDesp() { desp_ += speed_; } // Actualiza el desplazamiento
void updateStop(float delta_time); // Detiene el desplazamiento de forma ordenada (time-based) void updateStop(); // Detiene el desplazamiento de forma ordenada
}; };

View File

@@ -2,15 +2,16 @@
#include "text.h" // Para Text #include "text.h" // Para Text
// Actualiza el objeto (delta_time en ms) // Actualiza el objeto
void Writer::update(float delta_time) { void Writer::update() {
if (enabled_) { if (enabled_) {
if (!completed_) { if (!completed_) {
// No completado // No completado
writing_timer_ += delta_time; if (writing_counter_ > 0) {
if (writing_timer_ >= speed_interval_) { writing_counter_--;
} else {
index_++; index_++;
writing_timer_ = 0.0f; writing_counter_ = speed_;
} }
if (index_ == length_) { if (index_ == length_) {
@@ -18,18 +19,14 @@ void Writer::update(float delta_time) {
} }
} else { } else {
// Completado // Completado
enabled_timer_ += delta_time; finished_ = enabled_counter_ <= 0;
finished_ = enabled_timer_ >= enabled_timer_target_; if (!finished_) {
enabled_counter_--;
}
} }
} }
} }
// Actualiza el objeto (delta_time en segundos)
void Writer::updateS(float delta_time) {
// Convierte segundos a milisegundos y usa la lógica normal
update(delta_time * 1000.0f);
}
// Dibuja el objeto en pantalla // Dibuja el objeto en pantalla
void Writer::render() const { void Writer::render() const {
if (enabled_) { if (enabled_) {
@@ -58,19 +55,10 @@ void Writer::setCaption(const std::string &text) {
length_ = text.length(); length_ = text.length();
} }
// Establece el valor de la variable (frames) // Establece el valor de la variable
void Writer::setSpeed(int value) { void Writer::setSpeed(int value) {
// Convierte frames a milisegundos (frames * 16.67ms) speed_ = value;
constexpr float FRAME_TIME_MS = 16.67f; writing_counter_ = value;
speed_interval_ = static_cast<float>(value) * FRAME_TIME_MS;
writing_timer_ = 0.0f;
}
// Establece la velocidad en segundos entre caracteres
void Writer::setSpeedS(float value) {
// Convierte segundos a milisegundos para consistencia interna
speed_interval_ = value * 1000.0f;
writing_timer_ = 0.0f;
} }
// Establece el valor de la variable // Establece el valor de la variable
@@ -83,16 +71,9 @@ auto Writer::isEnabled() const -> bool {
return enabled_; return enabled_;
} }
// Establece el temporizador para deshabilitar el objeto (en milisegundos) // Establece el valor de la variable
void Writer::setFinishedTimerMs(float time_ms) { void Writer::setFinishedCounter(int time) {
enabled_timer_target_ = time_ms; enabled_counter_ = time;
enabled_timer_ = 0.0f;
}
// Establece el temporizador para deshabilitar el objeto (en segundos)
void Writer::setFinishedTimerS(float time_s) {
enabled_timer_target_ = time_s * 1000.0f; // Convertir segundos a milisegundos
enabled_timer_ = 0.0f;
} }
// Centra la cadena de texto a un punto X // Centra la cadena de texto a un punto X

View File

@@ -15,20 +15,17 @@ class Writer {
~Writer() = default; ~Writer() = default;
// --- Métodos principales --- // --- Métodos principales ---
void update(float delta_time); // Actualiza el objeto (delta_time en ms) void update(); // Actualiza el objeto
void updateS(float delta_time); // Actualiza el objeto (delta_time en segundos) void render() const; // Dibuja el objeto en pantalla
void render() const; // Dibuja el objeto en pantalla
// --- Setters --- // --- Setters ---
void setPosX(int value); // Establece la posición X void setPosX(int value); // Establece la posición X
void setPosY(int value); // Establece la posición Y void setPosY(int value); // Establece la posición Y
void setKerning(int value); // Establece el kerning (espaciado entre caracteres) void setKerning(int value); // Establece el kerning (espaciado entre caracteres)
void setCaption(const std::string &text); // Establece el texto a escribir void setCaption(const std::string &text); // Establece el texto a escribir
void setSpeed(int value); // Establece la velocidad de escritura (frames) void setSpeed(int value); // Establece la velocidad de escritura
void setSpeedS(float value); // Establece la velocidad de escritura (segundos entre caracteres)
void setEnabled(bool value); // Habilita o deshabilita el objeto void setEnabled(bool value); // Habilita o deshabilita el objeto
void setFinishedTimerMs(float time_ms); // Establece el temporizador para deshabilitar el objeto (en ms) void setFinishedCounter(int time); // Establece el temporizador para deshabilitar el objeto
void setFinishedTimerS(float time_s); // Establece el temporizador para deshabilitar el objeto (en segundos)
void center(int x); // Centra la cadena de texto a un punto X void center(int x); // Centra la cadena de texto a un punto X
@@ -41,17 +38,16 @@ class Writer {
std::shared_ptr<Text> text_; // Objeto encargado de escribir el texto std::shared_ptr<Text> text_; // Objeto encargado de escribir el texto
// --- Variables de estado --- // --- Variables de estado ---
std::string caption_; // El texto para escribir std::string caption_; // El texto para escribir
int pos_x_ = 0; // Posición en el eje X donde empezar a escribir el texto int pos_x_ = 0; // Posición en el eje X donde empezar a escribir el texto
int pos_y_ = 0; // Posición en el eje Y donde empezar a escribir el texto int pos_y_ = 0; // Posición en el eje Y donde empezar a escribir el texto
int kerning_ = 0; // Kerning del texto, es decir, espaciado entre caracteres int kerning_ = 0; // Kerning del texto, es decir, espaciado entre caracteres
float speed_interval_ = 0.0f; // Intervalo entre caracteres (ms para compatibilidad) int speed_ = 0; // Velocidad de escritura
float writing_timer_ = 0.0f; // Temporizador de escritura para cada caracter int writing_counter_ = 0; // Temporizador de escritura para cada caracter
int index_ = 0; // Posición del texto que se está escribiendo int index_ = 0; // Posición del texto que se está escribiendo
int length_ = 0; // Longitud de la cadena a escribir int length_ = 0; // Longitud de la cadena a escribir
float enabled_timer_ = 0.0f; // Temporizador para deshabilitar el objeto int enabled_counter_ = 0; // Temporizador para deshabilitar el objeto
float enabled_timer_target_ = 0.0f; // Tiempo objetivo para deshabilitar el objeto bool completed_ = false; // Indica si se ha escrito todo el texto
bool completed_ = false; // Indica si se ha escrito todo el texto bool enabled_ = false; // Indica si el objeto está habilitado
bool enabled_ = false; // Indica si el objeto está habilitado bool finished_ = false; // Indica si ya ha terminado
bool finished_ = false; // Indica si ya ha terminado
}; };