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
@@ -10,6 +10,11 @@ frameHeight: 32
|
|||||||
collision:
|
collision:
|
||||||
size: 16
|
size: 16
|
||||||
base_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:
|
animations:
|
||||||
- name: stand_down
|
- name: stand_down
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ frameHeight: 32
|
|||||||
collision:
|
collision:
|
||||||
size: 16
|
size: 16
|
||||||
base_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:
|
animations:
|
||||||
- name: stand_down
|
- name: stand_down
|
||||||
|
|||||||
@@ -65,7 +65,11 @@ void EntityManager::checkPlayerCollisions(const std::vector<Player*>& players) {
|
|||||||
const auto BOX = entity->getCollisionRect();
|
const auto BOX = entity->getCollisionRect();
|
||||||
for (auto* p : players) {
|
for (auto* p : players) {
|
||||||
if (p == nullptr) { continue; }
|
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;
|
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); }
|
if (OVERLAP) { entity->onPlayerContact(p); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,14 @@ void Player::loadFrom(const std::string& yaml_path) {
|
|||||||
collision_x_offset = -collision_padding;
|
collision_x_offset = -collision_padding;
|
||||||
collision_y_offset = (sprite_h - base_size) - 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
|
// 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).
|
// mecànica del gir de 180° no s'activa (canvis de direcció instantanis).
|
||||||
turn_config = TurnConfig{};
|
turn_config = TurnConfig{};
|
||||||
@@ -248,10 +256,16 @@ void Player::update(float delta_time) {
|
|||||||
auto Player::collidesWithWorld(const SDL_FRect& box) const -> bool {
|
auto Player::collidesWithWorld(const SDL_FRect& box) const -> bool {
|
||||||
if (map_.collides(box)) { return true; }
|
if (map_.collides(box)) { return true; }
|
||||||
if (all_players_ == nullptr) { return false; }
|
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; }
|
if (p == nullptr || p == this) { return false; }
|
||||||
const auto OTHER = p->getBaseBox();
|
const auto OTHER = p->getHitBox();
|
||||||
return SDL_HasRectIntersectionFloat(&box, &OTHER);
|
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(
|
void Player::snapToLane(
|
||||||
float delta_time,
|
float delta_time,
|
||||||
float base,
|
float base,
|
||||||
|
|||||||
@@ -40,6 +40,17 @@ class Player : public AnimatedSprite {
|
|||||||
inline static int collision_x_offset = 0;
|
inline static int collision_x_offset = 0;
|
||||||
inline static int collision_y_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.
|
// Ha de cridar-se abans d'instanciar Player; GameScene ho fa al ctor.
|
||||||
static void loadFrom(const std::string& yaml_path);
|
static void loadFrom(const std::string& yaml_path);
|
||||||
|
|
||||||
@@ -115,10 +126,21 @@ class Player : public AnimatedSprite {
|
|||||||
[[nodiscard]] auto getCurrentTile() const -> std::pair<int, int>;
|
[[nodiscard]] auto getCurrentTile() const -> std::pair<int, int>;
|
||||||
|
|
||||||
// Rectangle de col·lisió del jugador, en coordenades del canvas. Usat
|
// Rectangle de col·lisió del jugador, en coordenades del canvas. Usat
|
||||||
// pel mapa de col·lisions i per les entitats que comproven contacte
|
// pel mapa de col·lisions (tryMove contra murs/sarcòfags).
|
||||||
// amb el jugador (momies, trampes, porta).
|
|
||||||
[[nodiscard]] auto getBaseBox() const -> SDL_FRect;
|
[[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
|
// Override del hook de Sprite: `MovingSprite::tryMove` envia aquesta
|
||||||
// caixa al callback de col·lisió. La base del Player és més petita
|
// 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
|
// que el sprite (el cap pot sobresortir per murs/sarcòfags sense
|
||||||
|
|||||||
@@ -98,8 +98,13 @@ namespace {
|
|||||||
auto* b = ps[1];
|
auto* b = ps[1];
|
||||||
if (a == nullptr || b == nullptr) { return; }
|
if (a == nullptr || b == nullptr) { return; }
|
||||||
|
|
||||||
const SDL_FRect BOX_A = a->getBaseBox();
|
// Calculem l'overlap previst en l'espai de la hit-box petita:
|
||||||
const SDL_FRect BOX_B = b->getBaseBox();
|
// 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{
|
const SDL_FRect NEXT_A{
|
||||||
.x = BOX_A.x + (a->getIntendedVX() * dt),
|
.x = BOX_A.x + (a->getIntendedVX() * dt),
|
||||||
.y = BOX_A.y + (a->getIntendedVY() * dt),
|
.y = BOX_A.y + (a->getIntendedVY() * dt),
|
||||||
|
|||||||
Reference in new issue
Block a user