feat(player): sombra translúcida sota el jugador mentre està en l'aire
This commit is contained in:
1 parent
c4caa9021e
commit
64390f8ad7
3 files changed
+46
No files matched your search
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include "core/input/gamepad_manager.hpp"
|
#include "core/input/gamepad_manager.hpp"
|
||||||
#include "core/input/input_state.hpp"
|
#include "core/input/input_state.hpp"
|
||||||
|
#include "core/rendering/screen.hpp"
|
||||||
#include "core/resources/resource_cache.hpp"
|
#include "core/resources/resource_cache.hpp"
|
||||||
#include "core/resources/resource_helper.hpp"
|
#include "core/resources/resource_helper.hpp"
|
||||||
#include "external/fkyaml_node.hpp"
|
#include "external/fkyaml_node.hpp"
|
||||||
@@ -689,6 +690,39 @@ void Player::render() {
|
|||||||
pos_.y = SAVED_Y;
|
pos_.y = SAVED_Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Player::renderShadow() const {
|
||||||
|
// Sense salt actiu, no hi ha sombra: això simplifica els callsites,
|
||||||
|
// que poden cridar `renderShadow` per a tots els jugadors sense
|
||||||
|
// discriminar.
|
||||||
|
if (!isAirborne()) { return; }
|
||||||
|
|
||||||
|
// Sombra centrada al sprite, recolzada a la línia dels peus reals
|
||||||
|
// (ignorant `jump_z_`). L'amplada es redueix proporcional a l'altura
|
||||||
|
// del salt — al pic queda més estreta, donant la sensació de
|
||||||
|
// "elevació" sense necessitat de coordenades 3D reals. Si el
|
||||||
|
// jump_config té un divisor zero (cas degenerat), només dibuixem
|
||||||
|
// la sombra a mida plena per evitar nan.
|
||||||
|
const auto PEAK = (jump_config.gravity_px_per_s2 > 0.0F)
|
||||||
|
? (jump_config.impulse_px_per_s * jump_config.impulse_px_per_s) / (2.0F * jump_config.gravity_px_per_s2)
|
||||||
|
: 0.0F;
|
||||||
|
const float NORM_Z = (PEAK > 0.0F) ? std::clamp(jump_z_ / PEAK, 0.0F, 1.0F) : 0.0F;
|
||||||
|
constexpr float MAX_SHRINK = 0.4F; // Fins a un 40% més estreta al pic.
|
||||||
|
const float SHRINK = 1.0F - (NORM_Z * MAX_SHRINK);
|
||||||
|
|
||||||
|
constexpr int SHADOW_H = 3;
|
||||||
|
const auto SHADOW_W = static_cast<int>(static_cast<float>(base_size) * SHRINK);
|
||||||
|
const float CX = getPosX() + (static_cast<float>(sprite_w) * 0.5F);
|
||||||
|
const float GROUND_Y = getGroundFeetY();
|
||||||
|
const auto SX = static_cast<int>(CX - (static_cast<float>(SHADOW_W) * 0.5F));
|
||||||
|
const auto SY = static_cast<int>(GROUND_Y) - SHADOW_H;
|
||||||
|
|
||||||
|
// ARGB negre amb alpha ~40%. `blendRect` mescla sobre el canvas; així
|
||||||
|
// la sombra fosqueja el sòl (pintat o no) sense tapar-lo del tot.
|
||||||
|
constexpr std::uint32_t SHADOW_ARGB = 0x00000000U;
|
||||||
|
constexpr std::uint8_t SHADOW_ALPHA = 100;
|
||||||
|
Screen::get().blendRect(SX, SY, SHADOW_W, SHADOW_H, SHADOW_ARGB, SHADOW_ALPHA);
|
||||||
|
}
|
||||||
|
|
||||||
auto Player::animationName(bool moving) const -> const char* {
|
auto Player::animationName(bool moving) const -> const char* {
|
||||||
// En l'aire forcem la variant `jump_*` (per direcció), independent de
|
// En l'aire forcem la variant `jump_*` (per direcció), independent de
|
||||||
// si hi ha velocitat horitzontal: per al jugador, l'estat visual del
|
// si hi ha velocitat horitzontal: per al jugador, l'estat visual del
|
||||||
|
|||||||
@@ -212,6 +212,12 @@ class Player : public AnimatedSprite {
|
|||||||
// la posició al món no s'alteren.
|
// la posició al món no s'alteren.
|
||||||
void render() override;
|
void render() override;
|
||||||
|
|
||||||
|
// Pinta una sombra translúcida sota el jugador a nivell de sòl
|
||||||
|
// (sense aplicar `jump_z_`). El GameScene la crida en una passada
|
||||||
|
// pre-actors, així queda per sota dels actors i del propi jugador.
|
||||||
|
// No-op si el jugador no està en l'aire.
|
||||||
|
void renderShadow() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Alias privat històric. Mantingut perquè viu dins de l'implementació
|
// Alias privat històric. Mantingut perquè viu dins de l'implementació
|
||||||
// (snapToLane, clamp). Els consumidors externs usen `getBaseBox()`.
|
// (snapToLane, clamp). Els consumidors externs usen `getBaseBox()`.
|
||||||
|
|||||||
@@ -532,6 +532,12 @@ void GameScene::render() {
|
|||||||
// explícita: l'editor edita el tilemap, no la capa de pintat).
|
// explícita: l'editor edita el tilemap, no la capa de pintat).
|
||||||
if (!SKIP_ACTORS && paint_layer_) { paint_layer_->render(); }
|
if (!SKIP_ACTORS && paint_layer_) { paint_layer_->render(); }
|
||||||
if (!SKIP_ACTORS) {
|
if (!SKIP_ACTORS) {
|
||||||
|
// Passada de sombres dels jugadors saltant. Va per davant del
|
||||||
|
// pintat (al damunt) però per darrere de TOTS els actors —
|
||||||
|
// entitats i jugadors, inclòs el saltador mateix— així la sombra
|
||||||
|
// queda al sòl sense tapar res. `renderShadow` és no-op si el
|
||||||
|
// jugador està a terra, així que no cal filtrar al callsite.
|
||||||
|
for (const auto& p : players_) { p->renderShadow(); }
|
||||||
// Z-order interleaving entitats ↔ players per `getFeetY()` ascendent:
|
// Z-order interleaving entitats ↔ players per `getFeetY()` ascendent:
|
||||||
// els actors amb els peus més amunt queden dibuixats primer (al fons),
|
// els actors amb els peus més amunt queden dibuixats primer (al fons),
|
||||||
// els més avall al davant. Així un sarc situat per darrere d'un player
|
// els més avall al davant. Així un sarc situat per darrere d'un player
|
||||||
|
|||||||
Reference in new issue
Block a user