primer commit

This commit is contained in:
2025-11-23 11:44:31 +01:00
commit 6ada29eaf8
613 changed files with 484459 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
#include "game/entities/enemy.hpp"
#include <SDL3/SDL.h>
#include <cstdlib> // Para rand
#include "core/rendering/surface_animated_sprite.hpp" // Para SAnimatedSprite
#include "core/resources/resource_cache.hpp" // Para Resource
#include "utils/utils.hpp" // Para stringToColor
// Constructor
Enemy::Enemy(const Data& enemy)
: sprite_(std::make_shared<SurfaceAnimatedSprite>(Resource::Cache::get()->getAnimationData(enemy.animation_path))),
color_string_(enemy.color),
x1_(enemy.x1),
x2_(enemy.x2),
y1_(enemy.y1),
y2_(enemy.y2),
should_flip_(enemy.flip),
should_mirror_(enemy.mirror) {
// Obten el resto de valores
sprite_->setPosX(enemy.x);
sprite_->setPosY(enemy.y);
sprite_->setVelX(enemy.vx);
sprite_->setVelY(enemy.vy);
const int FLIP = (should_flip_ && enemy.vx < 0.0F) ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
const int MIRROR = should_mirror_ ? SDL_FLIP_VERTICAL : SDL_FLIP_NONE;
sprite_->setFlip(static_cast<SDL_FlipMode>(FLIP | MIRROR));
collider_ = getRect();
color_ = stringToColor(color_string_);
// Coloca un frame al azar o el designado
sprite_->setCurrentAnimationFrame((enemy.frame == -1) ? (rand() % sprite_->getCurrentAnimationSize()) : enemy.frame);
}
// Pinta el enemigo en pantalla
void Enemy::render() {
sprite_->render(1, color_);
}
// Actualiza las variables del objeto
void Enemy::update(float delta_time) {
sprite_->update(delta_time);
checkPath();
collider_ = getRect();
}
// Comprueba si ha llegado al limite del recorrido para darse media vuelta
void Enemy::checkPath() {
if (sprite_->getPosX() > x2_ || sprite_->getPosX() < x1_) {
// Recoloca
if (sprite_->getPosX() > x2_) {
sprite_->setPosX(x2_);
} else {
sprite_->setPosX(x1_);
}
// Cambia el sentido
sprite_->setVelX(sprite_->getVelX() * (-1));
// Invierte el sprite
if (should_flip_) {
sprite_->flip();
}
}
if (sprite_->getPosY() > y2_ || sprite_->getPosY() < y1_) {
// Recoloca
if (sprite_->getPosY() > y2_) {
sprite_->setPosY(y2_);
} else {
sprite_->setPosY(y1_);
}
// Cambia el sentido
sprite_->setVelY(sprite_->getVelY() * (-1));
// Invierte el sprite
if (should_flip_) {
sprite_->flip();
}
}
}
// Devuelve el rectangulo que contiene al enemigo
auto Enemy::getRect() -> SDL_FRect {
return sprite_->getRect();
}
// Obtiene el rectangulo de colision del enemigo
auto Enemy::getCollider() -> SDL_FRect& {
return collider_;
}

View File

@@ -0,0 +1,51 @@
#pragma once
#include <SDL3/SDL.h>
#include <memory> // Para shared_ptr
#include <string> // Para string
class SurfaceAnimatedSprite; // lines 7-7
class Enemy {
public:
struct Data {
std::string animation_path; // Ruta al fichero con la animación
float x{0.0F}; // Posición inicial en el eje X
float y{0.0F}; // Posición inicial en el eje Y
float vx{0.0F}; // Velocidad en el eje X
float vy{0.0F}; // Velocidad en el eje Y
int x1{0}; // Límite izquierdo de la ruta en el eje X
int x2{0}; // Límite derecho de la ruta en el eje X
int y1{0}; // Límite superior de la ruta en el eje Y
int y2{0}; // Límite inferior de la ruta en el eje Y
bool flip{false}; // Indica si el enemigo hace flip al terminar su ruta
bool mirror{false}; // Indica si el enemigo está volteado verticalmente
int frame{0}; // Frame inicial para la animación del enemigo
std::string color; // Color del enemigo
};
explicit Enemy(const Data& enemy); // Constructor
~Enemy() = default; // Destructor
void render(); // Pinta el enemigo en pantalla
void update(float delta_time); // Actualiza las variables del objeto
auto getRect() -> SDL_FRect; // Devuelve el rectangulo que contiene al enemigo
auto getCollider() -> SDL_FRect&; // Obtiene el rectangulo de colision del enemigo
private:
void checkPath(); // Comprueba si ha llegado al limite del recorrido para darse media vuelta
std::shared_ptr<SurfaceAnimatedSprite> sprite_; // Sprite del enemigo
// Variables
Uint8 color_{0}; // Color del enemigo
std::string color_string_; // Color del enemigo en formato texto
int x1_{0}; // Limite izquierdo de la ruta en el eje X
int x2_{0}; // Limite derecho de la ruta en el eje X
int y1_{0}; // Limite superior de la ruta en el eje Y
int y2_{0}; // Limite inferior de la ruta en el eje Y
SDL_FRect collider_{}; // Caja de colisión
bool should_flip_{false}; // Indica si el enemigo hace flip al terminar su ruta
bool should_mirror_{false}; // Indica si el enemigo se dibuja volteado verticalmente
};

View File

@@ -0,0 +1,56 @@
#include "game/entities/item.hpp"
#include "core/rendering/surface_sprite.hpp" // Para SSprite
#include "core/resources/resource_cache.hpp" // Para Resource
// Constructor
Item::Item(const Data& item)
: sprite_(std::make_shared<SurfaceSprite>(Resource::Cache::get()->getSurface(item.tile_set_file), item.x, item.y, ITEM_SIZE, ITEM_SIZE)),
time_accumulator_(static_cast<float>(item.counter) * COLOR_CHANGE_INTERVAL) {
// Inicia variables
sprite_->setClip((item.tile % 10) * ITEM_SIZE, (item.tile / 10) * ITEM_SIZE, ITEM_SIZE, ITEM_SIZE);
collider_ = sprite_->getRect();
// Inicializa los colores
color_.push_back(item.color1);
color_.push_back(item.color1);
color_.push_back(item.color2);
color_.push_back(item.color2);
}
// Actualiza las variables del objeto
void Item::update(float delta_time) {
if (is_paused_) {
return;
}
time_accumulator_ += delta_time;
}
// Pinta el objeto en pantalla
void Item::render() const {
// Calcula el índice de color basado en el tiempo acumulado
const int INDEX = static_cast<int>(time_accumulator_ / COLOR_CHANGE_INTERVAL) % static_cast<int>(color_.size());
sprite_->render(1, color_.at(INDEX));
}
// Obtiene su ubicación
auto Item::getPos() -> SDL_FPoint {
const SDL_FPoint P = {sprite_->getX(), sprite_->getY()};
return P;
}
// Asigna los colores del objeto
void Item::setColors(Uint8 col1, Uint8 col2) {
// Reinicializa el vector de colores
color_.clear();
// Añade el primer color
color_.push_back(col1);
color_.push_back(col1);
// Añade el segundo color
color_.push_back(col2);
color_.push_back(col2);
}

View File

@@ -0,0 +1,44 @@
#pragma once
#include <SDL3/SDL.h>
#include <memory> // Para shared_ptr
#include <string> // Para string
#include <vector> // Para vector
class SurfaceSprite;
class Item {
public:
struct Data {
std::string tile_set_file; // Ruta al fichero con los gráficos del item
float x{0.0F}; // Posición del item en pantalla
float y{0.0F}; // Posición del item en pantalla
int tile{0}; // Número de tile dentro de la textura
int counter{0}; // Contador inicial. Es el que lo hace cambiar de color
Uint8 color1{0}; // Uno de los dos colores que se utiliza para el item
Uint8 color2{0}; // Uno de los dos colores que se utiliza para el item
};
explicit Item(const Data& item); // Constructor
~Item() = default; // Destructor
void render() const; // Pinta el objeto en pantalla
void update(float delta_time); // Actualiza las variables del objeto
void setPaused(bool paused) { is_paused_ = paused; } // Pausa/despausa el item
auto getCollider() -> SDL_FRect& { return collider_; } // Obtiene el rectangulo de colision del objeto
auto getPos() -> SDL_FPoint; // Obtiene su ubicación
void setColors(Uint8 col1, Uint8 col2); // Asigna los colores del objeto
private:
static constexpr float ITEM_SIZE = 8.0F; // Tamaño del item en pixels
static constexpr float COLOR_CHANGE_INTERVAL = 0.06F; // Intervalo de cambio de color en segundos (4 frames a 66.67fps)
std::shared_ptr<SurfaceSprite> sprite_; // SSprite del objeto
// Variables
std::vector<Uint8> color_; // Vector con los colores del objeto
float time_accumulator_{0.0F}; // Acumulador de tiempo para cambio de color
SDL_FRect collider_{}; // Rectangulo de colisión
bool is_paused_{false}; // Indica si el item está pausado
};

View File

@@ -0,0 +1,559 @@
// IWYU pragma: no_include <bits/std_abs.h>
#include "game/entities/player.hpp"
#include <algorithm> // Para max, min
#include <cmath> // Para ceil, abs
#include <iostream>
#include <ranges> // 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/defaults.hpp" // Para Defaults::Sound
#include "game/gameplay/room.hpp" // Para Room, TileType
#include "game/options.hpp" // Para Cheat, Options, options
#include "utils/color.hpp" // Para Color
#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();
previous_state_ = state_;
}
// Pinta el jugador en pantalla
void Player::render() {
sprite_->render();
#ifdef _DEBUG
if (Debug::get()->isEnabled()) {
Screen::get()->getRendererSurface()->putPixel(under_right_foot_.x, under_right_foot_.y, Color::index(Color::Cpc::GREEN));
Screen::get()->getRendererSurface()->putPixel(under_left_foot_.x, under_left_foot_.y, Color::index(Color::Cpc::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);
border_ = handleBorders();
}
}
// Comprueba las entradas y modifica variables
void Player::handleInput() {
if (Input::get()->checkAction(InputAction::LEFT)) {
wanna_go_ = Direction::LEFT;
} else if (Input::get()->checkAction(InputAction::RIGHT)) {
wanna_go_ = Direction::RIGHT;
} else {
wanna_go_ = Direction::NONE;
}
wanna_jump_ = 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_AIR:
moveOnAir(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<int>(x_))));
Debug::get()->add(std::string("Y : " + std::to_string(static_cast<int>(y_))));
Debug::get()->add(std::string("LGP: " + std::to_string(last_grounded_position_)));
switch (state_) {
case State::ON_GROUND:
Debug::get()->add(std::string("ON_GROUND"));
break;
case State::ON_AIR:
Debug::get()->add(std::string("ON_AIR"));
break;
}
#endif
}
void Player::handleConveyorBelts() {
if (!auto_movement_ and isOnConveyorBelt() and wanna_go_ == Direction::NONE) {
auto_movement_ = true;
}
if (auto_movement_ and !isOnConveyorBelt()) {
auto_movement_ = false;
}
}
void Player::handleShouldFall() {
if (!isOnFloor() and state_ == State::ON_GROUND) {
// Pasar a ON_AIR sin impulso de salto (caída)
previous_state_ = state_;
state_ = State::ON_AIR;
last_grounded_position_ = static_cast<int>(y_);
vy_ = 0.0F; // Sin impulso inicial, la gravedad lo acelerará
}
}
void Player::transitionToState(State state) {
previous_state_ = state_;
state_ = state;
switch (state) {
case State::ON_GROUND:
vy_ = 0;
break;
case State::ON_AIR:
if (previous_state_ == State::ON_GROUND) {
vy_ = JUMP_VELOCITY; // Impulso de salto
last_grounded_position_ = y_;
Audio::get()->playSound(Defaults::Sound::JUMP, Audio::Group::GAME);
}
break;
}
}
void Player::updateState(float delta_time) {
switch (state_) {
case State::ON_GROUND:
updateOnGround(delta_time);
break;
case State::ON_AIR:
updateOnAir(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 (wanna_jump_) { transitionToState(State::ON_AIR); }
}
// Actualización lógica del estado ON_AIR
void Player::updateOnAir(float delta_time) {
(void)delta_time; // No usado, pero se mantiene por consistencia de interfaz
auto_movement_ = false; // Desactiva el movimiento automático mientras está en el aire
}
// Movimiento físico del estado ON_GROUND
void Player::moveOnGround(float delta_time) {
// Determina cuál debe ser la velocidad a partir de automovement o de wanna_go_
updateVelocity(delta_time);
if (vx_ == 0.0F) { return; }
// Movimiento horizontal y colision con muros
applyHorizontalMovement(delta_time);
}
// Movimiento físico del estado ON_AIR
void Player::moveOnAir(float delta_time) {
// Movimiento horizontal (el jugador puede moverse en el aire)
updateVelocity(delta_time);
applyHorizontalMovement(delta_time);
// Aplicar gravedad
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);
const int POS = room_->checkBottomSurfaces(PROJECTION);
if (POS == Collision::NONE) {
y_ += DISPLACEMENT_Y;
} else {
// Colisión con el techo: detener ascenso
y_ = POS + 1;
vy_ = 0.0F;
}
}
// Movimiento vertical hacia abajo
else if (vy_ > 0.0F) {
const SDL_FRect PROJECTION = getProjection(Direction::DOWN, DISPLACEMENT_Y);
handleLandingFromAir(DISPLACEMENT_Y, PROJECTION);
}
}
// Comprueba si está situado en alguno de los cuatro bordes de la habitación
auto Player::handleBorders() -> Room::Border {
if (x_ < PlayArea::LEFT) {
return Room::Border::LEFT;
}
if (x_ + WIDTH > PlayArea::RIGHT) {
return Room::Border::RIGHT;
}
if (y_ < PlayArea::TOP) {
return Room::Border::TOP;
}
if (y_ + HEIGHT > PlayArea::BOTTOM) {
return Room::Border::BOTTOM;
}
return Room::Border::NONE;
}
// Cambia al jugador de un borde al opuesto. Util para el cambio de pantalla
void Player::switchBorders() {
switch (border_) {
case Room::Border::TOP:
y_ = PlayArea::BOTTOM - HEIGHT - Tile::SIZE;
// CRÍTICO: Resetear last_grounded_position_ para evitar muerte falsa por diferencia de Y entre pantallas
last_grounded_position_ = static_cast<int>(y_);
transitionToState(State::ON_GROUND);
break;
case Room::Border::BOTTOM:
y_ = PlayArea::TOP;
// CRÍTICO: Resetear last_grounded_position_ para evitar muerte falsa por diferencia de Y entre pantallas
last_grounded_position_ = static_cast<int>(y_);
transitionToState(State::ON_GROUND);
break;
case Room::Border::RIGHT:
x_ = PlayArea::LEFT;
break;
case Room::Border::LEFT:
x_ = PlayArea::RIGHT - WIDTH;
break;
default:
break;
}
border_ = Room::Border::NONE;
syncSpriteAndCollider();
}
// Aplica gravedad al jugador
void Player::applyGravity(float delta_time) {
// La gravedad se aplica siempre que el jugador está en el aire
if (state_ == State::ON_AIR) {
vy_ += GRAVITY_FORCE * delta_time;
}
}
// Establece la animación del jugador
void Player::animate(float delta_time) {
if (vx_ != 0) {
sprite_->update(delta_time);
}
}
// 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_);
return on_top_surface || on_conveyor_belt;
}
// 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 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;
})) {
markAsDead(); // 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 (0 = automático según cheats)
void Player::setColor(Uint8 color) {
if (color != 0) {
color_ = color;
return;
}
if (Options::cheats.invincible == Options::Cheat::State::ENABLED) {
color_ = Color::index(Color::Cpc::CYAN);
} else if (Options::cheats.infinite_lives == Options::Cheat::State::ENABLED) {
color_ = Color::index(Color::Cpc::YELLOW);
} else {
color_ = Color::index(Color::Cpc::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};
}
// 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<SurfaceAnimatedSprite>(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_);
}
// Calcula la velocidad en x con sistema caminar/correr y momentum
void Player::updateVelocity(float delta_time) {
if (auto_movement_) {
// La cinta transportadora tiene el control (velocidad fija)
vx_ = RUN_VELOCITY * room_->getConveyorBeltDirection();
sprite_->setFlip(vx_ < 0.0F ? Flip::LEFT : Flip::RIGHT);
movement_time_ = 0.0F;
} else {
// El jugador tiene el control
switch (wanna_go_) {
case Direction::LEFT:
movement_time_ += delta_time;
if (movement_time_ < TIME_TO_RUN) {
// Caminando: velocidad fija inmediata
vx_ = -WALK_VELOCITY;
} else {
// Corriendo: acelerar hacia RUN_VELOCITY
vx_ -= RUN_ACCELERATION * delta_time;
vx_ = std::max(vx_, -RUN_VELOCITY);
}
sprite_->setFlip(Flip::LEFT);
break;
case Direction::RIGHT:
movement_time_ += delta_time;
if (movement_time_ < TIME_TO_RUN) {
// Caminando: velocidad fija inmediata
vx_ = WALK_VELOCITY;
} else {
// Corriendo: acelerar hacia RUN_VELOCITY
vx_ += RUN_ACCELERATION * delta_time;
vx_ = std::min(vx_, RUN_VELOCITY);
}
sprite_->setFlip(Flip::RIGHT);
break;
case Direction::NONE:
movement_time_ = 0.0F;
// Desacelerar gradualmente (momentum)
if (vx_ > 0.0F) {
vx_ -= HORIZONTAL_DECELERATION * delta_time;
vx_ = std::max(vx_, 0.0F);
} else if (vx_ < 0.0F) {
vx_ += HORIZONTAL_DECELERATION * delta_time;
vx_ = std::min(vx_, 0.0F);
}
break;
default:
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
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);
Audio::get()->playSound(Defaults::Sound::LAND, Audio::Group::GAME);
return true;
}
// No hay colisión
y_ += displacement;
return false;
}
// 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};
}
}
// Marca al jugador como muerto
void Player::markAsDead() {
if (Options::cheats.invincible == Options::Cheat::State::ENABLED) {
is_alive_ = true; // No puede morir
} else {
is_alive_ = false; // Muere
}
}
#ifdef _DEBUG
// Establece la posición del jugador directamente (debug)
void Player::setDebugPosition(float x, float y) {
x_ = x;
y_ = y;
syncSpriteAndCollider();
}
// Fija estado ON_GROUND, velocidades a 0, actualiza last_grounded_position_ (debug)
void Player::finalizeDebugTeleport() {
vx_ = 0.0F;
vy_ = 0.0F;
last_grounded_position_ = static_cast<int>(y_);
transitionToState(State::ON_GROUND);
syncSpriteAndCollider();
}
#endif

View File

@@ -0,0 +1,174 @@
#pragma once
#include <SDL3/SDL.h>
#include <array> // Para array
#include <limits> // Para numeric_limits
#include <memory> // Para shared_ptr, __shared_ptr_access
#include <string> // Para string
#include <utility>
#include "core/rendering/surface_animated_sprite.hpp" // Para SAnimatedSprite
#include "game/gameplay/room.hpp"
#include "game/options.hpp" // Para Cheat, Options, options
#include "utils/defines.hpp" // Para BORDER_TOP, BLOCK
#include "utils/utils.hpp" // Para Color
class Player {
public:
// --- Enums y Structs ---
enum class State {
ON_GROUND, // En suelo plano o conveyor belt
ON_AIR, // En el aire (saltando o cayendo)
};
enum class Direction {
LEFT,
RIGHT,
UP,
DOWN,
NONE
};
struct SpawnData {
float x = 0;
float y = 0;
float vx = 0;
float vy = 0;
int last_grounded_position = 0;
State state = State::ON_GROUND;
SDL_FlipMode flip = SDL_FLIP_NONE;
};
struct Data {
SpawnData spawn_data;
std::string animations_path;
std::shared_ptr<Room> room = nullptr;
};
// --- 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 border_ != Room::Border::NONE; } // 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 = x_, .y = y_, .vx = vx_, .vy = vy_, .last_grounded_position = last_grounded_position_, .state = state_, .flip = sprite_->getFlip()}; } // Obtiene el estado de reaparición del jugador
void setColor(Uint8 color = 0); // Establece el color del jugador (0 = automático según cheats)
void setRoom(std::shared_ptr<Room> room) { room_ = std::move(room); } // Establece la habitación en la que se encuentra el jugador
//[[nodiscard]] auto isAlive() const -> bool { return is_alive_ || (Options::cheats.invincible == Options::Cheat::State::ENABLED); } // Comprueba si el jugador esta vivo
[[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
#ifdef _DEBUG
// --- Funciones de debug ---
void setDebugPosition(float x, float y); // Establece la posición del jugador directamente (debug)
void finalizeDebugTeleport(); // Fija estado ON_GROUND, velocidades a 0, actualiza last_grounded_position_ (debug)
#endif
private:
// --- Constantes de tamaño ---
static constexpr int WIDTH = 8; // Ancho del jugador en pixels
static constexpr int HEIGHT = 16; // Alto del jugador en pixels
// --- Constantes de movimiento horizontal ---
static constexpr float WALK_VELOCITY = 50.0F; // Velocidad al caminar (inmediata) en pixels/segundo
static constexpr float RUN_VELOCITY = 80.0F; // Velocidad al correr en pixels/segundo
static constexpr float TIME_TO_RUN = 0.8F; // Segundos caminando antes de empezar a correr
static constexpr float RUN_ACCELERATION = 150.0F; // Aceleración de caminar a correr en pixels/segundo²
static constexpr float HORIZONTAL_DECELERATION = 250.0F; // Desaceleración al soltar (momentum) en pixels/segundo²
// --- Constantes de salto ---
static constexpr float JUMP_VELOCITY = -160.0F; // Impulso de salto en pixels/segundo (más fuerte, menos floaty)
static constexpr float GRAVITY_FORCE = 280.0F; // Gravedad en pixels/segundo² (más alta, menos floaty)
// --- Objetos y punteros ---
std::shared_ptr<Room> room_; // Objeto encargado de gestionar cada habitación del juego
std::unique_ptr<SurfaceAnimatedSprite> 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
float movement_time_ = 0.0F; // Tiempo que lleva moviéndose en la misma dirección (para transición caminar→correr)
Direction wanna_go_ = Direction::NONE;
bool wanna_jump_ = false;
// --- Variables de estado ---
State state_ = State::ON_GROUND; // Estado en el que se encuentra el jugador. Util apara saber si está saltando o cayendo
State previous_state_ = State::ON_GROUND; // 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<SDL_FPoint, 8> collider_points_{}; // Puntos de colisión con el mapa
SDL_FPoint under_left_foot_ = {0.0F, 0.0F}; // El punto bajo la esquina inferior izquierda del jugador
SDL_FPoint under_right_foot_ = {0.0F, 0.0F}; // El punto bajo la esquina inferior derecha del jugador
// --- Variables de juego ---
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 ---
Uint8 color_ = 0; // Color del jugador
void handleConveyorBelts();
void handleShouldFall();
void updateState(float delta_time);
// --- Métodos de actualización por estado ---
void updateOnGround(float delta_time); // Actualización lógica estado ON_GROUND
void updateOnAir(float delta_time); // Actualización lógica estado ON_AIR
// --- Métodos de movimiento por estado ---
void moveOnGround(float delta_time); // Movimiento físico estado ON_GROUND
void moveOnAir(float delta_time); // Movimiento físico estado ON_AIR
// --- Funciones de inicialización ---
void initSprite(const std::string& animations_path); // Inicializa el sprite del jugador
void applySpawnValues(const SpawnData& spawn); // Aplica los valores de spawn al jugador
// --- Funciones de procesamiento de entrada ---
void handleInput(); // Comprueba las entradas y modifica variables
// --- Funciones de gestión de estado ---
void transitionToState(State state); // 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
auto getProjection(Direction direction, float displacement) -> SDL_FRect; // Devuelve el rectangulo de proyeccion
void applyHorizontalMovement(float delta_time); // Aplica movimiento horizontal con colisión de muros
auto handleLandingFromAir(float displacement, const SDL_FRect& projection) -> bool; // Detecta aterrizaje en superficies
// --- Funciones de detección de superficies ---
auto isOnFloor() -> bool; // Comprueba si el jugador tiene suelo debajo de los pies
auto isOnTopSurface() -> bool; // Comprueba si el jugador está sobre una superficie
auto isOnConveyorBelt() -> bool; // Comprueba si el jugador esta sobre una cinta transportadora
// --- Funciones de actualización de geometría ---
void syncSpriteAndCollider(); // 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
auto handleBorders() -> Room::Border; // Comprueba si se halla en alguno de los cuatro bordes
auto handleKillingTiles() -> bool; // Comprueba que el jugador no toque ningun tile de los que matan
void updateVelocity(float delta_time); // Calcula la velocidad en x con aceleración
void markAsDead(); // Marca al jugador como muerto
};