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
@@ -27,6 +27,9 @@ animations:
|
||||
stand_down: { frames: [0] }
|
||||
stand_up: { frames: [4] }
|
||||
stand: { frames: [8] }
|
||||
jump_down: { frames: [0] }
|
||||
jump_up: { frames: [4] }
|
||||
jump: { frames: [8] }
|
||||
|
||||
walk_down:
|
||||
frames: [0, 1, 2, 3]
|
||||
@@ -64,3 +67,18 @@ turn:
|
||||
east_to_west: up # passa per dalt (stand_up)
|
||||
north_to_south: east # passa per la dreta (stand, sense flip)
|
||||
south_to_north: west # passa per l'esquerra (stand, amb flip)
|
||||
|
||||
# Salt vertical (px verticals "visuals", no afecta la posició al món).
|
||||
# impulse → velocitat vertical inicial (px/s) en deixar el sòl.
|
||||
# gravity → acceleració descendent constant (px/s²).
|
||||
# landing_stun_ms → temps en què el jugador queda paralitzat en aterrar
|
||||
# (no es pot moure ni tornar a saltar).
|
||||
# height → alçada del cos del jugador en píxels. S'usa per al
|
||||
# filtratge de col·lisions per altura: un jugador
|
||||
# saltant per damunt d'aquest valor no col·lisiona.
|
||||
# Amb impulse=240 i gravity=600: pic ≈ 48 px, temps total en l'aire ≈ 0.8 s.
|
||||
jump:
|
||||
impulse: 240
|
||||
gravity: 600
|
||||
landing_stun_ms: 200
|
||||
height: 24
|
||||
@@ -27,6 +27,9 @@ animations:
|
||||
stand_down: { frames: [1] }
|
||||
stand_up: { frames: [5] }
|
||||
stand: { frames: [9] }
|
||||
jump_down: { frames: [1] }
|
||||
jump_up: { frames: [5] }
|
||||
jump: { frames: [9] }
|
||||
|
||||
walk_down:
|
||||
frames: [0, 1, 2, 3]
|
||||
@@ -64,3 +67,18 @@ turn:
|
||||
east_to_west: up # passa per dalt (stand_up)
|
||||
north_to_south: east # passa per la dreta (stand, sense flip)
|
||||
south_to_north: west # passa per l'esquerra (stand, amb flip)
|
||||
|
||||
# Salt vertical (px verticals "visuals", no afecta la posició al món).
|
||||
# impulse → velocitat vertical inicial (px/s) en deixar el sòl.
|
||||
# gravity → acceleració descendent constant (px/s²).
|
||||
# landing_stun_ms → temps en què el jugador queda paralitzat en aterrar
|
||||
# (no es pot moure ni tornar a saltar).
|
||||
# height → alçada del cos del jugador en píxels. S'usa per al
|
||||
# filtratge de col·lisions per altura: un jugador
|
||||
# saltant per damunt d'aquest valor no col·lisiona.
|
||||
# Amb impulse=240 i gravity=600: pic ≈ 48 px, temps total en l'aire ≈ 0.8 s.
|
||||
jump:
|
||||
impulse: 240
|
||||
gravity: 600
|
||||
landing_stun_ms: 200
|
||||
height: 24
|
||||
@@ -63,6 +63,7 @@ namespace {
|
||||
|
||||
Player::TurnConfig Player::turn_config;
|
||||
Player::InertiaConfig Player::inertia_config;
|
||||
Player::JumpConfig Player::jump_config;
|
||||
|
||||
void Player::loadFrom(const std::string& yaml_path) {
|
||||
auto bytes = Resource::Helper::loadFile(yaml_path);
|
||||
@@ -121,6 +122,22 @@ void Player::loadFrom(const std::string& yaml_path) {
|
||||
if (tr.contains("south_to_north")) { turn_config.s_to_n = cardinalFromString(tr["south_to_north"].get_value<std::string>()); }
|
||||
}
|
||||
}
|
||||
|
||||
// Bloc opcional `jump:` — si manca, `impulse_px_per_s` queda a 0 i el
|
||||
// botó de salt no fa res. Extret a un mètode estàtic per acotar la
|
||||
// complexitat cognitiva de `loadFrom` sota el llindar de clang-tidy.
|
||||
readJumpBlock(yaml);
|
||||
}
|
||||
|
||||
void Player::readJumpBlock(const fkyaml::node& yaml) {
|
||||
jump_config = JumpConfig{};
|
||||
body_height = 0;
|
||||
if (!yaml.contains("jump")) { return; }
|
||||
const auto& j = yaml["jump"];
|
||||
if (j.contains("impulse")) { jump_config.impulse_px_per_s = j["impulse"].get_value<float>(); }
|
||||
if (j.contains("gravity")) { jump_config.gravity_px_per_s2 = j["gravity"].get_value<float>(); }
|
||||
if (j.contains("landing_stun_ms")) { jump_config.landing_stun_ms = j["landing_stun_ms"].get_value<int>(); }
|
||||
if (j.contains("height")) { body_height = j["height"].get_value<int>(); }
|
||||
}
|
||||
|
||||
void Player::applyCardinal(Cardinal c) {
|
||||
@@ -329,6 +346,42 @@ void Player::tick(float delta_time) {
|
||||
// (un deixa d'empentar, l'altre comença a caminar), l'animació torna
|
||||
// a governar-se per la velocitat real sense estat romanent.
|
||||
being_pushed_ = false;
|
||||
|
||||
// En LANDING_STUN, descomptem el timer i ignorem qualsevol input
|
||||
// (moviment i salt). Quan s'esgota, tornem a GROUNDED al pròxim
|
||||
// frame. La velocitat queda a zero — després del stun hi tornarà
|
||||
// a haver intenció si el jugador prem alguna tecla.
|
||||
if (state_ == State::LANDING_STUN) {
|
||||
landing_stun_remaining_ms_ -= delta_time * 1000.0F;
|
||||
if (landing_stun_remaining_ms_ <= 0.0F) {
|
||||
landing_stun_remaining_ms_ = 0.0F;
|
||||
state_ = State::GROUNDED;
|
||||
}
|
||||
input_vx_ = 0.0F;
|
||||
input_vy_ = 0.0F;
|
||||
intended_vx_ = 0.0F;
|
||||
intended_vy_ = 0.0F;
|
||||
motion_time_ms_ = 0.0F;
|
||||
setVelX(0.0F);
|
||||
setVelY(0.0F);
|
||||
// Mantenim els flancs del botó de salt actualitzats fins i tot
|
||||
// durant el stun: així si l'usuari l'ha estat prement abans, no
|
||||
// se'l consumeix com a salt nou en sortir del stun.
|
||||
(void)consumeJumpEdge();
|
||||
return;
|
||||
}
|
||||
|
||||
// En AIRBORNE el jugador no llig input direccional: la velocitat
|
||||
// horitzontal queda fixada al moment del despegue. Tampoc canvia
|
||||
// facing. Sí actualitzem els flancs del botó perquè la propera
|
||||
// pulsació quan torne a terra es detecte com a flanc nou.
|
||||
if (state_ == State::AIRBORNE) {
|
||||
(void)consumeJumpEdge();
|
||||
setVelX(intended_vx_);
|
||||
setVelY(intended_vy_);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -379,9 +432,96 @@ void Player::tick(float delta_time) {
|
||||
// canvia el facing (la darrera orientació es preserva), i els girs
|
||||
// només disparen amb intenció explícita del jugador.
|
||||
updateFacingAndTurn(delta_time, input_vx_, input_vy_);
|
||||
|
||||
// Detecció de flanc del botó de salt. Es processa DESPRÉS d'aplicar
|
||||
// el facing perquè el sprite del salt usa el facing del moment del
|
||||
// despegue. `being_pushed_` no ho pot bloquejar ací (encara no s'ha
|
||||
// marcat aquest frame, ho fa el resolver entre tick i applyMovement);
|
||||
// ho gestionem deixant que el resolver vinga, i si en el moment del
|
||||
// despegue (consumeJumpEdge → startJump) ja estem AIRBORNE, l'altre
|
||||
// braç del switch no canvia res.
|
||||
if (consumeJumpEdge() && jump_config.impulse_px_per_s > 0.0F) {
|
||||
startJump();
|
||||
}
|
||||
}
|
||||
|
||||
auto Player::consumeJumpEdge() -> bool {
|
||||
// Edge-detection independent per teclat i mando: si qualsevol dels
|
||||
// dos passa de no-premut a premut aquest frame, comptem com a salt.
|
||||
// Sense detecció de flanc, mantenir la tecla provocaria saltar
|
||||
// immediatament en aterrar.
|
||||
const bool* keys = InputState::getKeyboardState();
|
||||
const bool KEY_NOW = keys[controls_.jump];
|
||||
bool edge = (KEY_NOW && !jump_held_keyboard_);
|
||||
jump_held_keyboard_ = KEY_NOW;
|
||||
|
||||
SDL_Gamepad* pad = GamepadManager::get().getForPlayer(id_);
|
||||
const bool PAD_NOW = (pad != nullptr) && InputState::getGamepadButton(pad, SDL_GAMEPAD_BUTTON_SOUTH);
|
||||
if (PAD_NOW && !jump_held_gamepad_) { edge = true; }
|
||||
jump_held_gamepad_ = PAD_NOW;
|
||||
|
||||
return edge;
|
||||
}
|
||||
|
||||
void Player::startJump() {
|
||||
// No saltem si l'estat no ho permet. `being_pushed_` no és segur de
|
||||
// consultar ací (es pot haver marcat o no, segons l'ordre del frame),
|
||||
// així que la regla és: només GROUNDED arrenca salts. El push viu
|
||||
// entre `tick` i `applyMovement`, així que si el resolver acaba
|
||||
// empentant en mig d'un frame on intentàvem saltar, el flanc es perd
|
||||
// — comportament acceptable: l'empenta guanya al toc curt.
|
||||
if (state_ != State::GROUNDED) { return; }
|
||||
state_ = State::AIRBORNE;
|
||||
jump_z_ = 0.0F;
|
||||
jump_vz_ = jump_config.impulse_px_per_s;
|
||||
// Prou: la velocitat horitzontal del salt és la `intended_v*` actual
|
||||
// (input del frame). El facing també queda com està. Ja no es
|
||||
// tocaran fins a aterrar.
|
||||
}
|
||||
|
||||
auto Player::integrateJump(float delta_time) -> bool {
|
||||
if (state_ != State::AIRBORNE) { return false; }
|
||||
jump_vz_ -= jump_config.gravity_px_per_s2 * delta_time;
|
||||
jump_z_ += jump_vz_ * delta_time;
|
||||
if (jump_z_ <= 0.0F) {
|
||||
jump_z_ = 0.0F;
|
||||
jump_vz_ = 0.0F;
|
||||
state_ = State::LANDING_STUN;
|
||||
landing_stun_remaining_ms_ = static_cast<float>(jump_config.landing_stun_ms);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Player::applyMovement(float delta_time) {
|
||||
// Integració del salt: si estem AIRBORNE, avança jump_z_/jump_vz_.
|
||||
// El facing i intended_v* es mantenen del moment del despegue (no es
|
||||
// recalculen). Si toquem el sòl aquest frame, transicionem a
|
||||
// LANDING_STUN ja al final d'`integrateJump`.
|
||||
integrateJump(delta_time);
|
||||
|
||||
// En LANDING_STUN no movem el jugador horitzontalment (la velocitat
|
||||
// ja s'ha posat a 0 al `tick`). Encara així animem (per estar al
|
||||
// frame correcte de la posada estàtica) i emetem el tile actual,
|
||||
// perquè la tile d'aterratge sí que es pinte.
|
||||
if (state_ == State::LANDING_STUN) {
|
||||
animate(delta_time);
|
||||
setCurrentAnimation(animationName(false));
|
||||
if (bus_ != nullptr) {
|
||||
const auto [COL, ROW] = getCurrentTile();
|
||||
if (COL != last_tile_col_ || ROW != last_tile_row_) {
|
||||
last_tile_col_ = COL;
|
||||
last_tile_row_ = ROW;
|
||||
bus_->publish(Event{
|
||||
.name = "on_tile_entered",
|
||||
.payload = PaintedTile{.col = COL, .row = ROW, .tile_index = paint_tile_index_},
|
||||
.player_id = id_,
|
||||
});
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -390,9 +530,12 @@ void Player::applyMovement(float delta_time) {
|
||||
|
||||
// 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.
|
||||
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. En l'aire
|
||||
// no fem snap: la trajectòria queda fixada al despegue, que el snap
|
||||
// mid-flight desviaria.
|
||||
const bool AIRBORNE = (state_ == State::AIRBORNE);
|
||||
const bool ONLY_H = !AIRBORNE && (VX != 0.0F) && (VY == 0.0F);
|
||||
const bool ONLY_V = !AIRBORNE && (VY != 0.0F) && (VX == 0.0F);
|
||||
|
||||
const float FRAME_START_X = getPosX();
|
||||
const float FRAME_START_Y = getPosY();
|
||||
@@ -420,7 +563,8 @@ void Player::applyMovement(float delta_time) {
|
||||
const bool MOVED = (getPosX() != FRAME_START_X) || (getPosY() != FRAME_START_Y);
|
||||
// `being_pushed_` força `stand_*`: el jugador es desplaça sense input,
|
||||
// animar-lo com a caminant faria creure que està caminant quan de
|
||||
// fet és arrossegat per l'altre.
|
||||
// fet és arrossegat per l'altre. AIRBORNE força `jump_*` (vegeu
|
||||
// `animationName`), independentment de la velocitat.
|
||||
const bool ANIM_AS_MOVING = MOVED && (turning_remaining_ms_ <= 0.0F) && !being_pushed_;
|
||||
setCurrentAnimation(animationName(ANIM_AS_MOVING));
|
||||
|
||||
@@ -444,7 +588,38 @@ void Player::applyMovement(float delta_time) {
|
||||
}
|
||||
}
|
||||
|
||||
void Player::render() {
|
||||
// Mentre el jugador està enlaire, el sprite es desplaça visualment
|
||||
// cap amunt `jump_z_` píxels. Modifiquem `pos_.y` (la posició entera
|
||||
// de render) abans de cridar el render base, i la restaurem després
|
||||
// perquè l'estat seguent del frame ho recalcule des de `y_` (la font
|
||||
// de veritat float). El sòl sota el jugador no es pinta des d'ací:
|
||||
// això ho fa el GameScene en una passada de sombres a banda.
|
||||
if (jump_z_ <= 0.0F) {
|
||||
AnimatedSprite::render();
|
||||
return;
|
||||
}
|
||||
const float SAVED_Y = pos_.y;
|
||||
pos_.y = SAVED_Y - jump_z_;
|
||||
AnimatedSprite::render();
|
||||
pos_.y = SAVED_Y;
|
||||
}
|
||||
|
||||
auto Player::animationName(bool moving) const -> const char* {
|
||||
// En l'aire forcem la variant `jump_*` (per direcció), independent de
|
||||
// si hi ha velocitat horitzontal: per al jugador, l'estat visual del
|
||||
// salt és per direcció, no per velocitat.
|
||||
if (state_ == State::AIRBORNE) {
|
||||
switch (facing_) {
|
||||
case Facing::UP:
|
||||
return "jump_up";
|
||||
case Facing::DOWN:
|
||||
return "jump_down";
|
||||
case Facing::SIDE:
|
||||
break;
|
||||
}
|
||||
return "jump";
|
||||
}
|
||||
switch (facing_) {
|
||||
case Facing::UP:
|
||||
return moving ? "walk_up" : "stand_up";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -239,12 +239,14 @@ GameScene::GameScene(Config config)
|
||||
.down = Defaults::KeysGame::DOWN,
|
||||
.left = Defaults::KeysGame::LEFT,
|
||||
.right = Defaults::KeysGame::RIGHT,
|
||||
.jump = Defaults::KeysGame::JUMP,
|
||||
};
|
||||
const Player::Controls CONTROLS_P2{
|
||||
.up = Defaults::KeysGameP2::UP,
|
||||
.down = Defaults::KeysGameP2::DOWN,
|
||||
.left = Defaults::KeysGameP2::LEFT,
|
||||
.right = Defaults::KeysGameP2::RIGHT,
|
||||
.jump = Defaults::KeysGameP2::JUMP,
|
||||
};
|
||||
// Spawn centrat al passadís exterior superior: la caixa de col·lisió
|
||||
// queda dins del passadís vertical del mig (entre columnes 1 i 2) i
|
||||
|
||||
@@ -77,6 +77,7 @@ namespace Defaults {
|
||||
constexpr SDL_Scancode DOWN = SDL_SCANCODE_DOWN;
|
||||
constexpr SDL_Scancode LEFT = SDL_SCANCODE_LEFT;
|
||||
constexpr SDL_Scancode RIGHT = SDL_SCANCODE_RIGHT;
|
||||
constexpr SDL_Scancode JUMP = SDL_SCANCODE_M;
|
||||
constexpr SDL_Scancode EXIT = SDL_SCANCODE_ESCAPE;
|
||||
} // namespace KeysGame
|
||||
|
||||
@@ -89,6 +90,7 @@ namespace Defaults {
|
||||
constexpr SDL_Scancode DOWN = SDL_SCANCODE_S;
|
||||
constexpr SDL_Scancode LEFT = SDL_SCANCODE_A;
|
||||
constexpr SDL_Scancode RIGHT = SDL_SCANCODE_D;
|
||||
constexpr SDL_Scancode JUMP = SDL_SCANCODE_V;
|
||||
} // namespace KeysGameP2
|
||||
|
||||
namespace Player {
|
||||
|
||||
Reference in new issue
Block a user