feat(hazard): nou hazard Hole — caiguda per forat amb clip rect
This commit is contained in:
1 parent
d4593d0686
commit
4da50571eb
17 files changed
+376
-15
No files matched your search
+117
-11
@@ -12,6 +12,7 @@
|
||||
#include "core/input/gamepad_bindings.hpp"
|
||||
#include "core/input/gamepad_manager.hpp"
|
||||
#include "core/input/input_state.hpp"
|
||||
#include "core/rendering/canvas.hpp"
|
||||
#include "core/rendering/screen.hpp"
|
||||
#include "core/resources/resource_cache.hpp"
|
||||
#include "core/resources/resource_helper.hpp"
|
||||
@@ -910,6 +911,17 @@ 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;
|
||||
// Caiguda per agujero: el sòl ja no és present sota el jugador, així
|
||||
// que NO clampem `jump_z_` a 0. Deixem baixar a negatius (visualment
|
||||
// el sprite "s'enfonsa" sota el sòl) i, en superar `HOLE_FALL_DEPTH`,
|
||||
// disparem la mort. La cinemàtica de respawn (`enterDying →
|
||||
// spawnFromAir`) reseteja el flag i la posició al spawn point.
|
||||
if (falling_through_hole_) {
|
||||
if (jump_z_ <= -HOLE_FALL_DEPTH) {
|
||||
enterDying(DeathCause::HOLE);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (jump_z_ <= 0.0F) {
|
||||
jump_z_ = 0.0F;
|
||||
jump_vz_ = 0.0F;
|
||||
@@ -1075,7 +1087,7 @@ void Player::applyMovement(float delta_time) {
|
||||
if (state_ == State::GROUNDED) { publishTileEnteredIfChanged(); }
|
||||
}
|
||||
|
||||
void Player::enterDying() {
|
||||
void Player::enterDying(DeathCause cause) {
|
||||
// Idempotent. Un segon contacte de momia mentre el jugador ja està
|
||||
// en DYING no reinicia l'animació ni el comptador. Tampoc disparem
|
||||
// el cicle si el jugador està fora de joc (EXITED al passar la
|
||||
@@ -1091,6 +1103,8 @@ void Player::enterDying() {
|
||||
being_pushed_ = false;
|
||||
jump_z_ = 0.0F;
|
||||
jump_vz_ = 0.0F;
|
||||
falling_through_hole_ = false;
|
||||
hole_clip_bottom_y_ = 0.0F;
|
||||
landing_stun_remaining_ms_ = 0.0F;
|
||||
attacking_remaining_ms_ = 0.0F;
|
||||
turning_remaining_ms_ = 0.0F;
|
||||
@@ -1102,7 +1116,18 @@ void Player::enterDying() {
|
||||
|
||||
death_animation_complete_ = false;
|
||||
respawn_remaining_ms_ = 0.0F;
|
||||
setCurrentAnimation("death_spinning");
|
||||
// Presentació segons la causa. Cada `DeathCause` té la seua animació
|
||||
// dedicada al YAML del player; `applyDyingMovement` arma el respawn
|
||||
// timer quan `animationIsCompleted()` flanqueja, igual per a totes.
|
||||
switch (cause) {
|
||||
case DeathCause::HOLE:
|
||||
setCurrentAnimation("death_hole");
|
||||
break;
|
||||
case DeathCause::GENERIC:
|
||||
default:
|
||||
setCurrentAnimation("death_spinning");
|
||||
break;
|
||||
}
|
||||
|
||||
// So del cop letal: reaprofitem `hurt.wav` (ja precarregat per
|
||||
// `readHurtBlock`). El bloc `death:` no té camp de so propi —
|
||||
@@ -1204,19 +1229,46 @@ void Player::render() {
|
||||
if (state_ == State::DYING && death_animation_complete_) { return; }
|
||||
|
||||
// Mentre el jugador està enlaire, el sprite es desplaça visualment
|
||||
// cap amunt `jump_z_` píxels. Modifiquem `pos_.y` (la posició entera
|
||||
// `jump_z_` píxels: cap amunt si és positiu (salt), cap avall si és
|
||||
// negatiu (caiguda per `Hole`, que deixa baixar `jump_z_` a negatius
|
||||
// fins a `-HOLE_FALL_DEPTH`). 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;
|
||||
// això ho fa el GameScene en una passada de sombres a banda. El cas
|
||||
// `jump_z_ == 0` (a terra) surt per l'early-return per evitar tocar
|
||||
// `pos_.y` innecessàriament.
|
||||
// Caiguda per agujero: el sprite es "menja" pel sòl. Fixem un clip
|
||||
// global a la franja [0, hole_clip_bottom_y_] del canvas (el blitter
|
||||
// de `Surface` respecta `Rendering::activeClip()`); a mesura que
|
||||
// `pos_.y - jump_z_` baixa, els píxels del sprite que creuen la
|
||||
// línia deixen de pintar-se. Fem el push/pop al voltant de tot el
|
||||
// render del sprite per cobrir tant el cas `jump_z_==0` (caiguda
|
||||
// just iniciada) com els frames posteriors. `pushClip` ja clamp
|
||||
// a la mida del canvas, així que un `h` negatiu es traduiria en
|
||||
// una franja buida i el sprite quedaria invisible — comportament
|
||||
// correcte si el clip ja ha "tapat" tota la pantalla.
|
||||
const bool CLIP_HOLE = falling_through_hole_;
|
||||
if (CLIP_HOLE) {
|
||||
const auto CANVAS = Rendering::activeCanvas();
|
||||
Rendering::pushClip({
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.w = CANVAS.width,
|
||||
.h = static_cast<int>(hole_clip_bottom_y_),
|
||||
});
|
||||
}
|
||||
const float SAVED_Y = pos_.y;
|
||||
pos_.y = SAVED_Y - jump_z_;
|
||||
AnimatedSprite::render();
|
||||
pos_.y = SAVED_Y;
|
||||
|
||||
if (jump_z_ == 0.0F) {
|
||||
AnimatedSprite::render();
|
||||
} else {
|
||||
const float SAVED_Y = pos_.y;
|
||||
pos_.y = SAVED_Y - jump_z_;
|
||||
AnimatedSprite::render();
|
||||
pos_.y = SAVED_Y;
|
||||
}
|
||||
|
||||
if (CLIP_HOLE) { Rendering::popClip(); }
|
||||
}
|
||||
|
||||
void Player::renderShadow() const {
|
||||
@@ -1224,6 +1276,10 @@ void Player::renderShadow() const {
|
||||
// que poden cridar `renderShadow` per a tots els jugadors sense
|
||||
// discriminar.
|
||||
if (!isAirborne()) { return; }
|
||||
// Caient per un agujero: el cos s'enfonsa sota el sòl, dibuixar una
|
||||
// sombra al ras del sòl trenca la il·lusió. Suprimim la sombra fins
|
||||
// que `enterDying` netegi el flag.
|
||||
if (falling_through_hole_) { return; }
|
||||
|
||||
// Sombra centrada al sprite, recolzada a la línia dels peus reals
|
||||
// (ignorant `jump_z_`). L'amplada es redueix proporcional a l'altura
|
||||
@@ -1359,6 +1415,8 @@ void Player::respawn(float x, float y) {
|
||||
being_pushed_ = false;
|
||||
jump_z_ = 0.0F;
|
||||
jump_vz_ = 0.0F;
|
||||
falling_through_hole_ = false;
|
||||
hole_clip_bottom_y_ = 0.0F;
|
||||
landing_stun_remaining_ms_ = 0.0F;
|
||||
attacking_remaining_ms_ = 0.0F;
|
||||
turning_remaining_ms_ = 0.0F;
|
||||
@@ -1396,6 +1454,54 @@ void Player::disable() {
|
||||
last_input_cardinal_ = Cardinal::NONE;
|
||||
}
|
||||
|
||||
void Player::enterHoleFall(float center_x, float center_y, float floor_y) {
|
||||
// Defensa: només transicionem des de GROUNDED. Saltadors, AIRBORNE,
|
||||
// HURT, DYING, etc. no han de caure pel forat (entre altres coses,
|
||||
// perquè `checkPlayerCollisions` ja filtra airborne via altura, però
|
||||
// afegim el gat ací per coherència amb la resta de transicions).
|
||||
if (state_ != State::GROUNDED) { return; }
|
||||
|
||||
// Snap del centre de la hit-box a la coordenada central del forat.
|
||||
// Resolem `pos = center - (hit_offset + hit_size/2)` per a cada eix:
|
||||
// així el centre exacte de la caja de contacte queda alineat amb
|
||||
// (center_x, center_y), independentment de la geometria interna del
|
||||
// sprite. `setPosX/Y` és l'API canònica per teleportar el jugador
|
||||
// (la usen `respawn` i `spawnFromAir`).
|
||||
const float HIT_HALF = static_cast<float>(hit_size) * 0.5F;
|
||||
setPosX(center_x - static_cast<float>(hit_x_offset) - HIT_HALF);
|
||||
setPosY(center_y - static_cast<float>(hit_y_offset) - HIT_HALF);
|
||||
|
||||
// Frenem qualsevol inèrcia prèvia: la caiguda ha de ser estàtica al
|
||||
// centre del forat.
|
||||
intended_vx_ = 0.0F;
|
||||
intended_vy_ = 0.0F;
|
||||
input_vx_ = 0.0F;
|
||||
input_vy_ = 0.0F;
|
||||
motion_time_ms_ = 0.0F;
|
||||
being_pushed_ = false;
|
||||
setVelX(0.0F);
|
||||
setVelY(0.0F);
|
||||
|
||||
jump_z_ = 0.0F;
|
||||
jump_vz_ = 0.0F;
|
||||
state_ = State::AIRBORNE;
|
||||
falling_through_hole_ = true;
|
||||
// Línia visual on el clip rect ha de retallar el sprite. Restem
|
||||
// `SPRITE_FLOOR_OFFSET` perquè la base del sprite té píxels
|
||||
// transparents — el retall ha de coincidir amb la silueta visible,
|
||||
// no amb la vora del rect del sprite.
|
||||
hole_clip_bottom_y_ = floor_y - SPRITE_FLOOR_OFFSET;
|
||||
|
||||
// Animació de caiguda: respectem el `facing_` que ja portava el jugador
|
||||
// (no té sentit reorientar-lo cap al sud només perquè caigui en un
|
||||
// forat). Com `state_` ja és AIRBORNE, `animationName(false)` resol el
|
||||
// prefix `jump_` i hi afegeix el sufix cardinal correcte —
|
||||
// `jump_north`, `jump_south`, `jump_east` o `jump_west` segons el
|
||||
// facing actual. Si demà cal una animació de "cara de pànic", només
|
||||
// cal canviar aquí pel `setCurrentAnimation` corresponent.
|
||||
setCurrentAnimation(animationName(false));
|
||||
}
|
||||
|
||||
void Player::spawnFromAir(float x, float y) {
|
||||
// Net total: equival a un `respawn` seguit d'una entrada en l'aire.
|
||||
// Així el flag de gir, l'animació, els flancs del salt i les
|
||||
|
||||
Reference in new issue
Block a user