// 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 // 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_); } // 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 } 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: vy_ = 0; handleDeathByFalling(); jump_sound_ctrl_.reset(); fall_sound_ctrl_.reset(); break; case State::ON_SLOPE: vy_ = 0; handleDeathByFalling(); jump_sound_ctrl_.reset(); fall_sound_ctrl_.reset(); break; case State::JUMPING: // 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(); } break; case State::FALLING: 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_); 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 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 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; } 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 if (vx_ > 0.0F) { const SDL_FRect PROJECTION = getProjection(Direction::RIGHT, DISPLACEMENT); const int POS = room_->checkLeftSurfaces(PROJECTION); if (POS == Collision::NONE) { x_ += DISPLACEMENT; } else { x_ = POS - WIDTH; } } } // 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; } // Movimiento horizontal 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 if (vx_ > 0.0F) { const SDL_FRect PROJECTION = getProjection(Direction::RIGHT, DISPLACEMENT); const int POS = room_->checkLeftSurfaces(PROJECTION); if (POS == Collision::NONE) { x_ += DISPLACEMENT; } else { x_ = POS - WIDTH; } } // Movimiento vertical // 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 = 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 // --- INICIO DE LA CORRECCIÓN --- // Esta es la nueva posición "ideal" a la que nos queremos teletransportar const float new_y = SLOPE_Y - HEIGHT; // Solo aplicamos el "snap" si es para SUBIR (new_y < y_) if (new_y < y_) { // "y_ - 1.0F" es la posición máxima que podemos subir en 1 frame. // std::max coge la posición más alta (más baja en pantalla): // - O la posición ideal (new_y) // - O la posición actual - 1 píxel // Esto "suaviza" el salto de 2 píxeles (p.ej. 84 -> 82) en dos // fotogramas (84 -> 83, y luego 83 -> 82) y_ = std::max(new_y, y_ - 1.0F); } // --- FIN DE LA CORRECCIÓN --- } } // Movimiento físico del estado JUMPING void Player::moveJumping(float delta_time) { // Movimiento horizontal if (vx_ != 0.0F) { const float DISPLACEMENT_X = vx_ * delta_time; const Direction DIRECTION = vx_ > 0.0F ? Direction::RIGHT : Direction::LEFT; const SDL_FRect PROJECTION = getProjection(DIRECTION, DISPLACEMENT_X); // Comprueba la colisión con las superficies const int POS = DIRECTION == Direction::LEFT ? room_->checkRightSurfaces(PROJECTION) : room_->checkLeftSurfaces(PROJECTION); // Calcula la nueva posición if (POS == Collision::NONE) { // No hay colisión: mueve al jugador x_ += DISPLACEMENT_X; } else { // Hay colisión: reposiciona al jugador en el punto de colisión x_ = DIRECTION == Direction::LEFT ? POS + 1 : POS - WIDTH; } } // 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); // 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); // Aterrizó en superficie plana o conveyor belt } // Comprueba la colisión con las rampas else { // JUMPING colisiona con rampas solo si vx_ == 0 if (vx_ == 0.0F) { 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); // Aterrizó en rampa } else { // No hay colisón con una rampa y_ += DISPLACEMENT_Y; } } 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 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); // Aterrizó en superficie plana o conveyor belt } // Comprueba la colisión con las rampas else { 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); // Aterrizó en rampa } else { y_ += DISPLACEMENT; } } } // 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) { // Reproduce sonidos según el estado if (state_ == State::FALLING) { playFallSound(delta_time); } else if (state_ == State::ON_GROUND || state_ == State::ON_SLOPE) { // Si no tiene suelo debajo y no está en rampa descendente -> FALLING if (!isOnFloor() && !isOnConveyorBelt() && !isOnDownSlope()) { transitionToState(State::FALLING); // setState() establece vx_=0, vy_=MAX_VY } } else if (state_ == State::JUMPING) { playJumpSound(delta_time); } } // 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 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 left_foot = under_left_foot_; SDL_FPoint right_foot = under_right_foot_; left_foot.y += 1.0F; right_foot.y += 1.0F; // Comprueba las rampas on_slope |= room_->checkLeftSlopes(left_foot); on_slope |= room_->checkRightSlopes(right_foot); return on_slope; } // Comprueba si el jugador está sobre una rampa auto Player::isOnSlope() -> bool { bool on_slope = false; updateFeet(); // Comprueba las rampas on_slope |= room_->checkLeftSlopes(under_left_foot_); on_slope |= room_->checkRightSlopes(under_right_foot_); 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); } } // 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) { auto animations = Resource::Cache::get()->getAnimations(animations_path); sprite_ = std::make_unique(animations); 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 } // 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; } } } // 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}; } }