# 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