From 858e444842c9a15459c6bae4bcfdda24c6d43f3f Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 24 Apr 2026 19:23:30 +0200 Subject: [PATCH] =?UTF-8?q?feat(player):=20hit-box=208=C3=978=20per=20a=20?= =?UTF-8?q?contacte=20actor-actor=20(player,=20momia,=20trampa)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/player/morcus.yaml | 5 +++ data/player/sam.yaml | 5 +++ .../entities/entity_manager_loadlevel.cpp | 6 ++- source/game/entities/player.cpp | 41 +++++++++++++++++-- source/game/entities/player.hpp | 26 +++++++++++- source/game/scenes/game_scene.cpp | 9 +++- 6 files changed, 84 insertions(+), 8 deletions(-) diff --git a/data/player/morcus.yaml b/data/player/morcus.yaml index 5146dbc..5d92dd0 100644 --- a/data/player/morcus.yaml +++ b/data/player/morcus.yaml @@ -10,6 +10,11 @@ frameHeight: 32 collision: size: 16 base_size: 16 + # Caixa de contacte actor-actor (jugador↔jugador, jugador↔momia/trampa). + # Més petita que `size` perquè els contactes d'acció siguin precisos; + # `size` es reserva per a la col·lisió amb murs/sarcòfags i per enfilar + # els passadissos. Centrada en el mateix punt que la caixa base. + hit_size: 8 animations: - name: stand_down diff --git a/data/player/sam.yaml b/data/player/sam.yaml index 233506b..b59e98f 100644 --- a/data/player/sam.yaml +++ b/data/player/sam.yaml @@ -10,6 +10,11 @@ frameHeight: 32 collision: size: 16 base_size: 16 + # Caixa de contacte actor-actor (jugador↔jugador, jugador↔momia/trampa). + # Més petita que `size` perquè els contactes d'acció siguin precisos; + # `size` es reserva per a la col·lisió amb murs/sarcòfags i per enfilar + # els passadissos. Centrada en el mateix punt que la caixa base. + hit_size: 8 animations: - name: stand_down diff --git a/source/game/entities/entity_manager_loadlevel.cpp b/source/game/entities/entity_manager_loadlevel.cpp index e1edc64..4d26778 100644 --- a/source/game/entities/entity_manager_loadlevel.cpp +++ b/source/game/entities/entity_manager_loadlevel.cpp @@ -65,7 +65,11 @@ void EntityManager::checkPlayerCollisions(const std::vector& 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); } } diff --git a/source/game/entities/player.cpp b/source/game/entities/player.cpp index dc8b54a..b33fc99 100644 --- a/source/game/entities/player.cpp +++ b/source/game/entities/player.cpp @@ -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() : 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(hit_x_offset), + .y = getPosY() + static_cast(hit_y_offset), + .w = static_cast(hit_size), + .h = static_cast(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(INSET), + .y = base.y + static_cast(INSET), + .w = static_cast(hit_size), + .h = static_cast(hit_size), + }; +} + void Player::snapToLane( float delta_time, float base, diff --git a/source/game/entities/player.hpp b/source/game/entities/player.hpp index 4c17985..a450587 100644 --- a/source/game/entities/player.hpp +++ b/source/game/entities/player.hpp @@ -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; // 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 diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index 629c9b8..d16ae04 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -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),