refactor(player): divideix update en tick+applyMovement i exposa velocitat intencionada
This commit is contained in:
1 parent
820b9f2d21
commit
1cc2e28b1b
2 files changed
+88
-23
No files matched your search
@@ -241,6 +241,21 @@ Player::Player(const std::string& anim_key, int id, float x, float y, const Cont
|
||||
}
|
||||
|
||||
void Player::update(float delta_time) {
|
||||
tick(delta_time);
|
||||
applyMovement(delta_time);
|
||||
}
|
||||
|
||||
auto Player::collidesWithWorld(const SDL_FRect& box) const -> bool {
|
||||
if (map_.collides(box)) { return true; }
|
||||
if (all_players_ == nullptr) { return false; }
|
||||
return std::ranges::any_of(*all_players_, [this, &box](const Player* p) {
|
||||
if (p == nullptr || p == this) { return false; }
|
||||
const auto OTHER = p->getBaseBox();
|
||||
return SDL_HasRectIntersectionFloat(&box, &OTHER);
|
||||
});
|
||||
}
|
||||
|
||||
void Player::tick(float delta_time) {
|
||||
// Via `InputState` en lloc de `SDL_GetKeyboardState` directe: quan el
|
||||
// service menu (o qualsevol UI modal) està obert, el wrapper retorna
|
||||
// un buffer tot-a-zero perquè el jugador no es mogui mentre l'usuari
|
||||
@@ -253,31 +268,32 @@ void Player::update(float delta_time) {
|
||||
if (keys[controls_.up]) { vy -= SPEED_PX_PER_S; }
|
||||
if (keys[controls_.down]) { vy += SPEED_PX_PER_S; }
|
||||
|
||||
// Cruceta i stick esquerre del primer gamepad (si n'hi ha). També
|
||||
// Cruceta i stick esquerre del mando assignat (si n'hi ha). També
|
||||
// respecten el bloqueig d'`InputState`, així que amb el menú obert
|
||||
// els botons tornen false/0 i no contribueixen a la velocitat.
|
||||
// Direccions oposades (teclat left + d-pad right) es cancel·len de
|
||||
// forma natural; premuda simultània en la mateixa direcció no duplica
|
||||
// la velocitat perquè cada branca només resta/suma una vegada.
|
||||
accumulateGamepadMovement(id_, vx, vy);
|
||||
|
||||
intended_vx_ = vx;
|
||||
intended_vy_ = vy;
|
||||
setVelX(vx);
|
||||
setVelY(vy);
|
||||
|
||||
updateFacingAndTurn(delta_time, vx, vy);
|
||||
}
|
||||
|
||||
void Player::applyMovement(float delta_time) {
|
||||
// El resolver del GameScene pot haver escalat `intended_vx_/vy_`
|
||||
// (cas push: velocitat * push_factor) o haver-les anul·lat (cas
|
||||
// head-on). Consumim-les aquí com a velocitat definitiva del frame.
|
||||
const float VX = intended_vx_;
|
||||
const float VY = intended_vy_;
|
||||
|
||||
// Snap al passadís: si només hi ha moviment horitzontal (o vertical) i
|
||||
// la caixa està prou prop d'una lane perpendicular (≤ mitja caixa), la
|
||||
// Y (o X) es corregeix gradualment per enfilar el passadís. Amb
|
||||
// moviment diagonal no es fa snap — l'usuari pilota manualment.
|
||||
const bool ONLY_H = (vx != 0.0F) && (vy == 0.0F);
|
||||
const bool ONLY_V = (vy != 0.0F) && (vx == 0.0F);
|
||||
// Y (o X) es corregeix gradualment per enfilar el passadís.
|
||||
const bool ONLY_H = (VX != 0.0F) && (VY == 0.0F);
|
||||
const bool ONLY_V = (VY != 0.0F) && (VX == 0.0F);
|
||||
|
||||
// Pos d'inici de frame (abans de snap i integració): si en acabar el
|
||||
// frame coincideix amb la posició final, el jugador no s'ha mogut
|
||||
// realment (ni per input, ni per snap, ni ha superat la col·lisió) i cal
|
||||
// forçar l'animació `stand` corresponent al facing — així s'atura el
|
||||
// caminat quan xoca contra un mur o un sarcòfag.
|
||||
const float FRAME_START_X = getPosX();
|
||||
const float FRAME_START_Y = getPosY();
|
||||
|
||||
@@ -288,20 +304,19 @@ void Player::update(float delta_time) {
|
||||
}
|
||||
|
||||
// Animació del frame + moviment col·lisió-aware. `tryMove` aplica els
|
||||
// deltes per eix i reverteix l'eix que col·lisiona, així el jugador
|
||||
// llisca contra sarcòfags i murs en compte de quedar-se bloquejat en
|
||||
// sec. La caixa que s'envia al callback és `collisionBox()` (override
|
||||
// a Player retornant `getBaseBox()`), no el sprite sencer.
|
||||
// deltes per eix i reverteix l'eix que col·lisiona; així el jugador
|
||||
// llisca contra sarcòfags, murs i altres jugadors en compte de quedar
|
||||
// bloquejat en sec. La callback tracta els altres jugadors com a
|
||||
// sòlids per evitar overlaps (el resolver del GameScene deixa les
|
||||
// velocitats "segures", i aquesta comprovació és la xarxa de
|
||||
// seguretat davant de casos d'ordre d'aplicació).
|
||||
animate(delta_time);
|
||||
tryMove(vx * delta_time, vy * delta_time, [this](const SDL_FRect& box) { return map_.collides(box); });
|
||||
tryMove(VX * delta_time, VY * delta_time, [this](const SDL_FRect& box) {
|
||||
return collidesWithWorld(box);
|
||||
});
|
||||
|
||||
clampToPlayfield();
|
||||
|
||||
// Animació basada en moviment real: si la col·lisió (o l'absència
|
||||
// d'input) ha deixat el jugador al mateix lloc que al principi del
|
||||
// frame, queda en `stand` del facing actual. Durant un gir de 180°
|
||||
// forcem també `stand` encara que el jugador s'estiga movent — la
|
||||
// pose intermèdia és sempre estàtica per disseny.
|
||||
const bool MOVED = (getPosX() != FRAME_START_X) || (getPosY() != FRAME_START_Y);
|
||||
const bool ANIM_AS_MOVING = MOVED && (turning_remaining_ms_ <= 0.0F);
|
||||
setCurrentAnimation(animationName(ANIM_AS_MOVING));
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "core/rendering/sprite/animated_sprite.hpp"
|
||||
#include "game/map/map.hpp"
|
||||
@@ -44,14 +45,48 @@ class Player : public AnimatedSprite {
|
||||
|
||||
Player(const std::string& anim_key, int id, float x, float y, const Controls& controls, const Map::Info& map_info, int paint_tile_index = -1);
|
||||
|
||||
// Update en una sola crida: manté l'API anterior per als tests i
|
||||
// escenaris amb un sol jugador. Internament és equivalent a cridar
|
||||
// `tick(dt)` seguit d'`applyMovement(dt)`.
|
||||
void update(float delta_time) override;
|
||||
|
||||
// Fase 1: llig l'input d'aquest frame i emmagatzema la velocitat
|
||||
// intencionada a `intended_vx_` / `intended_vy_`. No mou el jugador.
|
||||
// Separada d'`applyMovement` perquè el GameScene puga resoldre
|
||||
// interaccions entre jugadors (empentes, neutralitzacions head-on)
|
||||
// abans que ningú consume la posició.
|
||||
void tick(float delta_time);
|
||||
|
||||
// Fase 2: consumeix `intended_vx_`/`intended_vy_` (possiblement
|
||||
// modificats pel resolver), crida `tryMove` contra el mapa i els
|
||||
// altres jugadors, fa snap al passadís, clamp al playfield, avança
|
||||
// l'animació i publica `on_tile_entered` si ha canviat de tile.
|
||||
void applyMovement(float delta_time);
|
||||
|
||||
// Identificador local (0 = P1, 1 = P2…). S'usa als events del bus
|
||||
// perquè els consumidors (HUD, futures regles de puntuació) sàpiguen
|
||||
// quin jugador ha originat l'event. No té cap vinculació amb la
|
||||
// xarxa — és únic per dins del procés.
|
||||
[[nodiscard]] auto getId() const -> int { return id_; }
|
||||
|
||||
// Accés i mutació de la velocitat intencionada d'aquest frame. El
|
||||
// GameScene els toca entre `tick` i `applyMovement` per implementar
|
||||
// col·lisions jugador↔jugador (escalar a push_factor, anul·lar en
|
||||
// head-on). Expressades en píxels/segon, no en píxels/frame.
|
||||
[[nodiscard]] auto getIntendedVX() const -> float { return intended_vx_; }
|
||||
[[nodiscard]] auto getIntendedVY() const -> float { return intended_vy_; }
|
||||
void setIntendedVelocity(float vx, float vy) {
|
||||
intended_vx_ = vx;
|
||||
intended_vy_ = vy;
|
||||
}
|
||||
|
||||
// Llista de tots els jugadors del nivell (incloent-se ell mateix —
|
||||
// la callback ignora `this`). La caixa del jugador es considera un
|
||||
// sòlid a la callback de col·lisió: així `tryMove` no permet que un
|
||||
// jugador travesse l'altre, i els desplaçaments parcials (push
|
||||
// contra mur) s'emparen automàticament.
|
||||
void setOtherPlayers(const std::vector<Player*>* all) { all_players_ = all; }
|
||||
|
||||
// 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 sota → davant.
|
||||
@@ -87,6 +122,11 @@ class Player : public AnimatedSprite {
|
||||
// (snapToLane, clamp). Els consumidors externs usen `getBaseBox()`.
|
||||
[[nodiscard]] auto baseBox() const -> SDL_FRect { return getBaseBox(); }
|
||||
|
||||
// Callback de col·lisió per a `tryMove`: true si la caixa proposada
|
||||
// xoca amb el mapa o amb un altre jugador. Membre per mantenir la
|
||||
// complexitat cognitiva d'`applyMovement` sota el llindar.
|
||||
[[nodiscard]] auto collidesWithWorld(const SDL_FRect& box) const -> bool;
|
||||
|
||||
// Clamp de la caixa de col·lisió al playfield (no al sprite sencer:
|
||||
// el cap pot sobresortir pel mur superior sense afectar el joc).
|
||||
void clampToPlayfield();
|
||||
@@ -112,6 +152,16 @@ class Player : public AnimatedSprite {
|
||||
|
||||
int id_{-1};
|
||||
Controls controls_;
|
||||
|
||||
// Velocitat intencionada calculada a `tick` i aplicada a
|
||||
// `applyMovement`. El GameScene pot sobreescriure-la entremig per
|
||||
// resoldre interaccions jugador↔jugador (empentes, head-on).
|
||||
float intended_vx_{0.0F};
|
||||
float intended_vy_{0.0F};
|
||||
|
||||
// Punter a la llista de tots els jugadors del nivell (propietat del
|
||||
// GameScene). `nullptr` = jugador aïllat, sense col·lisió inter-P.
|
||||
const std::vector<Player*>* all_players_{nullptr};
|
||||
// Còpia plana de la geometria del mapa + punters a `collides`/
|
||||
// `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
|
||||
|
||||
Reference in new issue
Block a user