primer commit
This commit is contained in:
333
source/game/gameplay/collision_map.cpp
Normal file
333
source/game/gameplay/collision_map.cpp
Normal file
@@ -0,0 +1,333 @@
|
||||
#include "collision_map.hpp"
|
||||
|
||||
#include <algorithm> // Para std::ranges::any_of
|
||||
|
||||
#ifdef _DEBUG
|
||||
#include "core/system/debug.hpp" // Para Debug
|
||||
#endif
|
||||
#include "utils/defines.hpp" // Para Collision
|
||||
|
||||
// Constructor
|
||||
CollisionMap::CollisionMap(std::vector<int> collision_map, int conveyor_belt_direction)
|
||||
: tile_map_(std::move(collision_map)),
|
||||
conveyor_belt_direction_(conveyor_belt_direction) {
|
||||
// Inicializa todas las superficies de colisión
|
||||
initializeSurfaces();
|
||||
}
|
||||
|
||||
// Inicializa todas las superficies de colisión
|
||||
void CollisionMap::initializeSurfaces() {
|
||||
setBottomSurfaces();
|
||||
setTopSurfaces();
|
||||
setLeftSurfaces();
|
||||
setRightSurfaces();
|
||||
setAutoSurfaces();
|
||||
}
|
||||
|
||||
// Devuelve el tipo de tile que hay en ese pixel
|
||||
auto CollisionMap::getTile(SDL_FPoint point) const -> Tile {
|
||||
const int POS = ((point.y / TILE_SIZE) * MAP_WIDTH) + (point.x / TILE_SIZE);
|
||||
return getTile(POS);
|
||||
}
|
||||
|
||||
// Devuelve el tipo de tile que hay en ese indice
|
||||
// Mapeo directo desde collisionmap: 1=WALL, 2=PASSABLE, 3=ANIMATED, 6=KILL
|
||||
auto CollisionMap::getTile(int index) const -> Tile {
|
||||
const bool ON_RANGE = (index > -1) && (index < (int)tile_map_.size());
|
||||
|
||||
if (ON_RANGE) {
|
||||
switch (tile_map_[index]) {
|
||||
case 1:
|
||||
return Tile::WALL;
|
||||
case 2:
|
||||
return Tile::PASSABLE;
|
||||
case 3:
|
||||
return Tile::ANIMATED;
|
||||
case 6:
|
||||
return Tile::KILL;
|
||||
default:
|
||||
return Tile::EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
return Tile::EMPTY;
|
||||
}
|
||||
|
||||
// === Queries de colisión ===
|
||||
|
||||
// Comprueba las colisiones con paredes derechas
|
||||
auto CollisionMap::checkRightSurfaces(const SDL_FRect& rect) -> int {
|
||||
for (const auto& s : right_walls_) {
|
||||
if (checkCollision(s, rect)) {
|
||||
return s.x;
|
||||
}
|
||||
}
|
||||
return Collision::NONE;
|
||||
}
|
||||
|
||||
// Comprueba las colisiones con paredes izquierdas
|
||||
auto CollisionMap::checkLeftSurfaces(const SDL_FRect& rect) -> int {
|
||||
for (const auto& s : left_walls_) {
|
||||
if (checkCollision(s, rect)) {
|
||||
return s.x;
|
||||
}
|
||||
}
|
||||
return Collision::NONE;
|
||||
}
|
||||
|
||||
// Comprueba las colisiones con techos
|
||||
auto CollisionMap::checkTopSurfaces(const SDL_FRect& rect) -> int {
|
||||
for (const auto& s : top_floors_) {
|
||||
if (checkCollision(s, rect)) {
|
||||
return s.y;
|
||||
}
|
||||
}
|
||||
return Collision::NONE;
|
||||
}
|
||||
|
||||
// Comprueba las colisiones punto con techos
|
||||
auto CollisionMap::checkTopSurfaces(const SDL_FPoint& p) -> bool {
|
||||
return std::ranges::any_of(top_floors_, [&](const auto& s) {
|
||||
return checkCollision(s, p);
|
||||
});
|
||||
}
|
||||
|
||||
// Comprueba las colisiones con suelos
|
||||
auto CollisionMap::checkBottomSurfaces(const SDL_FRect& rect) -> int {
|
||||
for (const auto& s : bottom_floors_) {
|
||||
if (checkCollision(s, rect)) {
|
||||
return s.y;
|
||||
}
|
||||
}
|
||||
return Collision::NONE;
|
||||
}
|
||||
|
||||
// Comprueba las colisiones con conveyor belts
|
||||
auto CollisionMap::checkAutoSurfaces(const SDL_FRect& rect) -> int {
|
||||
for (const auto& s : conveyor_belt_floors_) {
|
||||
if (checkCollision(s, rect)) {
|
||||
return s.y;
|
||||
}
|
||||
}
|
||||
return Collision::NONE;
|
||||
}
|
||||
|
||||
// Comprueba las colisiones punto con conveyor belts
|
||||
auto CollisionMap::checkConveyorBelts(const SDL_FPoint& p) -> bool {
|
||||
return std::ranges::any_of(conveyor_belt_floors_, [&](const auto& s) {
|
||||
return checkCollision(s, p);
|
||||
});
|
||||
}
|
||||
|
||||
// === Helpers para recopilar tiles ===
|
||||
|
||||
// Helper: recopila tiles inferiores (muros sin muro debajo)
|
||||
auto CollisionMap::collectBottomTiles() -> std::vector<int> {
|
||||
std::vector<int> tile;
|
||||
|
||||
// Busca todos los tiles de tipo muro que no tengan debajo otro muro
|
||||
// Hay que recorrer la habitación por filas (excepto los de la última fila)
|
||||
for (int i = 0; i < (int)tile_map_.size() - MAP_WIDTH; ++i) {
|
||||
if (getTile(i) == Tile::WALL && getTile(i + MAP_WIDTH) != Tile::WALL) {
|
||||
tile.push_back(i);
|
||||
|
||||
// Si llega al final de la fila, introduce un separador
|
||||
if (i % MAP_WIDTH == MAP_WIDTH - 1) {
|
||||
tile.push_back(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Añade un terminador
|
||||
tile.push_back(-1);
|
||||
return tile;
|
||||
}
|
||||
|
||||
// Helper: recopila tiles superiores (muros o pasables sin muro encima)
|
||||
auto CollisionMap::collectTopTiles() -> std::vector<int> {
|
||||
std::vector<int> tile;
|
||||
|
||||
// Busca todos los tiles de tipo muro o pasable que no tengan encima un muro
|
||||
// Hay que recorrer la habitación por filas (excepto los de la primera fila)
|
||||
for (int i = MAP_WIDTH; i < (int)tile_map_.size(); ++i) {
|
||||
if ((getTile(i) == Tile::WALL || getTile(i) == Tile::PASSABLE) && getTile(i - MAP_WIDTH) != Tile::WALL) {
|
||||
tile.push_back(i);
|
||||
|
||||
// Si llega al final de la fila, introduce un separador
|
||||
if (i % MAP_WIDTH == MAP_WIDTH - 1) {
|
||||
tile.push_back(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Añade un terminador
|
||||
tile.push_back(-1);
|
||||
return tile;
|
||||
}
|
||||
|
||||
// Helper: recopila tiles animados (para superficies automaticas/conveyor belts)
|
||||
auto CollisionMap::collectAnimatedTiles() -> std::vector<int> {
|
||||
std::vector<int> tile;
|
||||
|
||||
// Busca todos los tiles de tipo animado
|
||||
// Hay que recorrer la habitación por filas (excepto los de la primera fila)
|
||||
for (int i = MAP_WIDTH; i < (int)tile_map_.size(); ++i) {
|
||||
if (getTile(i) == Tile::ANIMATED) {
|
||||
tile.push_back(i);
|
||||
|
||||
// Si llega al final de la fila, introduce un separador
|
||||
if (i % MAP_WIDTH == MAP_WIDTH - 1) {
|
||||
tile.push_back(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Añade un terminador si hay tiles
|
||||
if (!tile.empty()) {
|
||||
tile.push_back(-1);
|
||||
}
|
||||
|
||||
return tile;
|
||||
}
|
||||
|
||||
// Helper: construye lineas horizontales a partir de tiles consecutivos
|
||||
void CollisionMap::buildHorizontalLines(const std::vector<int>& tiles, std::vector<LineHorizontal>& lines, bool is_bottom_surface) {
|
||||
if (tiles.size() <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
while (i < (int)tiles.size() - 1) {
|
||||
LineHorizontal line;
|
||||
line.x1 = (tiles[i] % MAP_WIDTH) * TILE_SIZE;
|
||||
|
||||
// Calcula Y segun si es superficie inferior o superior
|
||||
if (is_bottom_surface) {
|
||||
line.y = ((tiles[i] / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1;
|
||||
} else {
|
||||
line.y = (tiles[i] / MAP_WIDTH) * TILE_SIZE;
|
||||
}
|
||||
|
||||
int last_one = i;
|
||||
i++;
|
||||
|
||||
// Encuentra tiles consecutivos
|
||||
if (i < (int)tiles.size()) {
|
||||
while (tiles[i] == tiles[i - 1] + 1) {
|
||||
last_one = i;
|
||||
i++;
|
||||
if (i >= (int)tiles.size()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
line.x2 = ((tiles[last_one] % MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1;
|
||||
lines.push_back(line);
|
||||
|
||||
// Salta separadores
|
||||
if (i < (int)tiles.size() && tiles[i] == -1) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === Métodos de generación de geometría ===
|
||||
|
||||
// Calcula las superficies inferiores
|
||||
void CollisionMap::setBottomSurfaces() {
|
||||
std::vector<int> tile = collectBottomTiles();
|
||||
buildHorizontalLines(tile, bottom_floors_, true);
|
||||
}
|
||||
|
||||
// Calcula las superficies superiores
|
||||
void CollisionMap::setTopSurfaces() {
|
||||
std::vector<int> tile = collectTopTiles();
|
||||
buildHorizontalLines(tile, top_floors_, false);
|
||||
}
|
||||
|
||||
// Calcula las superficies laterales izquierdas
|
||||
void CollisionMap::setLeftSurfaces() {
|
||||
std::vector<int> tile;
|
||||
|
||||
// Busca todos los tiles de tipo muro que no tienen a su izquierda un tile de tipo muro
|
||||
// Hay que recorrer la habitación por columnas (excepto los de la primera columna)
|
||||
for (int i = 1; i < MAP_WIDTH; ++i) {
|
||||
for (int j = 0; j < MAP_HEIGHT; ++j) {
|
||||
const int POS = ((j * MAP_WIDTH) + i);
|
||||
if (getTile(POS) == Tile::WALL && getTile(POS - 1) != Tile::WALL) {
|
||||
tile.push_back(POS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Añade un terminador
|
||||
tile.push_back(-1);
|
||||
|
||||
// Recorre el vector de tiles buscando tiles consecutivos
|
||||
// (Los tiles de la misma columna, la diferencia entre ellos es de mapWidth)
|
||||
// para localizar las superficies
|
||||
if ((int)tile.size() > 1) {
|
||||
int i = 0;
|
||||
do {
|
||||
LineVertical line;
|
||||
line.x = (tile[i] % MAP_WIDTH) * TILE_SIZE;
|
||||
line.y1 = ((tile[i] / MAP_WIDTH) * TILE_SIZE);
|
||||
while (tile[i] + MAP_WIDTH == tile[i + 1]) {
|
||||
if (i == (int)tile.size() - 1) {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
line.y2 = ((tile[i] / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1;
|
||||
left_walls_.push_back(line);
|
||||
i++;
|
||||
} while (i < (int)tile.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Calcula las superficies laterales derechas
|
||||
void CollisionMap::setRightSurfaces() {
|
||||
std::vector<int> tile;
|
||||
|
||||
// Busca todos los tiles de tipo muro que no tienen a su derecha un tile de tipo muro
|
||||
// Hay que recorrer la habitación por columnas (excepto los de la última columna)
|
||||
for (int i = 0; i < MAP_WIDTH - 1; ++i) {
|
||||
for (int j = 0; j < MAP_HEIGHT; ++j) {
|
||||
const int POS = ((j * MAP_WIDTH) + i);
|
||||
if (getTile(POS) == Tile::WALL && getTile(POS + 1) != Tile::WALL) {
|
||||
tile.push_back(POS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Añade un terminador
|
||||
tile.push_back(-1);
|
||||
|
||||
// Recorre el vector de tiles buscando tiles consecutivos
|
||||
// (Los tiles de la misma columna, la diferencia entre ellos es de mapWidth)
|
||||
// para localizar las superficies
|
||||
if ((int)tile.size() > 1) {
|
||||
int i = 0;
|
||||
do {
|
||||
LineVertical line;
|
||||
line.x = ((tile[i] % MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1;
|
||||
line.y1 = ((tile[i] / MAP_WIDTH) * TILE_SIZE);
|
||||
while (tile[i] + MAP_WIDTH == tile[i + 1]) {
|
||||
if (i == (int)tile.size() - 1) {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
line.y2 = ((tile[i] / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1;
|
||||
right_walls_.push_back(line);
|
||||
i++;
|
||||
} while (i < (int)tile.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Calcula las superficies automaticas (conveyor belts)
|
||||
void CollisionMap::setAutoSurfaces() {
|
||||
std::vector<int> tile = collectAnimatedTiles();
|
||||
buildHorizontalLines(tile, conveyor_belt_floors_, false);
|
||||
}
|
||||
104
source/game/gameplay/collision_map.hpp
Normal file
104
source/game/gameplay/collision_map.hpp
Normal file
@@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "utils/defines.hpp" // Para Tile::SIZE, PlayArea
|
||||
#include "utils/utils.hpp" // Para LineHorizontal, LineVertical
|
||||
|
||||
/**
|
||||
* @brief Mapa de colisiones de una habitación
|
||||
*
|
||||
* Responsabilidades:
|
||||
* - Almacenar la geometría de colisión (superficies, conveyor belts)
|
||||
* - Generar geometría a partir del tilemap
|
||||
* - Proporcionar queries de colisión para Player y otras entidades
|
||||
* - Determinar tipo de tile en posiciones específicas
|
||||
*/
|
||||
class CollisionMap {
|
||||
public:
|
||||
// Enumeración de tipos de tile (para colisiones)
|
||||
enum class Tile {
|
||||
EMPTY,
|
||||
WALL,
|
||||
PASSABLE,
|
||||
KILL,
|
||||
ANIMATED
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param collision_map Vector con índices de colisión (1=WALL, 2=PASSABLE, etc.)
|
||||
* @param conveyor_belt_direction Dirección de las cintas transportadoras (-1, 0, +1)
|
||||
*/
|
||||
CollisionMap(std::vector<int> collision_map, int conveyor_belt_direction);
|
||||
~CollisionMap() = default;
|
||||
|
||||
// Prohibir copia y movimiento
|
||||
CollisionMap(const CollisionMap&) = delete;
|
||||
auto operator=(const CollisionMap&) -> CollisionMap& = delete;
|
||||
CollisionMap(CollisionMap&&) = delete;
|
||||
auto operator=(CollisionMap&&) -> CollisionMap& = delete;
|
||||
|
||||
// --- Queries de tipo de tile ---
|
||||
[[nodiscard]] auto getTile(SDL_FPoint point) const -> Tile; // Devuelve el tipo de tile en un punto (pixel)
|
||||
[[nodiscard]] auto getTile(int index) const -> Tile; // Devuelve el tipo de tile en un índice del tilemap
|
||||
|
||||
// --- Queries de colisión con superficies ---
|
||||
auto checkRightSurfaces(const SDL_FRect& rect) -> int; // Colisión con paredes derechas (retorna X)
|
||||
auto checkLeftSurfaces(const SDL_FRect& rect) -> int; // Colisión con paredes izquierdas (retorna X)
|
||||
auto checkTopSurfaces(const SDL_FRect& rect) -> int; // Colisión con techos (retorna Y)
|
||||
auto checkTopSurfaces(const SDL_FPoint& p) -> bool; // Colisión punto con techos
|
||||
auto checkBottomSurfaces(const SDL_FRect& rect) -> int; // Colisión con suelos (retorna Y)
|
||||
|
||||
// --- Queries de colisión con superficies automáticas (conveyor belts) ---
|
||||
auto checkAutoSurfaces(const SDL_FRect& rect) -> int; // Colisión con conveyor belts (retorna Y)
|
||||
auto checkConveyorBelts(const SDL_FPoint& p) -> bool; // Colisión punto con conveyor belts
|
||||
|
||||
// --- Métodos estáticos ---
|
||||
static auto getTileSize() -> int { return TILE_SIZE; } // Tamaño del tile en pixels
|
||||
|
||||
// --- Getters ---
|
||||
[[nodiscard]] auto getConveyorBeltDirection() const -> int { return conveyor_belt_direction_; }
|
||||
|
||||
// Getters para debug visualization
|
||||
[[nodiscard]] auto getBottomFloors() const -> const std::vector<LineHorizontal>& { return bottom_floors_; }
|
||||
[[nodiscard]] auto getTopFloors() const -> const std::vector<LineHorizontal>& { return top_floors_; }
|
||||
[[nodiscard]] auto getLeftWalls() const -> const std::vector<LineVertical>& { return left_walls_; }
|
||||
[[nodiscard]] auto getRightWalls() const -> const std::vector<LineVertical>& { return right_walls_; }
|
||||
[[nodiscard]] auto getConveyorBeltFloors() const -> const std::vector<LineHorizontal>& { return conveyor_belt_floors_; }
|
||||
|
||||
private:
|
||||
// --- Constantes (usar defines de PlayArea) ---
|
||||
static constexpr int TILE_SIZE = ::Tile::SIZE; // Tamaño del tile en pixels
|
||||
static constexpr int MAP_WIDTH = PlayArea::TILE_COLS; // Ancho del mapa en tiles
|
||||
static constexpr int MAP_HEIGHT = PlayArea::TILE_ROWS; // Alto del mapa en tiles
|
||||
|
||||
// --- Datos de la habitación ---
|
||||
std::vector<int> tile_map_; // Índices de colisión de la habitación
|
||||
int conveyor_belt_direction_; // Dirección de conveyor belts
|
||||
|
||||
// --- Geometría de colisión ---
|
||||
std::vector<LineHorizontal> bottom_floors_; // Superficies inferiores (suelos)
|
||||
std::vector<LineHorizontal> top_floors_; // Superficies superiores (techos)
|
||||
std::vector<LineVertical> left_walls_; // Paredes izquierdas
|
||||
std::vector<LineVertical> right_walls_; // Paredes derechas
|
||||
std::vector<LineHorizontal> conveyor_belt_floors_; // Superficies automáticas (conveyor belts)
|
||||
|
||||
// --- Métodos privados de generación de geometría ---
|
||||
void initializeSurfaces(); // Inicializa todas las superficies de colisión
|
||||
|
||||
// Helpers para recopilar tiles
|
||||
auto collectBottomTiles() -> std::vector<int>; // Tiles con superficie inferior
|
||||
auto collectTopTiles() -> std::vector<int>; // Tiles con superficie superior
|
||||
auto collectAnimatedTiles() -> std::vector<int>; // Tiles animados (conveyor belts)
|
||||
|
||||
// Construcción de geometría
|
||||
static void buildHorizontalLines(const std::vector<int>& tiles, std::vector<LineHorizontal>& lines, bool is_bottom_surface);
|
||||
void setBottomSurfaces(); // Calcula superficies inferiores
|
||||
void setTopSurfaces(); // Calcula superficies superiores
|
||||
void setLeftSurfaces(); // Calcula paredes izquierdas
|
||||
void setRightSurfaces(); // Calcula paredes derechas
|
||||
void setAutoSurfaces(); // Calcula conveyor belts
|
||||
};
|
||||
49
source/game/gameplay/enemy_manager.cpp
Normal file
49
source/game/gameplay/enemy_manager.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "enemy_manager.hpp"
|
||||
|
||||
#include <algorithm> // Para std::ranges::any_of
|
||||
|
||||
#include "game/entities/enemy.hpp" // Para Enemy
|
||||
#include "utils/utils.hpp" // Para checkCollision
|
||||
|
||||
// Añade un enemigo a la colección
|
||||
void EnemyManager::addEnemy(std::shared_ptr<Enemy> enemy) {
|
||||
enemies_.push_back(std::move(enemy));
|
||||
}
|
||||
|
||||
// Elimina todos los enemigos
|
||||
void EnemyManager::clear() {
|
||||
enemies_.clear();
|
||||
}
|
||||
|
||||
// Elimina el último enemigo de la colección
|
||||
void EnemyManager::removeLastEnemy() {
|
||||
if (!enemies_.empty()) {
|
||||
enemies_.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba si no hay enemigos
|
||||
auto EnemyManager::isEmpty() const -> bool {
|
||||
return enemies_.empty();
|
||||
}
|
||||
|
||||
// Actualiza todos los enemigos
|
||||
void EnemyManager::update(float delta_time) {
|
||||
for (const auto& enemy : enemies_) {
|
||||
enemy->update(delta_time);
|
||||
}
|
||||
}
|
||||
|
||||
// Renderiza todos los enemigos
|
||||
void EnemyManager::render() {
|
||||
for (const auto& enemy : enemies_) {
|
||||
enemy->render();
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba si hay colisión con algún enemigo
|
||||
auto EnemyManager::checkCollision(SDL_FRect& rect) -> bool {
|
||||
return std::ranges::any_of(enemies_, [&rect](const auto& enemy) {
|
||||
return ::checkCollision(rect, enemy->getCollider());
|
||||
});
|
||||
}
|
||||
45
source/game/gameplay/enemy_manager.hpp
Normal file
45
source/game/gameplay/enemy_manager.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <vector> // Para vector
|
||||
|
||||
class Enemy;
|
||||
|
||||
/**
|
||||
* @brief Gestor de enemigos de una habitación
|
||||
*
|
||||
* Responsabilidades:
|
||||
* - Almacenar y gestionar la colección de enemigos
|
||||
* - Actualizar todos los enemigos
|
||||
* - Renderizar todos los enemigos
|
||||
* - Detectar colisiones con enemigos
|
||||
*/
|
||||
class EnemyManager {
|
||||
public:
|
||||
EnemyManager() = default;
|
||||
~EnemyManager() = default;
|
||||
|
||||
// Prohibir copia y movimiento para evitar duplicación accidental
|
||||
EnemyManager(const EnemyManager&) = delete;
|
||||
auto operator=(const EnemyManager&) -> EnemyManager& = delete;
|
||||
EnemyManager(EnemyManager&&) = delete;
|
||||
auto operator=(EnemyManager&&) -> EnemyManager& = delete;
|
||||
|
||||
// Gestión de enemigos
|
||||
void addEnemy(std::shared_ptr<Enemy> enemy); // Añade un enemigo a la colección
|
||||
void clear(); // Elimina todos los enemigos
|
||||
void removeLastEnemy(); // Elimina el último enemigo de la colección
|
||||
[[nodiscard]] auto isEmpty() const -> bool; // Comprueba si no hay enemigos
|
||||
|
||||
// Actualización y renderizado
|
||||
void update(float delta_time); // Actualiza todos los enemigos
|
||||
void render(); // Renderiza todos los enemigos
|
||||
|
||||
// Detección de colisiones
|
||||
auto checkCollision(SDL_FRect& rect) -> bool; // Comprueba si hay colisión con algún enemigo
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<Enemy>> enemies_; // Colección de enemigos
|
||||
};
|
||||
68
source/game/gameplay/item_manager.cpp
Normal file
68
source/game/gameplay/item_manager.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#include "item_manager.hpp"
|
||||
|
||||
#include "core/audio/audio.hpp" // Para Audio
|
||||
#include "game/entities/item.hpp" // Para Item
|
||||
#include "game/options.hpp" // Para Options
|
||||
#include "item_tracker.hpp" // Para ItemTracker
|
||||
#include "scoreboard.hpp" // Para Scoreboard::Data
|
||||
#include "utils/utils.hpp" // Para checkCollision
|
||||
|
||||
// Constructor
|
||||
ItemManager::ItemManager(std::string room_name, std::shared_ptr<Scoreboard::Data> scoreboard_data)
|
||||
: room_name_(std::move(room_name)),
|
||||
data_(std::move(scoreboard_data)) {
|
||||
}
|
||||
|
||||
// Añade un item a la colección
|
||||
void ItemManager::addItem(std::shared_ptr<Item> item) {
|
||||
items_.push_back(std::move(item));
|
||||
}
|
||||
|
||||
// Elimina todos los items
|
||||
void ItemManager::clear() {
|
||||
items_.clear();
|
||||
}
|
||||
|
||||
// Actualiza todos los items
|
||||
void ItemManager::update(float delta_time) {
|
||||
for (const auto& item : items_) {
|
||||
item->update(delta_time);
|
||||
}
|
||||
}
|
||||
|
||||
// Renderiza todos los items
|
||||
void ItemManager::render() {
|
||||
for (const auto& item : items_) {
|
||||
item->render();
|
||||
}
|
||||
}
|
||||
|
||||
// Pausa/despausa todos los items
|
||||
void ItemManager::setPaused(bool paused) {
|
||||
for (const auto& item : items_) {
|
||||
item->setPaused(paused);
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba si hay colisión con algún item
|
||||
auto ItemManager::checkCollision(SDL_FRect& rect) -> bool {
|
||||
for (int i = 0; i < static_cast<int>(items_.size()); ++i) {
|
||||
if (::checkCollision(rect, items_.at(i)->getCollider())) {
|
||||
// Registra el item como recogido
|
||||
ItemTracker::get()->addItem(room_name_, items_.at(i)->getPos());
|
||||
|
||||
// Elimina el item de la colección
|
||||
items_.erase(items_.begin() + i);
|
||||
|
||||
// Reproduce el sonido de pickup
|
||||
Audio::get()->playSound("item.wav", Audio::Group::GAME);
|
||||
|
||||
// Actualiza el scoreboard
|
||||
data_->items++;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
69
source/game/gameplay/item_manager.hpp
Normal file
69
source/game/gameplay/item_manager.hpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "scoreboard.hpp" // Para Scoreboard::Data
|
||||
|
||||
class Item;
|
||||
|
||||
/**
|
||||
* @brief Gestor de items de una habitación
|
||||
*
|
||||
* Responsabilidades:
|
||||
* - Almacenar y gestionar la colección de items
|
||||
* - Actualizar todos los items
|
||||
* - Renderizar todos los items
|
||||
* - Detectar colisiones con items y gestionar pickup
|
||||
* - Integración con ItemTracker, Scoreboard y Audio
|
||||
*/
|
||||
class ItemManager {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param room_name Nombre de la habitación (para ItemTracker)
|
||||
* @param scoreboard_data Puntero compartido a los datos del scoreboard
|
||||
*/
|
||||
ItemManager(std::string room_name, std::shared_ptr<Scoreboard::Data> scoreboard_data);
|
||||
~ItemManager() = default;
|
||||
|
||||
// Prohibir copia y movimiento para evitar duplicación accidental
|
||||
ItemManager(const ItemManager&) = delete;
|
||||
auto operator=(const ItemManager&) -> ItemManager& = delete;
|
||||
ItemManager(ItemManager&&) = delete;
|
||||
auto operator=(ItemManager&&) -> ItemManager& = delete;
|
||||
|
||||
// Gestión de items
|
||||
void addItem(std::shared_ptr<Item> item); // Añade un item a la colección
|
||||
void clear(); // Elimina todos los items
|
||||
|
||||
// Actualización y renderizado
|
||||
void update(float delta_time); // Actualiza todos los items
|
||||
void render(); // Renderiza todos los items
|
||||
|
||||
// Estado
|
||||
void setPaused(bool paused); // Pausa/despausa todos los items
|
||||
|
||||
// Detección de colisiones
|
||||
/**
|
||||
* @brief Comprueba si hay colisión con algún item
|
||||
*
|
||||
* Si hay colisión:
|
||||
* - Añade el item a ItemTracker
|
||||
* - Elimina el item de la colección
|
||||
* - Reproduce el sonido de pickup
|
||||
* - Actualiza el scoreboard y estadísticas
|
||||
*
|
||||
* @param rect Rectángulo de colisión
|
||||
* @return true si hubo colisión, false en caso contrario
|
||||
*/
|
||||
auto checkCollision(SDL_FRect& rect) -> bool;
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<Item>> items_; // Colección de items
|
||||
std::string room_name_; // Nombre de la habitación
|
||||
std::shared_ptr<Scoreboard::Data> data_; // Datos del scoreboard
|
||||
};
|
||||
75
source/game/gameplay/item_tracker.cpp
Normal file
75
source/game/gameplay/item_tracker.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "game/gameplay/item_tracker.hpp"
|
||||
|
||||
// [SINGLETON]
|
||||
ItemTracker* ItemTracker::item_tracker = nullptr;
|
||||
|
||||
// [SINGLETON] Crearemos el objeto con esta función estática
|
||||
void ItemTracker::init() {
|
||||
ItemTracker::item_tracker = new ItemTracker();
|
||||
}
|
||||
|
||||
// [SINGLETON] Destruiremos el objeto con esta función estática
|
||||
void ItemTracker::destroy() {
|
||||
delete ItemTracker::item_tracker;
|
||||
}
|
||||
|
||||
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
|
||||
auto ItemTracker::get() -> ItemTracker* {
|
||||
return ItemTracker::item_tracker;
|
||||
}
|
||||
|
||||
// Comprueba si el objeto ya ha sido cogido
|
||||
auto ItemTracker::hasBeenPicked(const std::string& name, SDL_FPoint pos) -> bool {
|
||||
// Primero busca si ya hay una entrada con ese nombre
|
||||
if (const int INDEX = findByName(name); INDEX != NOT_FOUND) {
|
||||
// Luego busca si existe ya una entrada con esa posición
|
||||
if (findByPos(INDEX, pos) != NOT_FOUND) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Añade el objeto a la lista de objetos cogidos
|
||||
void ItemTracker::addItem(const std::string& name, SDL_FPoint pos) {
|
||||
// Comprueba si el objeto no ha sido recogido con anterioridad
|
||||
if (!hasBeenPicked(name, pos)) {
|
||||
// Primero busca si ya hay una entrada con ese nombre
|
||||
if (const int INDEX = findByName(name); INDEX != NOT_FOUND) {
|
||||
items_.at(INDEX).pos.push_back(pos);
|
||||
}
|
||||
// En caso contrario crea la entrada
|
||||
else {
|
||||
items_.emplace_back(name, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Busca una entrada en la lista por nombre
|
||||
auto ItemTracker::findByName(const std::string& name) -> int {
|
||||
int i = 0;
|
||||
|
||||
for (const auto& item : items_) {
|
||||
if (item.name == name) {
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return NOT_FOUND;
|
||||
}
|
||||
|
||||
// Busca una entrada en la lista por posición
|
||||
auto ItemTracker::findByPos(int index, SDL_FPoint pos) -> int {
|
||||
int i = 0;
|
||||
|
||||
for (const auto& item : items_[index].pos) {
|
||||
if ((item.x == pos.x) && (item.y == pos.y)) {
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return NOT_FOUND;
|
||||
}
|
||||
49
source/game/gameplay/item_tracker.hpp
Normal file
49
source/game/gameplay/item_tracker.hpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <string> // Para string, basic_string
|
||||
#include <utility>
|
||||
#include <vector> // Para vector
|
||||
|
||||
class ItemTracker {
|
||||
public:
|
||||
// Gestión singleton
|
||||
static void init(); // Inicialización
|
||||
static void destroy(); // Destrucción
|
||||
static auto get() -> ItemTracker*; // Acceso al singleton
|
||||
|
||||
// Gestión de items
|
||||
auto hasBeenPicked(const std::string& name, SDL_FPoint pos) -> bool; // Comprueba si el objeto ya ha sido cogido
|
||||
void addItem(const std::string& name, SDL_FPoint pos); // Añade el objeto a la lista
|
||||
|
||||
private:
|
||||
// Tipos anidados privados
|
||||
struct Data {
|
||||
std::string name; // Nombre de la habitación donde se encuentra el objeto
|
||||
std::vector<SDL_FPoint> pos; // Lista de objetos cogidos de la habitación
|
||||
|
||||
// Constructor para facilitar creación con posición inicial
|
||||
Data(std::string name, const SDL_FPoint& position)
|
||||
: name(std::move(name)) {
|
||||
pos.push_back(position);
|
||||
}
|
||||
};
|
||||
|
||||
// Constantes privadas
|
||||
static constexpr int NOT_FOUND = -1; // Valor de retorno cuando no se encuentra un elemento
|
||||
|
||||
// Constantes singleton
|
||||
static ItemTracker* item_tracker; // [SINGLETON] Objeto privado
|
||||
|
||||
// Métodos privados
|
||||
auto findByName(const std::string& name) -> int; // Busca una entrada en la lista por nombre
|
||||
auto findByPos(int index, SDL_FPoint pos) -> int; // Busca una entrada en la lista por posición
|
||||
|
||||
// Constructor y destructor privados [SINGLETON]
|
||||
ItemTracker() = default;
|
||||
~ItemTracker() = default;
|
||||
|
||||
// Variables miembro
|
||||
std::vector<Data> items_; // Lista con todos los objetos recogidos
|
||||
};
|
||||
232
source/game/gameplay/room.cpp
Normal file
232
source/game/gameplay/room.cpp
Normal file
@@ -0,0 +1,232 @@
|
||||
#include "game/gameplay/room.hpp"
|
||||
|
||||
#include <utility> // Para std::move
|
||||
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/rendering/surface.hpp" // Para Surface
|
||||
#include "core/resources/resource_cache.hpp" // Para Resource
|
||||
#include "game/gameplay/collision_map.hpp" // Para CollisionMap
|
||||
#include "game/gameplay/enemy_manager.hpp" // Para EnemyManager
|
||||
#include "game/gameplay/item_manager.hpp" // Para ItemManager
|
||||
#include "game/gameplay/item_tracker.hpp" // Para ItemTracker
|
||||
#include "game/gameplay/room_loader.hpp" // Para RoomLoader
|
||||
#include "game/gameplay/scoreboard.hpp" // Para Scoreboard::Data
|
||||
#include "game/gameplay/tilemap_renderer.hpp" // Para TilemapRenderer
|
||||
#include "utils/defines.hpp" // Para TILE_SIZE
|
||||
#include "utils/utils.hpp" // Para stringToColor
|
||||
|
||||
// Constructor
|
||||
Room::Room(const std::string& room_path, std::shared_ptr<Scoreboard::Data> data)
|
||||
: data_(std::move(data)) {
|
||||
auto room = Resource::Cache::get()->getRoom(room_path);
|
||||
|
||||
// Crea los managers de enemigos e items
|
||||
enemy_manager_ = std::make_unique<EnemyManager>();
|
||||
item_manager_ = std::make_unique<ItemManager>(room->name, data_);
|
||||
|
||||
initializeRoom(*room);
|
||||
openTheJail(); // Abre la Jail si se da el caso
|
||||
|
||||
// Crea el mapa de colisiones (necesita collision_data_, conveyor_belt_direction_)
|
||||
collision_map_ = std::make_unique<CollisionMap>(collision_data_, conveyor_belt_direction_);
|
||||
|
||||
// Crea el renderizador del tilemap (necesita tile_map_, tile_set_width_, surface_, bg_color_, conveyor_belt_direction_)
|
||||
tilemap_renderer_ = std::make_unique<TilemapRenderer>(tile_map_, tile_set_width_, surface_, bg_color_, conveyor_belt_direction_);
|
||||
tilemap_renderer_->initialize(collision_map_.get()); // Inicializa (crea map_surface, pinta tiles, busca animados)
|
||||
|
||||
Screen::get()->setBorderColor(stringToColor(border_color_)); // Establece el color del borde
|
||||
}
|
||||
|
||||
// Destructor
|
||||
Room::~Room() = default;
|
||||
|
||||
void Room::initializeRoom(const Data& room) {
|
||||
// Asignar valores a las variables miembro
|
||||
number_ = room.number;
|
||||
name_ = room.name;
|
||||
bg_color_ = room.bg_color;
|
||||
border_color_ = room.border_color;
|
||||
item_color1_ = room.item_color1.empty() ? "yellow" : room.item_color1;
|
||||
item_color2_ = room.item_color2.empty() ? "magenta" : room.item_color2;
|
||||
upper_room_ = room.upper_room;
|
||||
lower_room_ = room.lower_room;
|
||||
left_room_ = room.left_room;
|
||||
right_room_ = room.right_room;
|
||||
tile_set_file_ = room.tile_set_file;
|
||||
conveyor_belt_direction_ = room.conveyor_belt_direction;
|
||||
tile_map_ = room.tile_map; // Tilemap para renderizado (viene embebido en el YAML)
|
||||
collision_data_ = room.collision_map; // Collisionmap para colisiones (viene embebido en el YAML)
|
||||
surface_ = Resource::Cache::get()->getSurface(room.tile_set_file);
|
||||
tile_set_width_ = surface_->getWidth() / TILE_SIZE;
|
||||
is_paused_ = false;
|
||||
|
||||
// Crear los enemigos usando el manager
|
||||
for (const auto& enemy_data : room.enemies) {
|
||||
enemy_manager_->addEnemy(std::make_shared<Enemy>(enemy_data));
|
||||
}
|
||||
|
||||
// Crear los items usando el manager
|
||||
for (const auto& item : room.items) {
|
||||
const SDL_FPoint ITEM_POS = {item.x, item.y};
|
||||
|
||||
if (!ItemTracker::get()->hasBeenPicked(room.name, ITEM_POS)) {
|
||||
// Crear una copia local de los datos del item
|
||||
Item::Data item_copy = item;
|
||||
item_copy.color1 = stringToColor(item_color1_);
|
||||
item_copy.color2 = stringToColor(item_color2_);
|
||||
|
||||
// Crear el objeto Item usando la copia modificada
|
||||
item_manager_->addItem(std::make_shared<Item>(item_copy));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Abre la jail para poder entrar
|
||||
void Room::openTheJail() {
|
||||
if (data_->jail_is_open && name_ == "THE JAIL") {
|
||||
// Elimina el último enemigo (Bry debe ser el último enemigo definido en el fichero)
|
||||
if (!enemy_manager_->isEmpty()) {
|
||||
enemy_manager_->removeLastEnemy();
|
||||
}
|
||||
|
||||
// Abre las puertas (tanto en tilemap para renderizado como en collisionmap para colisiones)
|
||||
constexpr int TILE_A = 16 + (13 * MAP_WIDTH);
|
||||
constexpr int TILE_B = 16 + (14 * MAP_WIDTH);
|
||||
if (TILE_A < tile_map_.size()) {
|
||||
tile_map_[TILE_A] = -1; // Renderizado: vacío
|
||||
collision_data_[TILE_A] = -1; // Colisiones: vacío
|
||||
}
|
||||
if (TILE_B < tile_map_.size()) {
|
||||
tile_map_[TILE_B] = -1; // Renderizado: vacío
|
||||
collision_data_[TILE_B] = -1; // Colisiones: vacío
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja el mapa en pantalla
|
||||
void Room::renderMap() {
|
||||
tilemap_renderer_->render();
|
||||
}
|
||||
|
||||
// Dibuja los enemigos en pantalla
|
||||
void Room::renderEnemies() {
|
||||
enemy_manager_->render();
|
||||
}
|
||||
|
||||
// Dibuja los objetos en pantalla
|
||||
void Room::renderItems() {
|
||||
item_manager_->render();
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
// Redibuja el mapa (para actualizar modo debug)
|
||||
void Room::redrawMap() {
|
||||
tilemap_renderer_->redrawMap(collision_map_.get());
|
||||
}
|
||||
#endif
|
||||
|
||||
// Actualiza las variables y objetos de la habitación
|
||||
void Room::update(float delta_time) {
|
||||
if (is_paused_) {
|
||||
// Si está en modo pausa no se actualiza nada
|
||||
return;
|
||||
}
|
||||
|
||||
// Actualiza los tiles animados usando el renderer
|
||||
tilemap_renderer_->update(delta_time);
|
||||
|
||||
// Actualiza los enemigos usando el manager
|
||||
enemy_manager_->update(delta_time);
|
||||
|
||||
// Actualiza los items usando el manager
|
||||
item_manager_->update(delta_time);
|
||||
}
|
||||
|
||||
// Pone el mapa en modo pausa
|
||||
void Room::setPaused(bool value) {
|
||||
is_paused_ = value;
|
||||
tilemap_renderer_->setPaused(value);
|
||||
item_manager_->setPaused(value);
|
||||
}
|
||||
|
||||
// Devuelve la cadena del fichero de la habitación contigua segun el borde
|
||||
auto Room::getRoom(Border border) -> std::string {
|
||||
switch (border) {
|
||||
case Border::TOP:
|
||||
return upper_room_;
|
||||
case Border::BOTTOM:
|
||||
return lower_room_;
|
||||
case Border::RIGHT:
|
||||
return right_room_;
|
||||
case Border::LEFT:
|
||||
return left_room_;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Devuelve el tipo de tile que hay en ese pixel
|
||||
auto Room::getTile(SDL_FPoint point) -> Tile {
|
||||
// Delega a CollisionMap y convierte el resultado
|
||||
const auto COLLISION_TILE = collision_map_->getTile(point);
|
||||
return static_cast<Tile>(COLLISION_TILE);
|
||||
}
|
||||
|
||||
// Devuelve el tipo de tile en un índice del tilemap
|
||||
auto Room::getTile(int index) -> Tile {
|
||||
// Delega a CollisionMap y convierte el resultado
|
||||
const auto COLLISION_TILE = collision_map_->getTile(index);
|
||||
return static_cast<Tile>(COLLISION_TILE);
|
||||
}
|
||||
|
||||
// Indica si hay colision con un enemigo a partir de un rectangulo
|
||||
auto Room::enemyCollision(SDL_FRect& rect) -> bool {
|
||||
return enemy_manager_->checkCollision(rect);
|
||||
}
|
||||
|
||||
// Indica si hay colision con un objeto a partir de un rectangulo
|
||||
auto Room::itemCollision(SDL_FRect& rect) -> bool {
|
||||
return item_manager_->checkCollision(rect);
|
||||
}
|
||||
|
||||
// === Métodos de colisión (delegados a CollisionMap) ===
|
||||
|
||||
// Comprueba las colisiones con paredes derechas
|
||||
auto Room::checkRightSurfaces(const SDL_FRect& rect) -> int {
|
||||
return collision_map_->checkRightSurfaces(rect);
|
||||
}
|
||||
|
||||
// Comprueba las colisiones con paredes izquierdas
|
||||
auto Room::checkLeftSurfaces(const SDL_FRect& rect) -> int {
|
||||
return collision_map_->checkLeftSurfaces(rect);
|
||||
}
|
||||
|
||||
// Comprueba las colisiones con techos
|
||||
auto Room::checkTopSurfaces(const SDL_FRect& rect) -> int {
|
||||
return collision_map_->checkTopSurfaces(rect);
|
||||
}
|
||||
|
||||
// Comprueba las colisiones punto con techos
|
||||
auto Room::checkTopSurfaces(const SDL_FPoint& p) -> bool {
|
||||
return collision_map_->checkTopSurfaces(p);
|
||||
}
|
||||
|
||||
// Comprueba las colisiones con suelos
|
||||
auto Room::checkBottomSurfaces(const SDL_FRect& rect) -> int {
|
||||
return collision_map_->checkBottomSurfaces(rect);
|
||||
}
|
||||
|
||||
// Comprueba las colisiones con conveyor belts
|
||||
auto Room::checkAutoSurfaces(const SDL_FRect& rect) -> int {
|
||||
return collision_map_->checkAutoSurfaces(rect);
|
||||
}
|
||||
|
||||
// Comprueba las colisiones punto con conveyor belts
|
||||
auto Room::checkConveyorBelts(const SDL_FPoint& p) -> bool {
|
||||
return collision_map_->checkConveyorBelts(p);
|
||||
}
|
||||
|
||||
// Carga una habitación desde un archivo YAML (delegado a RoomLoader)
|
||||
auto Room::loadYAML(const std::string& file_path, bool verbose) -> Data {
|
||||
return RoomLoader::loadYAML(file_path, verbose);
|
||||
}
|
||||
128
source/game/gameplay/room.hpp
Normal file
128
source/game/gameplay/room.hpp
Normal file
@@ -0,0 +1,128 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "game/entities/enemy.hpp" // Para EnemyData
|
||||
#include "game/entities/item.hpp" // Para ItemData
|
||||
#include "game/gameplay/scoreboard.hpp" // Para Scoreboard::Data
|
||||
#include "utils/defines.hpp" // Para Tile::SIZE, PlayArea
|
||||
#include "utils/utils.hpp" // Para LineHorizontal, LineVertical
|
||||
class SurfaceSprite; // lines 12-12
|
||||
class Surface; // lines 13-13
|
||||
class EnemyManager;
|
||||
class ItemManager;
|
||||
class CollisionMap;
|
||||
class TilemapRenderer;
|
||||
|
||||
class Room {
|
||||
public:
|
||||
// -- Enumeraciones y estructuras ---
|
||||
enum class Border : int {
|
||||
TOP = 0,
|
||||
RIGHT = 1,
|
||||
BOTTOM = 2,
|
||||
LEFT = 3,
|
||||
NONE = 4
|
||||
};
|
||||
|
||||
enum class Tile {
|
||||
EMPTY,
|
||||
WALL,
|
||||
PASSABLE,
|
||||
KILL,
|
||||
ANIMATED
|
||||
};
|
||||
|
||||
struct Data {
|
||||
std::string number; // Numero de la habitación
|
||||
std::string name; // Nombre de la habitación
|
||||
std::string bg_color; // Color de fondo de la habitación
|
||||
std::string border_color; // Color del borde de la pantalla
|
||||
std::string item_color1; // Color 1 para los items de la habitación
|
||||
std::string item_color2; // Color 2 para los items de la habitación
|
||||
std::string upper_room; // Identificador de la habitación que se encuentra arriba
|
||||
std::string lower_room; // Identificador de la habitación que se encuentra abajo
|
||||
std::string left_room; // Identificador de la habitación que se encuentra a la izquierda
|
||||
std::string right_room; // Identificador de la habitación que se encuentra a la derecha
|
||||
std::string tile_set_file; // Imagen con los gráficos para la habitación
|
||||
int conveyor_belt_direction{0}; // Sentido en el que arrastran las superficies automáticas de la habitación
|
||||
std::vector<int> tile_map; // Índice de los tiles a dibujar en la habitación (embebido desde YAML)
|
||||
std::vector<int> collision_map; // Índice de colisiones de la habitación (embebido desde YAML)
|
||||
std::vector<Enemy::Data> enemies; // Listado con los enemigos de la habitación
|
||||
std::vector<Item::Data> items; // Listado con los items que hay en la habitación
|
||||
};
|
||||
|
||||
// Constructor y destructor
|
||||
Room(const std::string& room_path, std::shared_ptr<Scoreboard::Data> data);
|
||||
~Room(); // Definido en .cpp para poder usar unique_ptr con forward declarations
|
||||
|
||||
// --- Funciones ---
|
||||
[[nodiscard]] auto getName() const -> const std::string& { return name_; } // Devuelve el nombre de la habitación
|
||||
[[nodiscard]] auto getBGColor() const -> Uint8 { return stringToColor(bg_color_); } // Devuelve el color de la habitación
|
||||
[[nodiscard]] auto getBorderColor() const -> Uint8 { return stringToColor(border_color_); } // Devuelve el color del borde
|
||||
void renderMap(); // Dibuja el mapa en pantalla
|
||||
void renderEnemies(); // Dibuja los enemigos en pantalla
|
||||
void renderItems(); // Dibuja los objetos en pantalla
|
||||
#ifdef _DEBUG
|
||||
void redrawMap(); // Redibuja el mapa (para actualizar modo debug)
|
||||
#endif
|
||||
void update(float delta_time); // Actualiza las variables y objetos de la habitación
|
||||
auto getRoom(Border border) -> std::string; // Devuelve la cadena del fichero de la habitación contigua segun el borde
|
||||
auto getTile(SDL_FPoint point) -> Tile; // Devuelve el tipo de tile que hay en ese pixel
|
||||
auto getTile(int index) -> Tile; // Devuelve el tipo de tile en un índice del tilemap
|
||||
auto enemyCollision(SDL_FRect& rect) -> bool; // Indica si hay colision con un enemigo a partir de un rectangulo
|
||||
auto itemCollision(SDL_FRect& rect) -> bool; // Indica si hay colision con un objeto a partir de un rectangulo
|
||||
static auto getTileSize() -> int { return TILE_SIZE; } // Obten el tamaño del tile
|
||||
auto checkRightSurfaces(const SDL_FRect& rect) -> int; // Comprueba las colisiones
|
||||
auto checkLeftSurfaces(const SDL_FRect& rect) -> int; // Comprueba las colisiones
|
||||
auto checkTopSurfaces(const SDL_FRect& rect) -> int; // Comprueba las colisiones
|
||||
auto checkBottomSurfaces(const SDL_FRect& rect) -> int; // Comprueba las colisiones
|
||||
auto checkAutoSurfaces(const SDL_FRect& rect) -> int; // Comprueba las colisiones
|
||||
auto checkTopSurfaces(const SDL_FPoint& p) -> bool; // Comprueba las colisiones
|
||||
auto checkConveyorBelts(const SDL_FPoint& p) -> bool; // Comprueba las colisiones
|
||||
void setPaused(bool value); // Pone el mapa en modo pausa
|
||||
[[nodiscard]] auto getConveyorBeltDirection() const -> int { return conveyor_belt_direction_; } // Obten la direccion de las superficies automaticas
|
||||
|
||||
// Método de carga de archivos YAML (delegado a RoomLoader)
|
||||
static auto loadYAML(const std::string& file_path, bool verbose = false) -> Data; // Carga habitación desde archivo YAML unificado
|
||||
|
||||
private:
|
||||
// Constantes (usar defines de PlayArea)
|
||||
static constexpr int TILE_SIZE = ::Tile::SIZE; // Ancho del tile en pixels
|
||||
static constexpr int MAP_WIDTH = PlayArea::TILE_COLS; // Ancho del mapa en tiles
|
||||
static constexpr int MAP_HEIGHT = PlayArea::TILE_ROWS; // Alto del mapa en tiles
|
||||
|
||||
// Objetos y punteros
|
||||
std::unique_ptr<EnemyManager> enemy_manager_; // Gestor de enemigos de la habitación
|
||||
std::unique_ptr<ItemManager> item_manager_; // Gestor de items de la habitación
|
||||
std::unique_ptr<CollisionMap> collision_map_; // Mapa de colisiones de la habitación
|
||||
std::unique_ptr<TilemapRenderer> tilemap_renderer_; // Renderizador del mapa de tiles
|
||||
std::shared_ptr<Surface> surface_; // Textura con los graficos de la habitación
|
||||
std::shared_ptr<Scoreboard::Data> data_; // Puntero a los datos del marcador
|
||||
|
||||
// --- Variables ---
|
||||
std::string number_; // Numero de la habitación
|
||||
std::string name_; // Nombre de la habitación
|
||||
std::string bg_color_; // Color de fondo de la habitación
|
||||
std::string border_color_; // Color del borde de la pantalla
|
||||
std::string item_color1_; // Color 1 para los items de la habitación
|
||||
std::string item_color2_; // Color 2 para los items de la habitación
|
||||
std::string upper_room_; // Identificador de la habitación que se encuentra arriba
|
||||
std::string lower_room_; // Identificador de la habitación que se encuentra abajp
|
||||
std::string left_room_; // Identificador de la habitación que se encuentra a la izquierda
|
||||
std::string right_room_; // Identificador de la habitación que se encuentra a la derecha
|
||||
std::string tile_set_file_; // Imagen con los graficos para la habitación
|
||||
std::vector<int> tile_map_; // Indice de los tiles a dibujar en la habitación (embebido desde YAML)
|
||||
std::vector<int> collision_data_; // Indice de colisiones de la habitación (embebido desde YAML)
|
||||
int conveyor_belt_direction_{0}; // Sentido en el que arrastran las superficies automáticas de la habitación
|
||||
bool is_paused_{false}; // Indica si el mapa esta en modo pausa
|
||||
int tile_set_width_{0}; // Ancho del tileset en tiles
|
||||
|
||||
// --- Funciones ---
|
||||
void initializeRoom(const Data& room); // Inicializa los valores
|
||||
void openTheJail(); // Abre la jail para poder entrar
|
||||
};
|
||||
388
source/game/gameplay/room_loader.cpp
Normal file
388
source/game/gameplay/room_loader.cpp
Normal file
@@ -0,0 +1,388 @@
|
||||
#include "room_loader.hpp"
|
||||
|
||||
#include <exception> // Para exception
|
||||
#include <iostream> // Para cout, cerr
|
||||
|
||||
#include "core/resources/resource_helper.hpp" // Para Resource::Helper
|
||||
#include "external/fkyaml_node.hpp" // Para fkyaml::node
|
||||
#include "utils/defines.hpp" // Para Tile::SIZE
|
||||
#include "utils/utils.hpp" // Para stringToColor
|
||||
|
||||
// Convierte room connection de YAML a formato interno
|
||||
auto RoomLoader::convertRoomConnection(const std::string& value) -> std::string {
|
||||
if (value == "null" || value.empty()) {
|
||||
return "0";
|
||||
}
|
||||
// Si ya tiene .yaml, devolverlo tal cual; si no, añadirlo
|
||||
if (value.size() > 5 && value.substr(value.size() - 5) == ".yaml") {
|
||||
return value;
|
||||
}
|
||||
return value + ".yaml";
|
||||
}
|
||||
|
||||
// Convierte string de autoSurface a int
|
||||
auto RoomLoader::convertAutoSurface(const fkyaml::node& node) -> int {
|
||||
if (node.is_integer()) {
|
||||
return node.get_value<int>();
|
||||
}
|
||||
if (node.is_string()) {
|
||||
const auto VALUE = node.get_value<std::string>();
|
||||
if (VALUE == "left") {
|
||||
return -1;
|
||||
}
|
||||
if (VALUE == "right") {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0; // "none" o default
|
||||
}
|
||||
|
||||
// Convierte un tilemap 2D a vector 1D flat
|
||||
auto RoomLoader::flattenTilemap(const std::vector<std::vector<int>>& tilemap_2d) -> std::vector<int> {
|
||||
std::vector<int> tilemap_flat;
|
||||
tilemap_flat.reserve(PlayArea::TILE_COUNT); // TILE_ROWS × TILE_COLS
|
||||
|
||||
for (const auto& row : tilemap_2d) {
|
||||
for (int tile : row) {
|
||||
tilemap_flat.push_back(tile);
|
||||
}
|
||||
}
|
||||
|
||||
return tilemap_flat;
|
||||
}
|
||||
|
||||
// Parsea la configuración general de la habitación
|
||||
void RoomLoader::parseRoomConfig(const fkyaml::node& yaml, Room::Data& room, const std::string& file_name) {
|
||||
if (!yaml.contains("room")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& room_node = yaml["room"];
|
||||
|
||||
// Extract room number from filename (e.g., "01.yaml" → "01")
|
||||
room.number = file_name.substr(0, file_name.find_last_of('.'));
|
||||
|
||||
// Basic properties
|
||||
if (room_node.contains("name")) {
|
||||
room.name = room_node["name"].get_value<std::string>();
|
||||
}
|
||||
if (room_node.contains("bgColor")) {
|
||||
room.bg_color = room_node["bgColor"].get_value<std::string>();
|
||||
}
|
||||
if (room_node.contains("border")) {
|
||||
room.border_color = room_node["border"].get_value<std::string>();
|
||||
}
|
||||
if (room_node.contains("tileSetFile")) {
|
||||
room.tile_set_file = room_node["tileSetFile"].get_value<std::string>();
|
||||
}
|
||||
|
||||
// Room connections
|
||||
if (room_node.contains("connections")) {
|
||||
parseRoomConnections(room_node["connections"], room);
|
||||
}
|
||||
|
||||
// Item colors
|
||||
room.item_color1 = room_node.contains("itemColor1")
|
||||
? room_node["itemColor1"].get_value_or<std::string>("yellow")
|
||||
: "yellow";
|
||||
|
||||
room.item_color2 = room_node.contains("itemColor2")
|
||||
? room_node["itemColor2"].get_value_or<std::string>("magenta")
|
||||
: "magenta";
|
||||
|
||||
// Dirección de la cinta transportadora (left/none/right)
|
||||
room.conveyor_belt_direction = room_node.contains("conveyorBelt")
|
||||
? convertAutoSurface(room_node["conveyorBelt"])
|
||||
: 0;
|
||||
}
|
||||
|
||||
// Parsea las conexiones de la habitación (arriba/abajo/izq/der)
|
||||
void RoomLoader::parseRoomConnections(const fkyaml::node& conn_node, Room::Data& room) {
|
||||
room.upper_room = conn_node.contains("up")
|
||||
? convertRoomConnection(conn_node["up"].get_value_or<std::string>("null"))
|
||||
: "0";
|
||||
|
||||
room.lower_room = conn_node.contains("down")
|
||||
? convertRoomConnection(conn_node["down"].get_value_or<std::string>("null"))
|
||||
: "0";
|
||||
|
||||
room.left_room = conn_node.contains("left")
|
||||
? convertRoomConnection(conn_node["left"].get_value_or<std::string>("null"))
|
||||
: "0";
|
||||
|
||||
room.right_room = conn_node.contains("right")
|
||||
? convertRoomConnection(conn_node["right"].get_value_or<std::string>("null"))
|
||||
: "0";
|
||||
}
|
||||
|
||||
// Parsea el tilemap de la habitación
|
||||
void RoomLoader::parseTilemap(const fkyaml::node& yaml, Room::Data& room, const std::string& file_name, bool verbose) {
|
||||
if (!yaml.contains("tilemap")) {
|
||||
std::cerr << "Warning: No tilemap found in " << file_name << '\n';
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& tilemap_node = yaml["tilemap"];
|
||||
|
||||
// Read 2D array
|
||||
std::vector<std::vector<int>> tilemap_2d;
|
||||
tilemap_2d.reserve(PlayArea::TILE_ROWS);
|
||||
|
||||
for (const auto& row_node : tilemap_node) {
|
||||
std::vector<int> row;
|
||||
row.reserve(PlayArea::TILE_COLS);
|
||||
|
||||
for (const auto& tile_node : row_node) {
|
||||
row.push_back(tile_node.get_value<int>());
|
||||
}
|
||||
|
||||
tilemap_2d.push_back(row);
|
||||
}
|
||||
|
||||
// Convert to 1D flat array
|
||||
room.tile_map = flattenTilemap(tilemap_2d);
|
||||
|
||||
if (verbose) {
|
||||
std::cout << "Loaded tilemap: " << room.tile_map.size() << " tiles\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Parsea el collisionmap de la habitación
|
||||
void RoomLoader::parseCollisionmap(const fkyaml::node& yaml, Room::Data& room, const std::string& file_name, bool verbose) {
|
||||
if (!yaml.contains("collisionmap")) {
|
||||
// Si no hay collisionmap, es opcional - no es error
|
||||
if (verbose) {
|
||||
std::cout << "No collisionmap found in " << file_name << " (optional)\n";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& collisionmap_node = yaml["collisionmap"];
|
||||
|
||||
// Read 2D array
|
||||
std::vector<std::vector<int>> collisionmap_2d;
|
||||
collisionmap_2d.reserve(PlayArea::TILE_ROWS);
|
||||
|
||||
for (const auto& row_node : collisionmap_node) {
|
||||
std::vector<int> row;
|
||||
row.reserve(PlayArea::TILE_COLS);
|
||||
|
||||
for (const auto& tile_node : row_node) {
|
||||
row.push_back(tile_node.get_value<int>());
|
||||
}
|
||||
|
||||
collisionmap_2d.push_back(row);
|
||||
}
|
||||
|
||||
// Convert to 1D flat array (reutilizamos flattenTilemap)
|
||||
room.collision_map = flattenTilemap(collisionmap_2d);
|
||||
|
||||
if (verbose) {
|
||||
std::cout << "Loaded collisionmap: " << room.collision_map.size() << " tiles\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Parsea los límites de movimiento de un enemigo
|
||||
void RoomLoader::parseEnemyBoundaries(const fkyaml::node& bounds_node, Enemy::Data& enemy) {
|
||||
// Nuevo formato: position1 y position2
|
||||
if (bounds_node.contains("position1")) {
|
||||
const auto& pos1 = bounds_node["position1"];
|
||||
if (pos1.contains("x")) {
|
||||
enemy.x1 = pos1["x"].get_value<int>() * Tile::SIZE;
|
||||
}
|
||||
if (pos1.contains("y")) {
|
||||
enemy.y1 = pos1["y"].get_value<int>() * Tile::SIZE;
|
||||
}
|
||||
}
|
||||
if (bounds_node.contains("position2")) {
|
||||
const auto& pos2 = bounds_node["position2"];
|
||||
if (pos2.contains("x")) {
|
||||
enemy.x2 = pos2["x"].get_value<int>() * Tile::SIZE;
|
||||
}
|
||||
if (pos2.contains("y")) {
|
||||
enemy.y2 = pos2["y"].get_value<int>() * Tile::SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
// Formato antiguo: x1/y1/x2/y2 (compatibilidad)
|
||||
if (bounds_node.contains("x1")) {
|
||||
enemy.x1 = bounds_node["x1"].get_value<int>() * Tile::SIZE;
|
||||
}
|
||||
if (bounds_node.contains("y1")) {
|
||||
enemy.y1 = bounds_node["y1"].get_value<int>() * Tile::SIZE;
|
||||
}
|
||||
if (bounds_node.contains("x2")) {
|
||||
enemy.x2 = bounds_node["x2"].get_value<int>() * Tile::SIZE;
|
||||
}
|
||||
if (bounds_node.contains("y2")) {
|
||||
enemy.y2 = bounds_node["y2"].get_value<int>() * Tile::SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
// Parsea los datos de un enemigo individual
|
||||
auto RoomLoader::parseEnemyData(const fkyaml::node& enemy_node) -> Enemy::Data {
|
||||
Enemy::Data enemy;
|
||||
|
||||
// Animation path
|
||||
if (enemy_node.contains("animation")) {
|
||||
enemy.animation_path = enemy_node["animation"].get_value<std::string>();
|
||||
}
|
||||
|
||||
// Position (in tiles, convert to pixels)
|
||||
if (enemy_node.contains("position")) {
|
||||
const auto& pos = enemy_node["position"];
|
||||
if (pos.contains("x")) {
|
||||
enemy.x = pos["x"].get_value<float>() * Tile::SIZE;
|
||||
}
|
||||
if (pos.contains("y")) {
|
||||
enemy.y = pos["y"].get_value<float>() * Tile::SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
// Velocity (already in pixels/second)
|
||||
if (enemy_node.contains("velocity")) {
|
||||
const auto& vel = enemy_node["velocity"];
|
||||
if (vel.contains("x")) {
|
||||
enemy.vx = vel["x"].get_value<float>();
|
||||
}
|
||||
if (vel.contains("y")) {
|
||||
enemy.vy = vel["y"].get_value<float>();
|
||||
}
|
||||
}
|
||||
|
||||
// Boundaries (in tiles, convert to pixels)
|
||||
if (enemy_node.contains("boundaries")) {
|
||||
parseEnemyBoundaries(enemy_node["boundaries"], enemy);
|
||||
}
|
||||
|
||||
// Color
|
||||
enemy.color = enemy_node.contains("color")
|
||||
? enemy_node["color"].get_value_or<std::string>("white")
|
||||
: "white";
|
||||
|
||||
// Optional fields
|
||||
enemy.flip = enemy_node.contains("flip")
|
||||
? enemy_node["flip"].get_value_or<bool>(false)
|
||||
: false;
|
||||
|
||||
enemy.mirror = enemy_node.contains("mirror")
|
||||
? enemy_node["mirror"].get_value_or<bool>(false)
|
||||
: false;
|
||||
|
||||
enemy.frame = enemy_node.contains("frame")
|
||||
? enemy_node["frame"].get_value_or<int>(-1)
|
||||
: -1;
|
||||
|
||||
return enemy;
|
||||
}
|
||||
|
||||
// Parsea la lista de enemigos de la habitación
|
||||
void RoomLoader::parseEnemies(const fkyaml::node& yaml, Room::Data& room, bool verbose) {
|
||||
if (!yaml.contains("enemies") || yaml["enemies"].is_null()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& enemies_node = yaml["enemies"];
|
||||
|
||||
for (const auto& enemy_node : enemies_node) {
|
||||
room.enemies.push_back(parseEnemyData(enemy_node));
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
std::cout << "Loaded " << room.enemies.size() << " enemies\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Parsea los datos de un item individual
|
||||
auto RoomLoader::parseItemData(const fkyaml::node& item_node, const Room::Data& room) -> Item::Data {
|
||||
Item::Data item;
|
||||
|
||||
// Tileset file
|
||||
if (item_node.contains("tileSetFile")) {
|
||||
item.tile_set_file = item_node["tileSetFile"].get_value<std::string>();
|
||||
}
|
||||
|
||||
// Tile index
|
||||
if (item_node.contains("tile")) {
|
||||
item.tile = item_node["tile"].get_value<int>();
|
||||
}
|
||||
|
||||
// Position (in tiles, convert to pixels)
|
||||
if (item_node.contains("position")) {
|
||||
const auto& pos = item_node["position"];
|
||||
if (pos.contains("x")) {
|
||||
item.x = pos["x"].get_value<float>() * Tile::SIZE;
|
||||
}
|
||||
if (pos.contains("y")) {
|
||||
item.y = pos["y"].get_value<float>() * Tile::SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
// Counter
|
||||
item.counter = item_node.contains("counter")
|
||||
? item_node["counter"].get_value_or<int>(0)
|
||||
: 0;
|
||||
|
||||
// Colors (assigned from room defaults)
|
||||
item.color1 = stringToColor(room.item_color1);
|
||||
item.color2 = stringToColor(room.item_color2);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
// Parsea la lista de items de la habitación
|
||||
void RoomLoader::parseItems(const fkyaml::node& yaml, Room::Data& room, bool verbose) {
|
||||
if (!yaml.contains("items") || yaml["items"].is_null()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& items_node = yaml["items"];
|
||||
|
||||
for (const auto& item_node : items_node) {
|
||||
room.items.push_back(parseItemData(item_node, room));
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
std::cout << "Loaded " << room.items.size() << " items\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Carga un archivo de room en formato YAML
|
||||
auto RoomLoader::loadYAML(const std::string& file_path, bool verbose) -> Room::Data {
|
||||
Room::Data room;
|
||||
|
||||
// Extract filename for logging
|
||||
const std::string FILE_NAME = file_path.substr(file_path.find_last_of("\\/") + 1);
|
||||
|
||||
try {
|
||||
// Load YAML file using ResourceHelper (supports both filesystem and pack)
|
||||
auto file_data = Resource::Helper::loadFile(file_path);
|
||||
|
||||
if (file_data.empty()) {
|
||||
std::cerr << "Error: Unable to load file " << FILE_NAME << '\n';
|
||||
return room;
|
||||
}
|
||||
|
||||
// Parse YAML from string
|
||||
std::string yaml_content(file_data.begin(), file_data.end());
|
||||
auto yaml = fkyaml::node::deserialize(yaml_content);
|
||||
|
||||
// Delegación a funciones especializadas
|
||||
parseRoomConfig(yaml, room, FILE_NAME);
|
||||
parseTilemap(yaml, room, FILE_NAME, verbose);
|
||||
parseCollisionmap(yaml, room, FILE_NAME, verbose);
|
||||
parseEnemies(yaml, room, verbose);
|
||||
parseItems(yaml, room, verbose);
|
||||
|
||||
if (verbose) {
|
||||
std::cout << "Room loaded successfully: " << FILE_NAME << '\n';
|
||||
}
|
||||
|
||||
} catch (const fkyaml::exception& e) {
|
||||
std::cerr << "YAML parsing error in " << FILE_NAME << ": " << e.what() << '\n';
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error loading room " << FILE_NAME << ": " << e.what() << '\n';
|
||||
}
|
||||
|
||||
return room;
|
||||
}
|
||||
142
source/game/gameplay/room_loader.hpp
Normal file
142
source/game/gameplay/room_loader.hpp
Normal file
@@ -0,0 +1,142 @@
|
||||
#pragma once
|
||||
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "external/fkyaml_node.hpp" // Para fkyaml::node
|
||||
#include "game/entities/enemy.hpp" // Para Enemy::Data
|
||||
#include "game/entities/item.hpp" // Para Item::Data
|
||||
#include "game/gameplay/room.hpp" // Para Room::Data
|
||||
|
||||
/**
|
||||
* @brief Cargador de archivos de habitaciones en formato YAML
|
||||
*
|
||||
* Responsabilidades:
|
||||
* - Cargar archivos de room en formato YAML unificado (.yaml)
|
||||
* - Parsear datos de room, tilemap, enemies e items
|
||||
* - Convertir tipos de datos (tiles, posiciones, colores)
|
||||
* - Validar y propagar errores de carga
|
||||
*
|
||||
* Esta clase contiene solo métodos estáticos y no debe instanciarse.
|
||||
*/
|
||||
class RoomLoader {
|
||||
public:
|
||||
// Constructor eliminado para prevenir instanciación
|
||||
RoomLoader() = delete;
|
||||
~RoomLoader() = delete;
|
||||
RoomLoader(const RoomLoader&) = delete;
|
||||
auto operator=(const RoomLoader&) -> RoomLoader& = delete;
|
||||
RoomLoader(RoomLoader&&) = delete;
|
||||
auto operator=(RoomLoader&&) -> RoomLoader& = delete;
|
||||
|
||||
/**
|
||||
* @brief Carga un archivo de room en formato YAML
|
||||
* @param file_path Ruta al archivo YAML de habitación
|
||||
* @param verbose Si true, muestra información de debug
|
||||
* @return Room::Data con todos los datos de la habitación incluyendo:
|
||||
* - Configuración de la habitación (nombre, colores, etc.)
|
||||
* - Tilemap completo (convertido de 2D a 1D)
|
||||
* - Lista de enemigos con todas sus propiedades
|
||||
* - Lista de items con todas sus propiedades
|
||||
*
|
||||
* El formato YAML esperado incluye:
|
||||
* - room: configuración general
|
||||
* - tilemap: array 2D de 24x40 tiles (convertido a vector 1D de 960 elementos)
|
||||
* - enemies: lista de enemigos (opcional)
|
||||
* - items: lista de items (opcional)
|
||||
*/
|
||||
static auto loadYAML(const std::string& file_path, bool verbose = false) -> Room::Data;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Convierte room connection de YAML a formato interno
|
||||
* @param value Valor del YAML (vacío, "02" o "02.yaml")
|
||||
* @return "0" para sin conexión, o nombre del archivo con extensión
|
||||
*/
|
||||
static auto convertRoomConnection(const std::string& value) -> std::string;
|
||||
|
||||
/**
|
||||
* @brief Convierte autoSurface de YAML a int
|
||||
* @param node Nodo YAML (puede ser int o string "left"/"none"/"right")
|
||||
* @return -1 para left, 0 para none, 1 para right
|
||||
*/
|
||||
static auto convertAutoSurface(const fkyaml::node& node) -> int;
|
||||
|
||||
/**
|
||||
* @brief Convierte un tilemap 2D a vector 1D flat
|
||||
* @param tilemap_2d Array 2D de tiles (24 rows × 40 cols)
|
||||
* @return Vector 1D flat con 960 elementos
|
||||
*/
|
||||
static auto flattenTilemap(const std::vector<std::vector<int>>& tilemap_2d) -> std::vector<int>;
|
||||
|
||||
/**
|
||||
* @brief Parsea la configuración general de la habitación
|
||||
* @param yaml Nodo raíz del YAML
|
||||
* @param room Estructura de datos de la habitación a rellenar
|
||||
* @param file_name Nombre del archivo para logging
|
||||
*/
|
||||
static void parseRoomConfig(const fkyaml::node& yaml, Room::Data& room, const std::string& file_name);
|
||||
|
||||
/**
|
||||
* @brief Parsea las conexiones de la habitación (arriba/abajo/izq/der)
|
||||
* @param conn_node Nodo YAML con las conexiones
|
||||
* @param room Estructura de datos de la habitación a rellenar
|
||||
*/
|
||||
static void parseRoomConnections(const fkyaml::node& conn_node, Room::Data& room);
|
||||
|
||||
/**
|
||||
* @brief Parsea el tilemap de la habitación
|
||||
* @param yaml Nodo raíz del YAML
|
||||
* @param room Estructura de datos de la habitación a rellenar
|
||||
* @param file_name Nombre del archivo para logging
|
||||
* @param verbose Si true, muestra información de debug
|
||||
*/
|
||||
static void parseTilemap(const fkyaml::node& yaml, Room::Data& room, const std::string& file_name, bool verbose);
|
||||
|
||||
/**
|
||||
* @brief Parsea el collisionmap de la habitación
|
||||
* @param yaml Nodo raíz del YAML
|
||||
* @param room Estructura de datos de la habitación a rellenar
|
||||
* @param file_name Nombre del archivo para logging
|
||||
* @param verbose Si true, muestra información de debug
|
||||
*/
|
||||
static void parseCollisionmap(const fkyaml::node& yaml, Room::Data& room, const std::string& file_name, bool verbose);
|
||||
|
||||
/**
|
||||
* @brief Parsea la lista de enemigos de la habitación
|
||||
* @param yaml Nodo raíz del YAML
|
||||
* @param room Estructura de datos de la habitación a rellenar
|
||||
* @param verbose Si true, muestra información de debug
|
||||
*/
|
||||
static void parseEnemies(const fkyaml::node& yaml, Room::Data& room, bool verbose);
|
||||
|
||||
/**
|
||||
* @brief Parsea los datos de un enemigo individual
|
||||
* @param enemy_node Nodo YAML del enemigo
|
||||
* @return Estructura Enemy::Data con los datos parseados
|
||||
*/
|
||||
static auto parseEnemyData(const fkyaml::node& enemy_node) -> Enemy::Data;
|
||||
|
||||
/**
|
||||
* @brief Parsea los límites de movimiento de un enemigo
|
||||
* @param bounds_node Nodo YAML con los límites
|
||||
* @param enemy Estructura del enemigo a rellenar
|
||||
*/
|
||||
static void parseEnemyBoundaries(const fkyaml::node& bounds_node, Enemy::Data& enemy);
|
||||
|
||||
/**
|
||||
* @brief Parsea la lista de items de la habitación
|
||||
* @param yaml Nodo raíz del YAML
|
||||
* @param room Estructura de datos de la habitación a rellenar
|
||||
* @param verbose Si true, muestra información de debug
|
||||
*/
|
||||
static void parseItems(const fkyaml::node& yaml, Room::Data& room, bool verbose);
|
||||
|
||||
/**
|
||||
* @brief Parsea los datos de un item individual
|
||||
* @param item_node Nodo YAML del item
|
||||
* @param room Datos de la habitación (para colores por defecto)
|
||||
* @return Estructura Item::Data con los datos parseados
|
||||
*/
|
||||
static auto parseItemData(const fkyaml::node& item_node, const Room::Data& room) -> Item::Data;
|
||||
};
|
||||
20
source/game/gameplay/room_tracker.cpp
Normal file
20
source/game/gameplay/room_tracker.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#include "game/gameplay/room_tracker.hpp"
|
||||
|
||||
#include <algorithm> // Para std::ranges::any_of
|
||||
|
||||
// Comprueba si la habitación ya ha sido visitada
|
||||
auto RoomTracker::hasBeenVisited(const std::string& name) -> bool {
|
||||
return std::ranges::any_of(rooms_, [&name](const auto& l) { return l == name; });
|
||||
}
|
||||
|
||||
// Añade la habitación a la lista
|
||||
auto RoomTracker::addRoom(const std::string& name) -> bool {
|
||||
// Comprueba si la habitación ya ha sido visitada
|
||||
if (!hasBeenVisited(name)) {
|
||||
// En caso contrario añádela a la lista
|
||||
rooms_.push_back(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
17
source/game/gameplay/room_tracker.hpp
Normal file
17
source/game/gameplay/room_tracker.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
class RoomTracker {
|
||||
public:
|
||||
RoomTracker() = default; // Constructor
|
||||
~RoomTracker() = default; // Destructor
|
||||
|
||||
auto addRoom(const std::string& name) -> bool; // Añade la habitación a la lista
|
||||
|
||||
private:
|
||||
auto hasBeenVisited(const std::string& name) -> bool; // Comprueba si ya ha sido visitada
|
||||
|
||||
std::vector<std::string> rooms_; // Lista con habitaciones visitadas
|
||||
};
|
||||
170
source/game/gameplay/scoreboard.cpp
Normal file
170
source/game/gameplay/scoreboard.cpp
Normal file
@@ -0,0 +1,170 @@
|
||||
#include "game/gameplay/scoreboard.hpp"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/rendering/surface.hpp" // Para Surface
|
||||
#include "core/rendering/surface_animated_sprite.hpp" // Para SAnimatedSprite
|
||||
#include "core/rendering/text.hpp" // Para Text
|
||||
#include "core/resources/resource_cache.hpp" // Para Resource
|
||||
#include "game/options.hpp" // Para Options, options, Cheat, OptionsGame
|
||||
#include "utils/defines.hpp" // Para BLOCK
|
||||
#include "utils/utils.hpp" // Para stringToColor
|
||||
|
||||
// Constructor
|
||||
Scoreboard::Scoreboard(std::shared_ptr<Data> data)
|
||||
: item_surface_(Resource::Cache::get()->getSurface("items.gif")),
|
||||
data_(std::move(std::move(data))) {
|
||||
constexpr float SURFACE_WIDTH = ScoreboardArea::WIDTH;
|
||||
constexpr float SURFACE_HEIGHT = ScoreboardArea::HEIGHT;
|
||||
|
||||
// Reserva memoria para los objetos
|
||||
const auto& player_animation_data = Resource::Cache::get()->getAnimationData(Options::cheats.alternate_skin == Options::Cheat::State::ENABLED ? "player2.yaml" : "player.yaml");
|
||||
player_sprite_ = std::make_shared<SurfaceAnimatedSprite>(player_animation_data);
|
||||
player_sprite_->setCurrentAnimation("walk_menu");
|
||||
|
||||
surface_ = std::make_shared<Surface>(SURFACE_WIDTH, SURFACE_HEIGHT);
|
||||
surface_dest_ = {.x = ScoreboardArea::X, .y = ScoreboardArea::Y, .w = SURFACE_WIDTH, .h = SURFACE_HEIGHT};
|
||||
|
||||
// Inicializa el color de items
|
||||
items_color_ = stringToColor("white");
|
||||
|
||||
// Inicializa el vector de colores
|
||||
const std::vector<std::string> COLORS = {"blue", "magenta", "green", "cyan", "yellow", "white", "bright_blue", "bright_magenta", "bright_green", "bright_cyan", "bright_yellow", "bright_white"};
|
||||
for (const auto& color : COLORS) {
|
||||
color_.push_back(stringToColor(color));
|
||||
}
|
||||
}
|
||||
|
||||
// Pinta el objeto en pantalla
|
||||
void Scoreboard::render() {
|
||||
surface_->render(nullptr, &surface_dest_);
|
||||
}
|
||||
|
||||
// Actualiza las variables del objeto
|
||||
void Scoreboard::update(float delta_time) {
|
||||
// Acumular tiempo para animaciones
|
||||
time_accumulator_ += delta_time;
|
||||
|
||||
// Actualizar sprite con delta time
|
||||
player_sprite_->update(delta_time);
|
||||
|
||||
// Actualiza el color de la cantidad de items recogidos
|
||||
updateItemsColor(delta_time);
|
||||
|
||||
// Dibuja la textura
|
||||
fillTexture();
|
||||
|
||||
if (!is_paused_) {
|
||||
// Si está en pausa no se actualiza el reloj
|
||||
clock_ = getTime();
|
||||
}
|
||||
}
|
||||
|
||||
// Obtiene el tiempo transcurrido de partida
|
||||
auto Scoreboard::getTime() -> Scoreboard::ClockData {
|
||||
const Uint32 TIME_ELAPSED = SDL_GetTicks() - data_->ini_clock - paused_time_elapsed_;
|
||||
|
||||
ClockData time;
|
||||
time.hours = TIME_ELAPSED / 3600000;
|
||||
time.minutes = TIME_ELAPSED / 60000;
|
||||
time.seconds = TIME_ELAPSED / 1000;
|
||||
time.separator = (TIME_ELAPSED % 1000 <= 500) ? ":" : " ";
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
// Pone el marcador en modo pausa
|
||||
void Scoreboard::setPaused(bool value) {
|
||||
if (is_paused_ == value) {
|
||||
// Evita ejecutar lógica si el estado no cambia
|
||||
return;
|
||||
}
|
||||
|
||||
is_paused_ = value;
|
||||
|
||||
if (is_paused_) {
|
||||
// Guarda el tiempo actual al pausar
|
||||
paused_time_ = SDL_GetTicks();
|
||||
} else {
|
||||
// Calcula el tiempo pausado acumulado al reanudar
|
||||
paused_time_elapsed_ += SDL_GetTicks() - paused_time_;
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza el color de la cantidad de items recogidos
|
||||
void Scoreboard::updateItemsColor(float delta_time) {
|
||||
if (!data_->jail_is_open) {
|
||||
return;
|
||||
}
|
||||
|
||||
items_color_timer_ += delta_time;
|
||||
|
||||
// Resetear timer cada 2 ciclos (0.666s total)
|
||||
if (items_color_timer_ >= ITEMS_COLOR_BLINK_DURATION * 2.0F) {
|
||||
items_color_timer_ = 0.0F;
|
||||
}
|
||||
|
||||
// Alternar color cada ITEMS_COLOR_BLINK_DURATION
|
||||
if (items_color_timer_ < ITEMS_COLOR_BLINK_DURATION) {
|
||||
items_color_ = stringToColor("white");
|
||||
} else {
|
||||
items_color_ = stringToColor("magenta");
|
||||
}
|
||||
}
|
||||
|
||||
// Devuelve la cantidad de minutos de juego transcurridos
|
||||
auto Scoreboard::getMinutes() -> int {
|
||||
return getTime().minutes;
|
||||
}
|
||||
|
||||
// Dibuja los elementos del marcador en la textura
|
||||
void Scoreboard::fillTexture() {
|
||||
// Empieza a dibujar en la textura
|
||||
auto previuos_renderer = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(surface_);
|
||||
|
||||
// Limpia la textura
|
||||
surface_->clear(stringToColor("black"));
|
||||
|
||||
// Anclas
|
||||
constexpr int LINE1 = Tile::SIZE;
|
||||
constexpr int LINE2 = 3 * Tile::SIZE;
|
||||
|
||||
// Dibuja las vidas
|
||||
// Calcular desplazamiento basado en tiempo
|
||||
const int DESP = static_cast<int>(time_accumulator_ / SPRITE_WALK_CYCLE_DURATION) % 8;
|
||||
const int FRAME = DESP % SPRITE_WALK_FRAMES;
|
||||
player_sprite_->setCurrentAnimationFrame(FRAME);
|
||||
player_sprite_->setPosY(LINE2);
|
||||
for (int i = 0; i < data_->lives; ++i) {
|
||||
player_sprite_->setPosX(8 + (16 * i) + DESP);
|
||||
const int INDEX = i % color_.size();
|
||||
player_sprite_->render(4, color_.at(INDEX));
|
||||
}
|
||||
|
||||
// Muestra si suena la música
|
||||
if (data_->music) {
|
||||
const Uint8 C = data_->color;
|
||||
SDL_FRect clip = {0, 8, 8, 8};
|
||||
item_surface_->renderWithColorReplace(20 * Tile::SIZE, LINE2, 1, C, &clip);
|
||||
}
|
||||
|
||||
// Escribe los textos
|
||||
auto text = Resource::Cache::get()->getText("smb2");
|
||||
const std::string TIME_TEXT = std::to_string((clock_.minutes % 100) / 10) + std::to_string(clock_.minutes % 10) + clock_.separator + std::to_string((clock_.seconds % 60) / 10) + std::to_string(clock_.seconds % 10);
|
||||
const std::string ITEMS_TEXT = std::to_string(data_->items / 100) + std::to_string((data_->items % 100) / 10) + std::to_string(data_->items % 10);
|
||||
text->writeColored(Tile::SIZE, LINE1, "Items collected ", data_->color);
|
||||
text->writeColored(17 * Tile::SIZE, LINE1, ITEMS_TEXT, items_color_);
|
||||
text->writeColored(20 * Tile::SIZE, LINE1, " Time ", data_->color);
|
||||
text->writeColored(26 * Tile::SIZE, LINE1, TIME_TEXT, stringToColor("white"));
|
||||
|
||||
const std::string ROOMS_TEXT = std::to_string(data_->rooms / 100) + std::to_string((data_->rooms % 100) / 10) + std::to_string(data_->rooms % 10);
|
||||
text->writeColored(22 * Tile::SIZE, LINE2, "Rooms", stringToColor("white"));
|
||||
text->writeColored(28 * Tile::SIZE, LINE2, ROOMS_TEXT, stringToColor("white"));
|
||||
|
||||
// Deja el renderizador como estaba
|
||||
Screen::get()->setRendererSurface(previuos_renderer);
|
||||
}
|
||||
68
source/game/gameplay/scoreboard.hpp
Normal file
68
source/game/gameplay/scoreboard.hpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string, basic_string
|
||||
#include <utility>
|
||||
#include <vector> // Para vector
|
||||
class SurfaceAnimatedSprite; // lines 10-10
|
||||
class Surface; // lines 11-11
|
||||
|
||||
class Scoreboard {
|
||||
public:
|
||||
// Tipos anidados
|
||||
struct Data {
|
||||
int items{0}; // Lleva la cuenta de los objetos recogidos
|
||||
int lives{0}; // Lleva la cuenta de las vidas restantes del jugador
|
||||
int rooms{0}; // Lleva la cuenta de las habitaciones visitadas
|
||||
bool music{true}; // Indica si ha de sonar la música durante el juego
|
||||
Uint8 color{0}; // Color para escribir el texto del marcador
|
||||
Uint32 ini_clock{0}; // Tiempo inicial para calcular el tiempo transcurrido
|
||||
bool jail_is_open{false}; // Indica si se puede entrar a la Jail
|
||||
};
|
||||
|
||||
// Métodos públicos
|
||||
explicit Scoreboard(std::shared_ptr<Data> data); // Constructor
|
||||
~Scoreboard() = default; // Destructor
|
||||
void render(); // Pinta el objeto en pantalla
|
||||
void update(float delta_time); // Actualiza las variables del objeto
|
||||
void setPaused(bool value); // Pone el marcador en modo pausa
|
||||
auto getMinutes() -> int; // Devuelve la cantidad de minutos de juego transcurridos
|
||||
|
||||
private:
|
||||
// Tipos anidados
|
||||
struct ClockData {
|
||||
int hours{0}; // Horas transcurridas
|
||||
int minutes{0}; // Minutos transcurridos
|
||||
int seconds{0}; // Segundos transcurridos
|
||||
std::string separator{":"}; // Separador para mostrar el tiempo
|
||||
};
|
||||
|
||||
// Constantes de tiempo
|
||||
static constexpr float ITEMS_COLOR_BLINK_DURATION = 0.333F; // Duración de cada estado del parpadeo (era 10 frames @ 60fps)
|
||||
static constexpr float SPRITE_WALK_CYCLE_DURATION = 0.667F; // Duración del ciclo de caminar (era 40 frames @ 60fps)
|
||||
static constexpr int SPRITE_WALK_FRAMES = 4; // Número de frames de animación
|
||||
|
||||
// Métodos privados
|
||||
auto getTime() -> ClockData; // Obtiene el tiempo transcurrido de partida
|
||||
void updateItemsColor(float delta_time); // Actualiza el color de la cantidad de items recogidos
|
||||
void fillTexture(); // Dibuja los elementos del marcador en la surface
|
||||
|
||||
// Objetos y punteros
|
||||
std::shared_ptr<SurfaceAnimatedSprite> player_sprite_; // Sprite para mostrar las vidas en el marcador
|
||||
std::shared_ptr<Surface> item_surface_; // Surface con los graficos para los elementos del marcador
|
||||
std::shared_ptr<Data> data_; // Contiene las variables a mostrar en el marcador
|
||||
std::shared_ptr<Surface> surface_; // Surface donde dibujar el marcador
|
||||
|
||||
// Variables de estado
|
||||
std::vector<Uint8> color_; // Vector con los colores del objeto
|
||||
bool is_paused_{false}; // Indica si el marcador esta en modo pausa
|
||||
Uint32 paused_time_{0}; // Milisegundos que ha estado el marcador en pausa
|
||||
Uint32 paused_time_elapsed_{0}; // Tiempo acumulado en pausa
|
||||
ClockData clock_{}; // Contiene las horas, minutos y segundos transcurridos desde el inicio de la partida
|
||||
Uint8 items_color_{0}; // Color de la cantidad de items recogidos
|
||||
SDL_FRect surface_dest_{}; // Rectangulo donde dibujar la surface del marcador
|
||||
float time_accumulator_{0.0F}; // Acumulador de tiempo para animaciones
|
||||
float items_color_timer_{0.0F}; // Timer para parpadeo de color de items
|
||||
};
|
||||
188
source/game/gameplay/tilemap_renderer.cpp
Normal file
188
source/game/gameplay/tilemap_renderer.cpp
Normal file
@@ -0,0 +1,188 @@
|
||||
#include "tilemap_renderer.hpp"
|
||||
|
||||
#include "core/rendering/screen.hpp"
|
||||
#include "core/rendering/surface.hpp"
|
||||
#include "core/rendering/surface_sprite.hpp"
|
||||
#ifdef _DEBUG
|
||||
#include "core/system/debug.hpp"
|
||||
#endif
|
||||
#include "game/gameplay/collision_map.hpp"
|
||||
#include "utils/color.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
// Constructor
|
||||
TilemapRenderer::TilemapRenderer(std::vector<int> tile_map, int tile_set_width, std::shared_ptr<Surface> tileset_surface, std::string bg_color, int conveyor_belt_direction)
|
||||
: tile_map_(std::move(tile_map)),
|
||||
tile_set_width_(tile_set_width),
|
||||
tileset_surface_(std::move(tileset_surface)),
|
||||
bg_color_(std::move(bg_color)),
|
||||
conveyor_belt_direction_(conveyor_belt_direction) {
|
||||
// Crear la surface del mapa
|
||||
map_surface_ = std::make_shared<Surface>(PlayArea::WIDTH, PlayArea::HEIGHT);
|
||||
}
|
||||
|
||||
// Inicializa el renderizador
|
||||
void TilemapRenderer::initialize(const CollisionMap* collision_map) {
|
||||
setAnimatedTiles(collision_map);
|
||||
fillMapTexture(collision_map);
|
||||
}
|
||||
|
||||
// Actualiza las animaciones de tiles
|
||||
void TilemapRenderer::update(float delta_time) {
|
||||
if (is_paused_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Actualiza el acumulador de tiempo
|
||||
time_accumulator_ += delta_time;
|
||||
|
||||
// Actualiza los tiles animados
|
||||
updateAnimatedTiles();
|
||||
}
|
||||
|
||||
// Renderiza el mapa completo en pantalla
|
||||
void TilemapRenderer::render() {
|
||||
// Dibuja la textura con el mapa en pantalla
|
||||
SDL_FRect dest = {0, 0, PlayArea::WIDTH, PlayArea::HEIGHT};
|
||||
map_surface_->render(nullptr, &dest);
|
||||
|
||||
// Dibuja los tiles animados
|
||||
#ifdef _DEBUG
|
||||
if (!Debug::get()->isEnabled()) {
|
||||
renderAnimatedTiles();
|
||||
}
|
||||
#else
|
||||
renderAnimatedTiles();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
// Renderiza las superficies de colisión en modo debug (función helper estática)
|
||||
static void renderDebugCollisionSurfaces(const CollisionMap* collision_map) {
|
||||
auto surface = Screen::get()->getRendererSurface();
|
||||
|
||||
// BottomSurfaces
|
||||
for (auto l : collision_map->getBottomFloors()) {
|
||||
surface->drawLine(l.x1, l.y, l.x2, l.y, Color::index(Color::Cpc::BLUE));
|
||||
}
|
||||
|
||||
// TopSurfaces
|
||||
for (auto l : collision_map->getTopFloors()) {
|
||||
surface->drawLine(l.x1, l.y, l.x2, l.y, Color::index(Color::Cpc::RED));
|
||||
}
|
||||
|
||||
// LeftSurfaces
|
||||
for (auto l : collision_map->getLeftWalls()) {
|
||||
surface->drawLine(l.x, l.y1, l.x, l.y2, Color::index(Color::Cpc::GREEN));
|
||||
}
|
||||
|
||||
// RightSurfaces
|
||||
for (auto l : collision_map->getRightWalls()) {
|
||||
surface->drawLine(l.x, l.y1, l.x, l.y2, Color::index(Color::Cpc::MAGENTA));
|
||||
}
|
||||
|
||||
// AutoSurfaces (Conveyor Belts)
|
||||
for (auto l : collision_map->getConveyorBeltFloors()) {
|
||||
surface->drawLine(l.x1, l.y, l.x2, l.y, Color::index(Color::Cpc::WHITE));
|
||||
}
|
||||
}
|
||||
|
||||
// Redibuja el tilemap (para actualizar modo debug)
|
||||
void TilemapRenderer::redrawMap(const CollisionMap* collision_map) {
|
||||
fillMapTexture(collision_map);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Pinta el mapa estático y debug lines
|
||||
void TilemapRenderer::fillMapTexture(const CollisionMap* collision_map) {
|
||||
const Uint8 COLOR = stringToColor(bg_color_);
|
||||
auto previous_renderer = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(map_surface_);
|
||||
map_surface_->clear(COLOR);
|
||||
|
||||
// Los tileSetFiles son de 20x20 tiles. El primer tile es el 0. Cuentan hacia la derecha y hacia abajo
|
||||
|
||||
SDL_FRect clip = {0, 0, TILE_SIZE, TILE_SIZE};
|
||||
for (int y = 0; y < MAP_HEIGHT; ++y) {
|
||||
for (int x = 0; x < MAP_WIDTH; ++x) {
|
||||
// Tiled pone los tiles vacios del mapa como cero y empieza a contar de 1 a n.
|
||||
// Al cargar el mapa en memoria, se resta uno, por tanto los tiles vacios son -1
|
||||
// Tampoco hay que dibujar los tiles animados (se dibujan en renderAnimatedTiles)
|
||||
const int INDEX = (y * MAP_WIDTH) + x;
|
||||
const bool IS_ANIMATED = collision_map->getTile(INDEX) == CollisionMap::Tile::ANIMATED;
|
||||
const bool IS_VISIBLE = tile_map_[INDEX] > -1;
|
||||
|
||||
if (IS_VISIBLE && !IS_ANIMATED) {
|
||||
clip.x = (tile_map_[INDEX] % tile_set_width_) * TILE_SIZE;
|
||||
clip.y = (tile_map_[INDEX] / tile_set_width_) * TILE_SIZE;
|
||||
#ifdef _DEBUG
|
||||
if (!Debug::get()->isEnabled()) {
|
||||
tileset_surface_->render(x * TILE_SIZE, y * TILE_SIZE, &clip);
|
||||
}
|
||||
#else
|
||||
tileset_surface_->render(x * TILE_SIZE, y * TILE_SIZE, &clip);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
// Pinta las superficies en el modo debug
|
||||
if (Debug::get()->isEnabled()) {
|
||||
renderDebugCollisionSurfaces(collision_map);
|
||||
}
|
||||
#endif // _DEBUG
|
||||
Screen::get()->setRendererSurface(previous_renderer);
|
||||
}
|
||||
|
||||
// Localiza todos los tiles animados
|
||||
void TilemapRenderer::setAnimatedTiles(const CollisionMap* collision_map) {
|
||||
// Recorre la habitación entera por filas buscando tiles de tipo t_animated
|
||||
for (int i = 0; i < (int)tile_map_.size(); ++i) {
|
||||
const auto TILE_TYPE = collision_map->getTile(i);
|
||||
if (TILE_TYPE == CollisionMap::Tile::ANIMATED) {
|
||||
// La i es la ubicación
|
||||
const int X = (i % MAP_WIDTH) * TILE_SIZE;
|
||||
const int Y = (i / MAP_WIDTH) * TILE_SIZE;
|
||||
|
||||
// TileMap[i] es el tile a poner
|
||||
const int XC = (tile_map_[i] % tile_set_width_) * TILE_SIZE;
|
||||
const int YC = (tile_map_[i] / tile_set_width_) * TILE_SIZE;
|
||||
|
||||
AnimatedTile at;
|
||||
at.sprite = std::make_shared<SurfaceSprite>(tileset_surface_, X, Y, 8, 8);
|
||||
at.sprite->setClip(XC, YC, 8, 8);
|
||||
at.x_orig = XC;
|
||||
animated_tiles_.push_back(at);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza tiles animados
|
||||
void TilemapRenderer::updateAnimatedTiles() {
|
||||
const int NUM_FRAMES = 4;
|
||||
|
||||
// Calcular frame actual basado en tiempo
|
||||
const int CURRENT_FRAME = static_cast<int>(time_accumulator_ / CONVEYOR_FRAME_DURATION) % NUM_FRAMES;
|
||||
|
||||
// Calcular offset basado en dirección
|
||||
int offset = 0;
|
||||
if (conveyor_belt_direction_ == -1) {
|
||||
offset = CURRENT_FRAME * TILE_SIZE;
|
||||
} else {
|
||||
offset = (NUM_FRAMES - 1 - CURRENT_FRAME) * TILE_SIZE;
|
||||
}
|
||||
|
||||
for (auto& a : animated_tiles_) {
|
||||
SDL_FRect rect = a.sprite->getClip();
|
||||
rect.x = a.x_orig + offset;
|
||||
a.sprite->setClip(rect);
|
||||
}
|
||||
}
|
||||
|
||||
// Renderiza tiles animados
|
||||
void TilemapRenderer::renderAnimatedTiles() {
|
||||
for (const auto& a : animated_tiles_) {
|
||||
a.sprite->render();
|
||||
}
|
||||
}
|
||||
118
source/game/gameplay/tilemap_renderer.hpp
Normal file
118
source/game/gameplay/tilemap_renderer.hpp
Normal file
@@ -0,0 +1,118 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "utils/defines.hpp"
|
||||
|
||||
class Surface;
|
||||
class SurfaceSprite;
|
||||
class CollisionMap;
|
||||
|
||||
/**
|
||||
* @brief Renderizador de tilemap de una habitación
|
||||
*
|
||||
* Responsabilidades:
|
||||
* - Renderizar el mapa de tiles estático
|
||||
* - Gestionar tiles animados (conveyor belts)
|
||||
* - Actualizar animaciones basadas en tiempo
|
||||
* - Renderizar debug visualization (en modo DEBUG)
|
||||
*/
|
||||
class TilemapRenderer {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param tile_map Vector con índices de tiles de la habitación
|
||||
* @param tile_set_width Ancho del tileset en tiles
|
||||
* @param tileset_surface Surface con los gráficos del tileset
|
||||
* @param bg_color Color de fondo de la habitación (como string)
|
||||
* @param conveyor_belt_direction Dirección de las cintas transportadoras (-1, 0, +1)
|
||||
*/
|
||||
TilemapRenderer(std::vector<int> tile_map, int tile_set_width, std::shared_ptr<Surface> tileset_surface, std::string bg_color, int conveyor_belt_direction);
|
||||
~TilemapRenderer() = default;
|
||||
|
||||
// Prohibir copia y movimiento
|
||||
TilemapRenderer(const TilemapRenderer&) = delete;
|
||||
auto operator=(const TilemapRenderer&) -> TilemapRenderer& = delete;
|
||||
TilemapRenderer(TilemapRenderer&&) = delete;
|
||||
auto operator=(TilemapRenderer&&) -> TilemapRenderer& = delete;
|
||||
|
||||
/**
|
||||
* @brief Inicializa el renderizador
|
||||
* @param collision_map Mapa de colisiones para determinar tiles animados
|
||||
*
|
||||
* Crea la textura del mapa, pinta los tiles estáticos, y localiza tiles animados
|
||||
*/
|
||||
void initialize(const CollisionMap* collision_map);
|
||||
|
||||
/**
|
||||
* @brief Actualiza las animaciones de tiles
|
||||
* @param delta_time Tiempo transcurrido desde el último frame (segundos)
|
||||
*/
|
||||
void update(float delta_time);
|
||||
|
||||
/**
|
||||
* @brief Renderiza el mapa completo en pantalla
|
||||
*
|
||||
* Dibuja la textura del mapa y los tiles animados
|
||||
*/
|
||||
void render();
|
||||
|
||||
#ifdef _DEBUG
|
||||
/**
|
||||
* @brief Redibuja el tilemap (para actualizar modo debug)
|
||||
* @param collision_map Mapa de colisiones para dibujar líneas de debug
|
||||
*
|
||||
* Llamado cuando se activa/desactiva el modo debug para actualizar la visualización
|
||||
*/
|
||||
void redrawMap(const CollisionMap* collision_map);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Activa/desactiva modo pausa
|
||||
* @param paused true para pausar, false para reanudar
|
||||
*
|
||||
* Nota: Actualmente no afecta al renderizado, pero mantiene consistencia con Room
|
||||
*/
|
||||
void setPaused(bool paused) { is_paused_ = paused; }
|
||||
|
||||
// Getter para la surface del mapa (usado por Room para acceso directo si es necesario)
|
||||
[[nodiscard]] auto getMapSurface() const -> std::shared_ptr<Surface> { return map_surface_; }
|
||||
|
||||
private:
|
||||
// Estructura para tiles animados (conveyor belts)
|
||||
struct AnimatedTile {
|
||||
std::shared_ptr<SurfaceSprite> sprite{nullptr}; // SurfaceSprite para dibujar el tile
|
||||
int x_orig{0}; // Posición X del primer tile de la animación en tilesheet
|
||||
};
|
||||
|
||||
// === Constantes ===
|
||||
static constexpr int TILE_SIZE = Tile::SIZE; // Ancho del tile en pixels
|
||||
static constexpr int MAP_WIDTH = PlayArea::WIDTH / Tile::SIZE; // Ancho del mapa en tiles
|
||||
static constexpr int MAP_HEIGHT = PlayArea::HEIGHT / Tile::SIZE; // Alto del mapa en tiles
|
||||
static constexpr int PLAY_AREA_WIDTH = PlayArea::WIDTH; // Ancho del área de juego en pixels
|
||||
static constexpr int PLAY_AREA_HEIGHT = PlayArea::HEIGHT; // Alto del área de juego en pixels
|
||||
static constexpr float CONVEYOR_FRAME_DURATION = 0.05F; // Duración de cada frame (3 frames @ 60fps)
|
||||
|
||||
// === Datos de la habitación ===
|
||||
std::vector<int> tile_map_; // Índices de tiles de la habitación
|
||||
int tile_set_width_; // Ancho del tileset en tiles
|
||||
std::shared_ptr<Surface> tileset_surface_; // Gráficos del tileset
|
||||
std::string bg_color_; // Color de fondo
|
||||
int conveyor_belt_direction_; // Dirección de conveyor belts
|
||||
|
||||
// === Renderizado ===
|
||||
std::shared_ptr<Surface> map_surface_; // Textura para el mapa de la habitación
|
||||
std::vector<AnimatedTile> animated_tiles_; // Tiles animados (conveyor belts)
|
||||
float time_accumulator_{0.0F}; // Acumulador de tiempo para animaciones
|
||||
bool is_paused_{false}; // Indica si está en modo pausa
|
||||
|
||||
// === Métodos privados ===
|
||||
void fillMapTexture(const CollisionMap* collision_map); // Pinta el mapa estático y debug lines
|
||||
void setAnimatedTiles(const CollisionMap* collision_map); // Localiza todos los tiles animados
|
||||
void updateAnimatedTiles(); // Actualiza tiles animados
|
||||
void renderAnimatedTiles(); // Renderiza tiles animados
|
||||
};
|
||||
Reference in New Issue
Block a user