feat(collisions): filtre per altura — saltar esquiva momies, roques i altres jugadors

This commit is contained in:
JailDesigner committed 2026-04-25 11:49:48 +02:00
1 parent 7b09a076b2
commit cd50626b88
11 files changed
+88 -1

No files matched your search

+12
View File
@@ -113,6 +113,17 @@ class Entity {
// Entitats sense animació retornen `nullopt` → frame 0 per defecte.
[[nodiscard]] auto getAnimation() const -> const std::optional<AnimationComponent>& { return anim_; }
// Alçada del cos de l'entitat en píxels. La consulten els filtres
// de col·lisió per altura: si un jugador en l'aire passa per damunt
// d'aquest valor (jump_z_ > height), no col·lisiona amb l'entitat —
// és la mecànica del salt per esquivar momies, roques, etc. Valor
// 0 = sense altura (no es pot esquivar saltant; per a entitats amb
// collides=false el camp és irrellevant). Per a obstacles que
// representen "murs" (sarcs, portes), el YAML els pot donar un
// valor molt alt (p.ex. 999) per fer-los infranquejables.
[[nodiscard]] auto getHeight() const -> int { return height_; }
void setHeight(int h) { height_ = h; }
// Caixa de col·lisió: per defecte és el `pixel_rect_` sencer (entitats
// AABB-aligned). Si s'ha configurat un `CollisionBox`, retorna el
// sub-rect dins del sprite — vida útil: validar col·lisions amb el
@@ -150,6 +161,7 @@ class Entity {
std::shared_ptr<Surface> surface_{nullptr};
bool collides_{false};
bool interactable_{false};
int height_{0};
SDL_FlipMode flip_{SDL_FLIP_NONE};
EventBus* bus_{nullptr};
};
@@ -63,8 +63,15 @@ void EntityManager::checkPlayerCollisions(const std::vector<Player*>& players) {
for (const auto& entity : entities_) {
if (!entity->collides()) { continue; }
const auto BOX = entity->getCollisionRect();
const int E_HEIGHT = entity->getHeight();
for (auto* p : players) {
if (p == nullptr) { continue; }
// Filtre per altura: si el jugador està en l'aire i està per
// damunt de l'alçada del cos de l'entitat, no hi ha contacte.
// És el mecanisme del salt per esquivar momies, roques, etc.
// Quan height és 0 (entitat sense altura declarada), el filtre
// no es dispara mai i la col·lisió funciona com abans.
if (p->isAirborne() && E_HEIGHT > 0 && p->getJumpZ() > static_cast<float>(E_HEIGHT)) { continue; }
// Contacte actor-actor amb hit-box petita: la caixa base
// 16×16 servia per moure (xoc amb murs), però per a l'event
// "toca una entitat" volem precisió — que la momia mate quan
@@ -109,6 +116,18 @@ namespace {
return info;
}
// Llig `collision.height` (alçada del cos en píxels) de la plantilla.
// 0 si no està declarada — ho usen els filtres de col·lisió per altura
// del salt: un jugador per damunt d'aquest valor no col·lisiona amb
// l'entitat. Per a obstacles "tipus mur" (sarc, porta) el YAML els
// dóna un valor molt alt per fer-los infranquejables.
auto readHeight(const fkyaml::node& tmpl) -> int {
if (!tmpl.contains("collision")) { return 0; }
const auto& c = tmpl["collision"];
if (!c.contains("height")) { return 0; }
return c["height"].get_value<int>();
}
// Bloc `collision: { size: [w, h], base_size: [bw, bh]?, offset: [x, y]? }`
// de la plantilla d'entitat.
// - Sense `base_size` ni `offset`: la caja queda alineada a la vora
@@ -212,6 +231,7 @@ namespace {
if (const auto BOX = readCollisionBox(tmpl, sprite_out.width, sprite_out.height); BOX.has_value()) {
entity->setCollisionBox(*BOX);
}
entity->setHeight(readHeight(tmpl));
anchor_out = readAnchor(tmpl);
return entity;
}
+10 -1
View File
@@ -300,9 +300,18 @@ auto Player::collidesWithWorld(const SDL_FRect& box) const -> bool {
// amb la hit-box actual de l'altre jugador. Així la caixa de moviment
// (16×16) segueix topant amb murs/sarcs però els jugadors poden
// acostar-se fins al contacte real.
//
// Filtre per altura: si aquest jugador (this) està en l'aire i passa
// per damunt del cos de l'altre, no hi ha col·lisió — saltar permet
// sobrevolar l'altre. `body_height` és estàtic (compartit entre
// jugadors), però per al filtre val la pena consultar-lo per actor
// perquè en el futur cada character podria tenir-ne un de propi.
const SDL_FRect PROPOSED_HIT = hitBoxFromBase(box);
return std::ranges::any_of(*all_players_, [this, &PROPOSED_HIT](const Player* p) {
const float Z = jump_z_;
const bool AIRBORNE = (state_ == State::AIRBORNE);
return std::ranges::any_of(*all_players_, [this, &PROPOSED_HIT, AIRBORNE, Z](const Player* p) {
if (p == nullptr || p == this) { return false; }
if (AIRBORNE && body_height > 0 && Z > static_cast<float>(body_height)) { return false; }
const auto OTHER = p->getHitBox();
return SDL_HasRectIntersectionFloat(&PROPOSED_HIT, &OTHER);
});