feat(player): estat ATTACKING amb edge-detect, animacions i spawn del whip via bus
This commit is contained in:
1 parent
5db3bb0bd0
commit
d208ba02df
7 files changed
+288
-1
No files matched your search
@@ -68,6 +68,20 @@ animations:
|
|||||||
frames: [8, 9, 10, 11]
|
frames: [8, 9, 10, 11]
|
||||||
frame_ms: 100
|
frame_ms: 100
|
||||||
|
|
||||||
|
# Placeholder: igual que sam.yaml, reaprofitem `walk_*` fins que
|
||||||
|
# arribe l'art específic del braç llançant el látigo.
|
||||||
|
attack_down:
|
||||||
|
frames: [0, 1, 2, 3]
|
||||||
|
frame_ms: 50
|
||||||
|
|
||||||
|
attack_up:
|
||||||
|
frames: [4, 5, 6, 7]
|
||||||
|
frame_ms: 50
|
||||||
|
|
||||||
|
attack:
|
||||||
|
frames: [8, 9, 10, 11]
|
||||||
|
frame_ms: 50
|
||||||
|
|
||||||
# Inèrcia només de frenada. L'acceleració és sempre instantània (soltar
|
# Inèrcia només de frenada. L'acceleració és sempre instantània (soltar
|
||||||
# el teclat + prémer una direcció = velocitat plena); en canvi, quan es
|
# el teclat + prémer una direcció = velocitat plena); en canvi, quan es
|
||||||
# solta la direcció el jugador decau linealment a 0 en `brake_time_ms`.
|
# solta la direcció el jugador decau linealment a 0 en `brake_time_ms`.
|
||||||
@@ -127,3 +141,11 @@ jump:
|
|||||||
hurt:
|
hurt:
|
||||||
speed: 120
|
speed: 120
|
||||||
brake_time_ms: 500
|
brake_time_ms: 500
|
||||||
|
|
||||||
|
# Configuració del látigo. Mateixos valors que Sam de moment;
|
||||||
|
# diferenciar quan equilibrem cada personatge.
|
||||||
|
whip:
|
||||||
|
range_tiles: 3
|
||||||
|
swing_ms: 200
|
||||||
|
cooldown_ms: 200
|
||||||
|
damage: 1
|
||||||
@@ -68,6 +68,22 @@ animations:
|
|||||||
frames: [8, 9, 10, 11]
|
frames: [8, 9, 10, 11]
|
||||||
frame_ms: 100
|
frame_ms: 100
|
||||||
|
|
||||||
|
# Animacions del braç llançant el látigo. Placeholder: reaprofiten els
|
||||||
|
# frames de `walk_*` perquè els sprites reals encara no existeixen
|
||||||
|
# (TODO: dibuixar-ne d'específics quan arribe l'art). El sprite del
|
||||||
|
# látigo viu al `WhipAttack` i es renderitza superposat per z-order.
|
||||||
|
attack_down:
|
||||||
|
frames: [0, 1, 2, 3]
|
||||||
|
frame_ms: 50
|
||||||
|
|
||||||
|
attack_up:
|
||||||
|
frames: [4, 5, 6, 7]
|
||||||
|
frame_ms: 50
|
||||||
|
|
||||||
|
attack:
|
||||||
|
frames: [8, 9, 10, 11]
|
||||||
|
frame_ms: 50
|
||||||
|
|
||||||
# Inèrcia només de frenada. L'acceleració és sempre instantània (soltar
|
# Inèrcia només de frenada. L'acceleració és sempre instantània (soltar
|
||||||
# el teclat + prémer una direcció = velocitat plena); en canvi, quan es
|
# el teclat + prémer una direcció = velocitat plena); en canvi, quan es
|
||||||
# solta la direcció el jugador decau linealment a 0 en `brake_time_ms`.
|
# solta la direcció el jugador decau linealment a 0 en `brake_time_ms`.
|
||||||
@@ -127,3 +143,16 @@ jump:
|
|||||||
hurt:
|
hurt:
|
||||||
speed: 120
|
speed: 120
|
||||||
brake_time_ms: 500
|
brake_time_ms: 500
|
||||||
|
|
||||||
|
# Configuració del látigo. `range_tiles` és la longitud de la hitbox
|
||||||
|
# abans de retallar contra murs/sarcs (3 tiles ≈ 48 px). `swing_ms` és
|
||||||
|
# la durada de l'animació de l'atac, durant la qual la hitbox del whip
|
||||||
|
# està viva. `cooldown_ms` allarga ATTACKING amb una recuperació en què
|
||||||
|
# el jugador segueix bloquejat però la hitbox ja ha desaparegut: castiga
|
||||||
|
# l'ús abusiu del látigo. `damage` queda llegit per a futur (els
|
||||||
|
# objectius reben `enterHurt` directament; cap entitat té HP encara).
|
||||||
|
whip:
|
||||||
|
range_tiles: 3
|
||||||
|
swing_ms: 200
|
||||||
|
cooldown_ms: 200
|
||||||
|
damage: 1
|
||||||
@@ -38,7 +38,7 @@ class MovingSprite : public Sprite {
|
|||||||
void setAccelY(float value) { ay_ = value; }
|
void setAccelY(float value) { ay_ = value; }
|
||||||
|
|
||||||
void setFlip(SDL_FlipMode flip) { flip_ = flip; }
|
void setFlip(SDL_FlipMode flip) { flip_ = flip; }
|
||||||
auto getFlip() -> SDL_FlipMode { return flip_; }
|
[[nodiscard]] auto getFlip() const -> SDL_FlipMode { return flip_; }
|
||||||
void flip() { flip_ = (flip_ == SDL_FLIP_HORIZONTAL) ? SDL_FLIP_NONE : SDL_FLIP_HORIZONTAL; }
|
void flip() { flip_ = (flip_ == SDL_FLIP_HORIZONTAL) ? SDL_FLIP_NONE : SDL_FLIP_HORIZONTAL; }
|
||||||
|
|
||||||
// Moviment eix-separat amb col·lisió: aplica `dx` (i si la nova
|
// Moviment eix-separat amb col·lisió: aplica `dx` (i si la nova
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ Player::TurnConfig Player::turn_config;
|
|||||||
Player::InertiaConfig Player::inertia_config;
|
Player::InertiaConfig Player::inertia_config;
|
||||||
Player::JumpConfig Player::jump_config;
|
Player::JumpConfig Player::jump_config;
|
||||||
Player::HurtConfig Player::hurt_config;
|
Player::HurtConfig Player::hurt_config;
|
||||||
|
Player::WhipConfig Player::whip_config;
|
||||||
|
|
||||||
void Player::loadFrom(const std::string& yaml_path) {
|
void Player::loadFrom(const std::string& yaml_path) {
|
||||||
auto bytes = Resource::Helper::loadFile(yaml_path);
|
auto bytes = Resource::Helper::loadFile(yaml_path);
|
||||||
@@ -114,6 +115,11 @@ void Player::loadFrom(const std::string& yaml_path) {
|
|||||||
// Bloc opcional `hurt:` — estat post-impacte. Si manca o el speed
|
// Bloc opcional `hurt:` — estat post-impacte. Si manca o el speed
|
||||||
// és 0, l'estat HURT mai s'activa (cas degenerat).
|
// és 0, l'estat HURT mai s'activa (cas degenerat).
|
||||||
readHurtBlock(yaml);
|
readHurtBlock(yaml);
|
||||||
|
|
||||||
|
// Bloc opcional `whip:` — paràmetres del látigo. Si manca, el
|
||||||
|
// jugador no atacarà efectivament (ATTACKING dura 0 ms i el flanc
|
||||||
|
// d'input es perd).
|
||||||
|
readWhipBlock(yaml);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::readCollisionBlock(const fkyaml::node& yaml, const std::string& yaml_path) {
|
void Player::readCollisionBlock(const fkyaml::node& yaml, const std::string& yaml_path) {
|
||||||
@@ -175,6 +181,16 @@ void Player::readHurtBlock(const fkyaml::node& yaml) {
|
|||||||
if (h.contains("brake_time_ms")) { hurt_config.brake_time_ms = h["brake_time_ms"].get_value<int>(); }
|
if (h.contains("brake_time_ms")) { hurt_config.brake_time_ms = h["brake_time_ms"].get_value<int>(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Player::readWhipBlock(const fkyaml::node& yaml) {
|
||||||
|
whip_config = WhipConfig{};
|
||||||
|
if (!yaml.contains("whip")) { return; }
|
||||||
|
const auto& w = yaml["whip"];
|
||||||
|
if (w.contains("range_tiles")) { whip_config.range_tiles = w["range_tiles"].get_value<float>(); }
|
||||||
|
if (w.contains("swing_ms")) { whip_config.swing_ms = w["swing_ms"].get_value<int>(); }
|
||||||
|
if (w.contains("cooldown_ms")) { whip_config.cooldown_ms = w["cooldown_ms"].get_value<int>(); }
|
||||||
|
if (w.contains("damage")) { whip_config.damage = w["damage"].get_value<int>(); }
|
||||||
|
}
|
||||||
|
|
||||||
void Player::applyCardinal(Cardinal c) {
|
void Player::applyCardinal(Cardinal c) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case Cardinal::NORTH:
|
case Cardinal::NORTH:
|
||||||
@@ -440,6 +456,7 @@ void Player::tick(float delta_time) {
|
|||||||
// durant el stun: així si l'usuari l'ha estat prement abans, no
|
// durant el stun: així si l'usuari l'ha estat prement abans, no
|
||||||
// se'l consumeix com a salt nou en sortir del stun.
|
// se'l consumeix com a salt nou en sortir del stun.
|
||||||
(void)consumeJumpEdge();
|
(void)consumeJumpEdge();
|
||||||
|
(void)consumeAttackEdge();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,11 +466,36 @@ void Player::tick(float delta_time) {
|
|||||||
// pulsació quan torne a terra es detecte com a flanc nou.
|
// pulsació quan torne a terra es detecte com a flanc nou.
|
||||||
if (state_ == State::AIRBORNE) {
|
if (state_ == State::AIRBORNE) {
|
||||||
(void)consumeJumpEdge();
|
(void)consumeJumpEdge();
|
||||||
|
(void)consumeAttackEdge();
|
||||||
setVelX(intended_vx_);
|
setVelX(intended_vx_);
|
||||||
setVelY(intended_vy_);
|
setVelY(intended_vy_);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// En ATTACKING el jugador queda bloquejat tot el `swing_ms +
|
||||||
|
// cooldown_ms`: ni input direccional, ni salt, ni atac, ni canvi
|
||||||
|
// de facing. El timer descompta amb `delta_time * 1000` per
|
||||||
|
// mantenir la convenció de delta_ms de la resta d'estats temporals.
|
||||||
|
// Mantenim els flancs dels botons actualitzats perquè una
|
||||||
|
// tecla mantinguda no es comporte com a flanc nou en sortir.
|
||||||
|
if (state_ == State::ATTACKING) {
|
||||||
|
(void)consumeJumpEdge();
|
||||||
|
(void)consumeAttackEdge();
|
||||||
|
attacking_remaining_ms_ -= delta_time * 1000.0F;
|
||||||
|
if (attacking_remaining_ms_ <= 0.0F) {
|
||||||
|
attacking_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);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// En HURT el jugador està projectat sense control: descomptem
|
// En HURT el jugador està projectat sense control: descomptem
|
||||||
// velocitat per inèrcia (decaiment lineal fins a 0). Quan arriba a
|
// velocitat per inèrcia (decaiment lineal fins a 0). Quan arriba a
|
||||||
// zero, tornem a GROUNDED i el frame següent ja torna a llegir input.
|
// zero, tornem a GROUNDED i el frame següent ja torna a llegir input.
|
||||||
@@ -461,6 +503,7 @@ void Player::tick(float delta_time) {
|
|||||||
// estats), que també gestiona el xoc contra murs.
|
// estats), que també gestiona el xoc contra murs.
|
||||||
if (state_ == State::HURT) {
|
if (state_ == State::HURT) {
|
||||||
(void)consumeJumpEdge();
|
(void)consumeJumpEdge();
|
||||||
|
(void)consumeAttackEdge();
|
||||||
const float DECEL = (hurt_config.brake_time_ms > 0)
|
const float DECEL = (hurt_config.brake_time_ms > 0)
|
||||||
? hurt_config.initial_speed_px_per_s / (static_cast<float>(hurt_config.brake_time_ms) / 1000.0F)
|
? hurt_config.initial_speed_px_per_s / (static_cast<float>(hurt_config.brake_time_ms) / 1000.0F)
|
||||||
: 0.0F;
|
: 0.0F;
|
||||||
@@ -535,6 +578,18 @@ void Player::tick(float delta_time) {
|
|||||||
// només disparen amb intenció explícita del jugador.
|
// només disparen amb intenció explícita del jugador.
|
||||||
updateFacingAndTurn(delta_time, input_vx_, input_vy_);
|
updateFacingAndTurn(delta_time, input_vx_, input_vy_);
|
||||||
|
|
||||||
|
// Detecció de flanc del botó d'atac. Es processa ABANS del salt
|
||||||
|
// perquè ATTACKING i AIRBORNE són mútuament excloents i, davant un
|
||||||
|
// mateix-frame, l'atac té prioritat (millor castigar la pulsació
|
||||||
|
// doble per excés que perdre l'atac per un toc accidental al salt).
|
||||||
|
// En entrar a ATTACKING, descartem el flanc del salt per no que
|
||||||
|
// queda penjat per a quan es desbloque.
|
||||||
|
if (consumeAttackEdge() && whip_config.swing_ms > 0) {
|
||||||
|
enterAttacking(currentFacingCardinal());
|
||||||
|
(void)consumeJumpEdge();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Detecció de flanc del botó de salt. Es processa DESPRÉS d'aplicar
|
// 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
|
// 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
|
// despegue. `being_pushed_` no ho pot bloquejar ací (encara no s'ha
|
||||||
@@ -565,6 +620,92 @@ auto Player::consumeJumpEdge() -> bool {
|
|||||||
return edge;
|
return edge;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Player::consumeAttackEdge() -> bool {
|
||||||
|
// Mateix patró que `consumeJumpEdge` però per al botó d'atac. Mando:
|
||||||
|
// SDL_GAMEPAD_BUTTON_WEST (X a Xbox / Quadrat a PlayStation), oposat
|
||||||
|
// a SOUTH per evitar topades semàntiques.
|
||||||
|
const bool* keys = InputState::getKeyboardState();
|
||||||
|
const bool KEY_NOW = keys[controls_.attack];
|
||||||
|
bool edge = (KEY_NOW && !attack_held_keyboard_);
|
||||||
|
attack_held_keyboard_ = KEY_NOW;
|
||||||
|
|
||||||
|
SDL_Gamepad* pad = GamepadManager::get().getForPlayer(id_);
|
||||||
|
const bool PAD_NOW = (pad != nullptr) && InputState::getGamepadButton(pad, SDL_GAMEPAD_BUTTON_WEST);
|
||||||
|
if (PAD_NOW && !attack_held_gamepad_) { edge = true; }
|
||||||
|
attack_held_gamepad_ = PAD_NOW;
|
||||||
|
|
||||||
|
return edge;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Player::currentFacingCardinal() const -> Cardinal {
|
||||||
|
switch (facing_) {
|
||||||
|
case Facing::UP:
|
||||||
|
return Cardinal::NORTH;
|
||||||
|
case Facing::DOWN:
|
||||||
|
return Cardinal::SOUTH;
|
||||||
|
case Facing::SIDE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Lateral: el flip discrimina E/W. Sense flip = mira a la dreta
|
||||||
|
// (convenció estable a tots els assets actuals).
|
||||||
|
return (getFlip() == SDL_FLIP_HORIZONTAL) ? Cardinal::WEST : Cardinal::EAST;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::enterAttacking(Cardinal facing) {
|
||||||
|
state_ = State::ATTACKING;
|
||||||
|
attacking_remaining_ms_ = static_cast<float>(whip_config.swing_ms + whip_config.cooldown_ms);
|
||||||
|
|
||||||
|
// Garantim que facing/flip i cardinal queden coherents amb la
|
||||||
|
// direcció triada (per si l'atac s'ha disparat amb facing residual,
|
||||||
|
// p.ex. just després d'un canvi d'input).
|
||||||
|
applyCardinal(facing);
|
||||||
|
|
||||||
|
// Geometria de spawn de la hitbox: alineada al rectangle de
|
||||||
|
// col·lisió del jugador, no al sprite (que té perspectiva falsa amb
|
||||||
|
// el cap dalt). La hitbox és `range_px × tile_size` (E/W) o
|
||||||
|
// `tile_size × range_px` (N/S); el GameScene la consumeix per
|
||||||
|
// crear el WhipAttack amb la mida adequada per direcció.
|
||||||
|
const auto BOX = getBaseBox();
|
||||||
|
const float RANGE_PX = whip_config.range_tiles * static_cast<float>(Map::tile_size);
|
||||||
|
float spawn_x = 0.0F;
|
||||||
|
float spawn_y = 0.0F;
|
||||||
|
int dir_int = 0;
|
||||||
|
switch (facing) {
|
||||||
|
case Cardinal::EAST:
|
||||||
|
spawn_x = BOX.x + BOX.w;
|
||||||
|
spawn_y = BOX.y;
|
||||||
|
dir_int = 3;
|
||||||
|
break;
|
||||||
|
case Cardinal::WEST:
|
||||||
|
spawn_x = BOX.x - RANGE_PX;
|
||||||
|
spawn_y = BOX.y;
|
||||||
|
dir_int = 4;
|
||||||
|
break;
|
||||||
|
case Cardinal::NORTH:
|
||||||
|
spawn_x = BOX.x;
|
||||||
|
spawn_y = BOX.y - RANGE_PX;
|
||||||
|
dir_int = 1;
|
||||||
|
break;
|
||||||
|
case Cardinal::SOUTH:
|
||||||
|
spawn_x = BOX.x;
|
||||||
|
spawn_y = BOX.y + BOX.h;
|
||||||
|
dir_int = 2;
|
||||||
|
break;
|
||||||
|
case Cardinal::NONE:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bus_ != nullptr) {
|
||||||
|
bus_->publish("whip_spawn", WhipSpawnPayload{
|
||||||
|
.owner = this,
|
||||||
|
.direction = dir_int,
|
||||||
|
.spawn_x = spawn_x,
|
||||||
|
.spawn_y = spawn_y,
|
||||||
|
.range_px = RANGE_PX,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Player::startJump() {
|
void Player::startJump() {
|
||||||
// No saltem si l'estat no ho permet. `being_pushed_` no és segur de
|
// 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),
|
// consultar ací (es pot haver marcat o no, segons l'ordre del frame),
|
||||||
@@ -788,6 +929,15 @@ void Player::applyMovement(float delta_time) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state_ == State::ATTACKING) {
|
||||||
|
// Cap moviment durant ATTACKING. Animem (perquè avance la
|
||||||
|
// seqüència `attack_*`) i actualitzem la clau d'animació segons
|
||||||
|
// la sub-fase (swing vs cooldown).
|
||||||
|
animate(delta_time);
|
||||||
|
setCurrentAnimation(animationName(false));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// El resolver del GameScene pot haver escalat `intended_vx_/vy_`
|
// El resolver del GameScene pot haver escalat `intended_vx_/vy_`
|
||||||
// (cas push: velocitat * push_factor) o haver-les anul·lat (cas
|
// (cas push: velocitat * push_factor) o haver-les anul·lat (cas
|
||||||
// head-on). Consumim-les aquí com a velocitat definitiva del frame.
|
// head-on). Consumim-les aquí com a velocitat definitiva del frame.
|
||||||
@@ -989,6 +1139,33 @@ auto Player::animationName(bool moving) const -> const char* {
|
|||||||
}
|
}
|
||||||
return "hurt";
|
return "hurt";
|
||||||
}
|
}
|
||||||
|
// ATTACKING té dues sub-fases: durant `swing_ms` el sprite és
|
||||||
|
// `attack_*` (animació del braç llançant el látigo); durant
|
||||||
|
// `cooldown_ms` posterior tornem al `stand_*` de la direcció. Fem
|
||||||
|
// la decisió comparant el remaining_ms_ amb el cooldown configurat.
|
||||||
|
if (state_ == State::ATTACKING) {
|
||||||
|
const bool IN_SWING = (attacking_remaining_ms_ > static_cast<float>(whip_config.cooldown_ms));
|
||||||
|
if (IN_SWING) {
|
||||||
|
switch (facing_) {
|
||||||
|
case Facing::UP:
|
||||||
|
return "attack_up";
|
||||||
|
case Facing::DOWN:
|
||||||
|
return "attack_down";
|
||||||
|
case Facing::SIDE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return "attack";
|
||||||
|
}
|
||||||
|
switch (facing_) {
|
||||||
|
case Facing::UP:
|
||||||
|
return "stand_up";
|
||||||
|
case Facing::DOWN:
|
||||||
|
return "stand_down";
|
||||||
|
case Facing::SIDE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return "stand";
|
||||||
|
}
|
||||||
switch (facing_) {
|
switch (facing_) {
|
||||||
case Facing::UP:
|
case Facing::UP:
|
||||||
return moving ? "walk_up" : "stand_up";
|
return moving ? "walk_up" : "stand_up";
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ class Player : public AnimatedSprite {
|
|||||||
SDL_Scancode left;
|
SDL_Scancode left;
|
||||||
SDL_Scancode right;
|
SDL_Scancode right;
|
||||||
SDL_Scancode jump;
|
SDL_Scancode jump;
|
||||||
|
SDL_Scancode attack;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Estat d'alt nivell del jugador. Ortogonal a `Facing`/`Cardinal`
|
// Estat d'alt nivell del jugador. Ortogonal a `Facing`/`Cardinal`
|
||||||
@@ -44,6 +45,15 @@ class Player : public AnimatedSprite {
|
|||||||
AIRBORNE,
|
AIRBORNE,
|
||||||
LANDING_STUN,
|
LANDING_STUN,
|
||||||
HURT,
|
HURT,
|
||||||
|
// Atacant amb látigo. Estat mútuament exclusiu amb AIRBORNE: no
|
||||||
|
// es pot atacar saltant, ni saltar atacant. Cobreix swing +
|
||||||
|
// cooldown internament: durant `whip_swing_ms` es spawneja
|
||||||
|
// l'entitat WhipAttack i corre l'animació `attack_*`; durant
|
||||||
|
// `whip_cooldown_ms` posterior, el jugador segueix bloquejat
|
||||||
|
// (no input, no salt, no atac) però visualment torna a
|
||||||
|
// `stand_*`. En total, ATTACKING dura `swing_ms + cooldown_ms`;
|
||||||
|
// en acabar, torna a GROUNDED.
|
||||||
|
ATTACKING,
|
||||||
// Jugador que ha eixit per la porta de la sala. Despawn lògic:
|
// Jugador que ha eixit per la porta de la sala. Despawn lògic:
|
||||||
// no es renderitza, no processa input, no es mou, no col·lisiona
|
// no es renderitza, no processa input, no es mou, no col·lisiona
|
||||||
// amb res ni rep contacte d'entitats. Idempotent: les crides
|
// amb res ni rep contacte d'entitats. Idempotent: les crides
|
||||||
@@ -503,6 +513,37 @@ class Player : public AnimatedSprite {
|
|||||||
// Llig el bloc `hurt:` del YAML i n'omple `hurt_config`.
|
// Llig el bloc `hurt:` del YAML i n'omple `hurt_config`.
|
||||||
static void readHurtBlock(const fkyaml::node& yaml);
|
static void readHurtBlock(const fkyaml::node& yaml);
|
||||||
|
|
||||||
|
// Configuració del látigo del jugador llegida del bloc `whip:` del
|
||||||
|
// YAML. `range_tiles` defineix la longitud de la hitbox abans de
|
||||||
|
// retallar; `swing_ms` és la durada de l'animació d'atac (des
|
||||||
|
// d'`enterAttacking` fins a desbloquejar el cooldown); `cooldown_ms`
|
||||||
|
// afegeix una fase posterior durant la qual el jugador segueix
|
||||||
|
// bloquejat però la hitbox ja no fa dany. `damage` queda llegit per
|
||||||
|
// a futur; per ara els objectius reben sempre `enterHurt`.
|
||||||
|
struct WhipConfig {
|
||||||
|
float range_tiles{0.0F};
|
||||||
|
int swing_ms{0};
|
||||||
|
int cooldown_ms{0};
|
||||||
|
int damage{0};
|
||||||
|
};
|
||||||
|
static WhipConfig whip_config;
|
||||||
|
|
||||||
|
// Llig el bloc `whip:` del YAML i n'omple `whip_config`. Si manca el
|
||||||
|
// bloc, els camps queden a 0 i el jugador no pot atacar (el flanc
|
||||||
|
// d'input es perd silenciosament).
|
||||||
|
static void readWhipBlock(const fkyaml::node& yaml);
|
||||||
|
|
||||||
|
// Activa l'estat ATTACKING en la direcció cardinal indicada (la
|
||||||
|
// direcció cap on mira el jugador) i publica `whip_spawn` al bus
|
||||||
|
// perquè el GameScene cree el `WhipAttack` corresponent. Posa el
|
||||||
|
// timer `attacking_remaining_ms_` a `swing_ms + cooldown_ms`.
|
||||||
|
void enterAttacking(Cardinal facing);
|
||||||
|
|
||||||
|
// Cardinal que correspon al `(facing_, flip_)` actual del sprite —
|
||||||
|
// la direcció cap on mira el jugador. Si `facing_ == SIDE` el
|
||||||
|
// resultat depèn del flip; UP/DOWN són trivials. Mai retorna NONE.
|
||||||
|
[[nodiscard]] auto currentFacingCardinal() const -> Cardinal;
|
||||||
|
|
||||||
// Activa l'estat HURT: el jugador queda projectat amb velocitat
|
// Activa l'estat HURT: el jugador queda projectat amb velocitat
|
||||||
// inicial `hurt_config.initial_speed_px_per_s`. La direcció es tria
|
// inicial `hurt_config.initial_speed_px_per_s`. La direcció es tria
|
||||||
// així:
|
// així:
|
||||||
@@ -549,10 +590,24 @@ class Player : public AnimatedSprite {
|
|||||||
bool jump_held_keyboard_{false};
|
bool jump_held_keyboard_{false};
|
||||||
bool jump_held_gamepad_{false};
|
bool jump_held_gamepad_{false};
|
||||||
|
|
||||||
|
// Mateixa idea per al botó d'atac: edge-detection independent
|
||||||
|
// teclat/mando, perquè mantenir la tecla no genere atacs en cadena.
|
||||||
|
bool attack_held_keyboard_{false};
|
||||||
|
bool attack_held_gamepad_{false};
|
||||||
|
|
||||||
|
// Temps que queda dins de l'estat ATTACKING (swing + cooldown). Quan
|
||||||
|
// arriba a 0, transició a GROUNDED. Mentre dura, el jugador queda
|
||||||
|
// bloquejat i no rep input direccional ni de salt ni d'atac.
|
||||||
|
float attacking_remaining_ms_{0.0F};
|
||||||
|
|
||||||
// Llig el botó de salt aquest frame (teclat + mando assignat) i,
|
// Llig el botó de salt aquest frame (teclat + mando assignat) i,
|
||||||
// amb edge-detection, retorna true si s'ha de despegar el salt.
|
// amb edge-detection, retorna true si s'ha de despegar el salt.
|
||||||
[[nodiscard]] auto consumeJumpEdge() -> bool;
|
[[nodiscard]] auto consumeJumpEdge() -> bool;
|
||||||
|
|
||||||
|
// Anàleg per a l'atac: edge-detection del botó d'atac (teclat
|
||||||
|
// `controls_.attack` + `SDL_GAMEPAD_BUTTON_WEST`).
|
||||||
|
[[nodiscard]] auto consumeAttackEdge() -> bool;
|
||||||
|
|
||||||
// Inicia un salt: copia `intended_v*` com a velocitat fixada del salt,
|
// Inicia un salt: copia `intended_v*` com a velocitat fixada del salt,
|
||||||
// posa `jump_vz_ = impulse`, transiciona a AIRBORNE.
|
// posa `jump_vz_ = impulse`, transiciona a AIRBORNE.
|
||||||
void startJump();
|
void startJump();
|
||||||
|
|||||||
@@ -335,6 +335,7 @@ GameScene::GameScene(Config config)
|
|||||||
.left = Defaults::KeysGame::LEFT,
|
.left = Defaults::KeysGame::LEFT,
|
||||||
.right = Defaults::KeysGame::RIGHT,
|
.right = Defaults::KeysGame::RIGHT,
|
||||||
.jump = Defaults::KeysGame::JUMP,
|
.jump = Defaults::KeysGame::JUMP,
|
||||||
|
.attack = Defaults::KeysGame::ATTACK,
|
||||||
};
|
};
|
||||||
const Player::Controls CONTROLS_P2{
|
const Player::Controls CONTROLS_P2{
|
||||||
.up = Defaults::KeysGameP2::UP,
|
.up = Defaults::KeysGameP2::UP,
|
||||||
@@ -342,6 +343,7 @@ GameScene::GameScene(Config config)
|
|||||||
.left = Defaults::KeysGameP2::LEFT,
|
.left = Defaults::KeysGameP2::LEFT,
|
||||||
.right = Defaults::KeysGameP2::RIGHT,
|
.right = Defaults::KeysGameP2::RIGHT,
|
||||||
.jump = Defaults::KeysGameP2::JUMP,
|
.jump = Defaults::KeysGameP2::JUMP,
|
||||||
|
.attack = Defaults::KeysGameP2::ATTACK,
|
||||||
};
|
};
|
||||||
// Spawn lligat a la porta: tots els jugadors apareixen davant del
|
// Spawn lligat a la porta: tots els jugadors apareixen davant del
|
||||||
// passadís de sortida. `computeSpawn` deriva les coordenades de
|
// passadís de sortida. `computeSpawn` deriva les coordenades de
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ namespace Defaults {
|
|||||||
constexpr SDL_Scancode LEFT = SDL_SCANCODE_LEFT;
|
constexpr SDL_Scancode LEFT = SDL_SCANCODE_LEFT;
|
||||||
constexpr SDL_Scancode RIGHT = SDL_SCANCODE_RIGHT;
|
constexpr SDL_Scancode RIGHT = SDL_SCANCODE_RIGHT;
|
||||||
constexpr SDL_Scancode JUMP = SDL_SCANCODE_M;
|
constexpr SDL_Scancode JUMP = SDL_SCANCODE_M;
|
||||||
|
constexpr SDL_Scancode ATTACK = SDL_SCANCODE_N;
|
||||||
constexpr SDL_Scancode EXIT = SDL_SCANCODE_ESCAPE;
|
constexpr SDL_Scancode EXIT = SDL_SCANCODE_ESCAPE;
|
||||||
} // namespace KeysGame
|
} // namespace KeysGame
|
||||||
|
|
||||||
@@ -91,6 +92,7 @@ namespace Defaults {
|
|||||||
constexpr SDL_Scancode LEFT = SDL_SCANCODE_A;
|
constexpr SDL_Scancode LEFT = SDL_SCANCODE_A;
|
||||||
constexpr SDL_Scancode RIGHT = SDL_SCANCODE_D;
|
constexpr SDL_Scancode RIGHT = SDL_SCANCODE_D;
|
||||||
constexpr SDL_Scancode JUMP = SDL_SCANCODE_V;
|
constexpr SDL_Scancode JUMP = SDL_SCANCODE_V;
|
||||||
|
constexpr SDL_Scancode ATTACK = SDL_SCANCODE_C;
|
||||||
} // namespace KeysGameP2
|
} // namespace KeysGameP2
|
||||||
|
|
||||||
namespace Player {
|
namespace Player {
|
||||||
|
|||||||
Reference in new issue
Block a user