feat(entities): integració del sistema d'entitats al GameScene
This commit is contained in:
1 parent
96b71b0f65
commit
7191a08618
16 files changed
+683
-15
No files matched your search
@@ -0,0 +1,188 @@
|
||||
// Implementació d'`EntityManager::loadFromLevel`. Viu en un TU separat
|
||||
// perquè aquesta és l'única via per la qual el manager toca
|
||||
// `Resource::Cache`, `Surface` i les seues dependències (GIF, àudio). Els
|
||||
// consumidors que només volen la base del manager (p.ex. el smoke test
|
||||
// stand-alone) enllacen `entity_manager.cpp` sense arrossegar aquest fitxer.
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "core/rendering/surface.hpp"
|
||||
#include "core/resources/resource_cache.hpp"
|
||||
#include "external/fkyaml_node.hpp"
|
||||
#include "game/entities/concrete/door.hpp"
|
||||
#include "game/entities/concrete/sarcophagus.hpp"
|
||||
#include "game/entities/entity.hpp"
|
||||
#include "game/entities/entity_factory.hpp"
|
||||
#include "game/entities/entity_loader.hpp"
|
||||
#include "game/entities/entity_manager.hpp"
|
||||
#include "game/map/map.hpp"
|
||||
|
||||
// Render centralitzat al manager per confinar la dependència cap a
|
||||
// `Surface`. Les entitats exposen `getSurface()` + `getPixelRect()` i el
|
||||
// manager dispatcha el blit. Si una entitat no té surface (trigger
|
||||
// invisible, encara sense sprite) simplement es salta.
|
||||
void EntityManager::renderAll() {
|
||||
for (const auto& e : entities_) {
|
||||
const auto& surface = e->getSurface();
|
||||
if (surface == nullptr) { continue; }
|
||||
const auto RECT = e->getPixelRect();
|
||||
surface->render(static_cast<int>(RECT.x), static_cast<int>(RECT.y));
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Llegeix `sprite.size` o `sprite.path` d'una plantilla i retorna
|
||||
// (surface_key, pixel_size). Si falta algun camp, torna valors
|
||||
// raonables (empty key → no carrega; size [16,16] per defecte).
|
||||
struct SpriteInfo {
|
||||
std::string key; // clau al Resource::Cache (basename del GIF)
|
||||
std::string full_path; // ruta absoluta per a loadSurface
|
||||
float width{16.0F};
|
||||
float height{16.0F};
|
||||
bool valid{false};
|
||||
};
|
||||
|
||||
auto readSpriteInfo(const fkyaml::node& tmpl, const std::string& type) -> SpriteInfo {
|
||||
SpriteInfo info;
|
||||
if (!tmpl.contains("sprite")) { return info; }
|
||||
const auto& s = tmpl["sprite"];
|
||||
if (s.contains("path")) {
|
||||
const auto REL = s["path"].get_value<std::string>();
|
||||
info.key = std::filesystem::path(REL).filename().string();
|
||||
info.full_path = "data/entities/" + type + "/" + REL;
|
||||
info.valid = true;
|
||||
}
|
||||
if (s.contains("size")) {
|
||||
const auto& sz = s["size"];
|
||||
if (sz.is_sequence() && sz.size() == 2) {
|
||||
info.width = static_cast<float>(sz[0].get_value<int>());
|
||||
info.height = static_cast<float>(sz[1].get_value<int>());
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
// Coordenades píxel (canvas) d'una cel·la (col, row) del tilemap. El
|
||||
// grid del tilemap està 16-alineat respecte a `tilemap_origin_*`, que
|
||||
// pot ser negatiu per overscan CPC.
|
||||
auto tileToPixel(int col, int row) -> SDL_FPoint {
|
||||
return {
|
||||
.x = static_cast<float>((col * Map::tile_size) + Map::tilemap_origin_x),
|
||||
.y = static_cast<float>((row * Map::tile_size) + Map::tilemap_origin_y),
|
||||
};
|
||||
}
|
||||
|
||||
// Converteix un rect píxel (canvas) a la seua cantonada superior-esquerra
|
||||
// en coordenades de tile del tilemap. Només per a sarcòfags, que naixen
|
||||
// ancorats a una cel·la exacta de la graella.
|
||||
struct TileAnchor {
|
||||
int col;
|
||||
int row;
|
||||
};
|
||||
auto pixelToTileAnchor(const SDL_FRect& rect) -> TileAnchor {
|
||||
return {
|
||||
.col = (static_cast<int>(rect.x) - Map::tilemap_origin_x) / Map::tile_size,
|
||||
.row = (static_cast<int>(rect.y) - Map::tilemap_origin_y) / Map::tile_size,
|
||||
};
|
||||
}
|
||||
|
||||
// Crea l'entitat per factory i li adjunta la surface si n'hi ha. Retorna
|
||||
// nullptr si la plantilla no es pot carregar o si el tipus no està
|
||||
// registrat al factory — el loop principal ja imprimeix el missatge.
|
||||
auto buildEntity(const std::string& type, SpriteInfo& sprite_out) -> std::unique_ptr<Entity> {
|
||||
fkyaml::node tmpl;
|
||||
try {
|
||||
tmpl = EntityLoader::loadTemplate(type);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "EntityManager: " << e.what() << '\n';
|
||||
return nullptr;
|
||||
}
|
||||
auto entity = EntityFactory::instance().create(type, tmpl);
|
||||
if (entity == nullptr) {
|
||||
std::cerr << "EntityManager: tipus desconegut '" << type << "'\n";
|
||||
return nullptr;
|
||||
}
|
||||
sprite_out = readSpriteInfo(tmpl, type);
|
||||
if (sprite_out.valid) {
|
||||
Resource::Cache::get().loadSurface(sprite_out.key, sprite_out.full_path);
|
||||
entity->setSurface(Resource::Cache::get().getSurface(sprite_out.key));
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Col·loca i afegeix un Sarcophagus a partir del spawn. `entity` es consumeix
|
||||
// en èxit (ownership transferit al manager); en error es destrueix aquí.
|
||||
// Membre privat amagat dins del TU: `EntityManager::` qualifica, però com
|
||||
// que no hi ha declaració al header, només és visible a aquest fitxer.
|
||||
namespace {
|
||||
auto placeSarcophagus(EntityManager& manager, std::unique_ptr<Entity> entity, const fkyaml::node& spawn) -> bool {
|
||||
if (!spawn.contains("sarc_position")) {
|
||||
std::cerr << "EntityManager: sarcophagus sense sarc_position\n";
|
||||
return false;
|
||||
}
|
||||
const auto& sp = spawn["sarc_position"];
|
||||
const auto RECT = Map::getSarcRect(sp[0].get_value<int>(), sp[1].get_value<int>());
|
||||
const auto ANCHOR = pixelToTileAnchor(RECT);
|
||||
entity->setPixelRect(RECT);
|
||||
|
||||
// Downcast segur: la factory ens garanteix que "sarcophagus" crea
|
||||
// un `Sarcophagus`. Usant el retorn de `release` al ctor del nou
|
||||
// `unique_ptr` evitem el lint `bugprone-unused-return-value`.
|
||||
if (dynamic_cast<Sarcophagus*>(entity.get()) == nullptr) {
|
||||
std::cerr << "EntityManager: la factory no ha creat un Sarcophagus\n";
|
||||
return false;
|
||||
}
|
||||
std::unique_ptr<Sarcophagus> sarc(static_cast<Sarcophagus*>(entity.release()));
|
||||
sarc->place(ANCHOR.col, ANCHOR.row, {});
|
||||
manager.addSarcophagus(std::move(sarc));
|
||||
return true;
|
||||
}
|
||||
|
||||
auto placeGeneric(EntityManager& manager, std::unique_ptr<Entity> entity, const std::string& type, const SpriteInfo& sprite, const fkyaml::node& spawn) -> bool {
|
||||
if (!spawn.contains("position")) {
|
||||
std::cerr << "EntityManager: " << type << " sense position\n";
|
||||
return false;
|
||||
}
|
||||
const auto& p = spawn["position"];
|
||||
const auto PX = tileToPixel(p[0].get_value<int>(), p[1].get_value<int>());
|
||||
entity->setPixelRect({.x = PX.x, .y = PX.y, .w = sprite.width, .h = sprite.height});
|
||||
|
||||
if (type == "door") {
|
||||
auto* door_ptr = dynamic_cast<Door*>(entity.get());
|
||||
manager.add(std::move(entity));
|
||||
if (door_ptr != nullptr) { manager.setDoor(door_ptr); }
|
||||
} else {
|
||||
manager.add(std::move(entity));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void EntityManager::loadFromLevel(const std::string& level_yaml_path) {
|
||||
const auto SPAWNS = EntityLoader::readSpawns(level_yaml_path);
|
||||
int loaded = 0;
|
||||
|
||||
for (const auto& spawn : SPAWNS) {
|
||||
if (!spawn.contains("type")) { continue; }
|
||||
const auto TYPE = spawn["type"].get_value<std::string>();
|
||||
|
||||
SpriteInfo sprite;
|
||||
auto entity = buildEntity(TYPE, sprite);
|
||||
if (entity == nullptr) { continue; }
|
||||
|
||||
const bool OK = (TYPE == "sarcophagus")
|
||||
? placeSarcophagus(*this, std::move(entity), spawn)
|
||||
: placeGeneric(*this, std::move(entity), TYPE, sprite, spawn);
|
||||
if (OK) { ++loaded; }
|
||||
}
|
||||
|
||||
std::cout << "EntityManager: " << loaded << " entitats carregades des de "
|
||||
<< level_yaml_path << '\n';
|
||||
}
|
||||
Reference in new issue
Block a user