Merge branch 'fix/bullet-collision-swept': col·lisió bales swept + debris
This commit is contained in:
@@ -26,7 +26,6 @@ namespace Defaults::Game {
|
|||||||
// Friendly fire system
|
// Friendly fire system
|
||||||
constexpr bool FRIENDLY_FIRE_ENABLED = true; // Activar friendly fire
|
constexpr bool FRIENDLY_FIRE_ENABLED = true; // Activar friendly fire
|
||||||
constexpr float COLLISION_BULLET_PLAYER_AMPLIFIER = 1.0F; // Hitbox exacto (100%)
|
constexpr float COLLISION_BULLET_PLAYER_AMPLIFIER = 1.0F; // Hitbox exacto (100%)
|
||||||
constexpr float BULLET_GRACE_PERIOD = 0.2F; // Inmunidad post-disparo (s)
|
|
||||||
constexpr float BULLET_SPEED = 700.0F; // Velocidad escalar (px/s). Pascal: 7 px/frame × 20 FPS
|
constexpr float BULLET_SPEED = 700.0F; // Velocidad escalar (px/s). Pascal: 7 px/frame × 20 FPS
|
||||||
|
|
||||||
// Transición LEVEL_START (mensajes aleatorios PRE-level)
|
// Transición LEVEL_START (mensajes aleatorios PRE-level)
|
||||||
|
|||||||
@@ -3,13 +3,15 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include "core/entities/entity.hpp"
|
#include "core/entities/entity.hpp"
|
||||||
#include "core/types.hpp"
|
#include "core/types.hpp"
|
||||||
|
|
||||||
namespace Physics {
|
namespace Physics {
|
||||||
|
|
||||||
// Comprobación genèrica de colisión entre dues entidades
|
// Comprobación genèrica de colisión entre dues entidades
|
||||||
inline auto checkCollision(const Entities::Entity& a, const Entities::Entity& b, float amplifier = 1.0F) -> bool {
|
inline auto checkCollision(const Entities::Entity& a, const Entities::Entity& b, float amplifier = 1.0F) -> bool {
|
||||||
// Comprovar si ambdós són col·lisionables
|
// Comprovar si ambdós són col·lisionables
|
||||||
if (!a.isCollidable() || !b.isCollidable()) {
|
if (!a.isCollidable() || !b.isCollidable()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -27,6 +29,36 @@ inline auto checkCollision(const Entities::Entity& a, const Entities::Entity& b,
|
|||||||
float dist_sq = (dx * dx) + (dy * dy);
|
float dist_sq = (dx * dx) + (dy * dy);
|
||||||
|
|
||||||
return dist_sq <= suma_radis_sq;
|
return dist_sq <= suma_radis_sq;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Swept collision: una entitat mòbil (radi r_a) s'ha desplaçat de p0 a p1 aquest
|
||||||
|
// frame. Comprova si el segment expandit pel radi conjunt (r_a + radi de b, amb
|
||||||
|
// amplificador) toca el cercle de l'entity b. Equival al check discrete quan
|
||||||
|
// p0 == p1 (sense moviment). Evita tunneling a velocitats altes.
|
||||||
|
inline auto checkCollisionSwept(const Vec2& p0, const Vec2& p1, float r_a, const Entities::Entity& b, float amplifier = 1.0F) -> bool {
|
||||||
|
if (!b.isCollidable()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const float SUM_R = (r_a + b.getCollisionRadius()) * amplifier;
|
||||||
|
const float SUM_R_SQ = SUM_R * SUM_R;
|
||||||
|
const Vec2& center_b = b.getCenter();
|
||||||
|
const float DX_SEG = p1.x - p0.x;
|
||||||
|
const float DY_SEG = p1.y - p0.y;
|
||||||
|
const float LEN_SQ = (DX_SEG * DX_SEG) + (DY_SEG * DY_SEG);
|
||||||
|
// Degenerat: punt-cercle (frame de spawn, o entitat parada).
|
||||||
|
if (LEN_SQ <= 0.0F) {
|
||||||
|
const float DX = p0.x - center_b.x;
|
||||||
|
const float DY = p0.y - center_b.y;
|
||||||
|
return ((DX * DX) + (DY * DY)) <= SUM_R_SQ;
|
||||||
|
}
|
||||||
|
// Projecció del centre sobre la recta del segment, clamp a [0,1] per acotar al segment.
|
||||||
|
const float T_RAW = (((center_b.x - p0.x) * DX_SEG) + ((center_b.y - p0.y) * DY_SEG)) / LEN_SQ;
|
||||||
|
const float T_CLAMPED = std::clamp(T_RAW, 0.0F, 1.0F);
|
||||||
|
const float CLOSEST_X = p0.x + (DX_SEG * T_CLAMPED);
|
||||||
|
const float CLOSEST_Y = p0.y + (DY_SEG * T_CLAMPED);
|
||||||
|
const float DX = CLOSEST_X - center_b.x;
|
||||||
|
const float DY = CLOSEST_Y - center_b.y;
|
||||||
|
return ((DX * DX) + (DY * DY)) <= SUM_R_SQ;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Physics
|
} // namespace Physics
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
#include "game/entities/bullet.hpp"
|
#include "game/entities/bullet.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -43,8 +42,8 @@ void Bullet::init() {
|
|||||||
// Inicialment inactiva
|
// Inicialment inactiva
|
||||||
is_active_ = false;
|
is_active_ = false;
|
||||||
center_ = {.x = 0.0F, .y = 0.0F};
|
center_ = {.x = 0.0F, .y = 0.0F};
|
||||||
|
prev_position_ = {.x = 0.0F, .y = 0.0F};
|
||||||
angle_ = 0.0F;
|
angle_ = 0.0F;
|
||||||
grace_timer_ = 0.0F;
|
|
||||||
|
|
||||||
// Reset del cuerpo físico
|
// Reset del cuerpo físico
|
||||||
body_.position = Vec2{};
|
body_.position = Vec2{};
|
||||||
@@ -61,11 +60,9 @@ void Bullet::disparar(const Vec2& position, float angle, uint8_t owner_id) {
|
|||||||
// Almacenar propietario (0=P1, 1=P2)
|
// Almacenar propietario (0=P1, 1=P2)
|
||||||
owner_id_ = owner_id;
|
owner_id_ = owner_id;
|
||||||
|
|
||||||
// Activar grace period (prevents instant self-collision)
|
|
||||||
grace_timer_ = Defaults::Game::BULLET_GRACE_PERIOD;
|
|
||||||
|
|
||||||
// Posición y orientación iniciales = ship
|
// Posición y orientación iniciales = ship
|
||||||
center_ = position;
|
center_ = position;
|
||||||
|
prev_position_ = position; // Al spawn no hi ha moviment encara: swept degenera a punt-cercle
|
||||||
angle_ = angle;
|
angle_ = angle;
|
||||||
|
|
||||||
// Sincronizar el body físico: posición + velocidad cartesiana
|
// Sincronizar el body físico: posición + velocidad cartesiana
|
||||||
@@ -82,37 +79,18 @@ void Bullet::disparar(const Vec2& position, float angle, uint8_t owner_id) {
|
|||||||
Audio::get()->playSound(Defaults::Sound::LASER, Audio::Group::GAME);
|
Audio::get()->playSound(Defaults::Sound::LASER, Audio::Group::GAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bullet::update(float delta_time) {
|
void Bullet::update(float /*delta_time*/) {
|
||||||
if (!is_active_) {
|
// No-op: la desactivació per fora-de-zona viu a
|
||||||
return;
|
// Systems::Collision::desactivateOutOfBoundsBullets() perquè així té accés
|
||||||
}
|
// al DebrisManager i pot generar el "trencament" visual de la bala alhora.
|
||||||
|
// El moviment l'integra PhysicsWorld; postUpdate sincronitza center_ i prev_position_.
|
||||||
// Decrementar grace timer
|
|
||||||
if (grace_timer_ > 0.0F) {
|
|
||||||
grace_timer_ -= delta_time;
|
|
||||||
grace_timer_ = std::max(grace_timer_, 0.0F);
|
|
||||||
}
|
|
||||||
|
|
||||||
// El movimiento real lo hace PhysicsWorld::update() (integración).
|
|
||||||
// Aquí solo lógica de estado: detectar salida del PLAYAREA y desactivar.
|
|
||||||
float min_x;
|
|
||||||
float max_x;
|
|
||||||
float min_y;
|
|
||||||
float max_y;
|
|
||||||
Constants::getSafePlayAreaBounds(Defaults::Entities::BULLET_RADIUS,
|
|
||||||
min_x,
|
|
||||||
max_x,
|
|
||||||
min_y,
|
|
||||||
max_y);
|
|
||||||
|
|
||||||
if (body_.position.x < min_x || body_.position.x > max_x ||
|
|
||||||
body_.position.y < min_y || body_.position.y > max_y) {
|
|
||||||
desactivar();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bullet::postUpdate(float /*delta_time*/) {
|
void Bullet::postUpdate(float /*delta_time*/) {
|
||||||
// Sincronizar mirror desde body_ tras la integración del world.
|
// Captura la posició al final del frame anterior abans de sobreescriure center_;
|
||||||
|
// així el sistema de col·lisions pot fer swept (segment-vs-cercle) entre prev_position_
|
||||||
|
// i la nova center_, evitant tunneling a velocitats altes.
|
||||||
|
prev_position_ = center_;
|
||||||
center_ = body_.position;
|
center_ = body_.position;
|
||||||
// angle_ no cambia (las balas no rotan visualmente).
|
// angle_ no cambia (las balas no rotan visualmente).
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,19 +30,20 @@ class Bullet : public Entities::Entity {
|
|||||||
return Defaults::Entities::BULLET_RADIUS;
|
return Defaults::Entities::BULLET_RADIUS;
|
||||||
}
|
}
|
||||||
[[nodiscard]] auto isCollidable() const -> bool override {
|
[[nodiscard]] auto isCollidable() const -> bool override {
|
||||||
return is_active_ && grace_timer_ <= 0.0F;
|
return is_active_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters (API pública sin cambios)
|
// Getters (API pública sin cambios)
|
||||||
[[nodiscard]] auto getOwnerId() const -> uint8_t { return owner_id_; }
|
[[nodiscard]] auto getOwnerId() const -> uint8_t { return owner_id_; }
|
||||||
[[nodiscard]] auto getGraceTimer() const -> float { return grace_timer_; }
|
// Posició al final del frame anterior, per a CCD segment-vs-cercle.
|
||||||
|
[[nodiscard]] auto getPrevPosition() const -> const Vec2& { return prev_position_; }
|
||||||
void desactivar();
|
void desactivar();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Miembros específicos de Bullet (heredados: renderer_, shape_, center_, angle_, brightness_, body_).
|
// Miembros específicos de Bullet (heredados: renderer_, shape_, center_, angle_, brightness_, body_).
|
||||||
// Inicializados en la declaración para que tanto el ctor por defecto como el que toma renderer
|
// Inicializados en la declaración para que tanto el ctor por defecto como el que toma renderer
|
||||||
// dejen el objeto en estado coherente (proyectil inactivo, sin owner, sin grace timer).
|
// dejen el objeto en estado coherente (proyectil inactivo, sin owner).
|
||||||
bool is_active_{false};
|
bool is_active_{false};
|
||||||
uint8_t owner_id_{0}; // 0=P1, 1=P2
|
uint8_t owner_id_{0}; // 0=P1, 1=P2
|
||||||
float grace_timer_{0.0F}; // Grace period timer (0.0 = vulnerable)
|
Vec2 prev_position_{}; // Posició al final del frame anterior (per a swept collision)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include "core/audio/audio.hpp"
|
|
||||||
#include "core/defaults.hpp"
|
#include "core/defaults.hpp"
|
||||||
#include "core/entities/entity.hpp"
|
#include "core/entities/entity.hpp"
|
||||||
#include "core/graphics/shape_loader.hpp"
|
#include "core/graphics/shape_loader.hpp"
|
||||||
@@ -277,7 +276,8 @@ void Enemy::destruir() {
|
|||||||
void Enemy::herir(uint8_t shooter_id) {
|
void Enemy::herir(uint8_t shooter_id) {
|
||||||
wounded_timer_ = Defaults::Enemies::Wounded::DURATION;
|
wounded_timer_ = Defaults::Enemies::Wounded::DURATION;
|
||||||
last_hit_by_ = shooter_id;
|
last_hit_by_ = shooter_id;
|
||||||
Audio::get()->playSound(Defaults::Sound::HIT, Audio::Group::GAME);
|
// El so HIT ara el reprodueix la bala quan es trenca en debris
|
||||||
|
// (Systems::Collision::breakBullet), no l'enemic en entrar a HURT.
|
||||||
}
|
}
|
||||||
|
|
||||||
void Enemy::applyImpulse(const Vec2& impulse) {
|
void Enemy::applyImpulse(const Vec2& impulse) {
|
||||||
|
|||||||
@@ -296,6 +296,7 @@ auto GameScene::stepContinueScreen(float delta_time) -> bool {
|
|||||||
for (auto& bullet : bullets_) {
|
for (auto& bullet : bullets_) {
|
||||||
bullet.update(delta_time);
|
bullet.update(delta_time);
|
||||||
}
|
}
|
||||||
|
Systems::Collision::desactivateOutOfBoundsBullets(bullets_, debris_manager_);
|
||||||
debris_manager_.update(delta_time);
|
debris_manager_.update(delta_time);
|
||||||
firework_manager_.update(delta_time);
|
firework_manager_.update(delta_time);
|
||||||
floating_score_manager_.update(delta_time);
|
floating_score_manager_.update(delta_time);
|
||||||
@@ -321,6 +322,7 @@ auto GameScene::stepGameOver(float delta_time) -> bool {
|
|||||||
for (auto& bullet : bullets_) {
|
for (auto& bullet : bullets_) {
|
||||||
bullet.update(delta_time);
|
bullet.update(delta_time);
|
||||||
}
|
}
|
||||||
|
Systems::Collision::desactivateOutOfBoundsBullets(bullets_, debris_manager_);
|
||||||
debris_manager_.update(delta_time);
|
debris_manager_.update(delta_time);
|
||||||
firework_manager_.update(delta_time);
|
firework_manager_.update(delta_time);
|
||||||
floating_score_manager_.update(delta_time);
|
floating_score_manager_.update(delta_time);
|
||||||
@@ -438,6 +440,7 @@ void GameScene::runStageLevelStart(float delta_time) {
|
|||||||
for (auto& bullet : bullets_) {
|
for (auto& bullet : bullets_) {
|
||||||
bullet.update(delta_time);
|
bullet.update(delta_time);
|
||||||
}
|
}
|
||||||
|
Systems::Collision::desactivateOutOfBoundsBullets(bullets_, debris_manager_);
|
||||||
debris_manager_.update(delta_time);
|
debris_manager_.update(delta_time);
|
||||||
firework_manager_.update(delta_time);
|
firework_manager_.update(delta_time);
|
||||||
}
|
}
|
||||||
@@ -465,11 +468,15 @@ void GameScene::runStagePlaying(float delta_time) {
|
|||||||
for (auto& enemy : enemies_) {
|
for (auto& enemy : enemies_) {
|
||||||
enemy.update(delta_time);
|
enemy.update(delta_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Col·lisions primer, després desactivació per fora-de-zona: així una bala que
|
||||||
|
// el mateix frame xoca amb un enemic i alhora surt del PLAYAREA es compta com a
|
||||||
|
// impacte abans no se la trenqui per sortir.
|
||||||
|
runCollisionDetections();
|
||||||
for (auto& bullet : bullets_) {
|
for (auto& bullet : bullets_) {
|
||||||
bullet.update(delta_time);
|
bullet.update(delta_time);
|
||||||
}
|
}
|
||||||
|
Systems::Collision::desactivateOutOfBoundsBullets(bullets_, debris_manager_);
|
||||||
runCollisionDetections();
|
|
||||||
debris_manager_.update(delta_time);
|
debris_manager_.update(delta_time);
|
||||||
firework_manager_.update(delta_time);
|
firework_manager_.update(delta_time);
|
||||||
floating_score_manager_.update(delta_time);
|
floating_score_manager_.update(delta_time);
|
||||||
@@ -487,6 +494,7 @@ void GameScene::runStageLevelCompleted(float delta_time) {
|
|||||||
for (auto& bullet : bullets_) {
|
for (auto& bullet : bullets_) {
|
||||||
bullet.update(delta_time);
|
bullet.update(delta_time);
|
||||||
}
|
}
|
||||||
|
Systems::Collision::desactivateOutOfBoundsBullets(bullets_, debris_manager_);
|
||||||
debris_manager_.update(delta_time);
|
debris_manager_.update(delta_time);
|
||||||
firework_manager_.update(delta_time);
|
firework_manager_.update(delta_time);
|
||||||
floating_score_manager_.update(delta_time);
|
floating_score_manager_.update(delta_time);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "core/audio/audio.hpp"
|
#include "core/audio/audio.hpp"
|
||||||
#include "core/physics/collision.hpp"
|
#include "core/physics/collision.hpp"
|
||||||
#include "core/types.hpp"
|
#include "core/types.hpp"
|
||||||
|
#include "game/constants.hpp"
|
||||||
|
|
||||||
namespace Systems::Collision {
|
namespace Systems::Collision {
|
||||||
|
|
||||||
@@ -81,14 +82,40 @@ namespace Systems::Collision {
|
|||||||
// No heretem color: el burst usa el blanc per defecte per a un feel més lluminós.
|
// No heretem color: el burst usa el blanc per defecte per a un feel més lluminós.
|
||||||
ctx.firework_manager.spawn(ENEMY_POS);
|
ctx.firework_manager.spawn(ENEMY_POS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trenca una bala en debris (8 fragments de l'octàgon) + so HIT + desactiva.
|
||||||
|
// S'invoca des de qualsevol desactivació de bala (impacte amb enemic, amb jugador,
|
||||||
|
// o sortida del PLAYAREA) per a un feedback visual i sonor consistent.
|
||||||
|
void breakBullet(Effects::DebrisManager& debris_manager, Bullet& bullet) {
|
||||||
|
constexpr float DEBRIS_VELOCITY = 60.0F;
|
||||||
|
debris_manager.explode(
|
||||||
|
bullet.getShape(),
|
||||||
|
bullet.getCenter(),
|
||||||
|
bullet.getAngle(),
|
||||||
|
1.0F, // scale
|
||||||
|
DEBRIS_VELOCITY,
|
||||||
|
bullet.getBrightness(),
|
||||||
|
Vec2{}, // sense herència de velocitat (fragments radials)
|
||||||
|
0.0F, // sense velocity angular heretada
|
||||||
|
0.0F, // sense rotació visual heretada
|
||||||
|
Defaults::Sound::HIT,
|
||||||
|
Defaults::Palette::BULLET,
|
||||||
|
Defaults::Physics::Debris::TEMPS_VIDA,
|
||||||
|
Defaults::Physics::Debris::ACCELERACIO,
|
||||||
|
1); // sense duplicat de segments
|
||||||
|
bullet.desactivar();
|
||||||
|
}
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
void detectBulletEnemy(Context& ctx) {
|
void detectBulletEnemy(Context& ctx) {
|
||||||
constexpr float AMPLIFIER = Defaults::Game::COLLISION_BULLET_ENEMY_AMPLIFIER;
|
constexpr float AMPLIFIER = Defaults::Game::COLLISION_BULLET_ENEMY_AMPLIFIER;
|
||||||
|
|
||||||
for (auto& bullet : ctx.bullets) {
|
for (auto& bullet : ctx.bullets) {
|
||||||
|
if (!bullet.isActive()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
for (auto& enemy : ctx.enemies) {
|
for (auto& enemy : ctx.enemies) {
|
||||||
if (!Physics::checkCollision(bullet, enemy, AMPLIFIER)) {
|
if (!Physics::checkCollisionSwept(bullet.getPrevPosition(), bullet.getCenter(), Defaults::Entities::BULLET_RADIUS, enemy, AMPLIFIER)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +138,7 @@ namespace Systems::Collision {
|
|||||||
enemy.herir(SHOOTER);
|
enemy.herir(SHOOTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
bullet.desactivar();
|
breakBullet(ctx.debris_manager, bullet);
|
||||||
break; // Una bala impacta a un enemy y muere
|
break; // Una bala impacta a un enemy y muere
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,12 +234,17 @@ namespace Systems::Collision {
|
|||||||
constexpr float AMPLIFIER = Defaults::Game::COLLISION_BULLET_PLAYER_AMPLIFIER;
|
constexpr float AMPLIFIER = Defaults::Game::COLLISION_BULLET_PLAYER_AMPLIFIER;
|
||||||
|
|
||||||
for (auto& bullet : ctx.bullets) {
|
for (auto& bullet : ctx.bullets) {
|
||||||
if (!bullet.isActive() || bullet.getGraceTimer() > 0.0F) {
|
if (!bullet.isActive()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const uint8_t BULLET_OWNER = bullet.getOwnerId();
|
const uint8_t BULLET_OWNER = bullet.getOwnerId();
|
||||||
|
|
||||||
for (uint8_t player_id = 0; player_id < 2; player_id++) {
|
for (uint8_t player_id = 0; player_id < 2; player_id++) {
|
||||||
|
// Una bala mai no impacta al seu propi shooter: les bales d'aquest joc no
|
||||||
|
// reboten ni el shooter pot atrapar-les, així que la prevenció és per disseny.
|
||||||
|
if (BULLET_OWNER == player_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (ctx.hit_timer_per_player[player_id] > 0.0F ||
|
if (ctx.hit_timer_per_player[player_id] > 0.0F ||
|
||||||
!ctx.ships[player_id].isActive() ||
|
!ctx.ships[player_id].isActive() ||
|
||||||
ctx.ships[player_id].isInvulnerable()) {
|
ctx.ships[player_id].isInvulnerable()) {
|
||||||
@@ -225,22 +257,16 @@ namespace Systems::Collision {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Physics::checkCollision(bullet, ctx.ships[player_id], AMPLIFIER)) {
|
if (!Physics::checkCollisionSwept(bullet.getPrevPosition(), bullet.getCenter(), Defaults::Entities::BULLET_RADIUS, ctx.ships[player_id], AMPLIFIER)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// *** FRIENDLY FIRE HIT ***
|
// *** TEAMMATE HIT (friendly fire) ***
|
||||||
if (BULLET_OWNER == player_id) {
|
// Víctima perd 1 vida, atacant en guanya 1.
|
||||||
// Self-hit: víctima pierde 1 vida.
|
|
||||||
ctx.on_player_hit(player_id);
|
|
||||||
} else {
|
|
||||||
// Teammate hit: víctima pierde 1, atacante gana 1.
|
|
||||||
ctx.on_player_hit(player_id);
|
ctx.on_player_hit(player_id);
|
||||||
ctx.lives_per_player[BULLET_OWNER]++;
|
ctx.lives_per_player[BULLET_OWNER]++;
|
||||||
}
|
|
||||||
|
|
||||||
Audio::get()->playSound(Defaults::Sound::FRIENDLY_FIRE_HIT, Audio::Group::GAME);
|
Audio::get()->playSound(Defaults::Sound::FRIENDLY_FIRE_HIT, Audio::Group::GAME);
|
||||||
bullet.desactivar();
|
breakBullet(ctx.debris_manager, bullet);
|
||||||
break; // Una bullet solo impacta una vez por frame
|
break; // Una bullet solo impacta una vez por frame
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,4 +280,26 @@ namespace Systems::Collision {
|
|||||||
detectBulletPlayer(ctx);
|
detectBulletPlayer(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void desactivateOutOfBoundsBullets(
|
||||||
|
std::array<Bullet, static_cast<std::size_t>(Defaults::Entities::MAX_BALES) * 2>& bullets,
|
||||||
|
Effects::DebrisManager& debris_manager) {
|
||||||
|
float min_x;
|
||||||
|
float max_x;
|
||||||
|
float min_y;
|
||||||
|
float max_y;
|
||||||
|
Constants::getPlayAreaBounds(min_x, max_x, min_y, max_y);
|
||||||
|
constexpr float R = Defaults::Entities::BULLET_RADIUS;
|
||||||
|
|
||||||
|
for (auto& bullet : bullets) {
|
||||||
|
if (!bullet.isActive()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const Vec2& pos = bullet.getCenter();
|
||||||
|
if (pos.x < min_x + R || pos.x > max_x - R ||
|
||||||
|
pos.y < min_y + R || pos.y > max_y - R) {
|
||||||
|
breakBullet(debris_manager, bullet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Systems::Collision
|
} // namespace Systems::Collision
|
||||||
|
|||||||
@@ -70,4 +70,11 @@ namespace Systems::Collision {
|
|||||||
// Las tres en orden lógico del frame.
|
// Las tres en orden lógico del frame.
|
||||||
void detectAll(Context& ctx);
|
void detectAll(Context& ctx);
|
||||||
|
|
||||||
|
// Desactiva les bales que han sortit del PLAYAREA, generant debris visual
|
||||||
|
// (8 fragments de l'octàgon) i el so HIT. Cal cridar-la després de detectAll()
|
||||||
|
// perquè una bala que el mateix frame xoca i alhora surt es comptabilitzi com a impacte.
|
||||||
|
void desactivateOutOfBoundsBullets(
|
||||||
|
std::array<Bullet, static_cast<std::size_t>(Defaults::Entities::MAX_BALES) * 2>& bullets,
|
||||||
|
Effects::DebrisManager& debris_manager);
|
||||||
|
|
||||||
} // namespace Systems::Collision
|
} // namespace Systems::Collision
|
||||||
|
|||||||
Reference in New Issue
Block a user