feat(entities): clipping per frame, falsa perspectiva del sarcòfag i piràmide amb 20 sarcs
This commit is contained in:
1 parent
7191a08618
commit
1af5ef861f
11 files changed
+162
-25
No files matched your search
@@ -95,6 +95,11 @@ class Entity {
|
||||
// manager per tal de confinar la dependència cap a `Surface`.
|
||||
[[nodiscard]] auto getSurface() const -> const std::shared_ptr<Surface>& { return surface_; }
|
||||
|
||||
// Accessor del component d'animació. `EntityManager::renderAll` el
|
||||
// consulta per decidir quin frame del strip vertical clipa al blit.
|
||||
// Entitats sense animació retornen `nullopt` → frame 0 per defecte.
|
||||
[[nodiscard]] auto getAnimation() const -> const std::optional<AnimationComponent>& { return anim_; }
|
||||
|
||||
protected:
|
||||
std::string name_;
|
||||
TilePos tile_{};
|
||||
|
||||
@@ -25,12 +25,33 @@
|
||||
// `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.
|
||||
//
|
||||
// Convenció dels GIFs d'actors: els frames s'estenen en una graella
|
||||
// left-to-right, top-to-bottom, amb mida de frame igual a (pixel_rect.w,
|
||||
// pixel_rect.h). El nombre de columnes es dedueix dividint l'amplada del
|
||||
// surface per l'amplada del frame — així un GIF de 4 frames pot venir com
|
||||
// a 1×4, 4×1 o 2×2 indistintament. `renderAll` clipa la cel·la del frame
|
||||
// actual amb un `src_rect`. Entitats sense animació es dibuixen al frame 0.
|
||||
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));
|
||||
const auto& anim = e->getAnimation();
|
||||
const int FRAME = anim.has_value() ? anim->currentFrame() : 0;
|
||||
const int FRAME_W = static_cast<int>(RECT.w);
|
||||
const int FRAME_H = static_cast<int>(RECT.h);
|
||||
const int COLS = (FRAME_W > 0) ? (surface->getWidth() / FRAME_W) : 1;
|
||||
const int COLS_SAFE = (COLS > 0) ? COLS : 1;
|
||||
const int COL = FRAME % COLS_SAFE;
|
||||
const int ROW = FRAME / COLS_SAFE;
|
||||
const SDL_FRect SRC{
|
||||
.x = static_cast<float>(COL * FRAME_W),
|
||||
.y = static_cast<float>(ROW * FRAME_H),
|
||||
.w = RECT.w,
|
||||
.h = RECT.h,
|
||||
};
|
||||
surface->render(static_cast<int>(RECT.x), static_cast<int>(RECT.y), &SRC);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,15 +143,29 @@ namespace {
|
||||
// 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 {
|
||||
auto placeSarcophagus(EntityManager& manager, std::unique_ptr<Entity> entity, const SpriteInfo& sprite, 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);
|
||||
const auto LOGICAL = Map::getSarcRect(sp[0].get_value<int>(), sp[1].get_value<int>());
|
||||
const auto ANCHOR = pixelToTileAnchor(LOGICAL);
|
||||
|
||||
// Convenció de falsa perspectiva (igual que el jugador): el rect de
|
||||
// render és bottom-aligned amb la caixa lògica de col·lisió. Si el
|
||||
// sprite fa sprite.height > LOGICAL.h (p.ex. 48×32 sobre caixa 48×16),
|
||||
// els píxels sobresortits queden per damunt sense afectar la
|
||||
// col·lisió ni el perímetre — només decoren en alçada. Si sprite.h
|
||||
// coincideix amb LOGICAL.h, el render coincideix exactament amb la
|
||||
// caixa.
|
||||
const SDL_FRect RENDER{
|
||||
.x = LOGICAL.x,
|
||||
.y = LOGICAL.y + LOGICAL.h - sprite.height,
|
||||
.w = sprite.width,
|
||||
.h = sprite.height,
|
||||
};
|
||||
entity->setPixelRect(RENDER);
|
||||
|
||||
// Downcast segur: la factory ens garanteix que "sarcophagus" crea
|
||||
// un `Sarcophagus`. Usant el retorn de `release` al ctor del nou
|
||||
@@ -178,7 +213,7 @@ void EntityManager::loadFromLevel(const std::string& level_yaml_path) {
|
||||
if (entity == nullptr) { continue; }
|
||||
|
||||
const bool OK = (TYPE == "sarcophagus")
|
||||
? placeSarcophagus(*this, std::move(entity), spawn)
|
||||
? placeSarcophagus(*this, std::move(entity), sprite, spawn)
|
||||
: placeGeneric(*this, std::move(entity), TYPE, sprite, spawn);
|
||||
if (OK) { ++loaded; }
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user