From 288e01e47fd1f8e315e6557b5e90bd8fe81b0785 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Wed, 5 Nov 2025 14:15:00 +0100 Subject: [PATCH] treballant en la nova clase Player --- source/game/entities/player.cpp | 202 ++++++--- source/game/entities/player.cpp.bak | 628 ++++++++++++++++++++++++++++ source/game/entities/player.hpp | 24 +- source/game/entities/player.hpp.bak | 171 ++++++++ source/game/gameplay/room.cpp | 2 +- source/game/gameplay/room.hpp | 4 +- source/game/scene_manager.hpp | 2 +- 7 files changed, 970 insertions(+), 63 deletions(-) create mode 100644 source/game/entities/player.cpp.bak create mode 100644 source/game/entities/player.hpp.bak diff --git a/source/game/entities/player.cpp b/source/game/entities/player.cpp index cd23f374..69c306fd 100644 --- a/source/game/entities/player.cpp +++ b/source/game/entities/player.cpp @@ -35,66 +35,154 @@ void Player::render() { // Actualiza las variables del objeto void Player::update(float delta_time) { if (!is_paused_) { - handleInput(delta_time); // Comprueba las entradas y modifica variables - move(delta_time); // Recalcula la posición del jugador - animate(delta_time); // Establece la animación del jugador - handleBorders(); // Comprueba si está situado en alguno de los cuatro bordes de la habitación - handleJumpEnd(); // Comprueba si ha finalizado el salto al alcanzar la altura de inicio - handleKillingTiles(); // Comprueba que el jugador no toque ningun tile de los que matan - setColor(); // Establece el color del jugador + /* + handleInput(); // Comprueba las entradas y modifica variables + move(delta_time); // Recalcula la posición del jugador + animate(delta_time); // Establece la animación del jugador + handleBorders(); // Comprueba si está situado en alguno de los cuatro bordes de la habitación + handleJumpEnd(); // Comprueba si ha finalizado el salto al alcanzar la altura de inicio + handleKillingTiles(); // Comprueba que el jugador no toque ningun tile de los que matan + setColor(); // Establece el color del jugador + */ + handleInput(); + updateState(); + move(delta_time); + animate(delta_time); + handleBorders(); + setColor(); } } // Comprueba las entradas y modifica variables -void Player::handleInput(float delta_time) { - (void)delta_time; // No usado en este método, pero mantenido para consistencia +void Player::handleInput() { + if (Input::get()->checkAction(InputAction::LEFT)) { + wannaGo = Direction::LEFT; + } else if (Input::get()->checkAction(InputAction::RIGHT)) { + wannaGo = Direction::RIGHT; + } else { + wannaGo = Direction::STAY; + } - // Solo comprueba las entradas de dirección cuando está sobre una superficie - if (state_ != State::STANDING) { + wannaJump = Input::get()->checkAction(InputAction::JUMP); +} + +void Player::move(float delta_time) { + handleHorizontalMovement(delta_time); + handleVerticalMovement(delta_time); + updateColliderGeometry(); +} + +void Player::handleHorizontalMovement(float delta_time) { + if (state_ == State::STANDING) { + // 1. Primero, determinamos cuál debe ser la velocidad (vx_) + if (auto_movement_) { + // La cinta transportadora tiene el control + vx_ = HORIZONTAL_VELOCITY * room_->getConveyorBeltDirection(); + } else { + // El jugador tiene el control + switch (wannaGo) { + case Direction::LEFT: + vx_ = -HORIZONTAL_VELOCITY; + break; + case Direction::RIGHT: + vx_ = HORIZONTAL_VELOCITY; + break; + case Direction::STAY: + vx_ = 0.0F; + break; + default: + break; + } + } + } + + // 2. Ahora, aplicamos el movimiento y el flip basado en la velocidad resultante + if (vx_ < 0.0F) { + moveHorizontal(delta_time, -1); + sprite_->setFlip(SDL_FLIP_HORIZONTAL); + } else if (vx_ > 0.0F) { + moveHorizontal(delta_time, 1); + sprite_->setFlip(SDL_FLIP_NONE); + } + // Si vx_ es 0.0F, no se llama a moveHorizontal, lo cual es correcto. +} + +void Player::handleVerticalMovement(float delta_time) { + if (state_ == State::STANDING) { return; } - if (!auto_movement_) { - // Comprueba las entradas de desplazamiento lateral solo en el caso de no estar enganchado a una superficie automatica - if (Input::get()->checkAction(InputAction::LEFT)) { - vx_ = -HORIZONTAL_VELOCITY; - sprite_->setFlip(SDL_FLIP_HORIZONTAL); - } - - else if (Input::get()->checkAction(InputAction::RIGHT)) { - vx_ = HORIZONTAL_VELOCITY; - sprite_->setFlip(SDL_FLIP_NONE); - } - - else { - // No se pulsa ninguna dirección - vx_ = 0.0F; - if (isOnAutoSurface()) { - // Si deja de moverse sobre una superficie se engancha - auto_movement_ = true; - } - } - } else { // El movimiento lo proporciona la superficie - vx_ = HORIZONTAL_VELOCITY * room_->getAutoSurfaceDirection(); - - if (vx_ > 0.0F) { - sprite_->setFlip(SDL_FLIP_NONE); - } else { - sprite_->setFlip(SDL_FLIP_HORIZONTAL); - } + if (state_ == State::JUMPING) { + applyGravity(delta_time); } - if (Input::get()->checkAction(InputAction::JUMP)) { - // Solo puede saltar si ademas de estar (state == STANDING) - // Esta sobre el suelo, rampa o suelo que se mueve - // Esto es para evitar el salto desde el vacio al cambiar de pantalla verticalmente - // Ya que se coloca el estado STANDING al cambiar de pantalla + // Movimiento vertical + if (vy_ < 0.0F) { + moveVerticalUp(delta_time); + } else if (vy_ > 0.0F) { + moveVerticalDown(delta_time); + } +} - if (isOnFloor() || isOnAutoSurface()) { - transitionToState(State::JUMPING); - vy_ = JUMP_VELOCITY; - last_grounded_position_ = static_cast(y_); - } +void Player::moveAndCollide(float delta_time) { +} + +void Player::handleConveyorBelts() { + if (!auto_movement_ and isOnConveyorBelt() and wannaGo == Direction::STAY) { + auto_movement_ = true; + } + + if (auto_movement_ and !isOnConveyorBelt()) { + auto_movement_ = false; + } +} + +void Player::handleShouldFall() { + if (!isOnFloor() and state_ == State::STANDING) { + transitionToState(State::FALLING); + } +} + +void Player::transitionToState(State state) { + previous_state_ = state_; + state_ = state; + + switch (state) { + case State::STANDING: + vy_ = 0; + break; + case State::JUMPING: + if (previous_state_ == State::STANDING) { + vy_ = -MAX_VY; + last_grounded_position_ = y_; + } + break; + case State::FALLING: + last_grounded_position_ = y_; + vy_ = MAX_VY; + vx_ = 0.0F; + break; + } +} + +void Player::updateState() { + switch (state_) { + case State::STANDING: + handleConveyorBelts(); + handleShouldFall(); + if (wannaJump) { + transitionToState(State::JUMPING); + } + break; + case State::JUMPING: + auto_movement_ = false; + // playJumpSound(); + handleJumpEnd(); + break; + case State::FALLING: + auto_movement_ = false; + // playFallSound(); + break; } } @@ -134,7 +222,7 @@ void Player::handleState(float delta_time) { playFallSound(); } else if (state_ == State::STANDING) { // Si no tiene suelo debajo y no está en rampa descendente -> FALLING - if (!isOnFloor() && !isOnAutoSurface() && !isOnDownSlope()) { + if (!isOnFloor() && !isOnConveyorBelt() && !isOnDownSlope()) { last_grounded_position_ = static_cast(y_); // Guarda Y actual al SALIR de STANDING transitionToState(State::FALLING); // setState() establece vx_=0, vy_=MAX_VY playFallSound(); @@ -345,6 +433,7 @@ void Player::moveVerticalDown(float delta_time) { } // Orquesta el movimiento del jugador +/* void Player::move(float delta_time) { applyGravity(delta_time); // Aplica gravedad al jugador handleState(delta_time); // Comprueba el estado del jugador @@ -379,6 +468,7 @@ void Player::move(float delta_time) { // Actualiza la geometría del collider y sprite updateColliderGeometry(); } +*/ // Establece la animación del jugador void Player::animate(float delta_time) { @@ -439,7 +529,7 @@ auto Player::isOnFloor() -> bool { // Comprueba las superficies for (auto f : under_feet_) { on_floor |= room_->checkTopSurfaces(&f); - on_floor |= room_->checkAutoSurfaces(&f); + on_floor |= room_->checkConveyorBelts(&f); } // Comprueba las rampas @@ -450,17 +540,17 @@ auto Player::isOnFloor() -> bool { } // Comprueba si el jugador esta sobre una superficie automática -auto Player::isOnAutoSurface() -> bool { - bool on_auto_surface = false; +auto Player::isOnConveyorBelt() -> bool { + bool on_conveyor_belt = false; updateFeet(); // Comprueba las superficies for (auto f : under_feet_) { - on_auto_surface |= room_->checkAutoSurfaces(&f); + on_conveyor_belt |= room_->checkConveyorBelts(&f); } - return on_auto_surface; + return on_conveyor_belt; } // Comprueba si el jugador está sobre una rampa hacia abajo @@ -551,6 +641,7 @@ void Player::updateFeet() { } // Cambia el estado del jugador +/* void Player::transitionToState(State value) { previous_state_ = state_; state_ = value; @@ -577,6 +668,7 @@ void Player::transitionToState(State value) { break; } } +*/ // Inicializa los sonidos de salto y caida void Player::initSounds() { diff --git a/source/game/entities/player.cpp.bak b/source/game/entities/player.cpp.bak new file mode 100644 index 00000000..cd23f374 --- /dev/null +++ b/source/game/entities/player.cpp.bak @@ -0,0 +1,628 @@ +// IWYU pragma: no_include +#include "game/entities/player.hpp" + +#include // Para max, min +#include // Para ceil, abs +#include +#include // Para std::ranges::any_of + +#include "core/audio/audio.hpp" // Para Audio +#include "core/input/input.hpp" // Para Input, InputAction +#include "core/rendering/surface_animated_sprite.hpp" // Para SAnimatedSprite +#include "core/resources/resource.hpp" // Para Resource +#include "game/gameplay/room.hpp" // Para Room, TileType +#include "game/options.hpp" // Para Cheat, Options, options +#include "utils/defines.hpp" // Para RoomBorder::BOTTOM, RoomBorder::LEFT, RoomBorder::RIGHT + +// Constructor +Player::Player(const Data& player) + : room_(player.room) { + // Inicializa algunas variables + initSprite(player.animations_path); + setColor(); + applySpawnValues(player.spawn_data); + placeSprite(); + initSounds(); + + previous_state_ = state_; +} + +// Pinta el jugador en pantalla +void Player::render() { + sprite_->render(1, color_); +} + +// Actualiza las variables del objeto +void Player::update(float delta_time) { + if (!is_paused_) { + handleInput(delta_time); // Comprueba las entradas y modifica variables + move(delta_time); // Recalcula la posición del jugador + animate(delta_time); // Establece la animación del jugador + handleBorders(); // Comprueba si está situado en alguno de los cuatro bordes de la habitación + handleJumpEnd(); // Comprueba si ha finalizado el salto al alcanzar la altura de inicio + handleKillingTiles(); // Comprueba que el jugador no toque ningun tile de los que matan + setColor(); // Establece el color del jugador + } +} + +// Comprueba las entradas y modifica variables +void Player::handleInput(float delta_time) { + (void)delta_time; // No usado en este método, pero mantenido para consistencia + + // Solo comprueba las entradas de dirección cuando está sobre una superficie + if (state_ != State::STANDING) { + return; + } + + if (!auto_movement_) { + // Comprueba las entradas de desplazamiento lateral solo en el caso de no estar enganchado a una superficie automatica + if (Input::get()->checkAction(InputAction::LEFT)) { + vx_ = -HORIZONTAL_VELOCITY; + sprite_->setFlip(SDL_FLIP_HORIZONTAL); + } + + else if (Input::get()->checkAction(InputAction::RIGHT)) { + vx_ = HORIZONTAL_VELOCITY; + sprite_->setFlip(SDL_FLIP_NONE); + } + + else { + // No se pulsa ninguna dirección + vx_ = 0.0F; + if (isOnAutoSurface()) { + // Si deja de moverse sobre una superficie se engancha + auto_movement_ = true; + } + } + } else { // El movimiento lo proporciona la superficie + vx_ = HORIZONTAL_VELOCITY * room_->getAutoSurfaceDirection(); + + if (vx_ > 0.0F) { + sprite_->setFlip(SDL_FLIP_NONE); + } else { + sprite_->setFlip(SDL_FLIP_HORIZONTAL); + } + } + + if (Input::get()->checkAction(InputAction::JUMP)) { + // Solo puede saltar si ademas de estar (state == STANDING) + // Esta sobre el suelo, rampa o suelo que se mueve + // Esto es para evitar el salto desde el vacio al cambiar de pantalla verticalmente + // Ya que se coloca el estado STANDING al cambiar de pantalla + + if (isOnFloor() || isOnAutoSurface()) { + transitionToState(State::JUMPING); + vy_ = JUMP_VELOCITY; + last_grounded_position_ = static_cast(y_); + } + } +} + +// Comprueba si está situado en alguno de los cuatro bordes de la habitación +void Player::handleBorders() { + if (x_ < PLAY_AREA_LEFT) { + border_ = Room::Border::LEFT; + is_on_border_ = true; + } + + else if (x_ + WIDTH > PLAY_AREA_RIGHT) { + border_ = Room::Border::RIGHT; + is_on_border_ = true; + } + + else if (y_ < PLAY_AREA_TOP) { + border_ = Room::Border::TOP; + is_on_border_ = true; + } + + else if (y_ + HEIGHT > PLAY_AREA_BOTTOM) { + border_ = Room::Border::BOTTOM; + is_on_border_ = true; + } + + else { + is_on_border_ = false; + } +} + +// Comprueba el estado del jugador +void Player::handleState(float delta_time) { + (void)delta_time; // No usado actualmente + + // Reproduce sonidos según el estado + if (state_ == State::FALLING) { + playFallSound(); + } else if (state_ == State::STANDING) { + // Si no tiene suelo debajo y no está en rampa descendente -> FALLING + if (!isOnFloor() && !isOnAutoSurface() && !isOnDownSlope()) { + last_grounded_position_ = static_cast(y_); // Guarda Y actual al SALIR de STANDING + transitionToState(State::FALLING); // setState() establece vx_=0, vy_=MAX_VY + playFallSound(); + } + } else if (state_ == State::JUMPING) { + playJumpSound(); + } +} + +// Cambia al jugador de un borde al opuesto. Util para el cambio de pantalla +void Player::switchBorders() { + switch (border_) { + case Room::Border::TOP: + y_ = PLAY_AREA_BOTTOM - HEIGHT - TILE_SIZE; + transitionToState(State::STANDING); + break; + + case Room::Border::BOTTOM: + y_ = PLAY_AREA_TOP; + transitionToState(State::STANDING); + break; + + case Room::Border::RIGHT: + x_ = PLAY_AREA_LEFT; + break; + + case Room::Border::LEFT: + x_ = PLAY_AREA_RIGHT - WIDTH; + break; + + default: + break; + } + + // CRÍTICO: Resetear last_grounded_position_ para evitar muerte falsa por diferencia de Y entre pantallas + last_grounded_position_ = static_cast(y_); + is_on_border_ = false; + placeSprite(); +} + +// Aplica gravedad al jugador +void Player::applyGravity(float delta_time) { + // La gravedad solo se aplica cuando el jugador esta saltando + // Nunca mientras cae o esta de pie + if (state_ == State::JUMPING) { + vy_ += GRAVITY_FORCE * delta_time; + vy_ = std::min(vy_, MAX_VY); + } +} + +// Maneja el movimiento sobre rampas +// direction: -1 para izquierda, 1 para derecha +void Player::handleSlopeMovement(int direction) { + // No procesa rampas durante el salto (permite atravesarlas cuando salta con movimiento horizontal) + // Pero SÍ procesa en STANDING y FALLING (para pegarse a las rampas) + if (state_ == State::JUMPING) { + return; + } + + // Regla: Si está bajando la rampa, se pega a la slope + if (isOnDownSlope()) { + y_ += 1; + return; + } + + // Regla: Si está STANDING y tropieza lateralmente con una Slope, se pega a la slope + // Comprueba si hay rampa en contacto lateral (solo los dos pixels inferiores) + const int SIDE_X = direction < 0 ? static_cast(x_) : static_cast(x_) + WIDTH - 1; + const LineVertical SIDE = { + .x = SIDE_X, + .y1 = static_cast(y_) + HEIGHT - 2, + .y2 = static_cast(y_) + HEIGHT - 1}; + + // Comprueba la rampa correspondiente según la dirección + const int SLOPE_Y = direction < 0 ? room_->checkLeftSlopes(&SIDE) : room_->checkRightSlopes(&SIDE); + + if (SLOPE_Y > -1) { + // Hay rampa: sube al jugador para pegarlo a la rampa + y_ = SLOPE_Y - HEIGHT; + } +} + +// Maneja el movimiento horizontal +// direction: -1 para izquierda, 1 para derecha +void Player::moveHorizontal(float delta_time, int direction) { + const float DISPLACEMENT = vx_ * delta_time; + + // Crea el rectangulo de proyección en el eje X para ver si colisiona + SDL_FRect proj; + if (direction < 0) { + // Movimiento a la izquierda + proj = { + .x = x_ + DISPLACEMENT, + .y = y_, + .w = std::ceil(std::fabs(DISPLACEMENT)), + .h = HEIGHT}; + } else { + // Movimiento a la derecha + proj = { + .x = x_ + WIDTH, + .y = y_, + .w = std::ceil(DISPLACEMENT), + .h = HEIGHT}; + } + + // Comprueba la colisión con las superficies + const int POS = direction < 0 ? room_->checkRightSurfaces(&proj) : room_->checkLeftSurfaces(&proj); + + // Calcula la nueva posición + if (POS == -1) { + // No hay colisión: mueve al jugador + x_ += DISPLACEMENT; + } else { + // Hay colisión: reposiciona al jugador en el punto de colisión + x_ = direction < 0 ? POS + 1 : POS - WIDTH; + } + + // Maneja el movimiento sobre rampas + handleSlopeMovement(direction); +} + +// Maneja el movimiento vertical hacia arriba +void Player::moveVerticalUp(float delta_time) { + // Crea el rectangulo de proyección en el eje Y para ver si colisiona + const float DISPLACEMENT = vy_ * delta_time; + SDL_FRect proj = { + .x = x_, + .y = y_ + DISPLACEMENT, + .w = WIDTH, + .h = std::ceil(std::fabs(DISPLACEMENT)) // Para evitar que tenga una altura de 0 pixels + }; + + // Comprueba la colisión + const int POS = room_->checkBottomSurfaces(&proj); + + // Calcula la nueva posición + if (POS == -1) { + // Si no hay colisión + y_ += DISPLACEMENT; + } else { + // Si hay colisión lo mueve hasta donde no colisiona + // Regla: Si está JUMPING y tropieza contra el techo -> FALLING + y_ = POS + 1; + transitionToState(State::FALLING); + } +} + +// Maneja el movimiento vertical hacia abajo +void Player::moveVerticalDown(float delta_time) { + // Crea el rectangulo de proyección en el eje Y para ver si colisiona + const float DISPLACEMENT = vy_ * delta_time; + SDL_FRect proj = { + .x = x_, + .y = y_ + HEIGHT, + .w = WIDTH, + .h = std::ceil(DISPLACEMENT) // Para evitar que tenga una altura de 0 pixels + }; + + // Comprueba la colisión con las superficies normales y las automáticas + const float POS = std::max(room_->checkTopSurfaces(&proj), room_->checkAutoSurfaces(&proj)); + if (POS > -1) { + // Si hay colisión lo mueve hasta donde no colisiona y pasa a estar sobre la superficie + y_ = POS - HEIGHT; + + // VERIFICAR MUERTE ANTES de cambiar de estado (PLAYER_MECHANICS.md línea 1268-1274) + const int FALL_DISTANCE = static_cast(y_) - last_grounded_position_; + if (previous_state_ == State::FALLING && FALL_DISTANCE > MAX_FALLING_HEIGHT) { + is_alive_ = false; // Muere si cae más de 32 píxeles + } + + transitionToState(State::STANDING); + last_grounded_position_ = static_cast(y_); // Actualizar AL ENTRAR en STANDING + // Deja de estar enganchado a la superficie automatica + auto_movement_ = false; + } else { + // Si no hay colisión con los muros, comprueba la colisión con las rampas + // CORRECCIÓN: FALLING siempre se pega a rampas, JUMPING se pega solo si vx_ == 0 + if (state_ == State::FALLING || (state_ == State::JUMPING && vx_ == 0.0F)) { + // No está saltando O salta recto: se pega a las rampas + auto rect = toSDLRect(proj); + const LineVertical LEFT_SIDE = {.x = rect.x, .y1 = rect.y, .y2 = rect.y + rect.h - 1}; + const LineVertical RIGHT_SIDE = {.x = rect.x + rect.w - 1, .y1 = rect.y, .y2 = rect.y + rect.h - 1}; + const float POINT = std::max(room_->checkRightSlopes(&RIGHT_SIDE), room_->checkLeftSlopes(&LEFT_SIDE)); + if (POINT > -1) { + // No está saltando y hay colisión con una rampa + // Calcula la nueva posición + y_ = POINT - HEIGHT; + + // VERIFICAR MUERTE ANTES de cambiar de estado (PLAYER_MECHANICS.md línea 1268-1274) + const int FALL_DISTANCE = static_cast(y_) - last_grounded_position_; + if (previous_state_ == State::FALLING && FALL_DISTANCE > MAX_FALLING_HEIGHT) { + is_alive_ = false; // Muere si cae más de 32 píxeles + } + + transitionToState(State::STANDING); + last_grounded_position_ = static_cast(y_); // Actualizar AL ENTRAR en STANDING + } else { + // No está saltando y no hay colisón con una rampa + // Calcula la nueva posición + y_ += DISPLACEMENT; + } + } else { + // Esta saltando con movimiento horizontal y no hay colisión con los muros + // Calcula la nueva posición (atraviesa rampas) + y_ += DISPLACEMENT; + } + } +} + +// Orquesta el movimiento del jugador +void Player::move(float delta_time) { + applyGravity(delta_time); // Aplica gravedad al jugador + handleState(delta_time); // Comprueba el estado del jugador + + // Movimiento horizontal + if (vx_ < 0.0F) { + moveHorizontal(delta_time, -1); // Izquierda + } else if (vx_ > 0.0F) { + moveHorizontal(delta_time, 1); // Derecha + } + + // Si ha salido del suelo, el jugador cae + if (state_ == State::STANDING && !isOnFloor()) { + transitionToState(State::FALLING); + auto_movement_ = false; + } + + // Si ha salido de una superficie automatica, detiene el movimiento automatico + if (state_ == State::STANDING && isOnFloor() && !isOnAutoSurface()) { + auto_movement_ = false; + } + + // Movimiento vertical + if (vy_ < 0.0F) { + moveVerticalUp(delta_time); + } else if (vy_ > 0.0F) { + moveVerticalDown(delta_time); + } + + y_prev_ = y_; // Guarda Y DESPUÉS de todo el movimiento (para detectar hitos en sonidos) + + // Actualiza la geometría del collider y sprite + updateColliderGeometry(); +} + +// Establece la animación del jugador +void Player::animate(float delta_time) { + if (vx_ != 0) { + sprite_->update(delta_time); + } +} + +// Comprueba si ha finalizado el salto al alcanzar la altura de inicio +void Player::handleJumpEnd() { + // Si el jugador vuelve EXACTAMENTE a la altura inicial, debe CONTINUAR en JUMPING + // Solo cuando la SUPERA (desciende más allá) cambia a FALLING + if (state_ == State::JUMPING && vy_ > 0.0F && static_cast(y_) > last_grounded_position_) { + transitionToState(State::FALLING); + } +} + +// Calcula y reproduce el sonido de salto basado en distancia vertical recorrida +void Player::playJumpSound() { + // Sistema basado en distancia vertical, no en tiempo (PLAYER_MECHANICS.md línea 107-120) + 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())) { + Audio::get()->playSound(jumping_sound_[SOUND_INDEX], Audio::Group::GAME); + } +} + +// Calcula y reproduce el sonido de caída basado en distancia vertical recorrida +void Player::playFallSound() { + // Sistema basado en distancia vertical, no en tiempo (PLAYER_MECHANICS.md línea 193-206) + 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 (nuevo hito alcanzado) + if (SOUND_INDEX != PREVIOUS_INDEX && SOUND_INDEX < static_cast(falling_sound_.size())) { + Audio::get()->playSound(falling_sound_[SOUND_INDEX], Audio::Group::GAME); + } +} + +// Comprueba si el jugador tiene suelo debajo de los pies +auto Player::isOnFloor() -> bool { + bool on_floor = false; + bool on_slope_l = false; + bool on_slope_r = false; + + updateFeet(); + + // Comprueba las superficies + for (auto f : under_feet_) { + on_floor |= room_->checkTopSurfaces(&f); + on_floor |= room_->checkAutoSurfaces(&f); + } + + // Comprueba las rampas + on_slope_l = room_->checkLeftSlopes(under_feet_.data()); + on_slope_r = room_->checkRightSlopes(&under_feet_[1]); + + return on_floor || on_slope_l || on_slope_r; +} + +// Comprueba si el jugador esta sobre una superficie automática +auto Player::isOnAutoSurface() -> bool { + bool on_auto_surface = false; + + updateFeet(); + + // Comprueba las superficies + for (auto f : under_feet_) { + on_auto_surface |= room_->checkAutoSurfaces(&f); + } + + return on_auto_surface; +} + +// Comprueba si el jugador está sobre una rampa hacia abajo +auto Player::isOnDownSlope() -> bool { + bool on_slope = false; + + updateFeet(); + + // Cuando el jugador baja una escalera, se queda volando + // Hay que mirar otro pixel más por debajo + SDL_FPoint foot0 = under_feet_[0]; + SDL_FPoint foot1 = under_feet_[1]; + foot0.y += 1.0F; + foot1.y += 1.0F; + + // Comprueba las rampas + on_slope |= room_->checkLeftSlopes(&foot0); + on_slope |= room_->checkRightSlopes(&foot1); + + return on_slope; +} + +// Comprueba que el jugador no toque ningun tile de los que matan +auto Player::handleKillingTiles() -> bool { + // Comprueba si hay contacto con algún tile que mata + if (std::ranges::any_of(collider_points_, [this](const auto& c) { + return room_->getTile(c) == Room::Tile::KILL; + })) { + is_alive_ = false; // Mata al jugador inmediatamente + return true; // Retorna en cuanto se detecta una colisión + } + + return false; // No se encontró ninguna colisión +} + +// Establece el color del jugador +void Player::setColor() { + /* + if (Options::cheats.invincible == Options::Cheat::State::ENABLED) { + color_ = static_cast(PaletteColor::CYAN); + } else if (Options::cheats.infinite_lives == Options::Cheat::State::ENABLED) { + color_ = static_cast(PaletteColor::YELLOW); + } else { + color_ = static_cast(PaletteColor::WHITE); + } + */ + + switch (state_) { + case State::STANDING: + color_ = static_cast(PaletteColor::YELLOW); + break; + + case State::JUMPING: + color_ = static_cast(PaletteColor::GREEN); + break; + + case State::FALLING: + color_ = static_cast(PaletteColor::RED); + break; + + default: + break; + } +} + +// Actualiza los puntos de colisión +void Player::updateColliderPoints() { + const SDL_FRect RECT = getRect(); + collider_points_[0] = {.x = RECT.x, .y = RECT.y}; + collider_points_[1] = {.x = RECT.x + 7, .y = RECT.y}; + collider_points_[2] = {.x = RECT.x + 7, .y = RECT.y + 7}; + collider_points_[3] = {.x = RECT.x, .y = RECT.y + 7}; + collider_points_[4] = {.x = RECT.x, .y = RECT.y + 8}; + collider_points_[5] = {.x = RECT.x + 7, .y = RECT.y + 8}; + collider_points_[6] = {.x = RECT.x + 7, .y = RECT.y + 15}; + collider_points_[7] = {.x = RECT.x, .y = RECT.y + 15}; +} + +// Actualiza los puntos de los pies +void Player::updateFeet() { + const SDL_FPoint P = {x_, y_}; + + under_feet_[0] = {.x = P.x, .y = P.y + HEIGHT}; + under_feet_[1] = {.x = P.x + 7, .y = P.y + HEIGHT}; + + feet_[0] = {.x = P.x, .y = P.y + HEIGHT - 1}; + feet_[1] = {.x = P.x + 7, .y = P.y + HEIGHT - 1}; +} + +// Cambia el estado del jugador +void Player::transitionToState(State value) { + previous_state_ = state_; + state_ = value; + + // Establecer velocidades INMEDIATAMENTE al cambiar de estado + switch (state_) { + case State::STANDING: + vx_ = 0.0F; + vy_ = 0.0F; + break; + + case State::JUMPING: + // vx_ mantiene su valor actual (heredado de STANDING) + vy_ = JUMP_VELOCITY; + break; + + case State::FALLING: + vx_ = 0.0F; // CRÍTICO para pegarse a rampas + vy_ = MAX_VY; + auto_movement_ = false; + break; + + default: + break; + } +} + +// Inicializa los sonidos de salto y caida +void Player::initSounds() { + jumping_sound_.clear(); + falling_sound_.clear(); + + for (int i = 1; i <= 24; ++i) { + std::string sound_file = "jump" + std::to_string(i) + ".wav"; + jumping_sound_.push_back(Resource::get()->getSound(sound_file)); + + if (i >= 11) { + falling_sound_.push_back(Resource::get()->getSound(sound_file)); + } + } +} + +// Aplica los valores de spawn al jugador +void Player::applySpawnValues(const SpawnData& spawn) { + x_ = spawn.x; + y_ = spawn.y; + y_prev_ = spawn.y; // Inicializar y_prev_ igual a y_ para evitar saltos en primer frame + vx_ = spawn.vx; + vy_ = spawn.vy; + last_grounded_position_ = spawn.last_grounded_position; + state_ = spawn.state; + sprite_->setFlip(spawn.flip); +} + +// Inicializa el sprite del jugador +void Player::initSprite(const std::string& animations_path) { + auto animations = Resource::get()->getAnimations(animations_path); + + sprite_ = std::make_unique(animations); + sprite_->setWidth(WIDTH); + sprite_->setHeight(HEIGHT); + sprite_->setCurrentAnimation("walk"); +} + +// Actualiza collider_box y collision points +void Player::updateColliderGeometry() { + placeSprite(); // Coloca el sprite en la posición del jugador + collider_box_ = getRect(); // Actualiza el rectangulo de colisión + updateColliderPoints(); // Actualiza los puntos de colisión +} + +// Coloca el sprite en la posición del jugador +void Player::placeSprite() { + sprite_->setPos(x_, y_); +} \ No newline at end of file diff --git a/source/game/entities/player.hpp b/source/game/entities/player.hpp index 40695880..11f4849d 100644 --- a/source/game/entities/player.hpp +++ b/source/game/entities/player.hpp @@ -23,6 +23,12 @@ class Player { FALLING, }; + enum class Direction { + LEFT, + RIGHT, + STAY + }; + struct SpawnData { float x = 0; float y = 0; @@ -105,6 +111,9 @@ class Player { float vx_ = 0.0F; // Velocidad/desplazamiento del jugador en el eje X float vy_ = 0.0F; // Velocidad/desplazamiento del jugador en el eje Y + Direction wannaGo = Direction::STAY; + bool wannaJump = false; + // --- Variables de estado --- State state_ = State::STANDING; // Estado en el que se encuentra el jugador. Util apara saber si está saltando o cayendo State previous_state_ = State::STANDING; // Estado previo en el que se encontraba el jugador @@ -128,13 +137,20 @@ class Player { std::vector jumping_sound_; // Vecor con todos los sonidos del salto std::vector falling_sound_; // Vecor con todos los sonidos de la caída + void handleHorizontalMovement(float delta_time); + void handleVerticalMovement(float delta_time); + void handleConveyorBelts(); + void handleShouldFall(); + void updateState(); + void moveAndCollide(float delta_time); + // --- Funciones de inicialización --- void initSprite(const std::string& animations_path); // Inicializa el sprite del jugador void initSounds(); // Inicializa los sonidos de salto y caida void applySpawnValues(const SpawnData& spawn); // Aplica los valores de spawn al jugador // --- Funciones de procesamiento de entrada --- - void handleInput(float delta_time); // Comprueba las entradas y modifica variables + void handleInput(); // Comprueba las entradas y modifica variables // --- Funciones de gestión de estado --- void handleState(float delta_time); // Comprueba el estado del jugador y actualiza variables @@ -151,9 +167,9 @@ class Player { void handleSlopeMovement(int direction); // Maneja el movimiento sobre rampas // --- Funciones de detección de superficies --- - auto isOnFloor() -> bool; // Comprueba si el jugador tiene suelo debajo de los pies - auto isOnAutoSurface() -> bool; // Comprueba si el jugador esta sobre una superficie automática - auto isOnDownSlope() -> bool; // Comprueba si el jugador está sobre una rampa hacia abajo + auto isOnFloor() -> bool; // Comprueba si el jugador tiene suelo debajo de los pies + auto isOnConveyorBelt() -> bool; // Comprueba si el jugador esta sobre una superficie automática + auto isOnDownSlope() -> bool; // Comprueba si el jugador está sobre una rampa hacia abajo // --- Funciones de actualización de geometría --- void updateColliderGeometry(); // Actualiza collider_box y collision points diff --git a/source/game/entities/player.hpp.bak b/source/game/entities/player.hpp.bak new file mode 100644 index 00000000..40695880 --- /dev/null +++ b/source/game/entities/player.hpp.bak @@ -0,0 +1,171 @@ +#pragma once + +#include + +#include // Para array +#include // Para shared_ptr, __shared_ptr_access +#include // Para string +#include +#include // Para vector + +#include "core/rendering/surface_animated_sprite.hpp" // Para SAnimatedSprite +#include "game/gameplay/room.hpp" +#include "utils/defines.hpp" // Para BORDER_TOP, BLOCK +#include "utils/utils.hpp" // Para Color +struct JA_Sound_t; // lines 13-13 + +class Player { + public: + // --- Enums y Structs --- + enum class State { + STANDING, + JUMPING, + FALLING, + }; + + struct SpawnData { + float x = 0; + float y = 0; + float vx = 0; + float vy = 0; + int last_grounded_position = 0; + State state = State::STANDING; + SDL_FlipMode flip = SDL_FLIP_NONE; + + // Constructor por defecto + SpawnData() = default; + + // Constructor con parámetros + SpawnData(float x, float y, float vx, float vy, int last_grounded_position, State state, SDL_FlipMode flip) + : x(x), + y(y), + vx(vx), + vy(vy), + last_grounded_position(last_grounded_position), + state(state), + flip(flip) {} + }; + + struct Data { + SpawnData spawn_data; + std::string animations_path; + std::shared_ptr room = nullptr; + + // Constructor por defecto + Data() = default; + + // Constructor con parámetros + Data(SpawnData spawn_data, const std::string& texture_path, std::string animations_path, std::shared_ptr room) + : spawn_data(spawn_data), + animations_path(std::move(animations_path)), + room(std::move(room)) {} + }; + + // --- Constructor y Destructor --- + explicit Player(const Data& player); + ~Player() = default; + + // --- Funciones --- + void render(); // Pinta el enemigo en pantalla + void update(float delta_time); // Actualiza las variables del objeto + [[nodiscard]] auto isOnBorder() const -> bool { return is_on_border_; } // Indica si el jugador esta en uno de los cuatro bordes de la pantalla + [[nodiscard]] auto getBorder() const -> Room::Border { return border_; } // Indica en cual de los cuatro bordes se encuentra + void switchBorders(); // Cambia al jugador de un borde al opuesto. Util para el cambio de pantalla + auto getRect() -> SDL_FRect { return {x_, y_, WIDTH, HEIGHT}; } // Obtiene el rectangulo que delimita al jugador + auto getCollider() -> SDL_FRect& { return collider_box_; } // Obtiene el rectangulo de colision del jugador + auto getSpawnParams() -> SpawnData { return {x_, y_, vx_, vy_, last_grounded_position_, state_, sprite_->getFlip()}; } // Obtiene el estado de reaparición del jugador + void setColor(); // Establece el color del jugador + void setRoom(std::shared_ptr room) { room_ = std::move(room); } // Establece la habitación en la que se encuentra el jugador + [[nodiscard]] auto isAlive() const -> bool { return is_alive_; } // Comprueba si el jugador esta vivo + void setPaused(bool value) { is_paused_ = value; } // Pone el jugador en modo pausa + + private: + // --- Constantes --- + static constexpr int WIDTH = 8; // Ancho del jugador + static constexpr int HEIGHT = 16; // ALto del jugador + static constexpr int MAX_FALLING_HEIGHT = TILE_SIZE * 4; // Altura maxima permitida de caída en pixels + + // --- Constantes de física (per-second values) --- + static constexpr float HORIZONTAL_VELOCITY = 40.0F; // Velocidad horizontal en pixels/segundo (0.6 * 66.67fps) + static constexpr float MAX_VY = 80.0F; // Velocidad vertical máxima en pixels/segundo (1.2 * 66.67fps) + static constexpr float JUMP_VELOCITY = -80.0F; // Velocidad inicial del salto en pixels/segundo + static constexpr float GRAVITY_FORCE = 155.6F; // Fuerza de gravedad en pixels/segundo² (0.035 * 66.67²) + + // --- Constantes de sonido --- + static constexpr float SOUND_DISTANCE_INTERVAL = 3.0F; // Distancia en píxeles entre cada sonido de salto/caída + + // --- --- Objetos y punteros --- --- + std::shared_ptr room_; // Objeto encargado de gestionar cada habitación del juego + std::unique_ptr sprite_; // Sprite del jugador + + // --- Variables de posición y física --- + float x_ = 0.0F; // Posición del jugador en el eje X + float y_ = 0.0F; // Posición del jugador en el eje Y + float y_prev_ = 0.0F; // Posición Y del frame anterior (para detectar hitos de distancia en sonidos) + float vx_ = 0.0F; // Velocidad/desplazamiento del jugador en el eje X + float vy_ = 0.0F; // Velocidad/desplazamiento del jugador en el eje Y + + // --- Variables de estado --- + State state_ = State::STANDING; // Estado en el que se encuentra el jugador. Util apara saber si está saltando o cayendo + State previous_state_ = State::STANDING; // Estado previo en el que se encontraba el jugador + + // --- Variables de colisión --- + SDL_FRect collider_box_; // Caja de colisión con los enemigos u objetos + std::array collider_points_{}; // Puntos de colisión con el mapa + std::array under_feet_{}; // Contiene los puntos que hay bajo cada pie del jugador + std::array feet_{}; // Contiene los puntos que hay en el pie del jugador + + // --- Variables de juego --- + bool is_on_border_ = false; // Indica si el jugador esta en uno de los cuatro bordes de la pantalla + bool is_alive_ = true; // Indica si el jugador esta vivo o no + bool is_paused_ = false; // Indica si el jugador esta en modo pausa + bool auto_movement_ = false; // Indica si esta siendo arrastrado por una superficie automatica + Room::Border border_ = Room::Border::TOP; // Indica en cual de los cuatro bordes se encuentra + int last_grounded_position_ = 0; // Ultima posición en Y en la que se estaba en contacto con el suelo (hace doble función: tracking de caída + altura inicial del salto) + + // --- Variables de renderizado y sonido --- + Uint8 color_ = 0; // Color del jugador + std::vector jumping_sound_; // Vecor con todos los sonidos del salto + std::vector falling_sound_; // Vecor con todos los sonidos de la caída + + // --- Funciones de inicialización --- + void initSprite(const std::string& animations_path); // Inicializa el sprite del jugador + void initSounds(); // Inicializa los sonidos de salto y caida + void applySpawnValues(const SpawnData& spawn); // Aplica los valores de spawn al jugador + + // --- Funciones de procesamiento de entrada --- + void handleInput(float delta_time); // Comprueba las entradas y modifica variables + + // --- Funciones de gestión de estado --- + void handleState(float delta_time); // Comprueba el estado del jugador y actualiza variables + void transitionToState(State value); // Cambia el estado del jugador + + // --- Funciones de física --- + void applyGravity(float delta_time); // Aplica gravedad al jugador + + // --- Funciones de movimiento y colisión --- + void move(float delta_time); // Orquesta el movimiento del jugador + void moveHorizontal(float delta_time, int direction); // Maneja el movimiento horizontal (-1: izq, 1: der) + void moveVerticalUp(float delta_time); // Maneja el movimiento vertical hacia arriba + void moveVerticalDown(float delta_time); // Maneja el movimiento vertical hacia abajo + void handleSlopeMovement(int direction); // Maneja el movimiento sobre rampas + + // --- Funciones de detección de superficies --- + auto isOnFloor() -> bool; // Comprueba si el jugador tiene suelo debajo de los pies + auto isOnAutoSurface() -> bool; // Comprueba si el jugador esta sobre una superficie automática + auto isOnDownSlope() -> bool; // Comprueba si el jugador está sobre una rampa hacia abajo + + // --- Funciones de actualización de geometría --- + void updateColliderGeometry(); // Actualiza collider_box y collision points + void updateColliderPoints(); // Actualiza los puntos de colisión + void updateFeet(); // Actualiza los puntos de los pies + void placeSprite(); // Coloca el sprite en la posición del jugador + + // --- Funciones de finalización --- + void animate(float delta_time); // Establece la animación del jugador + void handleBorders(); // Comprueba si se halla en alguno de los cuatro bordes + void handleJumpEnd(); // Comprueba si ha finalizado el salto al alcanzar la altura de inicio + auto handleKillingTiles() -> bool; // Comprueba que el jugador no toque ningun tile de los que matan + void playJumpSound(); // Calcula y reproduce el sonido de salto + void playFallSound(); // Calcula y reproduce el sonido de caer +}; \ No newline at end of file diff --git a/source/game/gameplay/room.cpp b/source/game/gameplay/room.cpp index 41f3a4e8..ea9e4428 100644 --- a/source/game/gameplay/room.cpp +++ b/source/game/gameplay/room.cpp @@ -743,7 +743,7 @@ auto Room::checkTopSurfaces(SDL_FPoint* p) -> bool { } // Comprueba las colisiones -auto Room::checkAutoSurfaces(SDL_FPoint* p) -> bool { +auto Room::checkConveyorBelts(SDL_FPoint* p) -> bool { return std::ranges::any_of(conveyor_belt_floors_, [&](const auto& s) { return checkCollision(s, *p); }); diff --git a/source/game/gameplay/room.hpp b/source/game/gameplay/room.hpp index a758c9c1..3de6ea5e 100644 --- a/source/game/gameplay/room.hpp +++ b/source/game/gameplay/room.hpp @@ -84,13 +84,13 @@ class Room { auto checkBottomSurfaces(SDL_FRect* rect) -> int; // Comprueba las colisiones auto checkAutoSurfaces(SDL_FRect* rect) -> int; // Comprueba las colisiones auto checkTopSurfaces(SDL_FPoint* p) -> bool; // Comprueba las colisiones - auto checkAutoSurfaces(SDL_FPoint* p) -> bool; // Comprueba las colisiones + auto checkConveyorBelts(SDL_FPoint* p) -> bool; // Comprueba las colisiones auto checkLeftSlopes(const LineVertical* line) -> int; // Comprueba las colisiones auto checkLeftSlopes(SDL_FPoint* p) -> bool; // Comprueba las colisiones auto checkRightSlopes(const LineVertical* line) -> int; // Comprueba las colisiones auto checkRightSlopes(SDL_FPoint* p) -> bool; // Comprueba las colisiones void setPaused(bool value) { is_paused_ = value; }; // Pone el mapa en modo pausa - [[nodiscard]] auto getAutoSurfaceDirection() const -> int { return conveyor_belt_direction_; } // Obten la direccion de las superficies automaticas + [[nodiscard]] auto getConveyorBeltDirection() const -> int { return conveyor_belt_direction_; } // Obten la direccion de las superficies automaticas static auto loadRoomFile(const std::string& file_path, bool verbose = false) -> Data; // Carga las variables desde un fichero de mapa static auto loadRoomTileFile(const std::string& file_path, bool verbose = false) -> std::vector; // Carga las variables y texturas desde un fichero de mapa de tiles diff --git a/source/game/scene_manager.hpp b/source/game/scene_manager.hpp index e718ee95..9ee627ac 100644 --- a/source/game/scene_manager.hpp +++ b/source/game/scene_manager.hpp @@ -34,7 +34,7 @@ enum class Options { // --- Variables de estado globales --- #ifdef _DEBUG -inline Scene current = Scene::TITLE; // Escena actual +inline Scene current = Scene::GAME; // Escena actual inline Options options = Options::LOGO_TO_LOADING_SCREEN; // Opciones de la escena actual #else inline Scene current = Scene::LOGO; // Escena actual