// 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_cache.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 #ifdef _DEBUG #include "core/system/debug.hpp" // Para Debug #endif // Constructor Player::Player(const Data& player) : room_(player.room) { 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_); #ifdef _DEBUG if (Debug::get()->isEnabled()) { Screen::get()->getRendererSurface()->putPixel(under_right_foot_.x, under_right_foot_.y, static_cast(PaletteColor::GREEN)); Screen::get()->getRendererSurface()->putPixel(under_left_foot_.x, under_left_foot_.y, static_cast(PaletteColor::GREEN)); } #endif } // Actualiza las variables del objeto void Player::update(float delta_time) { if (!is_paused_) { handleInput(); updateState(delta_time); move(delta_time); animate(delta_time); handleBorders(); } } // Comprueba las entradas y modifica variables 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::NONE; } wannaJump = Input::get()->checkAction(InputAction::JUMP); } // La lógica de movimiento está distribuida en move void Player::move(float delta_time) { switch (state_) { case State::ON_GROUND: moveOnGround(delta_time); break; case State::ON_SLOPE: moveOnSlope(delta_time); break; case State::JUMPING: moveJumping(delta_time); break; case State::FALLING: moveFalling(delta_time); break; } syncSpriteAndCollider(); // Actualiza la posición del sprite y las colisiones #ifdef _DEBUG Debug::get()->add(std::string("X: " + std::to_string(static_cast(x_)))); Debug::get()->add(std::string("Y: " + std::to_string(static_cast(y_)))); #endif } void Player::handleConveyorBelts() { if (!auto_movement_ and isOnConveyorBelt() and wannaGo == Direction::NONE) { auto_movement_ = true; } if (auto_movement_ and !isOnConveyorBelt()) { auto_movement_ = false; } } void Player::handleShouldFall() { if (!isOnFloor() and (state_ == State::ON_GROUND || state_ == State::ON_SLOPE)) { transitionToState(State::FALLING); } } void Player::transitionToState(State state) { previous_state_ = state_; state_ = state; switch (state) { case State::ON_GROUND: //std::cout << "ON_GROUND\n"; vy_ = 0; handleDeathByFalling(); resetSoundControllersOnLanding(); current_slope_ = nullptr; break; case State::ON_SLOPE: //std::cout << "ON_SLOPE\n"; vy_ = 0; handleDeathByFalling(); resetSoundControllersOnLanding(); updateCurrentSlope(); break; case State::JUMPING: //std::cout << "JUMPING\n"; // Puede saltar desde ON_GROUND o ON_SLOPE if (previous_state_ == State::ON_GROUND || previous_state_ == State::ON_SLOPE) { vy_ = -MAX_VY; last_grounded_position_ = y_; updateVelocity(); jump_sound_ctrl_.start(); current_slope_ = nullptr; } break; case State::FALLING: //std::cout << "FALLING\n"; fall_start_position_ = static_cast(y_); last_grounded_position_ = static_cast(y_); vy_ = MAX_VY; vx_ = 0.0F; jump_sound_ctrl_.reset(); fall_sound_ctrl_.start(y_); current_slope_ = nullptr; break; } } void Player::updateState(float delta_time) { switch (state_) { case State::ON_GROUND: updateOnGround(delta_time); break; case State::ON_SLOPE: updateOnSlope(delta_time); break; case State::JUMPING: updateJumping(delta_time); break; case State::FALLING: updateFalling(delta_time); break; } } // Actualización lógica del estado ON_GROUND void Player::updateOnGround(float delta_time) { (void)delta_time; // No usado en este método, pero se mantiene por consistencia handleConveyorBelts(); // Gestiona las cintas transportadoras handleShouldFall(); // Verifica si debe caer (no tiene suelo) // Verifica si el jugador quiere saltar if (wannaJump) { transitionToState(State::JUMPING); } } // Actualización lógica del estado ON_SLOPE void Player::updateOnSlope(float delta_time) { (void)delta_time; // No usado en este método, pero se mantiene por consistencia // NOTA: No llamamos handleShouldFall() aquí porque moveOnSlope() ya maneja // todas las condiciones de salida de la rampa (out of bounds, transición a superficie plana) // Verifica si el jugador quiere saltar if (wannaJump) { transitionToState(State::JUMPING); } } // Actualización lógica del estado JUMPING void Player::updateJumping(float delta_time) { auto_movement_ = false; // Desactiva el movimiento automático durante el salto playJumpSound(delta_time); // Reproduce los sonidos de salto handleJumpEnd(); // Verifica si el salto ha terminado (alcanzó la altura inicial) } // Actualización lógica del estado FALLING void Player::updateFalling(float delta_time) { auto_movement_ = false; // Desactiva el movimiento automático durante la caída playFallSound(delta_time); // Reproduce los sonidos de caída } // Movimiento físico del estado ON_GROUND void Player::moveOnGround(float delta_time) { // Determinama cuál debe ser la velocidad a partir de automovement o de wannaGo updateVelocity(); if (vx_ == 0.0F) { return; } // Movimiento horizontal y colision con muros applyHorizontalMovement(delta_time); // Comprueba colision con rampas, corrige y cambia estado const int SIDE_X = vx_ < 0.0F ? 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 = vx_ < 0.0F ? room_->checkLeftSlopes(SIDE) : room_->checkRightSlopes(SIDE); if (SLOPE_Y != Collision::NONE) { // Hay rampa: sube al jugador para pegarlo a la rampa y_ = SLOPE_Y - HEIGHT; transitionToState(State::ON_SLOPE); } // Comprueba si está sobre una rampa if (isOnSlope()) { transitionToState(State::ON_SLOPE); } } // Movimiento físico del estado ON_SLOPE void Player::moveOnSlope(float delta_time) { // Determinama cuál debe ser la velocidad a partir de automovement o de wannaGo updateVelocity(); if (vx_ == 0.0F) { return; } // Verificar que tenemos una rampa válida if (current_slope_ == nullptr) { transitionToState(State::FALLING); return; } // Determinar el tipo de rampa const bool IS_LEFT_SLOPE = isLeftSlope(); // Movimiento horizontal con colisión lateral applyHorizontalMovement(delta_time); // Seleccionar el pie apropiado según el tipo de rampa // Left slopes (forma \) colisionan con el pie izquierdo // Right slopes (forma /) colisionan con el pie derecho const int X = IS_LEFT_SLOPE ? x_ : x_ + WIDTH - 1; // Calcular la Y basada en la ecuación de la rampa (45 grados) // Left slope (\): y aumenta con x -> y = y1 + (x - x1) // Right slope (/): y disminuye con x -> y = y1 - (x - x1) if (IS_LEFT_SLOPE) { y_ = current_slope_->y1 + (X - current_slope_->x1) - HEIGHT; } else { y_ = current_slope_->y1 - (X - current_slope_->x1) - HEIGHT; } // Verificar si el pie ha salido de los límites horizontales de la rampa // Usar min/max porque LEFT slopes tienen x1x2 const int MIN_X = std::min(current_slope_->x1, current_slope_->x2); const int MAX_X = std::max(current_slope_->x1, current_slope_->x2); const bool OUT_OF_BOUNDS = (X < MIN_X) || (X > MAX_X); if (OUT_OF_BOUNDS) { // Determinar si estamos saliendo por arriba o por abajo de la rampa const bool EXITING_DOWNWARD = (X > current_slope_->x2 && IS_LEFT_SLOPE) || (X < current_slope_->x1 && !IS_LEFT_SLOPE); const bool EXITING_UPWARD = (X < current_slope_->x1 && IS_LEFT_SLOPE) || (X > current_slope_->x2 && !IS_LEFT_SLOPE); if (EXITING_DOWNWARD) { // Salida por abajo: no hacer nada // y_ += 1.0F; } if (EXITING_UPWARD) { // Salida por arriba: bajar un pixel ya que ha subido 1 de mas al salirse de la recta y_ += 1.0F; } // Verificar si hay soporte debajo (suelo plano o conveyor belt) if (isOnTopSurface() || isOnConveyorBelt()) { // Hay soporte: transición a ON_GROUND (podría ser superficie o conveyor belt) transitionToState(State::ON_GROUND); } else { // Sin soporte: empezar a caer transitionToState(State::FALLING); } return; } // Verificar transición a superficie plana /*if (isOnTopSurface()) { transitionToState(State::ON_GROUND); return; }*/ } // Movimiento físico del estado JUMPING void Player::moveJumping(float delta_time) { // Movimiento horizontal applyHorizontalMovement(delta_time); // Movimiento vertical applyGravity(delta_time); const float DISPLACEMENT_Y = vy_ * delta_time; // Movimiento vertical hacia arriba if (vy_ < 0.0F) { const SDL_FRect PROJECTION = getProjection(Direction::UP, DISPLACEMENT_Y); // Comprueba la colisión const int POS = room_->checkBottomSurfaces(PROJECTION); // Calcula la nueva posición if (POS == Collision::NONE) { // Si no hay colisión y_ += DISPLACEMENT_Y; } else { // Si hay colisión lo mueve hasta donde no colisiona -> FALLING y_ = POS + 1; transitionToState(State::FALLING); } } // Movimiento vertical hacia abajo else if (vy_ > 0.0F) { // Crea el rectangulo de proyección en el eje Y para ver si colisiona const SDL_FRect PROJECTION = getProjection(Direction::DOWN, DISPLACEMENT_Y); // JUMPING colisiona con rampas solo si vx_ == 0 if (vx_ == 0.0F) { handleLandingFromAir(DISPLACEMENT_Y, PROJECTION); } else { // Comprueba la colisión con las superficies y las cintas transportadoras (sin rampas) const float POS = std::max(room_->checkTopSurfaces(PROJECTION), room_->checkAutoSurfaces(PROJECTION)); if (POS != Collision::NONE) { // Si hay colisión lo mueve hasta donde no colisiona y pasa a estar sobre la superficie y_ = POS - HEIGHT; transitionToState(State::ON_GROUND); } else { // Esta saltando con movimiento horizontal y no hay colisión con los muros // Calcula la nueva posición (atraviesa rampas) y_ += DISPLACEMENT_Y; } } } } // Movimiento físico del estado FALLING void Player::moveFalling(float delta_time) { // Crea el rectangulo de proyección en el eje Y para ver si colisiona const float DISPLACEMENT = vy_ * delta_time; const SDL_FRect PROJECTION = getProjection(Direction::DOWN, DISPLACEMENT); // Comprueba aterrizaje en superficies y rampas handleLandingFromAir(DISPLACEMENT, PROJECTION); } // 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; } } // 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::ON_GROUND); // TODO: Detectar si debe ser ON_SLOPE break; case Room::Border::BOTTOM: y_ = PLAY_AREA_TOP; transitionToState(State::ON_GROUND); // TODO: Detectar si debe ser ON_SLOPE 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); } } // 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 tiempo transcurrido void Player::playJumpSound(float delta_time) { size_t sound_index; if (jump_sound_ctrl_.shouldPlay(delta_time, sound_index)) { if (sound_index < 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(float delta_time) { size_t sound_index; if (fall_sound_ctrl_.shouldPlay(delta_time, y_, sound_index)) { if (sound_index < 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_top_surface = false; bool on_conveyor_belt = false; updateFeet(); // Comprueba las superficies on_top_surface |= room_->checkTopSurfaces(under_left_foot_); on_top_surface |= room_->checkTopSurfaces(under_right_foot_); // Comprueba las cintas transportadoras on_conveyor_belt |= room_->checkConveyorBelts(under_left_foot_); on_conveyor_belt |= room_->checkConveyorBelts(under_right_foot_); // Comprueba las rampas auto on_slope_l = room_->checkLeftSlopes(under_left_foot_); auto on_slope_r = room_->checkRightSlopes(under_right_foot_); return on_top_surface || on_conveyor_belt || on_slope_l || on_slope_r; } // Comprueba si el jugador está sobre una superficie auto Player::isOnTopSurface() -> bool { bool on_top_surface = false; updateFeet(); // Comprueba las superficies on_top_surface |= room_->checkTopSurfaces(under_left_foot_); on_top_surface |= room_->checkTopSurfaces(under_right_foot_); return on_top_surface; } // Comprueba si el jugador esta sobre una cinta transportadora auto Player::isOnConveyorBelt() -> bool { bool on_conveyor_belt = false; updateFeet(); // Comprueba las superficies on_conveyor_belt |= room_->checkConveyorBelts(under_left_foot_); on_conveyor_belt |= room_->checkConveyorBelts(under_right_foot_); return on_conveyor_belt; } // Comprueba si el jugador está sobre una rampa // Retorna true SOLO si un pie está en rampa Y el otro pie está volando (sin soporte) auto Player::isOnSlope() -> bool { updateFeet(); // Verificar qué pie está en qué tipo de rampa const bool LEFT_FOOT_ON_LEFT_SLOPE = room_->checkLeftSlopes(under_left_foot_); const bool RIGHT_FOOT_ON_RIGHT_SLOPE = room_->checkRightSlopes(under_right_foot_); // Verificar si cada pie está "volando" (sin soporte: ni top surface ni conveyor belt) const bool LEFT_FOOT_FLYING = !(room_->checkTopSurfaces(under_left_foot_) || room_->checkConveyorBelts(under_left_foot_)); const bool RIGHT_FOOT_FLYING = !(room_->checkTopSurfaces(under_right_foot_) || room_->checkConveyorBelts(under_right_foot_)); // Retornar true si UN pie en rampa Y el OTRO volando return (LEFT_FOOT_ON_LEFT_SLOPE && RIGHT_FOOT_FLYING) || (RIGHT_FOOT_ON_RIGHT_SLOPE && LEFT_FOOT_FLYING); } // Comprueba si current_slope_ es una rampa izquierda (ascendente a la izquierda) // Las rampas izquierdas tienen forma \ con x1 < x2 (x aumenta de izq a der) auto Player::isLeftSlope() -> bool { if (current_slope_ == nullptr) { return false; } // Left slopes (\): x1 < x2 (x aumenta de izquierda a derecha) // Right slopes (/): x1 > x2 (x decrece de izquierda a derecha) return current_slope_->x1 < current_slope_->x2; } // Actualiza current_slope_ con la rampa correcta según el pie que toca void Player::updateCurrentSlope() { updateFeet(); // Left slopes (\) ascendentes a izquierda tocan el pie izquierdo if (room_->checkLeftSlopes(under_left_foot_)) { current_slope_ = room_->getSlopeAtPoint(under_left_foot_); } // Right slopes (/) ascendentes a derecha tocan el pie derecho else if (room_->checkRightSlopes(under_right_foot_)) { current_slope_ = room_->getSlopeAtPoint(under_right_foot_); } // Fallback para casos edge else { current_slope_ = room_->getSlopeAtPoint(under_left_foot_); if (current_slope_ == nullptr) { current_slope_ = room_->getSlopeAtPoint(under_right_foot_); } } // Debug output /* if (current_slope_ != nullptr) { const char* TYPE = isLeftSlope() ? "Left \\" : "Right /"; std::cout << "[SLOPE] " << TYPE << " from (" << current_slope_->x1 << "," << current_slope_->y1 << ")" << " to (" << current_slope_->x2 << "," << current_slope_->y2 << ")\n"; } else { std::cout << "[SLOPE] nullptr\n"; } */ } // 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); } } // 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() { under_left_foot_ = { .x = x_, .y = y_ + HEIGHT}; under_right_foot_ = { .x = x_ + WIDTH - 1, .y = y_ + HEIGHT}; } // Inicializa los sonidos de salto y caida void Player::initSounds() { for (int i = 0; i < 24; ++i) { std::string sound_file = "jump" + std::to_string(i + 1) + ".wav"; jumping_sound_[i] = Resource::Cache::get()->getSound(sound_file); if (i >= 10) { // i+1 >= 11 falling_sound_[i - 10] = Resource::Cache::get()->getSound(sound_file); } } } // Implementación de JumpSoundController::start void Player::JumpSoundController::start() { current_index_ = 0; elapsed_time_ = 0.0F; active_ = true; } // Implementación de JumpSoundController::reset void Player::JumpSoundController::reset() { active_ = false; current_index_ = 0; elapsed_time_ = 0.0F; } // Implementación de JumpSoundController::shouldPlay auto Player::JumpSoundController::shouldPlay(float delta_time, size_t& out_index) -> bool { if (!active_) { return false; } // Acumula el tiempo transcurrido durante el salto elapsed_time_ += delta_time; // Calcula qué sonido debería estar sonando según el tiempo size_t target_index = FIRST_SOUND + static_cast(elapsed_time_ / SECONDS_PER_SOUND); target_index = std::min(target_index, LAST_SOUND); // Reproduce si hemos avanzado a un nuevo sonido if (target_index > current_index_) { current_index_ = target_index; out_index = current_index_; return true; } return false; } // Implementación de FallSoundController::start void Player::FallSoundController::start(float start_y) { current_index_ = 0; distance_traveled_ = 0.0F; last_y_ = start_y; active_ = true; } // Implementación de FallSoundController::reset void Player::FallSoundController::reset() { active_ = false; current_index_ = 0; distance_traveled_ = 0.0F; } // Implementación de FallSoundController::shouldPlay auto Player::FallSoundController::shouldPlay(float delta_time, float current_y, size_t& out_index) -> bool { (void)delta_time; // No usado actualmente, pero recibido por consistencia if (!active_) { return false; } // Acumula la distancia recorrida (solo hacia abajo) if (current_y > last_y_) { distance_traveled_ += (current_y - last_y_); } last_y_ = current_y; // Calcula qué sonido debería estar sonando según el intervalo size_t target_index = FIRST_SOUND + static_cast(distance_traveled_ / PIXELS_PER_SOUND); // El sonido a reproducir se limita a LAST_SOUND (13), pero el índice interno sigue creciendo size_t sound_to_play = std::min(target_index, LAST_SOUND); // Reproduce si hemos avanzado a un nuevo índice (permite repetición de sonido 13) if (target_index > current_index_) { current_index_ = target_index; // Guardamos el índice real (puede ser > LAST_SOUND) out_index = sound_to_play; // Pero reproducimos LAST_SOUND cuando corresponde return true; } return false; } // 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) { const auto& animation_data = Resource::Cache::get()->getAnimationData(animations_path); sprite_ = std::make_unique(animation_data); sprite_->setWidth(WIDTH); sprite_->setHeight(HEIGHT); sprite_->setCurrentAnimation("walk"); } // Actualiza la posición del sprite y las colisiones void Player::syncSpriteAndCollider() { 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 #ifdef _DEBUG updateFeet(); #endif } // Coloca el sprite en la posición del jugador void Player::placeSprite() { sprite_->setPos(x_, y_); } // Gestiona la muerta al ccaer desde muy alto void Player::handleDeathByFalling() { 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 } } // Calcula la velocidad en x void Player::updateVelocity() { if (auto_movement_) { // La cinta transportadora tiene el control vx_ = HORIZONTAL_VELOCITY * room_->getConveyorBeltDirection(); sprite_->setFlip(vx_ < 0.0F ? Flip::LEFT : Flip::RIGHT); } else { // El jugador tiene el control switch (wannaGo) { case Direction::LEFT: vx_ = -HORIZONTAL_VELOCITY; sprite_->setFlip(Flip::LEFT); break; case Direction::RIGHT: vx_ = HORIZONTAL_VELOCITY; sprite_->setFlip(Flip::RIGHT); break; case Direction::NONE: vx_ = 0.0F; break; default: vx_ = 0.0F; break; } } } // Aplica movimiento horizontal con colisión de muros void Player::applyHorizontalMovement(float delta_time) { if (vx_ == 0.0F) { return; } const float DISPLACEMENT = vx_ * delta_time; if (vx_ < 0.0F) { const SDL_FRect PROJECTION = getProjection(Direction::LEFT, DISPLACEMENT); const int POS = room_->checkRightSurfaces(PROJECTION); if (POS == Collision::NONE) { x_ += DISPLACEMENT; } else { x_ = POS + 1; } } else { const SDL_FRect PROJECTION = getProjection(Direction::RIGHT, DISPLACEMENT); const int POS = room_->checkLeftSurfaces(PROJECTION); if (POS == Collision::NONE) { x_ += DISPLACEMENT; } else { x_ = POS - WIDTH; } } } // Detecta aterrizaje en superficies y rampas auto Player::handleLandingFromAir(float displacement, const SDL_FRect& projection) -> bool { // Comprueba la colisión con las superficies y las cintas transportadoras const float POS = std::max(room_->checkTopSurfaces(projection), room_->checkAutoSurfaces(projection)); if (POS != Collision::NONE) { // Si hay colisión lo mueve hasta donde no colisiona y pasa a estar sobre la superficie y_ = POS - HEIGHT; transitionToState(State::ON_GROUND); return true; } // Comprueba la colisión con las rampas auto rect = toSDLRect(projection); const LineVertical LEFT_SIDE = {.x = rect.x, .y1 = rect.y, .y2 = rect.y + rect.h}; const LineVertical RIGHT_SIDE = {.x = rect.x + rect.w - 1, .y1 = rect.y, .y2 = rect.y + rect.h}; const float POINT = std::max(room_->checkRightSlopes(RIGHT_SIDE), room_->checkLeftSlopes(LEFT_SIDE)); if (POINT != Collision::NONE) { y_ = POINT - HEIGHT; transitionToState(State::ON_SLOPE); return true; } // No hay colisión y_ += displacement; return false; } // Resetea los controladores de sonido al aterrizar void Player::resetSoundControllersOnLanding() { jump_sound_ctrl_.reset(); fall_sound_ctrl_.reset(); } // Devuelve el rectangulo de proyeccion auto Player::getProjection(Direction direction, float displacement) -> SDL_FRect { switch (direction) { case Direction::LEFT: return { .x = x_ + displacement, .y = y_, .w = std::ceil(std::fabs(displacement)), // Para evitar que tenga una anchura de 0 pixels .h = HEIGHT}; case Direction::RIGHT: return { .x = x_ + WIDTH, .y = y_, .w = std::ceil(displacement), // Para evitar que tenga una anchura de 0 pixels .h = HEIGHT}; case Direction::UP: return { .x = x_, .y = y_ + displacement, .w = WIDTH, .h = std::ceil(std::fabs(displacement)) // Para evitar que tenga una altura de 0 pixels }; case Direction::DOWN: return { .x = x_, .y = y_ + HEIGHT, .w = WIDTH, .h = std::ceil(displacement) // Para evitar que tenga una altura de 0 pixels }; default: return { .x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F}; } }