feat(player): saltar travessa el caminant; rebot mid-aire entre dos saltadors
This commit is contained in:
1 parent
3646e6a4aa
commit
188f035333
4 files changed
+78
-32
No files matched your search
+11
-6
@@ -15,11 +15,16 @@ collision:
|
||||
# `size` es reserva per a la col·lisió amb murs/sarcòfags i per enfilar
|
||||
# els passadissos. Centrada en el mateix punt que la caixa base.
|
||||
hit_size: 8
|
||||
# Alçada del cos en píxels per al filtre de col·lisió per altura: un
|
||||
# actor saltant amb jump_z > height passa per damunt sense col·lisió.
|
||||
# Mateix camp i semàntica que als YAML d'entitats (mummy, rolling_rock,
|
||||
# etc.) — així el filtre queda uniforme per a tots els colls·lisionables.
|
||||
height: 24
|
||||
# Alçada del cos en píxels. Funció DOBLE segons amb què col·lisione:
|
||||
# · Contra entitats (mummy, rolling_rock, sarc): si `jump_z > height`
|
||||
# d'aquella entitat, no hi ha col·lisió. Igual que als YAMLs
|
||||
# d'entitats.
|
||||
# · Contra altres jugadors: la col·lisió és binària per estat aeri.
|
||||
# Si un està en l'aire i l'altre no → cap col·lisió. Si tots dos
|
||||
# estan en l'aire, llavors `body_height` decideix si els rangs
|
||||
# verticals solapen i s'aplica el rebot mid-aire. `0` = sense cos
|
||||
# (ni rebot quan tots dos volen).
|
||||
height: 12
|
||||
|
||||
# Animacions en mapa (clau = nom). Camps per animació:
|
||||
# frames → llista d'índexs dins de l'spritesheet.
|
||||
@@ -92,7 +97,7 @@ turn:
|
||||
# d'entitats: és una propietat de col·lisió, no de la física del salt.
|
||||
# Amb impulse=240 i gravity=600: pic ≈ 48 px, temps total en l'aire ≈ 0.8 s.
|
||||
jump:
|
||||
impulse: 240
|
||||
impulse: 200
|
||||
gravity: 600
|
||||
landing_stun_ms: 200
|
||||
shadow_y_offset: 3
|
||||
|
||||
+11
-6
@@ -15,11 +15,16 @@ collision:
|
||||
# `size` es reserva per a la col·lisió amb murs/sarcòfags i per enfilar
|
||||
# els passadissos. Centrada en el mateix punt que la caixa base.
|
||||
hit_size: 8
|
||||
# Alçada del cos en píxels per al filtre de col·lisió per altura: un
|
||||
# actor saltant amb jump_z > height passa per damunt sense col·lisió.
|
||||
# Mateix camp i semàntica que als YAML d'entitats (mummy, rolling_rock,
|
||||
# etc.) — així el filtre queda uniforme per a tots els colls·lisionables.
|
||||
height: 24
|
||||
# Alçada del cos en píxels. Funció DOBLE segons amb què col·lisione:
|
||||
# · Contra entitats (mummy, rolling_rock, sarc): si `jump_z > height`
|
||||
# d'aquella entitat, no hi ha col·lisió. Igual que als YAMLs
|
||||
# d'entitats.
|
||||
# · Contra altres jugadors: la col·lisió és binària per estat aeri.
|
||||
# Si un està en l'aire i l'altre no → cap col·lisió. Si tots dos
|
||||
# estan en l'aire, llavors `body_height` decideix si els rangs
|
||||
# verticals solapen i s'aplica el rebot mid-aire. `0` = sense cos
|
||||
# (ni rebot quan tots dos volen).
|
||||
height: 12
|
||||
|
||||
# Animacions en mapa (clau = nom). Camps per animació:
|
||||
# frames → llista d'índexs dins de l'spritesheet.
|
||||
@@ -92,7 +97,7 @@ turn:
|
||||
# d'entitats: és una propietat de col·lisió, no de la física del salt.
|
||||
# Amb impulse=240 i gravity=600: pic ≈ 48 px, temps total en l'aire ≈ 0.8 s.
|
||||
jump:
|
||||
impulse: 240
|
||||
impulse: 200
|
||||
gravity: 600
|
||||
landing_stun_ms: 200
|
||||
shadow_y_offset: 3
|
||||
|
||||
@@ -322,17 +322,27 @@ auto Player::collidesWithWorld(const SDL_FRect& box) const -> bool {
|
||||
// (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.
|
||||
// Regla de col·lisió jugador↔jugador (segons el feel triat):
|
||||
// - Tots dos a terra → col·lisió normal.
|
||||
// - Un en l'aire i l'altre no → SENSE col·lisió. El saltador es
|
||||
// travessa net del caminant; ningú actua de "muro" durant el
|
||||
// vol, ni a la pujada ni a la baixada.
|
||||
// - Tots dos en l'aire → col·lisió només si els rangs verticals
|
||||
// [jump_z, jump_z + body_height] solapen. Així permetem el xoc
|
||||
// mid-aire quan tots dos van un contra l'altre i estan a la
|
||||
// mateixa franja d'alçada.
|
||||
const SDL_FRect PROPOSED_HIT = hitBoxFromBase(box);
|
||||
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) {
|
||||
const bool MY_AIR = (state_ == State::AIRBORNE);
|
||||
const float MY_Z = jump_z_;
|
||||
return std::ranges::any_of(*all_players_, [this, &PROPOSED_HIT, MY_AIR, MY_Z](const Player* p) {
|
||||
if (p == nullptr || p == this) { return false; }
|
||||
if (AIRBORNE && body_height > 0 && Z > static_cast<float>(body_height)) { return false; }
|
||||
if (MY_AIR != p->isAirborne()) { return false; }
|
||||
// Aquí els dos estats coincideixen. Si tots dos volen, el filtre
|
||||
// per altura mira que els rangs verticals encara solapin; si no,
|
||||
// estan a prou distància en Y "real" i no es toquen.
|
||||
if (MY_AIR && body_height > 0 && std::fabs(MY_Z - p->jump_z_) >= static_cast<float>(body_height)) {
|
||||
return false;
|
||||
}
|
||||
const auto OTHER = p->getHitBox();
|
||||
return SDL_HasRectIntersectionFloat(&PROPOSED_HIT, &OTHER);
|
||||
});
|
||||
|
||||
@@ -103,22 +103,22 @@ 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; }
|
||||
const bool A_AIR = a->isAirborne();
|
||||
const bool B_AIR = b->isAirborne();
|
||||
|
||||
// Aire ↔ tierra: el saltador es travessa net del caminant. Sense
|
||||
// resolver, sense head-on, sense push. Els dos continuen amb la
|
||||
// velocitat que portaven. Important: això NO és el filtre per
|
||||
// altura del cos — és binari. Coincideix amb la regla d'`Player::
|
||||
// collidesWithWorld`: durant un salt no hi ha mai col·lisió amb
|
||||
// un caminant, ni baix ni alt.
|
||||
if (A_AIR != B_AIR) { 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
|
||||
// contacte real (hit-box 8×8) s'activaria, neutralitzem o apliquem
|
||||
// push.
|
||||
// push (a terra) o invertim velocitats (en l'aire).
|
||||
const SDL_FRect BOX_A = a->getHitBox();
|
||||
const SDL_FRect BOX_B = b->getHitBox();
|
||||
const SDL_FRect NEXT_A{
|
||||
@@ -135,6 +135,32 @@ namespace {
|
||||
};
|
||||
if (!rectsOverlap(NEXT_A, NEXT_B)) { return; }
|
||||
|
||||
// Tots dos en l'aire (A_AIR == B_AIR == true aquí, ja descartat
|
||||
// l'altre cas a dalt): rebot mid-aire si els rangs verticals
|
||||
// [z, z+body_height] solapen. Si la diferència d'alçades és
|
||||
// massa gran, ja no hi ha contacte real (un està molt més amunt
|
||||
// que l'altre) i no fem res. Si solapen, invertim ambdues
|
||||
// velocitats horitzontals i els deixem rebotar fins a aterrar
|
||||
// — els dos cauen en stun, com a càstig per abusar del salt.
|
||||
if (A_AIR) {
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
const auto BODY_H = static_cast<float>(Player::body_height);
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
if (BODY_H <= 0.0F) { return; }
|
||||
if (std::fabs(a->getJumpZ() - b->getJumpZ()) >= BODY_H) { return; }
|
||||
// Saltadors estàtics horitzontalment (cas degenerat: tots dos
|
||||
// jumpejant verticals al mateix x): no hi ha velocitat a
|
||||
// invertir, deixem-los — el predictiu no tornarà a disparar
|
||||
// perquè en el següent frame ja no hi haurà velocitat
|
||||
// d'acostament.
|
||||
const float REL_VX = a->getIntendedVX() - b->getIntendedVX();
|
||||
const float REL_VY = a->getIntendedVY() - b->getIntendedVY();
|
||||
if (REL_VX == 0.0F && REL_VY == 0.0F) { return; }
|
||||
a->setIntendedVelocity(-a->getIntendedVX(), -a->getIntendedVY());
|
||||
b->setIntendedVelocity(-b->getIntendedVX(), -b->getIntendedVY());
|
||||
return;
|
||||
}
|
||||
|
||||
const bool MOVING_A = (a->getInputVX() != 0.0F) || (a->getInputVY() != 0.0F);
|
||||
const bool MOVING_B = (b->getInputVX() != 0.0F) || (b->getInputVY() != 0.0F);
|
||||
|
||||
|
||||
Reference in new issue
Block a user