feat(player): salt vertical amb física, màquina d'estats i animació jump_*
This commit is contained in:
1 parent
91614fc545
commit
7b09a076b2
6 files changed
+313
-5
No files matched your search
@@ -8,6 +8,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "core/rendering/sprite/animated_sprite.hpp"
|
||||
#include "external/fkyaml_node.hpp"
|
||||
#include "game/map/map.hpp"
|
||||
|
||||
class EventBus;
|
||||
@@ -22,6 +23,20 @@ class Player : public AnimatedSprite {
|
||||
SDL_Scancode down;
|
||||
SDL_Scancode left;
|
||||
SDL_Scancode right;
|
||||
SDL_Scancode jump;
|
||||
};
|
||||
|
||||
// Estat d'alt nivell del jugador. Ortogonal a `Facing`/`Cardinal`
|
||||
// (direcció) i a `being_pushed_` (flag d'animació). Determina què pot
|
||||
// fer el jugador aquest frame: GROUNDED accepta tot l'input habitual
|
||||
// i el botó de salt; AIRBORNE ignora canvis de direcció (la velocitat
|
||||
// i el facing queden fixats al moment del despegue, sols la física
|
||||
// del salt mou jump_z_/jump_vz_); LANDING_STUN bloca tot l'input fins
|
||||
// que el timer s'esgota.
|
||||
enum class State : std::uint8_t {
|
||||
GROUNDED,
|
||||
AIRBORNE,
|
||||
LANDING_STUN,
|
||||
};
|
||||
|
||||
// Geometria carregada des del YAML del player (p.ex. `data/player/sam.yaml`).
|
||||
@@ -51,6 +66,11 @@ class Player : public AnimatedSprite {
|
||||
inline static int hit_x_offset = 0;
|
||||
inline static int hit_y_offset = 0;
|
||||
|
||||
// Alçada del cos del jugador en píxels (camp `jump.height` del YAML).
|
||||
// L'usen els filtres de col·lisió per altura: si un altre actor ens
|
||||
// creua mentre `jump_z_` és superior a aquest valor, no col·lisionem.
|
||||
inline static int body_height = 0;
|
||||
|
||||
// Ha de cridar-se abans d'instanciar Player; GameScene ho fa al ctor.
|
||||
static void loadFrom(const std::string& yaml_path);
|
||||
|
||||
@@ -127,11 +147,31 @@ class Player : public AnimatedSprite {
|
||||
|
||||
// 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.
|
||||
// per sota → davant. Mentre el jugador està en l'aire, el feet visual
|
||||
// puja amb `jump_z_` perquè s'ordene davant dels actors a la mateixa
|
||||
// fila lògica.
|
||||
[[nodiscard]] auto getFeetY() const -> float {
|
||||
return getPosY() + static_cast<float>(collision_y_offset + collision_size) - jump_z_;
|
||||
}
|
||||
|
||||
// Y de la base del jugador IGNORANT el salt (sempre la base del cos
|
||||
// al món, no la posició visual). L'usa el render de la sombra perquè
|
||||
// es pinte sempre al sòl, encara que el jugador estigui enlaire.
|
||||
[[nodiscard]] auto getGroundFeetY() const -> float {
|
||||
return getPosY() + static_cast<float>(collision_y_offset + collision_size);
|
||||
}
|
||||
|
||||
// Estat actual de la màquina d'estats. L'usa el render (animació
|
||||
// jump_*), el resolver de col·lisions (filtre per altura) i el
|
||||
// GameScene (per saber si renderitzar la sombra).
|
||||
[[nodiscard]] auto getState() const -> State { return state_; }
|
||||
[[nodiscard]] auto isAirborne() const -> bool { return state_ == State::AIRBORNE; }
|
||||
|
||||
// Alçada actual del salt (píxels visuals per damunt del sòl).
|
||||
// 0 quan està a terra. L'usa el render i el sistema de col·lisions
|
||||
// per altura.
|
||||
[[nodiscard]] auto getJumpZ() const -> float { return jump_z_; }
|
||||
|
||||
// Injecta el bus del nivell. El Player publica `on_tile_entered` cada
|
||||
// vegada que el centre de la seua caixa de col·lisió entra en una
|
||||
// cel·la nova del tilemap, amb payload `PaintedTile{col,row,paint_tile_index_}`.
|
||||
@@ -166,6 +206,12 @@ class Player : public AnimatedSprite {
|
||||
// bloquejar el moviment).
|
||||
[[nodiscard]] auto collisionBox() const -> SDL_FRect override { return getBaseBox(); }
|
||||
|
||||
// Render del jugador. Mentre està en l'aire, el sprite es desplaça
|
||||
// visualment cap amunt `jump_z_` píxels (el cos del jugador, no la
|
||||
// sombra al sòl, que la pinta el GameScene a banda). La col·lisió i
|
||||
// la posició al món no s'alteren.
|
||||
void render() override;
|
||||
|
||||
private:
|
||||
// Alias privat històric. Mantingut perquè viu dins de l'implementació
|
||||
// (snapToLane, clamp). Els consumidors externs usen `getBaseBox()`.
|
||||
@@ -308,6 +354,53 @@ class Player : public AnimatedSprite {
|
||||
// per defecte de TurnConfig encara no són visibles per al compilador.
|
||||
static TurnConfig turn_config;
|
||||
|
||||
// Configuració del salt llegida del bloc `jump:` del YAML del player.
|
||||
// Si `impulse_px_per_s == 0` (o el bloc no existeix) la mecànica del
|
||||
// salt queda desactivada — el botó no té efecte.
|
||||
struct JumpConfig {
|
||||
float impulse_px_per_s{0.0F}; // Velocitat vertical inicial.
|
||||
float gravity_px_per_s2{0.0F}; // Acceleració descendent constant.
|
||||
int landing_stun_ms{0}; // Stun en aterrar (no es mou ni salta).
|
||||
};
|
||||
static JumpConfig jump_config;
|
||||
|
||||
// Llig el bloc `jump:` del YAML i n'omple `jump_config` + `body_height`.
|
||||
// Membre estàtic perquè accedeixi als estàtics privats sense exposar-los.
|
||||
static void readJumpBlock(const fkyaml::node& yaml);
|
||||
|
||||
// Estat de la màquina d'estats. Mutat per `tick`/`applyMovement`. Per
|
||||
// defecte GROUNDED al spawn.
|
||||
State state_{State::GROUNDED};
|
||||
|
||||
// Física del salt (només té sentit en AIRBORNE; ambdós a 0 a terra).
|
||||
// jump_z_ → altura visual del jugador per damunt del sòl, en píxels.
|
||||
// jump_vz_ → velocitat vertical actual (positiva = puja, negativa = cau).
|
||||
float jump_z_{0.0F};
|
||||
float jump_vz_{0.0F};
|
||||
|
||||
// Temps restant de stun d'aterratge (ms). Mentre > 0, l'estat és
|
||||
// LANDING_STUN i tot l'input queda bloquejat.
|
||||
float landing_stun_remaining_ms_{0.0F};
|
||||
|
||||
// Estat anterior del botó de salt per fer detecció de flanc (saltem
|
||||
// només a la transició no-premut → premut, no mentre es manté). Es
|
||||
// manté un per teclat i un per mando perquè poden conviure.
|
||||
bool jump_held_keyboard_{false};
|
||||
bool jump_held_gamepad_{false};
|
||||
|
||||
// Llig el botó de salt aquest frame (teclat + mando assignat) i,
|
||||
// amb edge-detection, retorna true si s'ha de despegar el salt.
|
||||
[[nodiscard]] auto consumeJumpEdge() -> bool;
|
||||
|
||||
// Inicia un salt: copia `intended_v*` com a velocitat fixada del salt,
|
||||
// posa `jump_vz_ = impulse`, transiciona a AIRBORNE.
|
||||
void startJump();
|
||||
|
||||
// Integra la física del salt aquest frame i, si toca el sòl, marca el
|
||||
// canvi a LANDING_STUN. Retorna true si aquest frame ha aterrat (perquè
|
||||
// `applyMovement` puga disparar la resolució del solapament PvP).
|
||||
auto integrateJump(float delta_time) -> bool;
|
||||
|
||||
// Estat del gir en curs. Mentre `turning_remaining_ms_ > 0`, el facing/
|
||||
// flip es manté forçat a `intermediate_cardinal_` i l'animació es força
|
||||
// a `stand` encara que el moviment continuï. En esgotar-se el timer,
|
||||
|
||||
Reference in new issue
Block a user