feat(player): hit-box 8×8 per a contacte actor-actor (player, momia, trampa)

This commit is contained in:
JailDesigner committed 2026-04-24 19:23:30 +02:00
1 parent 511f8d5b09
commit 858e444842
6 files changed
+84 -8

No files matched your search

@@ -65,7 +65,11 @@ void EntityManager::checkPlayerCollisions(const std::vector<Player*>& players) {
const auto BOX = entity->getCollisionRect();
for (auto* p : players) {
if (p == nullptr) { continue; }
const auto PBOX = p->getBaseBox();
// Contacte actor-actor amb hit-box petita: la caixa base
// 16×16 servia per moure (xoc amb murs), però per a l'event
// "toca una entitat" volem precisió — que la momia mate quan
// els píxels centrals es solapen, no quan els caps es freguen.
const auto PBOX = p->getHitBox();
const bool OVERLAP = PBOX.x + PBOX.w > BOX.x && BOX.x + BOX.w > PBOX.x && PBOX.y + PBOX.h > BOX.y && BOX.y + BOX.h > PBOX.y;
if (OVERLAP) { entity->onPlayerContact(p); }
}
+38 -3
View File
@@ -77,6 +77,14 @@ void Player::loadFrom(const std::string& yaml_path) {
collision_x_offset = -collision_padding;
collision_y_offset = (sprite_h - base_size) - collision_padding;
// Hit-box opcional (actor-actor). Si no hi és, s'iguala a la base
// per preservar compatibilitat amb YAMLs antics. Concèntrica amb la
// caixa de col·lisió: offset interior = (collision_size - hit_size) / 2.
hit_size = c.contains("hit_size") ? c["hit_size"].get_value<int>() : collision_size;
const int HIT_INSET = (collision_size - hit_size) / 2;
hit_x_offset = collision_x_offset + HIT_INSET;
hit_y_offset = collision_y_offset + HIT_INSET;
// Bloc opcional `turn:` — si manca, `duration_ms` queda a 0 i la
// mecànica del gir de 180° no s'activa (canvis de direcció instantanis).
turn_config = TurnConfig{};
@@ -248,10 +256,16 @@ void Player::update(float delta_time) {
auto Player::collidesWithWorld(const SDL_FRect& box) const -> bool {
if (map_.collides(box)) { return true; }
if (all_players_ == nullptr) { return false; }
return std::ranges::any_of(*all_players_, [this, &box](const Player* p) {
// Per al contacte actor-actor usem la hit-box petita a banda i banda:
// projectem la hit-box des de la posició base proposada i la comparem
// amb la hit-box actual de l'altre jugador. Així la caixa de moviment
// (16×16) segueix topant amb murs/sarcs però els jugadors poden
// acostar-se fins al contacte real.
const SDL_FRect PROPOSED_HIT = hitBoxFromBase(box);
return std::ranges::any_of(*all_players_, [this, &PROPOSED_HIT](const Player* p) {
if (p == nullptr || p == this) { return false; }
const auto OTHER = p->getBaseBox();
return SDL_HasRectIntersectionFloat(&box, &OTHER);
const auto OTHER = p->getHitBox();
return SDL_HasRectIntersectionFloat(&PROPOSED_HIT, &OTHER);
});
}
@@ -397,6 +411,27 @@ auto Player::getBaseBox() const -> SDL_FRect {
};
}
auto Player::getHitBox() const -> SDL_FRect {
return {
.x = getPosX() + static_cast<float>(hit_x_offset),
.y = getPosY() + static_cast<float>(hit_y_offset),
.w = static_cast<float>(hit_size),
.h = static_cast<float>(hit_size),
};
}
auto Player::hitBoxFromBase(const SDL_FRect& base) -> SDL_FRect {
// Desplaçament intern respecte al cantó de la caixa base: mateixos
// offsets que `getHitBox()` però partint d'una caixa arbitrària.
const int INSET = (collision_size - hit_size) / 2;
return {
.x = base.x + static_cast<float>(INSET),
.y = base.y + static_cast<float>(INSET),
.w = static_cast<float>(hit_size),
.h = static_cast<float>(hit_size),
};
}
void Player::snapToLane(
float delta_time,
float base,
+24 -2
View File
@@ -40,6 +40,17 @@ class Player : public AnimatedSprite {
inline static int collision_x_offset = 0;
inline static int collision_y_offset = 0;
// Caixa de "contacte" més petita que la caixa de col·lisió: s'usa per
// a les interaccions actor-actor (player↔player, player↔momia/trampa),
// on volem que el contacte sigui precís i no dispari a "distància"
// de la vora del sprite. La caixa de col·lisió gran continua usant-se
// per xocar amb murs i sarcòfags (per mantenir el carril del
// passadís). Sense `hit_size` al YAML, s'iguala a `collision_size` i
// el comportament és idèntic a l'anterior.
inline static int hit_size = 0;
inline static int hit_x_offset = 0;
inline static int hit_y_offset = 0;
// Ha de cridar-se abans d'instanciar Player; GameScene ho fa al ctor.
static void loadFrom(const std::string& yaml_path);
@@ -115,10 +126,21 @@ class Player : public AnimatedSprite {
[[nodiscard]] auto getCurrentTile() const -> std::pair<int, int>;
// Rectangle de col·lisió del jugador, en coordenades del canvas. Usat
// pel mapa de col·lisions i per les entitats que comproven contacte
// amb el jugador (momies, trampes, porta).
// pel mapa de col·lisions (tryMove contra murs/sarcòfags).
[[nodiscard]] auto getBaseBox() const -> SDL_FRect;
// Rectangle de contacte actor-actor (hit-box), més petit que la caixa
// base i concèntric amb ella. L'usen `checkPlayerCollisions`
// (momies, trampes, porta) i el resolver jugador↔jugador per
// decidir quan es toquen.
[[nodiscard]] auto getHitBox() const -> SDL_FRect;
// Versió estàtica: deriva la hit-box a partir d'una caixa base
// arbitrària (p. ex. la posició proposada per `tryMove` abans
// d'aplicar-la). Evita recalcular `hit_x_offset`/`hit_y_offset` fora
// de Player.
[[nodiscard]] static auto hitBoxFromBase(const SDL_FRect& base) -> SDL_FRect;
// Override del hook de Sprite: `MovingSprite::tryMove` envia aquesta
// caixa al callback de col·lisió. La base del Player és més petita
// que el sprite (el cap pot sobresortir per murs/sarcòfags sense