feat(player): hit-box 8×8 per a contacte actor-actor (player, momia, trampa)
This commit is contained in:
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); }
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -98,8 +98,13 @@ namespace {
|
||||
auto* b = ps[1];
|
||||
if (a == nullptr || b == nullptr) { return; }
|
||||
|
||||
const SDL_FRect BOX_A = a->getBaseBox();
|
||||
const SDL_FRect BOX_B = b->getBaseBox();
|
||||
// Calculem l'overlap previst en l'espai de la hit-box petita:
|
||||
// els jugadors han de poder acostar-se molt (caixes de moviment
|
||||
// 16×16 quasi tocant) sense disparar el resolver; només quan el
|
||||
// contacte real (hit-box 8×8) s'activaria, neutralitzem o apliquem
|
||||
// push.
|
||||
const SDL_FRect BOX_A = a->getHitBox();
|
||||
const SDL_FRect BOX_B = b->getHitBox();
|
||||
const SDL_FRect NEXT_A{
|
||||
.x = BOX_A.x + (a->getIntendedVX() * dt),
|
||||
.y = BOX_A.y + (a->getIntendedVY() * dt),
|
||||
|
||||
Reference in new issue
Block a user