From 3a2015256a626fcdb970812cf21bc4ad6d8e1ac2 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sat, 21 Mar 2026 18:10:48 +0100 Subject: [PATCH] fix: pantalla completa, integer scale i vsync --- docs/BUGS_PLAYER_REWRITE.md | 141 -- docs/PLAYER_MECHANICS.md | 1529 ----------------- docs/PLAYER_RULES.md | 49 - source/core/rendering/screen.cpp | 10 + .../core/rendering/sdl3gpu/sdl3gpu_shader.cpp | 42 +- .../core/rendering/sdl3gpu/sdl3gpu_shader.hpp | 8 + source/core/rendering/shader_backend.hpp | 10 + 7 files changed, 68 insertions(+), 1721 deletions(-) delete mode 100644 docs/BUGS_PLAYER_REWRITE.md delete mode 100644 docs/PLAYER_MECHANICS.md delete mode 100644 docs/PLAYER_RULES.md diff --git a/docs/BUGS_PLAYER_REWRITE.md b/docs/BUGS_PLAYER_REWRITE.md deleted file mode 100644 index c203327c..00000000 --- a/docs/BUGS_PLAYER_REWRITE.md +++ /dev/null @@ -1,141 +0,0 @@ -# BUGS DETECTADOS - Reescritura Player - -**Fecha:** 2025-10-30 -**Commit funcional de referencia:** `7cd596a0b9876c75ff75efc13708a2ca00c8cfcd` -**Estado:** La reescritura según PLAYER_MECHANICS.md ha introducido múltiples regresiones - ---- - -## 🐛 BUGS CRÍTICOS DETECTADOS - -### 2. **Salto recto en rampa: atraviesa la rampa al caer** -- **Descripción:** Si el jugador salta verticalmente (sin movimiento horizontal) estando en una rampa, al caer atraviesa la rampa -- **Estado esperado:** Salto recto (vx_ == 0) debería pegarse a la rampa al descender -- **Regla violada:** PLAYER_MECHANICS.md líneas 386-391 - "JUMPING con vx_ == 0 se PEGA a la rampa" -- **Posible causa:** Lógica en `moveVerticalDown()` no detecta correctamente el caso vx_ == 0 - ---- - -### 4. **No suena al saltar** -- **Descripción:** Los sonidos de salto no se reproducen -- **Estado esperado:** Sonidos progresivos basados en distancia vertical -- **Posible causa:** - - `y_prev_` no se inicializa correctamente - - Sistema de detección de cambio de índice no funciona - - `last_grounded_position_` no se actualiza en el momento correcto - ---- - -### 5. **No suena al caer** -- **Descripción:** Los sonidos de caída no se reproducen -- **Estado esperado:** Sonidos progresivos basados en distancia vertical caída -- **Posible causa:** Similar al bug #4, sistema distance-based no detecta cambios - ---- - -### 6. **Caer desde precipicio con inercia sobre rampa: resbala en estado FALLING** -- **Descripción:** Al caer con inercia horizontal sobre una rampa, el jugador resbala en lugar de pegarse -- **Estado esperado:** FALLING siempre debe tener `vx_ = 0` y pegarse a rampas -- **Regla violada:** PLAYER_MECHANICS.md línea 147 - "FALLING se PEGA a TODAS las rampas" -- **Posible causa:** `vx_` no se establece a 0 correctamente en transición a FALLING, o se ejecuta después del movimiento horizontal - ---- - -### 7. **No muere al caer desde gran altura** -- **Descripción:** El jugador puede caer desde cualquier altura sin morir -- **Estado esperado:** Caída > 32 píxeles (4 tiles) debería matar al jugador -- **Regla violada:** PLAYER_MECHANICS.md líneas 158-178 -- **Posible causa:** - - La verificación de muerte en `moveVerticalDown()` no se ejecuta - - `previous_state_` no es FALLING cuando debería - - Cálculo de `FALL_DISTANCE` es incorrecto - ---- - -### 8. **No muere al caer sobre conveyor belt desde altura** -- **Descripción:** El jugador puede caer sobre una conveyor belt desde cualquier altura sin morir -- **Estado esperado:** Caída > 32 píxeles sobre conveyor belt también debería matar -- **Regla violada:** PLAYER_MECHANICS.md líneas 158-178 -- **Posible causa:** - - La verificación de muerte solo está en la rama de suelo normal (`checkTopSurfaces`) - - Posiblemente necesita estar también cuando aterriza en `checkAutoSurfaces` - - Podría ser un subcaso del bug #7 - ---- - -## 🔍 ANÁLISIS PRELIMINAR - -### Problema raíz principal: **Orden de ejecución** - -El orden actual es: -```cpp -move() { - y_prev_ = y_; - applyGravity(); - updateStateAndVelocity(); // ← Cambia estado y DEBERÍA cambiar vx_ - moveHorizontal(); // ← Aplica vx_ (que puede ser incorrecto) - moveVertical(); - updateColliderGeometry(); -} -``` - -**Problema:** `updateStateAndVelocity()` puede cambiar el estado a FALLING y establecer `vx_ = 0`, pero esto ocurre DENTRO del switch que solo se ejecuta DESPUÉS de que `previous_state_` ya esté establecido. En el código actual, cuando se hace la transición STANDING → FALLING, `setState()` NO establece `vx_ = 0` (se supone que lo hace `updateStateAndVelocity()`), pero esto puede no ejecutarse hasta el siguiente frame. - -### Problema raíz secundario: **setState() incompleto** - -En el commit funcional (`7cd596a0b9876c75ff75efc13708a2ca00c8cfcd`), `setState()` probablemente establecía TODAS las variables necesarias inmediatamente. En la reescritura actual, se delegó parte de esta responsabilidad a `updateStateAndVelocity()`, creando una desincronización. - ---- - -## 📋 PLAN DE SOLUCIÓN - -### Opción A: Volver al commit funcional y refactorizar con cuidado -1. Hacer `git diff` entre commit funcional y actual -2. Identificar QUÉ funcionaba en el original -3. Aplicar solo los cambios necesarios (eliminar `jumping_time_`, unificar variables) -4. Mantener la lógica de setState() completa - -### Opción B: Corregir bugs uno por uno en código actual -1. Arreglar `setState()` para que establezca TODAS las variables inmediatamente -2. Revisar orden de ejecución en `move()` -3. Depurar sistema de sonidos -4. Probar cada bug individualmente - -### Opción C: Híbrido -1. Extraer lógica funcional del commit 7cd596a -2. Aplicar solo las correcciones de PLAYER_MECHANICS.md que no rompen funcionalidad: - - Cambiar `jump_init_pos_` → `last_grounded_position_` (con cuidado) - - Eliminar `jumping_time_` (secundario, no crítico) - - Implementar sistema de sonidos distance-based (después de que todo funcione) - ---- - -## 🎯 RECOMENDACIÓN - -**Opción C (Híbrido)** parece la más sensata: - -1. Revertir a un punto funcional -2. Aplicar cambios incrementales uno por uno -3. Testear después de cada cambio -4. No hacer cambios arquitecturales grandes (mantener `updateState()` y `updateVelocity()` separados si funcionaba así) - ---- - -## 📝 NOTAS ADICIONALES - -- El commit `7cd596a` funciona perfecto según el usuario, aunque el código sea "caótico" -- **Lección aprendida:** Refactorizar código funcional sin tests automáticos es peligroso -- La sincronización estado-velocidad es más sutil de lo que parecía -- PLAYER_MECHANICS.md puede tener asunciones incorrectas sobre el orden de ejecución óptimo - ---- - -## ✅ PRÓXIMOS PASOS - -1. Comparar código actual vs commit 7cd596a -2. Identificar diferencias críticas en: - - `setState()` - - `move()` - - `updateState()`/`updateVelocity()` - - Transiciones de estado -3. Decidir estrategia: revertir completo, revertir parcial, o corregir in-place diff --git a/docs/PLAYER_MECHANICS.md b/docs/PLAYER_MECHANICS.md deleted file mode 100644 index f69136e7..00000000 --- a/docs/PLAYER_MECHANICS.md +++ /dev/null @@ -1,1529 +0,0 @@ -# PLAYER MECHANICS - Documentación Completa - -**Propósito:** Este documento define TODAS las reglas, comportamientos y casos edge del sistema de física del jugador en JailDoctor's Dilemma. Es la referencia definitiva para implementar y mantener la clase Player. - ---- - -## ⚠️ CAMBIOS IMPORTANTES EN ESTA VERSIÓN - -### 1. Unificación de Variables -- **`jump_init_pos_` ha sido ELIMINADO** -- **`last_grounded_position_` ahora hace doble función:** - 1. Guarda la última Y donde el jugador estuvo en tierra firme (para calcular distancia de caída) - 2. Sirve como altura inicial del salto (reemplaza a `jump_init_pos_`) -- **Justificación:** Ambas variables guardaban la misma información (Y al salir de STANDING) - -### 2. Condición de Transición JUMPING → FALLING Corregida -- **Antes (INCORRECTO):** `y >= jump_init_pos_` -- **Ahora (CORRECTO):** `y > last_grounded_position_` -- **Cambio crítico:** Usa `>` (mayor), NO `>=` (mayor o igual) -- **Razón:** Si el jugador vuelve EXACTAMENTE a la altura inicial, debe CONTINUAR en JUMPING - -### 3. Sistema de Sonidos Basado en Distancia Vertical -- **Antes:** Sistema frame-based o basado en tiempo (`jumping_time_`) -- **Ahora:** Basado en distancia vertical recorrida -- **Nueva constante:** `SOUND_DISTANCE_INTERVAL` (píxeles entre cada sonido) -- **Nueva variable requerida:** `float y_prev_` (para detectar cambios de hito) -- **Razón:** Delta-time variable hace que los sonidos basados en tiempo sean inconsistentes - -### 4. Sistema de Muerte por Caída Documentado -- Muerte si caída > 32 píxeles (4 tiles) -- `last_grounded_position_` se guarda AL SALIR de STANDING (no durante el salto) -- **CRÍTICO:** En `switchBorders()` debe resetearse `last_grounded_position_` para evitar muerte falsa - -### 5. SpawnData Ampliado -- Documentado el uso completo del sistema SpawnData -- Explicado el reseteo de variables en cambio de pantalla -- Advertencias sobre cálculos de caída entre rooms - ---- - -## 1. ESTADOS DEL JUGADOR - -El jugador tiene **tres estados mutuamente exclusivos**: - -### 1.1 STANDING (De pie) - -**Definición:** El jugador está parado sobre una superficie sólida (suelo, rampa, o conveyor belt). - -**Condiciones de entrada:** -- Aterrizar sobre suelo normal desde el aire (FALLING → STANDING) -- Aterrizar sobre rampa desde el aire (FALLING → STANDING) -- Aterrizar sobre conveyor belt desde el aire (FALLING → STANDING) -- Cambiar de pantalla verticalmente (forzado al cambio de room) - -**Condiciones de salida:** -- Input de salto + sobre superficie → JUMPING -- No hay superficie debajo de los pies → FALLING -- Nunca sale directamente a otro estado sin una de estas condiciones - -**Comportamiento mientras está en STANDING:** -- ✅ Puede moverse horizontalmente (izquierda/derecha) -- ✅ Puede saltar -- ✅ Se pega a las rampas al moverse lateralmente -- ✅ Desciende rampas incrementando Y cuando detecta rampa hacia abajo -- ✅ Puede ser movido por conveyor belts (auto_movement) -- ❌ NO puede cambiar de dirección en el aire - -**Variables afectadas:** -- `vy_` = 0 (siempre, mientras está STANDING) -- `vx_` = controlada por input o conveyor belt -- `last_grounded_position_` = Y actual (para calcular distancia de caída) -- `jumping_time_` = 0 - ---- - -### 1.2 JUMPING (Saltando) - -**Definición:** El jugador está en el aire debido a un salto iniciado por el jugador. - -**Condiciones de entrada:** -- Estado STANDING + input de salto + (isOnFloor() || isOnAutoSurface()) -- Nunca puede iniciar salto si no está sobre una superficie - -**Condiciones de salida:** -- Golpea el techo (colisión superior) → FALLING -- **Y actual > Y inicial del salto (SUPERA la altura de inicio)** → FALLING - - ⚠️ IMPORTANTE: Se usa `>` (mayor que), NO `>=` (mayor o igual) - - Si el jugador vuelve EXACTAMENTE a la altura inicial, debe CONTINUAR en JUMPING - - Solo cuando la SUPERA (desciende más allá) cambia a FALLING -- Nunca aterriza directamente en STANDING (siempre pasa por FALLING primero) - -**Comportamiento mientras está en JUMPING:** -- ✅ Gravedad se aplica constantemente (vy_ aumenta cada frame) -- ✅ Mantiene la velocidad horizontal con la que saltó (vx_ no cambia) -- ✅ Puede atravesar rampas SI tiene vx_ != 0 -- ❌ NO puede cambiar la dirección horizontal (vx_ es fijo) -- ❌ NO se pega a rampas durante el salto -- ⚠️ SI salta recto (vx_ == 0), se pega a rampas al descender - -**Variables afectadas:** -- `vy_` = JUMP_VELOCITY al inicio (-80 px/s), luego aumenta por gravedad -- `vx_` = valor que tenía al iniciar el salto (no cambia durante JUMPING) -- `jump_init_pos_` = Y en el momento de inicio del salto -- `jumping_time_` = se incrementa cada frame (usado para sonidos) -- `last_grounded_position_` = **SE GUARDA AL SALIR DE STANDING** (no cambia durante el salto) - -**Sonidos - Sistema Basado en Distancia Vertical:** - -El jugador reproduce sonidos progresivos durante el salto basándose en la **distancia vertical recorrida**, no en el tiempo. - -```cpp -// Cálculo del índice de sonido -const int SOUND_INDEX = static_cast(std::abs(y_ - jump_init_pos_) / SOUND_DISTANCE_INTERVAL); -const int PREVIOUS_INDEX = static_cast(std::abs(y_prev_ - jump_init_pos_) / SOUND_DISTANCE_INTERVAL); - -// Solo reproduce cuando cambia el índice -if (SOUND_INDEX != PREVIOUS_INDEX && SOUND_INDEX < jumping_sound_.size()) { - JA_PlaySound(jumping_sound_[SOUND_INDEX]); -} -``` - -**Constantes de sonido:** -- `SOUND_DISTANCE_INTERVAL` = Distancia en píxeles entre cada sonido (ej: 3-4 píxeles) -- `jumping_sound_` = Vector con 24 sonidos (jump1.wav a jump24.wav) - -**Justificación:** Con delta-time variable, usar tiempo produce sonidos inconsistentes a diferentes framerates. La distancia vertical es constante independientemente del framerate. - ---- - -### 1.3 FALLING (Cayendo) - -**Definición:** El jugador está cayendo debido a gravedad (sin estar en un salto activo). - -**Condiciones de entrada:** -- Estado STANDING + no hay superficie debajo → FALLING -- Estado JUMPING + Y > jump_init_pos_ → FALLING (nota: `>` no `>=`) -- Estado JUMPING + colisión con techo → FALLING - -**Condiciones de salida:** -- Colisión con suelo normal → STANDING -- Colisión con conveyor belt → STANDING -- Colisión con rampa → STANDING -- Nunca sale a JUMPING (no se puede saltar en el aire) - -**Comportamiento mientras está en FALLING:** -- ✅ Cae a velocidad constante (MAX_VY) -- ✅ Se pega a TODAS las rampas (independientemente de vx_) -- ❌ NO puede cambiar dirección horizontal (vx_ = 0) -- ❌ NO aplica gravedad (velocidad es constante MAX_VY) - -**Variables afectadas:** -- `vy_` = MAX_VY (80 px/s, velocidad máxima de caída) -- `vx_` = 0 (no puede moverse horizontalmente) -- `auto_movement_` = false (desactiva conveyor belts) -- `jumping_time_` = 0 -- `last_grounded_position_` = **NO CAMBIA** (se mantiene el valor guardado al salir de STANDING) - -**Sistema de Muerte por Caída Excesiva:** - -El jugador muere si cae desde una altura mayor a `MAX_FALLING_HEIGHT = 32 píxeles (4 tiles)`. - -**Algoritmo:** -1. **Al salir de STANDING** (transición a JUMPING o FALLING): - ```cpp - last_grounded_position_ = static_cast(y_); // Guarda Y actual - ``` - -2. **Durante JUMPING/FALLING:** - - `last_grounded_position_` NO cambia - - Representa la última Y donde estuvo en tierra firme - -3. **Al aterrizar (transición a STANDING):** - ```cpp - const int FALL_DISTANCE = static_cast(y_) - last_grounded_position_; - if (FALL_DISTANCE > MAX_FALLING_HEIGHT) { - is_alive_ = false; // Muere por caída - } - ``` - -**⚠️ Caso Especial - Cambio de Pantalla:** - -Al hacer `switchBorders()`, el jugador se teleporta al extremo opuesto. La diferencia de Y podría ser ~192 píxeles (altura total de pantalla), causando **muerte falsa**. - -**Solución:** Al cambiar de pantalla, se resetea `last_grounded_position_`: -```cpp -void Player::switchBorders() { - // ... teleportar jugador ... - setState(State::STANDING); - last_grounded_position_ = static_cast(y_); // ← RESETEAR -} -``` - -**Sonidos - Sistema Basado en Distancia Vertical:** - -Similar al sistema de JUMPING, pero usando los sonidos de caída: - -```cpp -// Cálculo del índice de sonido -const int SOUND_INDEX = static_cast((y_ - last_grounded_position_) / SOUND_DISTANCE_INTERVAL); -const int PREVIOUS_INDEX = static_cast((y_prev_ - last_grounded_position_) / SOUND_DISTANCE_INTERVAL); - -// Solo reproduce cuando cambia el índice -if (SOUND_INDEX != PREVIOUS_INDEX && SOUND_INDEX < falling_sound_.size()) { - JA_PlaySound(falling_sound_[SOUND_INDEX]); -} -``` - -**Constantes de sonido:** -- `falling_sound_` = Vector con 14 sonidos (jump11.wav a jump24.wav - reutiliza los últimos sonidos del salto) - ---- - -## 2. SISTEMA DE VELOCIDADES - -### 2.1 Velocidad Horizontal (vx_) - -**Unidades:** píxeles/segundo - -**Valores posibles:** -- `-HORIZONTAL_VELOCITY` (-40 px/s): Movimiento a la izquierda -- `0`: Sin movimiento horizontal -- `+HORIZONTAL_VELOCITY` (+40 px/s): Movimiento a la derecha -- `+/- HORIZONTAL_VELOCITY * direction`: En conveyor belts (direction = ±1) - -**Reglas de cambio por estado:** - -| Estado | ¿Puede cambiar vx_? | ¿Cómo se determina? | -|--------|---------------------|---------------------| -| STANDING | ✅ Sí | Input del jugador (izq/der) O conveyor belt | -| JUMPING | ❌ No | Se fija al valor que tenía al iniciar el salto | -| FALLING | ❌ No | Siempre 0 | - -**Casos especiales:** -- **Conveyor belt activo:** vx_ = HORIZONTAL_VELOCITY * room_->getAutoSurfaceDirection() -- **Sin input en STANDING:** vx_ = 0 (se detiene) -- **Flip del sprite:** Si vx_ < 0 → SDL_FLIP_HORIZONTAL, si vx_ > 0 → SDL_FLIP_NONE - ---- - -### 2.2 Velocidad Vertical (vy_) - -**Unidades:** píxeles/segundo - -**Valores posibles:** -- `0`: Sin movimiento vertical (solo en STANDING) -- `JUMP_VELOCITY` (-80 px/s): Inicio del salto (negativo = hacia arriba) -- `MAX_VY` (+80 px/s): Velocidad máxima de caída (positivo = hacia abajo) -- Cualquier valor entre JUMP_VELOCITY y MAX_VY durante JUMPING (por gravedad) - -**Reglas de cambio por estado:** - -| Estado | Valor de vy_ | ¿Cambia? | -|--------|--------------|----------| -| STANDING | 0 | ❌ No, siempre 0 | -| JUMPING | JUMP_VELOCITY → MAX_VY | ✅ Sí, por gravedad | -| FALLING | MAX_VY | ❌ No, constante | - -**Gravedad:** -- **Solo se aplica en JUMPING** -- Fórmula: `vy_ += GRAVITY_FORCE * delta_time` -- GRAVITY_FORCE = 155.6 px/s² -- vy_ se limita a MAX_VY: `vy_ = std::min(vy_, MAX_VY)` - ---- - -## 3. TIPOS DE SUPERFICIES - -### 3.1 Suelo Normal (Top Surfaces) - -**Definición:** Superficies sólidas horizontales sobre las que el jugador puede caminar. - -**Detección:** -- `room_->checkTopSurfaces(SDL_FRect*)` - Para aterrizaje (devuelve Y o -1) -- `room_->checkTopSurfaces(SDL_FPoint*)` - Para verificar si punto está en suelo (bool) - -**Comportamiento:** -- Jugador camina normalmente -- Puede saltar -- No afecta vx_ automáticamente -- Desactiva auto_movement si estaba activo - -**Interacción por estado:** -- STANDING: Camina sobre ella -- JUMPING: Puede pasar a través si está ascendiendo, aterriza si está descendiendo -- FALLING: Aterriza al tocarla (transición a STANDING) - ---- - -### 3.2 Conveyor Belts (Auto Surfaces) - -**Definición:** Superficies que mueven al jugador automáticamente en una dirección. - -**Detección:** -- `room_->checkAutoSurfaces(SDL_FRect*)` - Para aterrizaje (devuelve Y o -1) -- `room_->checkAutoSurfaces(SDL_FPoint*)` - Para verificar si punto está en conveyor (bool) - -**Propiedades:** -- Tienen una dirección: `room_->getAutoSurfaceDirection()` devuelve +1 (derecha) o -1 (izquierda) -- Se comportan como suelo normal + movimiento automático - -**Activación de auto_movement:** -``` -auto_movement se activa cuando: - - Jugador está STANDING - - Está sobre una conveyor belt (isOnAutoSurface()) - - NO está presionando ninguna tecla de dirección - - NO está sobre suelo normal simultáneamente -``` - -**Desactivación de auto_movement:** -``` -auto_movement se desactiva cuando: - - Jugador salta (transición a JUMPING) - - Jugador cae (transición a FALLING) - - Jugador aterriza en suelo normal - - Ambos pies del jugador dejan la conveyor belt -``` - -**Comportamiento mientras auto_movement = true:** -- vx_ = HORIZONTAL_VELOCITY * direction (sobrescribe input) -- Jugador NO puede cambiar dirección con teclas -- Jugador SÍ puede saltar (mantiene la inercia de la belt) - -**Casos especiales:** -- **Caer sobre conveyor belt:** El jugador aterriza normalmente, pero auto_movement NO se activa hasta que deje de pulsar direcciones -- **Rampa + Conveyor belt:** ¿Posible? (verificar en el juego) - ---- - -### 3.3 Rampas (Slopes) - -**Definición:** Superficies diagonales que el jugador puede subir, bajar o atravesar según el estado. - -#### 3.3.1 Rampa Izquierda (Left Slope) - -**Características:** -``` -Ascendente cuando te mueves a la IZQUIERDA -Descendente cuando te mueves a la DERECHA - -Visual: - #### - #### - #### - #### - #### -#### -``` - -**Detección:** -- `room_->checkLeftSlopes(LineVertical*)` - Devuelve Y de la rampa o -1 -- `room_->checkLeftSlopes(SDL_FPoint*)` - Devuelve bool - -**Comportamiento:** -- Moverse a la izquierda: El jugador sube la rampa (Y disminuye) -- Moverse a la derecha: El jugador baja la rampa (Y aumenta) - -#### 3.3.2 Rampa Derecha (Right Slope) - -**Características:** -``` -Ascendente cuando te mueves a la DERECHA -Descendente cuando te mueves a la IZQUIERDA - -Visual: -#### - #### - #### - #### - #### - #### -``` - -**Detección:** -- `room_->checkRightSlopes(LineVertical*)` - Devuelve Y de la rampa o -1 -- `room_->checkRightSlopes(SDL_FPoint*)` - Devuelve bool - -**Comportamiento:** -- Moverse a la derecha: El jugador sube la rampa (Y disminuye) -- Moverse a la izquierda: El jugador baja la rampa (Y aumenta) - -#### 3.3.3 Reglas de Interacción con Rampas - -**REGLA CRÍTICA:** Las rampas se comportan diferente según el estado del jugador. - -| Estado | vx_ | Comportamiento | Justificación | -|--------|-----|----------------|---------------| -| STANDING | Cualquiera | Se PEGA a la rampa | Camina sobre ella | -| JUMPING | != 0 | ATRAVIESA la rampa | Salta sobre ella en movimiento | -| JUMPING | == 0 | Se PEGA a la rampa | Salto recto no tiene momento horizontal | -| FALLING | Cualquiera | Se PEGA a la rampa | Aterriza en ella | - -**Detección de "Pegar a la Rampa":** - -1. **Contacto Lateral (durante movimiento horizontal):** - - Se usa una línea vertical en el borde izquierdo/derecho del jugador - - Solo se comprueban los 2 píxeles inferiores: `y1 = y + HEIGHT - 2`, `y2 = y + HEIGHT - 1` - - Si hay rampa, reposiciona Y: `y = rampa_y - HEIGHT` - -2. **Contacto Inferior (durante caída):** - - Se usan los pies del jugador (LineVertical de arriba a abajo del rectángulo) - - `LEFT_SIDE = {x, y, y + HEIGHT - 1}` - - `RIGHT_SIDE = {x + WIDTH - 1, y, y + HEIGHT - 1}` - - Comprueba ambas rampas (left y right), toma el máximo Y - - Si hay rampa, reposiciona Y: `y = rampa_y - HEIGHT` - -3. **Descenso de Rampa:** - - Se detecta con `isOnDownSlope()` - - Comprueba los puntos `under_feet_` + 1 píxel adicional hacia abajo - - Si hay rampa descendente, incrementa Y: `y += 1` - -**Casos Edge:** -- **Rampa + Suelo normal simultáneamente:** El suelo normal tiene prioridad (se usa std::max para elegir la Y más alta) -- **Rampa izquierda + Rampa derecha en el mismo punto:** Se toma el máximo Y - ---- - -## 4. SISTEMA DE COLISIONES - -### 4.1 Puntos de Colisión del Jugador - -El jugador usa múltiples conjuntos de puntos para detectar colisiones: - -#### 4.1.1 Rectángulo Principal (collider_box_) - -```cpp -SDL_FRect collider_box_ = {x_, y_, WIDTH, HEIGHT}; -``` - -- **Tamaño:** 8x16 píxeles -- **Uso:** Colisión con enemigos y objetos (no usado para colisión con tiles) - -#### 4.1.2 Puntos de Colisión (collider_points_) - -```cpp -std::array collider_points_; -``` - -**Disposición:** 4 esquinas de cada mitad vertical del jugador (8x8 píxeles cada mitad) - -``` -Píxel (0,0) → collider_points_[0] collider_points_[1] ← (7,0) - collider_points_[3] collider_points_[2] - ───────────────────────────────────────── - collider_points_[4] collider_points_[5] -Píxel (0,15)→ collider_points_[7] collider_points_[6] ← (7,15) -``` - -**Uso:** Detectar si el jugador está tocando tiles que matan (KILL tiles) - -**Cálculo:** -```cpp -collider_points_[0] = {x, y}; -collider_points_[1] = {x + 7, y}; -collider_points_[2] = {x + 7, y + 7}; -collider_points_[3] = {x, y + 7}; -collider_points_[4] = {x, y + 8}; -collider_points_[5] = {x + 7, y + 8}; -collider_points_[6] = {x + 7, y + 15}; -collider_points_[7] = {x, y + 15}; -``` - -#### 4.1.3 Pies (feet_) - -```cpp -std::array feet_; -``` - -**Posición:** Esquinas inferiores del jugador (Y = y + HEIGHT - 1) - -``` -feet_[0] = {x, y + 15} feet_[1] = {x + 7, y + 15} -``` - -**Uso:** Representan el límite inferior del jugador (usado para verificar si está parado) - -#### 4.1.4 Bajo los Pies (under_feet_) - -```cpp -std::array under_feet_; -``` - -**Posición:** 1 píxel debajo de los pies (Y = y + HEIGHT) - -``` -under_feet_[0] = {x, y + 16} under_feet_[1] = {x + 7, y + 16} -``` - -**Uso:** Detectar QUÉ superficie está debajo del jugador (suelo, conveyor belt, rampa) - ---- - -### 4.2 Rectángulos de Proyección - -**Concepto:** En lugar de comprobar colisiones en la posición actual, se crea un rectángulo que cubre el espacio entre la posición actual y la posición futura. Esto previene "tunneling" (atravesar paredes a alta velocidad). - -#### 4.2.1 Proyección Horizontal Izquierda - -```cpp -const float DISPLACEMENT = vx_ * delta_time; // Negativo para izquierda -SDL_FRect proj = { - .x = x_ + DISPLACEMENT, // Posición futura - .y = y_, - .w = std::ceil(std::fabs(DISPLACEMENT)), // Ancho = distancia recorrida - .h = HEIGHT -}; -``` - -**Comprobación:** `room_->checkRightSurfaces(&proj)` - Busca paredes a la derecha del proyección (porque nos movemos a la izquierda) - -#### 4.2.2 Proyección Horizontal Derecha - -```cpp -const float DISPLACEMENT = vx_ * delta_time; // Positivo para derecha -SDL_FRect proj = { - .x = x_ + WIDTH, // Empieza desde el borde derecho del jugador - .y = y_, - .w = std::ceil(DISPLACEMENT), // Ancho = distancia recorrida - .h = HEIGHT -}; -``` - -**Comprobación:** `room_->checkLeftSurfaces(&proj)` - Busca paredes a la izquierda de la proyección (porque nos movemos a la derecha) - -#### 4.2.3 Proyección Vertical Arriba - -```cpp -const float DISPLACEMENT = vy_ * delta_time; // Negativo para arriba -SDL_FRect proj = { - .x = x_, - .y = y_ + DISPLACEMENT, // Posición futura (más arriba) - .w = WIDTH, - .h = std::ceil(std::fabs(DISPLACEMENT)) // Alto = distancia recorrida -}; -``` - -**Comprobación:** `room_->checkBottomSurfaces(&proj)` - Busca techos (superficies inferiores de tiles) - -#### 4.2.4 Proyección Vertical Abajo - -```cpp -const float DISPLACEMENT = vy_ * delta_time; // Positivo para abajo -SDL_FRect proj = { - .x = x_, - .y = y_ + HEIGHT, // Empieza desde el borde inferior del jugador - .w = WIDTH, - .h = std::ceil(DISPLACEMENT) // Alto = distancia recorrida -}; -``` - -**Comprobación:** -- `room_->checkTopSurfaces(&proj)` - Busca suelos -- `room_->checkAutoSurfaces(&proj)` - Busca conveyor belts -- Se toma el máximo: `std::max(checkTopSurfaces, checkAutoSurfaces)` - ---- - -### 4.3 Orden de Resolución de Colisiones - -**Movimiento Horizontal:** -1. Crear proyección horizontal -2. Comprobar colisión con muros (checkLeftSurfaces o checkRightSurfaces) -3. Si hay colisión → reposicionar en el punto de colisión -4. Si NO hay colisión → aplicar desplazamiento -5. Comprobar rampas laterales (solo si STANDING o FALLING) -6. Comprobar rampas descendentes (isOnDownSlope) - -**Movimiento Vertical Arriba:** -1. Crear proyección vertical -2. Comprobar colisión con techos (checkBottomSurfaces) -3. Si hay colisión → reposicionar + cambiar a FALLING -4. Si NO hay colisión → aplicar desplazamiento - -**Movimiento Vertical Abajo:** -1. Crear proyección vertical -2. Comprobar colisión con suelos y conveyor belts (checkTopSurfaces + checkAutoSurfaces) -3. Si hay colisión → reposicionar + cambiar a STANDING -4. Si NO hay colisión → comprobar rampas (checkLeftSlopes + checkRightSlopes) -5. Si hay rampa Y no estamos atravesando → reposicionar en rampa + cambiar a STANDING -6. Si NO hay nada → aplicar desplazamiento - ---- - -## 5. MÉTODOS DE DETECCIÓN (Room) - -### 5.1 Métodos con SDL_FRect (para movimiento) - -Estos métodos toman un rectángulo de proyección y devuelven la **posición de colisión** o -1 si no hay colisión. - -| Método | Parámetro | Retorna | Propósito | -|--------|-----------|---------|-----------| -| `checkTopSurfaces(SDL_FRect*)` | Proyección abajo | Y o -1 | Detectar suelo al caer | -| `checkBottomSurfaces(SDL_FRect*)` | Proyección arriba | Y o -1 | Detectar techo al subir | -| `checkLeftSurfaces(SDL_FRect*)` | Proyección derecha | X o -1 | Detectar pared a la derecha | -| `checkRightSurfaces(SDL_FRect*)` | Proyección izquierda | X o -1 | Detectar pared a la izquierda | -| `checkAutoSurfaces(SDL_FRect*)` | Proyección abajo | Y o -1 | Detectar conveyor belt al caer | - -**Uso típico:** -```cpp -const float POS = room_->checkTopSurfaces(&proj); -if (POS > -1) { - // Hay colisión en Y = POS - y_ = POS - HEIGHT; // Reposicionar -} -``` - -### 5.2 Métodos con SDL_FPoint (para verificación) - -Estos métodos toman un punto y devuelven **bool** indicando si ese punto está en la superficie. - -| Método | Parámetro | Retorna | Propósito | -|--------|-----------|---------|-----------| -| `checkTopSurfaces(SDL_FPoint*)` | Punto | bool | ¿Hay suelo en este punto? | -| `checkAutoSurfaces(SDL_FPoint*)` | Punto | bool | ¿Hay conveyor belt en este punto? | -| `checkLeftSlopes(SDL_FPoint*)` | Punto | bool | ¿Hay rampa izquierda en este punto? | -| `checkRightSlopes(SDL_FPoint*)` | Punto | bool | ¿Hay rampa derecha en este punto? | - -**Uso típico:** -```cpp -bool on_floor = false; -for (auto f : under_feet_) { - on_floor |= room_->checkTopSurfaces(&f); -} -``` - -### 5.3 Métodos con LineVertical (para rampas) - -Estos métodos toman una línea vertical y devuelven la **Y de la rampa** o -1 si no hay rampa. - -| Método | Parámetro | Retorna | Propósito | -|--------|-----------|---------|-----------| -| `checkLeftSlopes(LineVertical*)` | Línea vertical | Y o -1 | Detectar rampa izquierda en contacto lateral | -| `checkRightSlopes(LineVertical*)` | Línea vertical | Y o -1 | Detectar rampa derecha en contacto lateral | - -**LineVertical:** -```cpp -struct LineVertical { - int x; // Posición X de la línea - int y1; // Y inicial - int y2; // Y final -}; -``` - -**Uso típico:** -```cpp -const LineVertical SIDE = { - .x = static_cast(x_), - .y1 = static_cast(y_) + HEIGHT - 2, - .y2 = static_cast(y_) + HEIGHT - 1 -}; -const int SLOPE_Y = room_->checkLeftSlopes(&SIDE); -if (SLOPE_Y > -1) { - y_ = SLOPE_Y - HEIGHT; // Pegar a la rampa -} -``` - -### 5.4 Otros Métodos de Room - -| Método | Propósito | -|--------|-----------| -| `getTile(SDL_FPoint)` | Devuelve el tipo de tile en ese punto (Room::Tile::KILL, etc.) | -| `getAutoSurfaceDirection()` | Devuelve +1 o -1 (dirección de conveyor belts) | - ---- - -## 6. MÉTODOS AUXILIARES DEL PLAYER - -### 6.1 isOnFloor() - -**Propósito:** Determinar si el jugador tiene una superficie sólida debajo de los pies. - -**Algoritmo:** -```cpp -auto Player::isOnFloor() -> bool { - updateFeet(); // Calcula under_feet_ positions - - bool on_floor = false; - bool on_slope_l = false; - bool on_slope_r = false; - - // Comprueba suelo normal y conveyor belts - for (auto f : under_feet_) { - on_floor |= room_->checkTopSurfaces(&f); - on_floor |= room_->checkAutoSurfaces(&f); - } - - // Comprueba rampas - on_slope_l = room_->checkLeftSlopes(&under_feet_[0]); - on_slope_r = room_->checkRightSlopes(&under_feet_[1]); - - return on_floor || on_slope_l || on_slope_r; -} -``` - -**Retorna:** `true` si hay suelo, conveyor belt, o rampa bajo los pies. - -**Cuándo usar:** Para verificar si el jugador debe caer (transición STANDING → FALLING) - ---- - -### 6.2 isOnAutoSurface() - -**Propósito:** Determinar si el jugador está sobre una conveyor belt. - -**Algoritmo:** -```cpp -auto Player::isOnAutoSurface() -> bool { - updateFeet(); - - bool on_auto_surface = false; - for (auto f : under_feet_) { - on_auto_surface |= room_->checkAutoSurfaces(&f); - } - - return on_auto_surface; -} -``` - -**Retorna:** `true` si alguno de los puntos under_feet_ está en una conveyor belt. - -**Cuándo usar:** Para activar auto_movement o para permitir saltos. - ---- - -### 6.3 isOnDownSlope() - -**Propósito:** Determinar si el jugador está sobre una rampa descendente (necesita incrementar Y para seguir la rampa). - -**Algoritmo:** -```cpp -auto Player::isOnDownSlope() -> bool { - updateFeet(); - - // Mira 1 píxel MÁS abajo que under_feet_ - SDL_FPoint foot0 = under_feet_[0]; - SDL_FPoint foot1 = under_feet_[1]; - foot0.y += 1.0f; - foot1.y += 1.0f; - - bool on_slope = false; - on_slope |= room_->checkLeftSlopes(&foot0); - on_slope |= room_->checkRightSlopes(&foot1); - - return on_slope; -} -``` - -**Retorna:** `true` si hay una rampa 1 píxel por debajo de los pies (indica descenso). - -**Cuándo usar:** Durante movimiento horizontal en STANDING, para pegar al jugador a la rampa al descender. - ---- - -## 7. CASOS EDGE DOCUMENTADOS - -### 7.1 Saltar Recto sobre Rampa - -**Situación:** Jugador está en rampa, salta sin moverse horizontalmente. - -**Input:** Salto (sin izquierda/derecha) - -**Comportamiento esperado:** -1. Estado STANDING → JUMPING -2. vx_ = 0 (no hay movimiento horizontal) -3. Jugador sube por el salto -4. Al descender, detecta rampa con vx_ == 0 -5. Se pega a la rampa (NO la atraviesa) -6. Aterriza en STANDING - -**Justificación:** Un salto recto no tiene momento horizontal, por lo que el jugador debe aterrizar donde saltó. - ---- - -### 7.2 Saltar con Movimiento Horizontal sobre Rampa - -**Situación:** Jugador está en rampa, salta mientras se mueve. - -**Input:** Salto + izquierda/derecha - -**Comportamiento esperado:** -1. Estado STANDING → JUMPING -2. vx_ = ±HORIZONTAL_VELOCITY (mantiene dirección) -3. Jugador sube por el salto -4. Durante JUMPING, vx_ != 0 → **atraviesa la rampa** -5. Continúa el arco del salto -6. Aterriza más allá de la rampa (si hay suelo) - -**Justificación:** El momento horizontal permite atravesar la rampa. - ---- - -### 7.3 Caer desde Altura sobre Rampa (Sin Salto Previo) - -**Situación:** Jugador camina fuera de un borde y cae sobre una rampa. - -**Input:** Ninguno (caída por gravedad) - -**Comportamiento esperado:** -1. Estado STANDING → FALLING (no hay suelo debajo) -2. vx_ = 0 (FALLING siempre tiene vx_ = 0) -3. Jugador cae a MAX_VY -4. Detecta rampa durante caída -5. Se pega a la rampa (vx_ == 0) -6. Aterriza en STANDING - -**Justificación:** Una caída sin salto previo no tiene momento horizontal, se pega a rampas. - ---- - -### 7.4 Cambiar de Pantalla Verticalmente - -**Situación:** Jugador alcanza el borde superior o inferior de la pantalla. - -**Input:** Movimiento que lleva al jugador al borde - -**Comportamiento completo:** - -1. **Detección:** `checkBorders()` detecta `is_on_border_ = true` y establece qué borde (`border_`) - -2. **Teleportación:** `switchBorders()` es llamado: - ```cpp - void Player::switchBorders() { - switch (border_) { - case Room::Border::TOP: - y_ = PLAY_AREA_BOTTOM - HEIGHT - TILE_SIZE; - break; - case Room::Border::BOTTOM: - y_ = PLAY_AREA_TOP; - break; - case Room::Border::LEFT: - x_ = PLAY_AREA_RIGHT - WIDTH; - break; - case Room::Border::RIGHT: - x_ = PLAY_AREA_LEFT; - break; - } - - // CRÍTICO: Resetear estado y variables de tracking - setState(State::STANDING); - last_grounded_position_ = static_cast(y_); // ← Evita muerte falsa - is_on_border_ = false; - placeSprite(); - } - ``` - -3. **Reseteo de tracking:** - - Estado forzado a STANDING (independientemente del estado anterior) - - `last_grounded_position_` se resetea a la nueva Y - - **Justificación:** Sin este reseteo, la diferencia de Y (~192 píxeles) causaría muerte falsa por "caída excesiva" - -4. **Frame siguiente:** - - Si no hay suelo en la nueva posición, `shouldFall()` detecta la ausencia de suelo - - Transición STANDING → FALLING - - El jugador cae normalmente - -**SpawnData - Sistema de Guardado/Restauración:** - -El sistema usa `SpawnData` para guardar/restaurar el estado completo del jugador: - -```cpp -struct SpawnData { - float x, y; // Posición - float vx, vy; // Velocidades - int jump_init_pos; // Altura inicial del salto - State state; // Estado actual - SDL_FlipMode flip; // Orientación del sprite -}; -``` - -**Usos de SpawnData:** - -1. **Cambio de pantalla:** Guardar estado antes de cambiar de room -2. **Muerte/Respawn:** Restaurar jugador al último checkpoint -3. **Save/Load:** Persistir estado del jugador entre sesiones - -**Métodos:** -- `getSpawnParams()` - Devuelve SpawnData actual -- `applySpawnValues(const SpawnData&)` - Restaura estado desde SpawnData - -**⚠️ Importante:** Al restaurar desde SpawnData después de un cambio de room, también se debe resetear `last_grounded_position_` para evitar cálculos incorrectos de distancia de caída entre rooms diferentes. - ---- - -### 7.5 Saltar desde Conveyor Belt - -**Situación:** Jugador está en conveyor belt (auto_movement = true) y salta. - -**Input:** Salto - -**Comportamiento esperado:** -1. Estado STANDING → JUMPING -2. vx_ = HORIZONTAL_VELOCITY * direction (mantiene inercia de la belt) -3. auto_movement = false (desactivado en setState(JUMPING)) -4. Jugador salta en la dirección de la belt -5. Durante JUMPING, vx_ != 0 (mantiene el momento) -6. Al aterrizar, auto_movement se mantiene false hasta que el jugador deje de pulsar direcciones - -**Justificación:** El jugador hereda el momento de la conveyor belt. - ---- - -### 7.6 Conveyor Belt en Rampa - -**Situación:** ¿Es posible que exista una conveyor belt sobre una rampa? - -**A verificar en el juego:** -- Si existe, ¿cómo se comporta? -- ¿Tiene prioridad la rampa o la conveyor belt? -- ¿El jugador es movido horizontalmente mientras sube/baja la rampa? - -**Hipótesis:** No debería existir en el diseño de niveles, pero si existe, se comportaría como conveyor belt normal (prioridad de suelo sobre rampa). - ---- - -### 7.7 Múltiples Superficies Simultáneas - -**Situación:** Los dos puntos under_feet_ detectan superficies diferentes. - -**Ejemplos:** -- under_feet_[0] en suelo normal, under_feet_[1] en conveyor belt -- under_feet_[0] en rampa, under_feet_[1] en suelo normal -- under_feet_[0] en conveyor belt, under_feet_[1] en rampa - -**Comportamiento:** -- `isOnFloor()` usa OR lógico → devuelve true si ANY pie está en superficie -- Para aterrizaje, se usa `std::max(Y_suelo, Y_conveyor, Y_rampa)` → jugador queda en la superficie más alta -- auto_movement se activa solo si está en conveyor belt Y NO en suelo normal - -**Justificación:** El jugador se para en la superficie más alta disponible. - ---- - -## 8. TABLA DE DECISIONES PARA COLISIONES - -Esta tabla define EXACTAMENTE qué hacer en cada caso: - -### 8.1 Movimiento Vertical Hacia Abajo (moveVerticalDown) - -| Estado Actual | vx_ | Superficie Detectada | Acción | Nuevo Estado | -|---------------|-----|----------------------|--------|--------------| -| STANDING | Cualquiera | Ninguna (aire) | Continuar cayendo | FALLING | -| STANDING | Cualquiera | Suelo | (No debería ocurrir, ya está STANDING) | STANDING | -| JUMPING | != 0 | Suelo | Aterrizar (y = suelo_y - HEIGHT) | STANDING | -| JUMPING | != 0 | Rampa | **ATRAVESAR** (y += displacement) | JUMPING | -| JUMPING | == 0 | Suelo | Aterrizar (y = suelo_y - HEIGHT) | STANDING | -| JUMPING | == 0 | Rampa | **PEGAR** (y = rampa_y - HEIGHT) | STANDING | -| FALLING | Cualquiera | Suelo | Aterrizar (y = suelo_y - HEIGHT) | STANDING | -| FALLING | Cualquiera | Rampa | **PEGAR** (y = rampa_y - HEIGHT) | STANDING | - -### 8.2 Movimiento Horizontal (moveHorizontal) - -| Estado Actual | Superficie Lateral | Rampa Bajo Pies | Acción | -|---------------|-------------------|-----------------|--------| -| STANDING | Pared | No | Detener en pared (x = pared_x ± width) | -| STANDING | Libre | Rampa lateral | Subir rampa (y = rampa_y - HEIGHT) | -| STANDING | Libre | Rampa descendente | Descender rampa (y += 1) | -| JUMPING | Pared | No | Detener en pared (x = pared_x ± width) | -| JUMPING | Libre | Rampa | **IGNORAR** (no pegar) | -| FALLING | Pared | No | Detener en pared (x = pared_x ± width) | -| FALLING | Libre | Rampa lateral | Subir rampa (y = rampa_y - HEIGHT) | - -### 8.3 Activación de auto_movement - -| Condiciones | auto_movement | -|-------------|---------------| -| Estado = STANDING + isOnAutoSurface() + !isOnFloor() + input = ninguno | **Activa** (true) | -| Estado = JUMPING | **Desactiva** (false) | -| Estado = FALLING | **Desactiva** (false) | -| Estado = STANDING + isOnFloor() (suelo normal) | **Desactiva** (false) | -| Estado = STANDING + input != ninguno | **Mantiene** (no cambia) | - ---- - -## 9. ORDEN DE EJECUCIÓN ÓPTIMO - -Basándose en todas las reglas documentadas, el orden de ejecución debe ser: - -### 9.1 Opción A: checkState Integrado (Versión Funcional) - -```cpp -update(delta_time) { - if (is_paused_) return; - - checkInput() // Modifica vx_ directamente, detecta want_to_jump - - move(delta_time) { - applyGravity() // Solo si JUMPING - - checkState(delta_time) // ← CRÍTICO: Estado + velocidades se actualizan JUNTOS - - moveHorizontal() // Usa vx_ actualizado - moveVertical() // Usa vy_ actualizado, comprueba estado actual - - updateColliderGeometry() - } - - animate() - checkBorders() - checkJumpEnd() - checkKillingTiles() - setColor() -} -``` - -**Ventajas:** -- ✅ Estado y velocidades siempre sincronizados -- ✅ moveVertical ve el estado correcto -- ✅ Probado funciona (commit 7cd596a) - -**Desventajas:** -- ❌ checkState hace demasiadas cosas (cambiar estado, modificar vx/vy, reproducir sonidos) -- ❌ Mezcla responsabilidades - ---- - -### 9.2 Opción B: State-First con updateState al FINAL - -```cpp -update(delta_time) { - if (is_paused_) return; - - checkInput() // Captura intent flags - - applyGravity() // Solo si JUMPING - updateVelocity() // Basado en estado ACTUAL - - move(delta_time) { - moveHorizontal() // Mueve en X - moveVertical() // Mueve en Y, puede cambiar estado (JUMPING→FALLING, FALLING→STANDING) - updateColliderGeometry() - } - - updateState(delta_time) // ← DESPUÉS del movimiento, confirma/ajusta el estado - - animate() - checkBorders() - checkJumpEnd() - checkKillingTiles() - setColor() -} -``` - -**Ventajas:** -- ✅ Separación clara de responsabilidades -- ✅ updateState solo gestiona transiciones de estado -- ✅ moveVertical puede cambiar estado localmente (aterrizaje) - -**Desventajas:** -- ⚠️ Requiere que moveVertical pueda cambiar estado directamente (setState) -- ⚠️ Dos lugares cambian estado: moveVertical (aterrizaje) y updateState (salto, caída) - ---- - -### 9.3 Opción C: Híbrido (updateState DENTRO de move, entre movimientos) - -```cpp -update(delta_time) { - if (is_paused_) return; - - checkInput() // Captura intent flags - - applyGravity() // Solo si JUMPING - updateVelocity() // Basado en estado actual - - move(delta_time) { - moveHorizontal() // Mueve en X - - updateState(delta_time) // ← ENTRE horizontal y vertical - - moveVertical() // Mueve en Y con estado actualizado - updateColliderGeometry() - } - - animate() - checkBorders() - checkJumpEnd() - checkKillingTiles() - setColor() -} -``` - -**Ventajas:** -- ✅ Estado se actualiza después de movimiento horizontal (conveyor belts OK) -- ✅ Estado se actualiza antes de movimiento vertical (necesario para rampas) - -**Desventajas:** -- ❌ **NO FUNCIONA** porque updateVelocity ya corrió antes de updateState -- ❌ Cuando updateState cambia JUMPING→FALLING, vx_ no se actualiza a 0 -- ❌ **Este es el problema actual que estamos teniendo** - ---- - -### 9.4 Recomendación Final - -**La mejor opción es la Opción A (checkState integrado)** con mejoras de organización: - -```cpp -update(delta_time) { - if (is_paused_) return; - - // 1. Captura input - checkInput() // Lee teclas, establece want_to_jump_, want_to_move_left/right_ - - // 2. Movimiento con estado integrado - move(delta_time) { - applyGravity() // Aplica gravedad si JUMPING - updateStateAndVelocity() // ← Nuevo: combina checkState + updateVelocity - moveHorizontal() // Movimiento X con vx_ correcto - moveVertical() // Movimiento Y con vy_ correcto - updateColliderGeometry() - } - - // 3. Finalización - animate() - checkBorders() - checkJumpEnd() - checkKillingTiles() - setColor() -} -``` - -**updateStateAndVelocity() haría:** -1. Comprobar transiciones de estado basadas en física + input -2. Actualizar `state_` y `previous_state_` -3. Actualizar `vx_` y `vy_` basándose en el nuevo estado -4. Actualizar `auto_movement_` basándose en el nuevo estado + input -5. Reproducir sonidos si corresponde - -**Esto garantiza que estado y velocidades están SIEMPRE sincronizados.** - ---- - -## 10. VARIABLES IMPORTANTES Y CUÁNDO CAMBIAN - -### 10.1 state_ (Estado actual) - -**Tipo:** `Player::State` (STANDING, JUMPING, FALLING) - -**Cuándo cambia:** - -| De → A | Condición | Dónde | -|--------|-----------|-------| -| STANDING → JUMPING | want_to_jump_ && (isOnFloor() \|\| isOnAutoSurface()) | updateStateAndVelocity() | -| STANDING → FALLING | !isOnFloor() && !isOnAutoSurface() && !isOnDownSlope() | updateStateAndVelocity() | -| JUMPING → FALLING | y > last_grounded_position_ && vy > 0 (nota: > no >=) | updateStateAndVelocity() | -| JUMPING → FALLING | Colisión con techo | moveVerticalUp() | -| FALLING → STANDING | Colisión con suelo/rampa | moveVerticalDown() | - ---- - -### 10.2 vx_ (Velocidad horizontal) - -**Tipo:** `float` (píxeles/segundo) - -**Cuándo cambia:** - -| Estado | Condición | Valor | Dónde | -|--------|-----------|-------|-------| -| STANDING | !auto_movement_ && want_to_move_left_ | -HORIZONTAL_VELOCITY | updateStateAndVelocity() | -| STANDING | !auto_movement_ && want_to_move_right_ | +HORIZONTAL_VELOCITY | updateStateAndVelocity() | -| STANDING | !auto_movement_ && sin input | 0 | updateStateAndVelocity() | -| STANDING | auto_movement_ | HORIZONTAL_VELOCITY * direction | updateStateAndVelocity() | -| JUMPING | - | (no cambia, mantiene valor inicial) | - | -| FALLING | Transición a FALLING | 0 | setState(FALLING) | - ---- - -### 10.3 vy_ (Velocidad vertical) - -**Tipo:** `float` (píxeles/segundo) - -**Cuándo cambia:** - -| Estado | Evento | Valor | Dónde | -|--------|--------|-------|-------| -| STANDING | Siempre | 0 | updateStateAndVelocity() | -| JUMPING | Inicio del salto | JUMP_VELOCITY | setState(JUMPING) | -| JUMPING | Cada frame | vy_ += GRAVITY * dt | applyGravity() | -| FALLING | Transición a FALLING | MAX_VY | setState(FALLING) | - ---- - -### 10.4 auto_movement_ (Conveyor belt activo) - -**Tipo:** `bool` - -**Cuándo cambia:** - -| De → A | Condición | Dónde | -|--------|-----------|-------| -| false → true | STANDING + isOnAutoSurface() + sin input + !isOnFloor() | updateStateAndVelocity() | -| true → false | Transición a JUMPING | setState(JUMPING) | -| true → false | Transición a FALLING | setState(FALLING) | -| true → false | Aterrizaje en suelo normal | moveVerticalDown() | - ---- - -## 11. CONSTANTES FÍSICAS - -```cpp -// Dimensiones del jugador -WIDTH = 8; // píxeles -HEIGHT = 16; // píxeles -MAX_FALLING_HEIGHT = TILE_SIZE * 4; // 32 píxeles (muere si cae más) -TILE_SIZE = 8; // píxeles por tile - -// Velocidades (píxeles/segundo) -HORIZONTAL_VELOCITY = 40.0F; // Velocidad de caminar/correr -MAX_VY = 80.0F; // Velocidad máxima de caída -JUMP_VELOCITY = -80.0F; // Velocidad inicial del salto (negativo = arriba) -GRAVITY_FORCE = 155.6F; // Aceleración de gravedad (px/s²) - -// Sonidos - Sistema basado en distancia vertical -SOUND_DISTANCE_INTERVAL = 3.0F; // Píxeles entre cada sonido (ajustar según diseño) -// Vectores de sonidos: -// - jumping_sound_: 24 sonidos (jump1.wav a jump24.wav) -// - falling_sound_: 14 sonidos (jump11.wav a jump24.wav) -``` - ---- - -## 12. PSEUDOCÓDIGO COMPLETO (Versión Definitiva) - -```cpp -// ==================== UPDATE ==================== -void Player::update(float delta_time) { - if (is_paused_) return; - - checkInput(); - move(delta_time); - animate(delta_time); - checkBorders(); - checkJumpEnd(); - checkKillingTiles(); - setColor(); -} - -// ==================== CHECK INPUT ==================== -void Player::checkInput() { - want_to_jump_ = Input::get()->checkInput(InputAction::JUMP); - want_to_move_left_ = Input::get()->checkInput(InputAction::LEFT); - want_to_move_right_ = Input::get()->checkInput(InputAction::RIGHT); -} - -// ==================== MOVE ==================== -void Player::move(float delta_time) { - applyGravity(delta_time); // Gravedad solo si JUMPING - updateStateAndVelocity(delta_time); // Actualiza estado + vx/vy juntos - moveHorizontal(delta_time); // Movimiento X - moveVertical(delta_time); // Movimiento Y (puede cambiar estado) - updateColliderGeometry(); -} - -// ==================== APPLY GRAVITY ==================== -void Player::applyGravity(float delta_time) { - if (state_ == State::JUMPING) { - vy_ += GRAVITY_FORCE * delta_time; - vy_ = std::min(vy_, MAX_VY); - } -} - -// ==================== UPDATE STATE AND VELOCITY ==================== -void Player::updateStateAndVelocity(float delta_time) { - previous_state_ = state_; - - switch (state_) { - case State::STANDING: - // Muerte por caída - if (previous_state_ == State::FALLING) { - int fall_distance = static_cast(y_) - last_grounded_position_; - if (fall_distance > MAX_FALLING_HEIGHT) { - is_alive_ = false; - } - } - - // Actualizar posición de tierra - // NOTA: last_grounded_position_ hace doble función: - // 1. Guarda la última Y en tierra (para calcular distancia de caída) - // 2. Sirve como jump_init_pos_ (altura inicial del salto) - last_grounded_position_ = static_cast(y_); - - // vy_ siempre 0 en STANDING - vy_ = 0.0F; - - // Transición a FALLING si no hay suelo - if (!isOnFloor() && !isOnAutoSurface() && !isOnDownSlope()) { - state_ = State::FALLING; - vx_ = 0.0F; - vy_ = MAX_VY; - auto_movement_ = false; - jumping_time_ = 0.0F; - return; // Salir para aplicar FALLING - } - - // Transición a JUMPING si se pulsa salto - if (want_to_jump_ && (isOnFloor() || isOnAutoSurface())) { - state_ = State::JUMPING; - // last_grounded_position_ ya está actualizado (líneas arriba) - // Se usa como altura inicial del salto - vy_ = JUMP_VELOCITY; - // vx_ se mantiene (hereda momento) - return; // Salir para mantener vx_ actual - } - - // Actualizar vx_ según input o auto_movement - if (!auto_movement_) { - if (want_to_move_left_) { - vx_ = -HORIZONTAL_VELOCITY; - sprite_->setFlip(SDL_FLIP_HORIZONTAL); - } else if (want_to_move_right_) { - vx_ = HORIZONTAL_VELOCITY; - sprite_->setFlip(SDL_FLIP_NONE); - } else { - vx_ = 0.0F; - // Activar auto_movement si está en conveyor belt - if (isOnAutoSurface() && !isOnFloor()) { - auto_movement_ = true; - } - } - } else { - // Auto movement activo: conveyor belt controla vx_ - vx_ = HORIZONTAL_VELOCITY * room_->getAutoSurfaceDirection(); - sprite_->setFlip(vx_ > 0.0F ? SDL_FLIP_NONE : SDL_FLIP_HORIZONTAL); - } - break; - - case State::JUMPING: - // Reproducir sonidos basados en distancia vertical - playJumpSound(); - - // Transición a FALLING si SUPERA altura inicial - // ⚠️ IMPORTANTE: Usar > (mayor), NO >= (mayor o igual) - if (static_cast(y_) > last_grounded_position_ && vy_ > 0.0F) { - state_ = State::FALLING; - vx_ = 0.0F; - vy_ = MAX_VY; - auto_movement_ = false; - // last_grounded_position_ NO cambia (se mantiene desde el inicio del salto) - } - - // vx_ no cambia durante JUMPING (mantiene momento) - // vy_ ya fue actualizado por applyGravity() - // last_grounded_position_ NO cambia durante el salto - break; - - case State::FALLING: - // Reproducir sonido de caída - playFallSound(); - - // vx_ = 0, vy_ = MAX_VY (ya establecidos en transición) - // No cambian durante FALLING - break; - } -} - -// ==================== MOVE HORIZONTAL ==================== -void Player::moveHorizontal(float delta_time) { - if (vx_ == 0.0F) return; // Sin movimiento horizontal - - int direction = (vx_ < 0.0F) ? -1 : 1; - float displacement = vx_ * delta_time; - - // Crear proyección - SDL_FRect proj; - if (direction < 0) { - proj = {x_ + displacement, y_, std::ceil(std::fabs(displacement)), HEIGHT}; - } else { - proj = {x_ + WIDTH, y_, std::ceil(displacement), HEIGHT}; - } - - // Comprobar colisión con muros - int wall_pos = (direction < 0) ? room_->checkRightSurfaces(&proj) - : room_->checkLeftSurfaces(&proj); - - if (wall_pos == -1) { - // No hay colisión: mover - x_ += displacement; - } else { - // Hay colisión: detener en muro - x_ = (direction < 0) ? wall_pos + 1 : wall_pos - WIDTH; - } - - // Manejar rampas solo si no está JUMPING - if (state_ != State::JUMPING) { - handleSlopeMovement(direction); - } -} - -// ==================== HANDLE SLOPE MOVEMENT ==================== -void Player::handleSlopeMovement(int direction) { - // Si está descendiendo rampa, pegar al jugador - if (isOnDownSlope()) { - y_ += 1; - return; - } - - // Comprobar rampa lateral (contacto lateral = subir rampa) - int side_x = (direction < 0) ? static_cast(x_) - : static_cast(x_) + WIDTH - 1; - LineVertical side = {side_x, static_cast(y_) + HEIGHT - 2, - static_cast(y_) + HEIGHT - 1}; - - int slope_y = (direction < 0) ? room_->checkLeftSlopes(&side) - : room_->checkRightSlopes(&side); - - if (slope_y > -1) { - y_ = slope_y - HEIGHT; // Subir a la rampa - } -} - -// ==================== MOVE VERTICAL ==================== -void Player::moveVertical(float delta_time) { - if (vy_ < 0.0F) { - moveVerticalUp(delta_time); - } else if (vy_ > 0.0F) { - moveVerticalDown(delta_time); - } -} - -// ==================== MOVE VERTICAL UP ==================== -void Player::moveVerticalUp(float delta_time) { - float displacement = vy_ * delta_time; - SDL_FRect proj = {x_, y_ + displacement, WIDTH, std::ceil(std::fabs(displacement))}; - - int ceiling_pos = room_->checkBottomSurfaces(&proj); - - if (ceiling_pos == -1) { - // No hay colisión: mover - y_ += displacement; - } else { - // Hay colisión con techo: detener y cambiar a FALLING - y_ = ceiling_pos + 1; - state_ = State::FALLING; - vx_ = 0.0F; - vy_ = MAX_VY; - auto_movement_ = false; - jumping_time_ = 0.0F; - } -} - -// ==================== MOVE VERTICAL DOWN ==================== -void Player::moveVerticalDown(float delta_time) { - float displacement = vy_ * delta_time; - SDL_FRect proj = {x_, y_ + HEIGHT, WIDTH, std::ceil(displacement)}; - - // Comprobar suelo y conveyor belts - float floor_pos = std::max(room_->checkTopSurfaces(&proj), - room_->checkAutoSurfaces(&proj)); - - if (floor_pos > -1) { - // Hay suelo: aterrizar - y_ = floor_pos - HEIGHT; - state_ = State::STANDING; - auto_movement_ = false; - return; - } - - // No hay suelo: comprobar rampas - // REGLA: Se pega a rampas SI: - // - NO está JUMPING, O - // - Está JUMPING pero vx_ == 0 (salto recto) - - if (state_ != State::JUMPING || vx_ == 0.0F) { - SDL_FRect rect = getRect(); - LineVertical left_side = {rect.x, rect.y, rect.y + rect.h - 1}; - LineVertical right_side = {rect.x + rect.w - 1, rect.y, rect.y + rect.h - 1}; - - float slope_pos = std::max(room_->checkRightSlopes(&right_side), - room_->checkLeftSlopes(&left_side)); - - if (slope_pos > -1) { - // Hay rampa: aterrizar - y_ = slope_pos - HEIGHT; - state_ = State::STANDING; - auto_movement_ = false; - return; - } - } - - // No hay nada: continuar cayendo - y_ += displacement; -} - -// ==================== PLAY JUMP SOUND ==================== -void Player::playJumpSound() { - // Sistema basado en distancia vertical recorrida - const float DISTANCE_FROM_START = std::abs(y_ - static_cast(last_grounded_position_)); - const int SOUND_INDEX = static_cast(DISTANCE_FROM_START / SOUND_DISTANCE_INTERVAL); - - // Calcular índice previo (frame anterior) - const float PREV_DISTANCE = std::abs(y_prev_ - static_cast(last_grounded_position_)); - const int PREVIOUS_INDEX = static_cast(PREV_DISTANCE / SOUND_DISTANCE_INTERVAL); - - // Solo reproduce cuando cambia de índice (nuevo hito alcanzado) - if (SOUND_INDEX != PREVIOUS_INDEX && SOUND_INDEX < static_cast(jumping_sound_.size())) { - JA_PlaySound(jumping_sound_[SOUND_INDEX]); - } - - // NOTA: Necesitamos guardar y_ del frame anterior - // Agregar variable: float y_prev_ = 0.0F; - // Actualizar en move(): y_prev_ = y_; -} - -// ==================== PLAY FALL SOUND ==================== -void Player::playFallSound() { - // Sistema basado en distancia vertical caída - const float DISTANCE_FALLEN = y_ - static_cast(last_grounded_position_); - const int SOUND_INDEX = static_cast(DISTANCE_FALLEN / SOUND_DISTANCE_INTERVAL); - - // Calcular índice previo (frame anterior) - const float PREV_DISTANCE = y_prev_ - static_cast(last_grounded_position_); - const int PREVIOUS_INDEX = static_cast(PREV_DISTANCE / SOUND_DISTANCE_INTERVAL); - - // Solo reproduce cuando cambia de índice - if (SOUND_INDEX != PREVIOUS_INDEX && SOUND_INDEX < static_cast(falling_sound_.size())) { - JA_PlaySound(falling_sound_[SOUND_INDEX]); - } -} -``` - ---- - -## FIN DEL DOCUMENTO - -Este documento define TODAS las reglas mecánicas del Player. Cualquier implementación debe seguir estas especificaciones exactamente. Si surge una ambigüedad o un caso no documentado, debe agregarse a este documento primero antes de implementarlo. - -**Versión:** 1.0 -**Fecha:** 2025-10-30 -**Autor:** Documentación basada en análisis del código existente y PLAYER_RULES.md diff --git a/docs/PLAYER_RULES.md b/docs/PLAYER_RULES.md deleted file mode 100644 index 48831623..00000000 --- a/docs/PLAYER_RULES.md +++ /dev/null @@ -1,49 +0,0 @@ -# Reglas para la clase Player - -## Estados - -El jugador tiene tres estados diferenciados: -- STANDING -- JUMPING -- FALLING - -## Puntos de colision - -El jugador tiene un rectabgulo que lo delimita. Se obtiene con getRect() -Tiene ademas dos puntos en un vector de puntos llamado feet_ con el punto inferior izquierdo y el inferior derecho -Tiene otros dos puntos llamados under_feet_ que son los puntos inmediatamente inferiores a los puntos en feet_. Se usan para saber SOBRE qué tipo de superficie esta el jugador o si bajo solo tiene aire -Tiene otros 8 puntos (el jugador ocupa dos tiles en vertical del mapa, los tiles son de 8 pixeles) que son las 4 esquinas de los dos rectangulos de 8px que formarian al jugador. Se usa para comprobar si el jugador está tocando algun tile de los que matan - -## Comprobar colisiones con el mapa -Esto es un poco marciano pero, el jugador genera un rectangulo de proyeccion. Digamos que si se va a mover 3 pixeles hacia la derecha, el rectangulo de proyeccion es el rectangulo que cubre la superficie entre donde está el jugador y donde estará el jugador. Se usa para ver si ese rectangulo (que suele ser de ancho o alto 1, ya que el jugador no llega a moverse mas de un pixel cada vez) colisiona con alguna superficie del mapa. En caso de colision se devuelve el punto de colision y se recoloca al jugador en ese punto. Eso para las superficies rectas. - -## Reglas -A continuacion las reglas que se han de cumplir para el jugador en todo momento -- Si está STANDING -> vy_ = 0; -- Si no tiene suelo debajo y no está JUMPING -> FALLING -- Si está JUMPING y tropieza contra el techo -> FALLING -- Si está FALLING -> vx_ = 0 -- Si está STANDING y tropieza lateralmente con una Slope, se pega a la slope -- Si esta FALLING y tiene una Slope bajo los pies, se pega a la slope -- La unica forma de atravesar una Slope es en estado JUMPING y con vx_ != 0 -- Si salta, se guarda la posicion inicial en Y, si durante el salto está mas bajo de Y (es decir, el valor de y pasa a ser superior al inicial) -> FALLING - -## Tipos de superficies -Hay tres tipos de superficies: -- Suelo normal -- Suelo tipo conveyor belt -- Rampas - -## Reglas explicadas de manera mas abierta -Debido a que las reglas anteriores pueden sonar confusas y ser incompletas, se describen aqui con lenguaje mas natural: -- El jugador camina sobre las superficies -- Si tiene los dos pies sobre el aire -> cae -- El jugador sube y baja por las rampas -- La unica forma de atravesar las rampas es saltando sobre ellas en movimiento -- Un salto recto o una caida (las caidas siempre son rectas) hacen aterrizar al jugador sobre las rampas -- Las conveyor belt desplazan al jugador en la direccion de la conveyor belt -- El jugador cuando esta siendo desplazado por una conveyor belt, no puede cambiar de direccion -- Solo puede saltar y la propia inercia le hace saltar en movimiento -- Hay una excepcion en las conveyor belts y es que cuando el jugador cae sobre ellas, puede mantener la direccion que tenia el jugador. En el momento que el jugador deja de estar pulsando una direccion, ya se acopla al movimiento de la conveyor belt y no puede cambiar de direccion -- El jugador deja de estar ligado al movimiento de la conveyor belt cuando sus dos pies ya no la tienen debajo, bien porque hay suelo o bien porque hay aire -> cae -- El jugador no puede cambiar de direccion en el aire diff --git a/source/core/rendering/screen.cpp b/source/core/rendering/screen.cpp index e7b7667f..b3b63982 100644 --- a/source/core/rendering/screen.cpp +++ b/source/core/rendering/screen.cpp @@ -431,12 +431,18 @@ void Screen::toggleDebugInfo() { show_debug_info_ = !show_debug_info_; } void Screen::toggleIntegerScale() { Options::video.integer_scale = !Options::video.integer_scale; SDL_SetRenderLogicalPresentation(renderer_, Options::game.width, Options::game.height, Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX); + if (shader_backend_) { + shader_backend_->setScaleMode(Options::video.integer_scale); + } } // Alterna entre activar y desactivar el V-Sync void Screen::toggleVSync() { Options::video.vertical_sync = !Options::video.vertical_sync; SDL_SetRenderVSync(renderer_, Options::video.vertical_sync ? 1 : SDL_RENDERER_VSYNC_DISABLED); + if (shader_backend_) { + shader_backend_->setVSync(Options::video.vertical_sync); + } } // Getters @@ -470,6 +476,10 @@ void Screen::initShaders() { } shader_backend_->init(window_, tex, "", ""); + // Propagar flags de vsync e integer scale al backend GPU + shader_backend_->setVSync(Options::video.vertical_sync); + shader_backend_->setScaleMode(Options::video.integer_scale); + if (Options::video.postfx) { applyCurrentPostFXPreset(); } else { diff --git a/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp b/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp index 6d14cb73..78be46d3 100644 --- a/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp +++ b/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp @@ -2,7 +2,9 @@ #include -#include // memcpy, strlen +#include // std::min, std::max, std::floor +#include // std::floor +#include // memcpy, strlen #ifndef __APPLE__ #include "core/rendering/sdl3gpu/postfx_frag_spv.h" @@ -218,7 +220,7 @@ auto SDL3GPUShader::init(SDL_Window* window, } SDL_SetGPUSwapchainParameters(device_, window_, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, - SDL_GPU_PRESENTMODE_VSYNC); + vsync_ ? SDL_GPU_PRESENTMODE_VSYNC : SDL_GPU_PRESENTMODE_IMMEDIATE); } // ---------------------------------------------------------------- @@ -410,6 +412,29 @@ void SDL3GPUShader::render() { if (pass != nullptr) { SDL_BindGPUGraphicsPipeline(pass, pipeline_); + // Calcular viewport para mantener relación de aspecto (letterbox o integer scale) + float vx = 0.0F; + float vy = 0.0F; + float vw = 0.0F; + float vh = 0.0F; + if (integer_scale_) { + const int scale = std::max(1, std::min( + static_cast(sw) / tex_width_, + static_cast(sh) / tex_height_)); + vw = static_cast(tex_width_ * scale); + vh = static_cast(tex_height_ * scale); + } else { + const float scale = std::min( + static_cast(sw) / static_cast(tex_width_), + static_cast(sh) / static_cast(tex_height_)); + vw = static_cast(tex_width_) * scale; + vh = static_cast(tex_height_) * scale; + } + vx = std::floor((static_cast(sw) - vw) * 0.5F); + vy = std::floor((static_cast(sh) - vh) * 0.5F); + SDL_GPUViewport vp = {vx, vy, vw, vh, 0.0F, 1.0F}; + SDL_SetGPUViewport(pass, &vp); + SDL_GPUTextureSamplerBinding binding = {}; binding.texture = scene_texture_; binding.sampler = sampler_; @@ -525,4 +550,17 @@ void SDL3GPUShader::setPostFXParams(const PostFXParams& p) { uniforms_.bleeding = p.bleeding; } +void SDL3GPUShader::setVSync(bool vsync) { + vsync_ = vsync; + if (device_ != nullptr && window_ != nullptr) { + SDL_SetGPUSwapchainParameters(device_, window_, + SDL_GPU_SWAPCHAINCOMPOSITION_SDR, + vsync_ ? SDL_GPU_PRESENTMODE_VSYNC : SDL_GPU_PRESENTMODE_IMMEDIATE); + } +} + +void SDL3GPUShader::setScaleMode(bool integer_scale) { + integer_scale_ = integer_scale; +} + } // namespace Rendering diff --git a/source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp b/source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp index e02ec3d2..4a426b5f 100644 --- a/source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp +++ b/source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp @@ -50,6 +50,12 @@ class SDL3GPUShader : public ShaderBackend { // Actualiza los parámetros de intensidad de los efectos PostFX void setPostFXParams(const PostFXParams& p) override; + // Activa/desactiva VSync en el swapchain + void setVSync(bool vsync) override; + + // Activa/desactiva escalado entero (integer scale) + void setScaleMode(bool integer_scale) override; + private: static auto createShaderMSL(SDL_GPUDevice* device, const char* msl_source, @@ -80,6 +86,8 @@ class SDL3GPUShader : public ShaderBackend { int tex_width_ = 0; int tex_height_ = 0; bool is_initialized_ = false; + bool vsync_ = true; + bool integer_scale_ = false; }; } // namespace Rendering diff --git a/source/core/rendering/shader_backend.hpp b/source/core/rendering/shader_backend.hpp index 291bf67d..6fb29867 100644 --- a/source/core/rendering/shader_backend.hpp +++ b/source/core/rendering/shader_backend.hpp @@ -72,6 +72,16 @@ class ShaderBackend { */ virtual void setPostFXParams(const PostFXParams& /*p*/) {} + /** + * @brief Activa o desactiva VSync en el swapchain del GPU device + */ + virtual void setVSync(bool /*vsync*/) {} + + /** + * @brief Activa o desactiva el escalado entero (integer scale) + */ + virtual void setScaleMode(bool /*integer_scale*/) {} + /** * @brief Verifica si el backend está usando aceleración por hardware * @return true si usa aceleración (OpenGL/Metal/Vulkan)