fix(bullet): col·lisió swept, sense grace_timer, mor al border visual
This commit is contained in:
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "game/entities/bullet.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
@@ -43,8 +42,8 @@ void Bullet::init() {
|
||||
// Inicialment inactiva
|
||||
is_active_ = false;
|
||||
center_ = {.x = 0.0F, .y = 0.0F};
|
||||
prev_position_ = {.x = 0.0F, .y = 0.0F};
|
||||
angle_ = 0.0F;
|
||||
grace_timer_ = 0.0F;
|
||||
|
||||
// Reset del cuerpo físico
|
||||
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)
|
||||
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
|
||||
center_ = position;
|
||||
prev_position_ = position; // Al spawn no hi ha moviment encara: swept degenera a punt-cercle
|
||||
angle_ = angle;
|
||||
|
||||
// Sincronizar el body físico: posición + velocidad cartesiana
|
||||
@@ -82,37 +79,34 @@ void Bullet::disparar(const Vec2& position, float angle, uint8_t owner_id) {
|
||||
Audio::get()->playSound(Defaults::Sound::LASER, Audio::Group::GAME);
|
||||
}
|
||||
|
||||
void Bullet::update(float delta_time) {
|
||||
void Bullet::update(float /*delta_time*/) {
|
||||
if (!is_active_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Aquí solo lógica de estado: detectar salida del PLAYAREA i desactivar.
|
||||
// Sense marge de seguretat: la bala mor quan la seva aresta toca el border visual
|
||||
// (centre a BULLET_RADIUS del límit). El MARGE_SEGURETAT de getSafePlayAreaBounds
|
||||
// és per a spawn d'enemics, no per a desactivació de bales.
|
||||
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);
|
||||
Constants::getPlayAreaBounds(min_x, max_x, min_y, max_y);
|
||||
constexpr float R = Defaults::Entities::BULLET_RADIUS;
|
||||
|
||||
if (body_.position.x < min_x || body_.position.x > max_x ||
|
||||
body_.position.y < min_y || body_.position.y > max_y) {
|
||||
if (body_.position.x < min_x + R || body_.position.x > max_x - R ||
|
||||
body_.position.y < min_y + R || body_.position.y > max_y - R) {
|
||||
desactivar();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
// angle_ no cambia (las balas no rotan visualmente).
|
||||
}
|
||||
|
||||
@@ -11,38 +11,39 @@
|
||||
#include "core/types.hpp"
|
||||
|
||||
class Bullet : public Entities::Entity {
|
||||
public:
|
||||
Bullet()
|
||||
: Entity(nullptr) {}
|
||||
explicit Bullet(Rendering::Renderer* renderer);
|
||||
public:
|
||||
Bullet()
|
||||
: Entity(nullptr) {}
|
||||
explicit Bullet(Rendering::Renderer* renderer);
|
||||
|
||||
void init() override;
|
||||
void disparar(const Vec2& position, float angle, uint8_t owner_id);
|
||||
void update(float delta_time) override;
|
||||
void postUpdate(float delta_time) override;
|
||||
void draw() const override;
|
||||
void init() override;
|
||||
void disparar(const Vec2& position, float angle, uint8_t owner_id);
|
||||
void update(float delta_time) override;
|
||||
void postUpdate(float delta_time) override;
|
||||
void draw() const override;
|
||||
|
||||
// Override: Interfaz de Entity
|
||||
[[nodiscard]] auto isActive() const -> bool override { return is_active_; }
|
||||
// Override: Interfaz de Entity
|
||||
[[nodiscard]] auto isActive() const -> bool override { return is_active_; }
|
||||
|
||||
// Override: Interfaz de colisión (gameplay-level: PLAYAREA bounds-check)
|
||||
[[nodiscard]] auto getCollisionRadius() const -> float override {
|
||||
return Defaults::Entities::BULLET_RADIUS;
|
||||
}
|
||||
[[nodiscard]] auto isCollidable() const -> bool override {
|
||||
return is_active_ && grace_timer_ <= 0.0F;
|
||||
}
|
||||
// Override: Interfaz de colisión (gameplay-level: PLAYAREA bounds-check)
|
||||
[[nodiscard]] auto getCollisionRadius() const -> float override {
|
||||
return Defaults::Entities::BULLET_RADIUS;
|
||||
}
|
||||
[[nodiscard]] auto isCollidable() const -> bool override {
|
||||
return is_active_;
|
||||
}
|
||||
|
||||
// Getters (API pública sin cambios)
|
||||
[[nodiscard]] auto getOwnerId() const -> uint8_t { return owner_id_; }
|
||||
[[nodiscard]] auto getGraceTimer() const -> float { return grace_timer_; }
|
||||
void desactivar();
|
||||
// Getters (API pública sin cambios)
|
||||
[[nodiscard]] auto getOwnerId() const -> uint8_t { return owner_id_; }
|
||||
// Posició al final del frame anterior, per a CCD segment-vs-cercle.
|
||||
[[nodiscard]] auto getPrevPosition() const -> const Vec2& { return prev_position_; }
|
||||
void desactivar();
|
||||
|
||||
private:
|
||||
// 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
|
||||
// dejen el objeto en estado coherente (proyectil inactivo, sin owner, sin grace timer).
|
||||
bool is_active_{false};
|
||||
uint8_t owner_id_{0}; // 0=P1, 1=P2
|
||||
float grace_timer_{0.0F}; // Grace period timer (0.0 = vulnerable)
|
||||
private:
|
||||
// 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
|
||||
// dejen el objeto en estado coherente (proyectil inactivo, sin owner).
|
||||
bool is_active_{false};
|
||||
uint8_t owner_id_{0}; // 0=P1, 1=P2
|
||||
Vec2 prev_position_{}; // Posició al final del frame anterior (per a swept collision)
|
||||
};
|
||||
|
||||
@@ -465,11 +465,14 @@ void GameScene::runStagePlaying(float delta_time) {
|
||||
for (auto& enemy : enemies_) {
|
||||
enemy.update(delta_time);
|
||||
}
|
||||
|
||||
// Col·lisions primer, després `bullet.update()`: si una bala el mateix frame xoca
|
||||
// amb un enemic i alhora surt del PLAYAREA, ha de comptar com a impacte abans de
|
||||
// ser desactivada per fora-de-zona.
|
||||
runCollisionDetections();
|
||||
for (auto& bullet : bullets_) {
|
||||
bullet.update(delta_time);
|
||||
}
|
||||
|
||||
runCollisionDetections();
|
||||
debris_manager_.update(delta_time);
|
||||
firework_manager_.update(delta_time);
|
||||
floating_score_manager_.update(delta_time);
|
||||
|
||||
@@ -87,8 +87,11 @@ namespace Systems::Collision {
|
||||
constexpr float AMPLIFIER = Defaults::Game::COLLISION_BULLET_ENEMY_AMPLIFIER;
|
||||
|
||||
for (auto& bullet : ctx.bullets) {
|
||||
if (!bullet.isActive()) {
|
||||
continue;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -207,12 +210,17 @@ namespace Systems::Collision {
|
||||
constexpr float AMPLIFIER = Defaults::Game::COLLISION_BULLET_PLAYER_AMPLIFIER;
|
||||
|
||||
for (auto& bullet : ctx.bullets) {
|
||||
if (!bullet.isActive() || bullet.getGraceTimer() > 0.0F) {
|
||||
if (!bullet.isActive()) {
|
||||
continue;
|
||||
}
|
||||
const uint8_t BULLET_OWNER = bullet.getOwnerId();
|
||||
|
||||
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 ||
|
||||
!ctx.ships[player_id].isActive() ||
|
||||
ctx.ships[player_id].isInvulnerable()) {
|
||||
@@ -225,20 +233,14 @@ namespace Systems::Collision {
|
||||
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;
|
||||
}
|
||||
|
||||
// *** FRIENDLY FIRE HIT ***
|
||||
if (BULLET_OWNER == player_id) {
|
||||
// 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.lives_per_player[BULLET_OWNER]++;
|
||||
}
|
||||
|
||||
// *** TEAMMATE HIT (friendly fire) ***
|
||||
// Víctima perd 1 vida, atacant en guanya 1.
|
||||
ctx.on_player_hit(player_id);
|
||||
ctx.lives_per_player[BULLET_OWNER]++;
|
||||
Audio::get()->playSound(Defaults::Sound::FRIENDLY_FIRE_HIT, Audio::Group::GAME);
|
||||
bullet.desactivar();
|
||||
break; // Una bullet solo impacta una vez por frame
|
||||
|
||||
Reference in New Issue
Block a user