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
|
path: door.gif
|
||||||
size: [48, 32]
|
size: [48, 32]
|
||||||
tile_size: [3, 2]
|
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:
|
flags:
|
||||||
collides: true
|
collides: true
|
||||||
interactable: true
|
interactable: true
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ sprite:
|
|||||||
path: floor_trap.gif
|
path: floor_trap.gif
|
||||||
size: [16, 16]
|
size: [16, 16]
|
||||||
tile_size: [1, 1]
|
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:
|
flags:
|
||||||
collides: false
|
collides: false
|
||||||
interactable: false
|
interactable: false
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ anchor: feet
|
|||||||
collision:
|
collision:
|
||||||
size: [16, 16]
|
size: [16, 16]
|
||||||
base_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:
|
flags:
|
||||||
collides: true
|
collides: true
|
||||||
interactable: false
|
interactable: false
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ sprite:
|
|||||||
path: rolling_rock.gif
|
path: rolling_rock.gif
|
||||||
size: [16, 16]
|
size: [16, 16]
|
||||||
tile_size: [1, 1]
|
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:
|
flags:
|
||||||
collides: true
|
collides: true
|
||||||
interactable: false
|
interactable: false
|
||||||
|
|||||||
@@ -7,6 +7,13 @@ sprite:
|
|||||||
# són "alçada" en falsa perspectiva, sense col·lisió. Perímetre ortogonal
|
# són "alçada" en falsa perspectiva, sense col·lisió. Perímetre ortogonal
|
||||||
# de 8 tiles (3 N + 3 S + 1 O + 1 E).
|
# de 8 tiles (3 N + 3 S + 1 O + 1 E).
|
||||||
tile_size: [3, 1]
|
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:
|
flags:
|
||||||
collides: true
|
collides: true
|
||||||
interactable: true
|
interactable: true
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ sprite:
|
|||||||
path: torch.gif
|
path: torch.gif
|
||||||
size: [16, 16]
|
size: [16, 16]
|
||||||
tile_size: [1, 1]
|
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:
|
flags:
|
||||||
collides: false
|
collides: false
|
||||||
interactable: false
|
interactable: false
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ sprite:
|
|||||||
path: wall_trap.gif
|
path: wall_trap.gif
|
||||||
size: [16, 16]
|
size: [16, 16]
|
||||||
tile_size: [1, 1]
|
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:
|
flags:
|
||||||
collides: false
|
collides: false
|
||||||
interactable: false
|
interactable: false
|
||||||
|
|||||||
@@ -113,6 +113,17 @@ class Entity {
|
|||||||
// Entitats sense animació retornen `nullopt` → frame 0 per defecte.
|
// Entitats sense animació retornen `nullopt` → frame 0 per defecte.
|
||||||
[[nodiscard]] auto getAnimation() const -> const std::optional<AnimationComponent>& { return anim_; }
|
[[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
|
// Caixa de col·lisió: per defecte és el `pixel_rect_` sencer (entitats
|
||||||
// AABB-aligned). Si s'ha configurat un `CollisionBox`, retorna el
|
// AABB-aligned). Si s'ha configurat un `CollisionBox`, retorna el
|
||||||
// sub-rect dins del sprite — vida útil: validar col·lisions amb 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};
|
std::shared_ptr<Surface> surface_{nullptr};
|
||||||
bool collides_{false};
|
bool collides_{false};
|
||||||
bool interactable_{false};
|
bool interactable_{false};
|
||||||
|
int height_{0};
|
||||||
SDL_FlipMode flip_{SDL_FLIP_NONE};
|
SDL_FlipMode flip_{SDL_FLIP_NONE};
|
||||||
EventBus* bus_{nullptr};
|
EventBus* bus_{nullptr};
|
||||||
};
|
};
|
||||||
@@ -63,8 +63,15 @@ void EntityManager::checkPlayerCollisions(const std::vector<Player*>& players) {
|
|||||||
for (const auto& entity : entities_) {
|
for (const auto& entity : entities_) {
|
||||||
if (!entity->collides()) { continue; }
|
if (!entity->collides()) { continue; }
|
||||||
const auto BOX = entity->getCollisionRect();
|
const auto BOX = entity->getCollisionRect();
|
||||||
|
const int E_HEIGHT = entity->getHeight();
|
||||||
for (auto* p : players) {
|
for (auto* p : players) {
|
||||||
if (p == nullptr) { continue; }
|
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
|
// Contacte actor-actor amb hit-box petita: la caixa base
|
||||||
// 16×16 servia per moure (xoc amb murs), però per a l'event
|
// 16×16 servia per moure (xoc amb murs), però per a l'event
|
||||||
// "toca una entitat" volem precisió — que la momia mate quan
|
// "toca una entitat" volem precisió — que la momia mate quan
|
||||||
@@ -109,6 +116,18 @@ namespace {
|
|||||||
return info;
|
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]? }`
|
// Bloc `collision: { size: [w, h], base_size: [bw, bh]?, offset: [x, y]? }`
|
||||||
// de la plantilla d'entitat.
|
// de la plantilla d'entitat.
|
||||||
// - Sense `base_size` ni `offset`: la caja queda alineada a la vora
|
// - 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()) {
|
if (const auto BOX = readCollisionBox(tmpl, sprite_out.width, sprite_out.height); BOX.has_value()) {
|
||||||
entity->setCollisionBox(*BOX);
|
entity->setCollisionBox(*BOX);
|
||||||
}
|
}
|
||||||
|
entity->setHeight(readHeight(tmpl));
|
||||||
anchor_out = readAnchor(tmpl);
|
anchor_out = readAnchor(tmpl);
|
||||||
return entity;
|
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
|
// 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
|
// (16×16) segueix topant amb murs/sarcs però els jugadors poden
|
||||||
// acostar-se fins al contacte real.
|
// 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);
|
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 (p == nullptr || p == this) { return false; }
|
||||||
|
if (AIRBORNE && body_height > 0 && Z > static_cast<float>(body_height)) { return false; }
|
||||||
const auto OTHER = p->getHitBox();
|
const auto OTHER = p->getHitBox();
|
||||||
return SDL_HasRectIntersectionFloat(&PROPOSED_HIT, &OTHER);
|
return SDL_HasRectIntersectionFloat(&PROPOSED_HIT, &OTHER);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -103,6 +103,17 @@ namespace {
|
|||||||
auto* b = ps[1];
|
auto* b = ps[1];
|
||||||
if (a == nullptr || b == nullptr) { return; }
|
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:
|
// Calculem l'overlap previst en l'espai de la hit-box petita:
|
||||||
// els jugadors han de poder acostar-se molt (caixes de moviment
|
// els jugadors han de poder acostar-se molt (caixes de moviment
|
||||||
// 16×16 quasi tocant) sense disparar el resolver; només quan el
|
// 16×16 quasi tocant) sense disparar el resolver; només quan el
|
||||||
|
|||||||
Reference in new issue
Block a user