fix(player): snapToLane respecta col·lisió PvP i empeny amb push_factor
This commit is contained in:
1 parent
1af43ef052
commit
4f1a3260a5
3 files changed
+107
-4
No files matched your search
@@ -531,12 +531,87 @@ void Player::snapToLane(
|
|||||||
const float LANE = base + (static_cast<float>(IDX) * pitch);
|
const float LANE = base + (static_cast<float>(IDX) * pitch);
|
||||||
const float DELTA = LANE - (this->*get_pos)();
|
const float DELTA = LANE - (this->*get_pos)();
|
||||||
|
|
||||||
if (std::abs(DELTA) > 0.0F && std::abs(DELTA) <= THRESHOLD) {
|
if (std::abs(DELTA) <= 0.0F || std::abs(DELTA) > THRESHOLD) { return; }
|
||||||
const float STEP = std::min(std::abs(DELTA), SPEED_PX_PER_S * delta_time);
|
|
||||||
|
const float FULL_STEP = std::min(std::abs(DELTA), SPEED_PX_PER_S * delta_time);
|
||||||
const float SAVED = (this->*get_pos)();
|
const float SAVED = (this->*get_pos)();
|
||||||
(this->*set_pos)(SAVED + std::copysign(STEP, DELTA));
|
|
||||||
if (map_.collides(baseBox())) { (this->*set_pos)(SAVED); }
|
// Fase 1: snap a velocitat plena. Si no xoca amb res, commit — és el
|
||||||
|
// cas habitual (carril lliure) i no ha de patir la friccó de l'empenta.
|
||||||
|
(this->*set_pos)(SAVED + std::copysign(FULL_STEP, DELTA));
|
||||||
|
if (!collidesWithWorld(baseBox())) { return; }
|
||||||
|
|
||||||
|
// Mur al carril objectiu: no hi ha ningú a qui empentar, revert directe.
|
||||||
|
if (map_.collides(baseBox())) {
|
||||||
|
(this->*set_pos)(SAVED);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fase 2: sols un altre jugador al carril. Reintentem amb step escalat
|
||||||
|
// per `push_factor_` (mateixa friccó que el push horitzontal del
|
||||||
|
// resolver) i mirem d'arrossegar els blockers. Si la pushada no cap
|
||||||
|
// (contra mur o tercer jugador), el snap es revertix sencer.
|
||||||
|
const float PUSH_STEP = std::min(std::abs(DELTA), SPEED_PX_PER_S * push_factor_ * delta_time);
|
||||||
|
const float SIGNED_PUSH = std::copysign(PUSH_STEP, DELTA);
|
||||||
|
(this->*set_pos)(SAVED + SIGNED_PUSH);
|
||||||
|
if (collidesWithWorld(baseBox())) {
|
||||||
|
if (map_.collides(baseBox()) || !tryPushSnapBlockers(SIGNED_PUSH, get_pos, set_pos)) {
|
||||||
|
(this->*set_pos)(SAVED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Player::tryPushSnapBlockers(
|
||||||
|
float signed_step,
|
||||||
|
float (Player::*get_pos)() const,
|
||||||
|
void (Player::*set_pos)(float)) -> bool {
|
||||||
|
if (all_players_ == nullptr) { return false; }
|
||||||
|
|
||||||
|
// Blockers = jugadors que ara mateix solapen la nostra hit-box (ja
|
||||||
|
// amb el snap aplicat). Mateix criteri actor-actor que `collidesWithWorld`.
|
||||||
|
std::vector<Player*> blockers;
|
||||||
|
const SDL_FRect MY_HIT = getHitBox();
|
||||||
|
for (Player* p : *all_players_) {
|
||||||
|
if (p == nullptr || p == this) { continue; }
|
||||||
|
const SDL_FRect OTHER_HIT = p->getHitBox();
|
||||||
|
if (SDL_HasRectIntersectionFloat(&MY_HIT, &OTHER_HIT)) {
|
||||||
|
blockers.push_back(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (blockers.empty()) { return false; }
|
||||||
|
|
||||||
|
// Desem la posició original de cada blocker i el desplacem el mateix
|
||||||
|
// step que s'ha aplicat a `this`: com que els dos es mouen igual en el
|
||||||
|
// mateix eix, la distància relativa es conserva i, per tant, si abans
|
||||||
|
// del snap no es solapaven, després de l'empenta tampoc.
|
||||||
|
std::vector<float> saved;
|
||||||
|
saved.reserve(blockers.size());
|
||||||
|
for (Player* p : blockers) {
|
||||||
|
const float S = (p->*get_pos)();
|
||||||
|
saved.push_back(S);
|
||||||
|
(p->*set_pos)(S + signed_step);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validació: `this` ha de quedar net (ni mur ni cap jugador), i cada
|
||||||
|
// blocker també (cobreix el cas de 3+ jugadors on un blocker podria
|
||||||
|
// acabar dins d'un tercer o contra un mur).
|
||||||
|
const bool OK = !collidesWithWorld(baseBox()) && std::ranges::none_of(blockers, [](const Player* p) {
|
||||||
|
return p->collidesWithWorld(p->getBaseBox());
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!OK) {
|
||||||
|
for (size_t i = 0; i < blockers.size(); ++i) {
|
||||||
|
(blockers[i]->*set_pos)(saved[i]);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Els blockers s'han desplaçat sense input propi: mateixa convenció
|
||||||
|
// d'animació que el push horitzontal del resolver (stand_*, no walk).
|
||||||
|
for (Player* p : blockers) {
|
||||||
|
p->setBeingPushed(true);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::snapToHorizontalLane(float delta_time) {
|
void Player::snapToHorizontalLane(float delta_time) {
|
||||||
|
|||||||
@@ -117,6 +117,14 @@ class Player : public AnimatedSprite {
|
|||||||
// posar cada frame que el push s'haja de mantenir.
|
// posar cada frame que el push s'haja de mantenir.
|
||||||
void setBeingPushed(bool pushed) { being_pushed_ = pushed; }
|
void setBeingPushed(bool pushed) { being_pushed_ = pushed; }
|
||||||
|
|
||||||
|
// Factor d'escala d'empenta jugador↔jugador (viu al YAML del mapa).
|
||||||
|
// L'usa el resolver de GameScene per al push horitzontal, i també
|
||||||
|
// `snapToLane` quan descobreix que un altre jugador li bloqueja el
|
||||||
|
// carril objectiu: per coherència, la pushada del snap va al mateix
|
||||||
|
// ritme que l'horitzontal. Si no es crida, el valor per defecte 1.0
|
||||||
|
// manté el snap a velocitat plena (comportament pre-multiplayer).
|
||||||
|
void setPushFactor(float factor) { push_factor_ = factor; }
|
||||||
|
|
||||||
// Y de la base del jugador (cara inferior de la caixa de colisió). Usada
|
// Y de la base del jugador (cara inferior de la caixa de colisió). Usada
|
||||||
// per al Z-order contra els sarcs: peus per sobre d'un sarc → darrere;
|
// per al Z-order contra els sarcs: peus per sobre d'un sarc → darrere;
|
||||||
// per sota → davant.
|
// per sota → davant.
|
||||||
@@ -191,6 +199,18 @@ class Player : public AnimatedSprite {
|
|||||||
float (Player::*get_pos)() const,
|
float (Player::*get_pos)() const,
|
||||||
void (Player::*set_pos)(float));
|
void (Player::*set_pos)(float));
|
||||||
|
|
||||||
|
// Quan el snap lateral trobaria un altre jugador al carril objectiu,
|
||||||
|
// l'empentem el mateix step en el mateix eix. Així `this` pot entrar
|
||||||
|
// al passadís encara que l'altre estiga estacionat al mitj — mateix
|
||||||
|
// contracte que el push horitzontal però disparat pel snap. Retorna
|
||||||
|
// true si la push ha quedat neta (ningú xoca amb el mapa ni amb un
|
||||||
|
// tercer jugador); false fa que el caller revertisca el propi snap.
|
||||||
|
// Marca els blockers amb `being_pushed_` per a la seua animació.
|
||||||
|
auto tryPushSnapBlockers(
|
||||||
|
float signed_step,
|
||||||
|
float (Player::*get_pos)() const,
|
||||||
|
void (Player::*set_pos)(float)) -> bool;
|
||||||
|
|
||||||
int id_{-1};
|
int id_{-1};
|
||||||
Controls controls_;
|
Controls controls_;
|
||||||
|
|
||||||
@@ -225,6 +245,11 @@ class Player : public AnimatedSprite {
|
|||||||
// Serveix només per a l'elecció d'animació — la posició ja la
|
// Serveix només per a l'elecció d'animació — la posició ja la
|
||||||
// gestiona `intended_vx_/vy_` escalats pel resolver.
|
// gestiona `intended_vx_/vy_` escalats pel resolver.
|
||||||
bool being_pushed_{false};
|
bool being_pushed_{false};
|
||||||
|
|
||||||
|
// Escala d'empenta usada per `snapToLane` quan descobreix un blocker
|
||||||
|
// (mateixa del resolver). 1.0 = sense escalar (comportament si
|
||||||
|
// GameScene no ha cridat setPushFactor).
|
||||||
|
float push_factor_{1.0F};
|
||||||
// Còpia plana de la geometria del mapa + punters a `collides`/
|
// Còpia plana de la geometria del mapa + punters a `collides`/
|
||||||
// `inside_playfield`. POD trivial; copiada al ctor i no muta durant
|
// `inside_playfield`. POD trivial; copiada al ctor i no muta durant
|
||||||
// la vida del Player. Vol dir que aquest Player només és vàlid per
|
// la vida del Player. Vol dir que aquest Player només és vàlid per
|
||||||
|
|||||||
@@ -303,6 +303,9 @@ GameScene::GameScene(Config config)
|
|||||||
players_view_.reserve(players_.size());
|
players_view_.reserve(players_.size());
|
||||||
std::ranges::transform(players_, std::back_inserter(players_view_), [](const auto& p) { return p.get(); });
|
std::ranges::transform(players_, std::back_inserter(players_view_), [](const auto& p) { return p.get(); });
|
||||||
for (auto& p : players_) { p->setOtherPlayers(&players_view_); }
|
for (auto& p : players_) { p->setOtherPlayers(&players_view_); }
|
||||||
|
// El snap també empeny lateralment si l'altre jugador ocupa el carril:
|
||||||
|
// passem el mateix factor perquè vaja al mateix ritme que el resolver.
|
||||||
|
for (auto& p : players_) { p->setPushFactor(push_factor_); }
|
||||||
|
|
||||||
// Marcador (HUD) de la franja superior. Reutilitza la font 8bithud i
|
// Marcador (HUD) de la franja superior. Reutilitza la font 8bithud i
|
||||||
// la paleta "ui" ja precarregades pel Director — no toca disc des
|
// la paleta "ui" ja precarregades pel Director — no toca disc des
|
||||||
|
|||||||
Reference in new issue
Block a user