feat(collisions): filtre per altura — saltar esquiva momies, roques i altres jugadors
This commit is contained in:
1 parent
7b09a076b2
commit
cd50626b88
11 files changed
+88
-1
No files matched your search
@@ -4,6 +4,10 @@ sprite:
|
||||
path: door.gif
|
||||
size: [48, 32]
|
||||
tile_size: [3, 2]
|
||||
collision:
|
||||
# Porta = mur infranquejable. Igual que el sarc, alçada efectiva
|
||||
# infinita per impedir que un jugador la salte per damunt.
|
||||
height: 999
|
||||
flags:
|
||||
collides: true
|
||||
interactable: true
|
||||
|
||||
@@ -4,6 +4,11 @@ sprite:
|
||||
path: floor_trap.gif
|
||||
size: [16, 16]
|
||||
tile_size: [1, 1]
|
||||
collision:
|
||||
# Trampa al sòl: alçada 0 → qualsevol salt l'esquiva. (Avui amb
|
||||
# `collides: false` la trampa no entra al filtre de contacte, però
|
||||
# declarem el camp per quan es cablege la detecció via àrea.)
|
||||
height: 0
|
||||
flags:
|
||||
collides: false
|
||||
interactable: false
|
||||
|
||||
@@ -14,6 +14,11 @@ anchor: feet
|
||||
collision:
|
||||
size: [16, 16]
|
||||
base_size: [16, 16]
|
||||
# Alçada del cos en píxels per al filtre de col·lisió per salt: un
|
||||
# jugador amb jump_z > height no col·lisiona amb la momia (la salta
|
||||
# per damunt). Amb impulse=240 + gravity=600 el pic ronda els 48 px,
|
||||
# així que 24 deixa marge per esquivar amb un salt no perfecte.
|
||||
height: 24
|
||||
flags:
|
||||
collides: true
|
||||
interactable: false
|
||||
|
||||
@@ -4,6 +4,11 @@ sprite:
|
||||
path: rolling_rock.gif
|
||||
size: [16, 16]
|
||||
tile_size: [1, 1]
|
||||
collision:
|
||||
# `size`/`base_size` no calen per a la roca (tot el sprite és cos),
|
||||
# però sí declarem `height` per al filtre de salt: 16 px → fàcil
|
||||
# d'esquivar amb un salt curt.
|
||||
height: 16
|
||||
flags:
|
||||
collides: true
|
||||
interactable: false
|
||||
|
||||
@@ -7,6 +7,13 @@ sprite:
|
||||
# són "alçada" en falsa perspectiva, sense col·lisió. Perímetre ortogonal
|
||||
# de 8 tiles (3 N + 3 S + 1 O + 1 E).
|
||||
tile_size: [3, 1]
|
||||
collision:
|
||||
# Sarcòfag = obstacle massís: alçada efectiva infinita (999) per al
|
||||
# filtre del salt. Un jugador no el pot saltar per damunt encara que
|
||||
# el salt arribe al punt àlgid. La caixa de col·lisió real (logical
|
||||
# 48×16) la calcula `placeSarcophagus` a partir de la geometria del
|
||||
# mapa, no des d'aquest YAML.
|
||||
height: 999
|
||||
flags:
|
||||
collides: true
|
||||
interactable: true
|
||||
|
||||
@@ -4,6 +4,10 @@ sprite:
|
||||
path: torch.gif
|
||||
size: [16, 16]
|
||||
tile_size: [1, 1]
|
||||
collision:
|
||||
# Decoratiu, no col·lisiona. Camp `height` declarat per coherència
|
||||
# amb la resta d'entitats; no té efecte mentre `collides: false`.
|
||||
height: 0
|
||||
flags:
|
||||
collides: false
|
||||
interactable: false
|
||||
|
||||
@@ -4,6 +4,11 @@ sprite:
|
||||
path: wall_trap.gif
|
||||
size: [16, 16]
|
||||
tile_size: [1, 1]
|
||||
collision:
|
||||
# Trampa de paret: ix d'una vora alta i no es pot saltar per damunt.
|
||||
# 999 = infranquejable. Igual que floor_trap, el filtre només té efecte
|
||||
# quan la detecció de contacte hi entre.
|
||||
height: 999
|
||||
flags:
|
||||
collides: false
|
||||
interactable: false
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -103,6 +103,17 @@ namespace {
|
||||
auto* b = ps[1];
|
||||
if (a == nullptr || b == nullptr) { return; }
|
||||
|
||||
// Filtre per altura: si un dels dos està en l'aire per damunt
|
||||
// del cos de l'altre, no hi ha contacte possible — el saltador
|
||||
// sobrevola. Saltar fa de "moviment lliure" per al resolver.
|
||||
// `body_height` és estàtic carregat des del YAML; cppcheck el llig
|
||||
// com a sempre-zero perquè no veu la inicialització en runtime.
|
||||
const auto BODY_H = static_cast<float>(Player::body_height);
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
if (BODY_H > 0.0F && a->isAirborne() && a->getJumpZ() > BODY_H) { return; }
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
if (BODY_H > 0.0F && b->isAirborne() && b->getJumpZ() > BODY_H) { return; }
|
||||
|
||||
// Calculem l'overlap previst en l'espai de la hit-box petita:
|
||||
// els jugadors han de poder acostar-se molt (caixes de moviment
|
||||
// 16×16 quasi tocant) sense disparar el resolver; només quan el
|
||||
|
||||
Reference in new issue
Block a user