feat(demo): demos a 1 i 2 jugadors, esquiva de bales enemigues i vides infinites
This commit is contained in:
@@ -32,6 +32,10 @@ namespace Systems::Demo {
|
||||
|
||||
constexpr float WALL_BIAS = 0.6F; // peso del empuje hacia el centro al esquivar
|
||||
|
||||
// Esquiva de balas enemigas.
|
||||
constexpr float DODGE_SCAN_RADIUS = 190.0F; // px: distancia a la que reacciona
|
||||
constexpr float DODGE_HEADING_MIN = 0.25F; // dot mínimo: la bala viene hacia la nave
|
||||
|
||||
// [-1, 1] aleatorio (estética: jitter de apuntado; no afecta a la simulación).
|
||||
auto randSigned() -> float {
|
||||
return (static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX) * 2.0F) - 1.0F;
|
||||
@@ -72,10 +76,71 @@ namespace Systems::Demo {
|
||||
return best;
|
||||
}
|
||||
|
||||
struct BulletThreat {
|
||||
bool found{false};
|
||||
Vec2 position{};
|
||||
Vec2 velocity{};
|
||||
};
|
||||
|
||||
// Bala enemiga más cercana que viene hacia la nave (dentro del radio de
|
||||
// reacción). Solo balas de enemic (owner_id >= ENEMY_OWNER_BASE).
|
||||
auto findBulletThreat(const Vec2& ship_pos,
|
||||
const std::array<Bullet, static_cast<std::size_t>(Defaults::Entities::MAX_BULLETS_TOTAL)>& bullets)
|
||||
-> BulletThreat {
|
||||
BulletThreat threat;
|
||||
float best_d2 = 0.0F;
|
||||
for (const auto& bullet : bullets) {
|
||||
if (!bullet.isActive() ||
|
||||
bullet.getOwnerId() < Defaults::Entities::ENEMY_OWNER_BASE) {
|
||||
continue;
|
||||
}
|
||||
const Vec2 BPOS = bullet.getCenter();
|
||||
const Vec2 TO_SHIP = ship_pos - BPOS;
|
||||
const float D2 = TO_SHIP.lengthSquared();
|
||||
if (D2 > DODGE_SCAN_RADIUS * DODGE_SCAN_RADIUS) {
|
||||
continue;
|
||||
}
|
||||
const Vec2 BVEL = bullet.getBody().velocity;
|
||||
if (BVEL.lengthSquared() < 1.0F) {
|
||||
continue;
|
||||
}
|
||||
// ¿La bala se dirige hacia la nave?
|
||||
if (BVEL.normalized().dot(TO_SHIP.normalized()) < DODGE_HEADING_MIN) {
|
||||
continue;
|
||||
}
|
||||
if (!threat.found || D2 < best_d2) {
|
||||
threat.found = true;
|
||||
best_d2 = D2;
|
||||
threat.position = BPOS;
|
||||
threat.velocity = BVEL;
|
||||
}
|
||||
}
|
||||
return threat;
|
||||
}
|
||||
|
||||
// Error angular (rad, [-PI,PI]) entre la nariz de la nave y desired_dir,
|
||||
// con el jitter de apuntado aplicado. La nariz apunta hacia (angle - PI/2).
|
||||
auto steerError(const Ship& ship, const Vec2& desired_dir, float jitter) -> float {
|
||||
const float DESIRED = std::atan2(desired_dir.y, desired_dir.x) + jitter;
|
||||
const float NOSE = ship.getAngle() - (PI / 2.0F);
|
||||
return wrapPi(DESIRED - NOSE);
|
||||
}
|
||||
|
||||
// Setea rotación según el error (RIGHT incrementa ship.angle; LEFT lo
|
||||
// decrementa), con zona muerta para no oscilar.
|
||||
void applyRotation(Control& ctrl, float error) {
|
||||
if (error > ROTATE_DEADZONE) {
|
||||
ctrl.right = true;
|
||||
} else if (error < -ROTATE_DEADZONE) {
|
||||
ctrl.left = true;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
auto DemoPilot::compute(const Ship& ship,
|
||||
const std::array<Enemy, Constants::MAX_ORNIS>& enemies,
|
||||
const std::array<Bullet, static_cast<std::size_t>(Defaults::Entities::MAX_BULLETS_TOTAL)>& bullets,
|
||||
const SDL_FRect& play_area,
|
||||
float delta_time) -> Control {
|
||||
Control ctrl;
|
||||
@@ -94,6 +159,26 @@ namespace Systems::Demo {
|
||||
}
|
||||
|
||||
const Vec2 SHIP_POS = ship.getCenter();
|
||||
const Vec2 PLAY_CENTRE{
|
||||
.x = play_area.x + (play_area.w / 2.0F),
|
||||
.y = play_area.y + (play_area.h / 2.0F)};
|
||||
const Vec2 TO_CENTRE = (PLAY_CENTRE - SHIP_POS).normalized();
|
||||
|
||||
// Prioridad 1: esquivar una bala enemiga entrante. Se mueve perpendicular
|
||||
// a la trayectoria de la bala (con sesgo al centro) y no dispara.
|
||||
const BulletThreat THREAT = findBulletThreat(SHIP_POS, bullets);
|
||||
if (THREAT.found) {
|
||||
const Vec2 BV = THREAT.velocity.normalized();
|
||||
Vec2 perp{.x = -BV.y, .y = BV.x};
|
||||
if ((SHIP_POS - THREAT.position).dot(perp) < 0.0F) {
|
||||
perp = -perp; // hacia el lado en que ya está la nave
|
||||
}
|
||||
const Vec2 ESCAPE = (perp + (TO_CENTRE * WALL_BIAS)).normalized();
|
||||
applyRotation(ctrl, steerError(ship, ESCAPE, aim_jitter_));
|
||||
ctrl.thrust = true;
|
||||
return ctrl;
|
||||
}
|
||||
|
||||
const Nearest TARGET = findNearest(SHIP_POS, enemies);
|
||||
target_idx_ = TARGET.index;
|
||||
|
||||
@@ -103,10 +188,6 @@ namespace Systems::Demo {
|
||||
return ctrl;
|
||||
}
|
||||
|
||||
// Centro de la zona de juego (sesgo anti-pared).
|
||||
const Vec2 PLAY_CENTRE{
|
||||
.x = play_area.x + (play_area.w / 2.0F),
|
||||
.y = play_area.y + (play_area.h / 2.0F)};
|
||||
const bool NEAR_WALL =
|
||||
SHIP_POS.x < play_area.x + WALL_MARGIN ||
|
||||
SHIP_POS.x > play_area.x + play_area.w - WALL_MARGIN ||
|
||||
@@ -116,10 +197,8 @@ namespace Systems::Demo {
|
||||
Vec2 desired_dir;
|
||||
const bool DANGER = TARGET.distance < DANGER_RADIUS;
|
||||
if (DANGER) {
|
||||
// Esquiva: alejarse del enemigo, con sesgo hacia el centro para no
|
||||
// quedar atrapada contra la pared. Empuje activo para crear espacio.
|
||||
// Prioridad 2: alejarse del enemigo pegado, con sesgo al centro.
|
||||
const Vec2 AWAY = (SHIP_POS - TARGET.center).normalized();
|
||||
const Vec2 TO_CENTRE = (PLAY_CENTRE - SHIP_POS).normalized();
|
||||
desired_dir = (AWAY + (TO_CENTRE * WALL_BIAS)).normalized();
|
||||
ctrl.thrust = true;
|
||||
} else {
|
||||
@@ -128,23 +207,13 @@ namespace Systems::Demo {
|
||||
desired_dir = (PREDICTED - SHIP_POS).normalized();
|
||||
}
|
||||
|
||||
// Ángulo deseado de la nariz; la nariz apunta hacia (angle - PI/2).
|
||||
float desired_angle = std::atan2(desired_dir.y, desired_dir.x) + aim_jitter_;
|
||||
const float NOSE_ANGLE = ship.getAngle() - (PI / 2.0F);
|
||||
const float ERROR = wrapPi(desired_angle - NOSE_ANGLE);
|
||||
|
||||
// RIGHT incrementa ship.angle (→ nariz); LEFT lo decrementa.
|
||||
if (ERROR > ROTATE_DEADZONE) {
|
||||
ctrl.right = true;
|
||||
} else if (ERROR < -ROTATE_DEADZONE) {
|
||||
ctrl.left = true;
|
||||
}
|
||||
const float ERROR = steerError(ship, desired_dir, aim_jitter_);
|
||||
applyRotation(ctrl, ERROR);
|
||||
|
||||
if (!DANGER) {
|
||||
// Acercarse si el objetivo está lejos (mantiene la nave cazando),
|
||||
// pero no empujar de cara a una pared.
|
||||
const bool FACING_WALL = NEAR_WALL &&
|
||||
(PLAY_CENTRE - SHIP_POS).normalized().dot(desired_dir) < 0.0F;
|
||||
const bool FACING_WALL = NEAR_WALL && TO_CENTRE.dot(desired_dir) < 0.0F;
|
||||
if (TARGET.distance > APPROACH_RADIUS && !FACING_WALL &&
|
||||
std::fabs(ERROR) < FIRE_TOLERANCE) {
|
||||
ctrl.thrust = true;
|
||||
|
||||
Reference in New Issue
Block a user