feat(enemy): afegir behaviors WANDER/CHASE/FLEE i target multi-ship
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
#include "game/entities/enemy.hpp"
|
||||
#include "game/entities/enemy_ai.hpp"
|
||||
#include "game/entities/enemy_config.hpp"
|
||||
#include "game/entities/ship.hpp"
|
||||
|
||||
namespace Systems::EnemyAi {
|
||||
|
||||
@@ -27,6 +28,28 @@ namespace Systems::EnemyAi {
|
||||
return std::atan2(velocity.y, velocity.x) + (Constants::PI / 2.0F);
|
||||
}
|
||||
|
||||
// Retorna el centre del ship actiu més proper a l'enemic, o nullptr si
|
||||
// no n'hi ha cap viu. Els ships destruïts (is_hit_) i els slots nullptr
|
||||
// (player no participant al match) queden filtrats.
|
||||
auto findNearestShipPosition(const Enemy& enemy) -> const Vec2* {
|
||||
const Vec2& self = enemy.getCenter();
|
||||
const Vec2* best = nullptr;
|
||||
float best_dist_sq = 0.0F;
|
||||
for (const Ship* ship : enemy.getShips()) {
|
||||
if (ship == nullptr || !ship->isActive()) {
|
||||
continue;
|
||||
}
|
||||
const Vec2& pos = ship->getCenter();
|
||||
const Vec2 DELTA = pos - self;
|
||||
const float DIST_SQ = DELTA.lengthSquared();
|
||||
if (best == nullptr || DIST_SQ < best_dist_sq) {
|
||||
best = &pos;
|
||||
best_dist_sq = DIST_SQ;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
// ZIGZAG: canvi de direcció probabilístic. Còpia literal del legacy
|
||||
// Enemy::behaviorPentagon.
|
||||
void moveZigzag(Enemy& enemy, float delta_time) {
|
||||
@@ -53,7 +76,7 @@ namespace Systems::EnemyAi {
|
||||
EnemyAiState& state = enemy.getAiState();
|
||||
state.tracking_timer += delta_time;
|
||||
|
||||
const Vec2* ship_pos = enemy.getShipPosition();
|
||||
const Vec2* ship_pos = findNearestShipPosition(enemy);
|
||||
if (state.tracking_timer < mv.tracking_interval || ship_pos == nullptr) {
|
||||
return;
|
||||
}
|
||||
@@ -79,12 +102,56 @@ namespace Systems::EnemyAi {
|
||||
enemy.getBody().velocity = new_vel;
|
||||
}
|
||||
|
||||
// CHASE / FLEE comparteixen lògica: steering continu cap a (o lluny de)
|
||||
// la direcció ideal, preservant la magnitud de velocitat. La força és
|
||||
// strength * dt clampejada a 1 (LERP frame-independent simple).
|
||||
void steerTowards(Enemy& enemy, const Vec2& desired_dir, float strength, float delta_time) {
|
||||
const float SPEED = enemy.getBody().velocity.length();
|
||||
if (SPEED <= 0.0F) {
|
||||
return;
|
||||
}
|
||||
const Vec2 DESIRED_VEL = desired_dir * SPEED;
|
||||
const float T = std::min(1.0F, strength * delta_time);
|
||||
Vec2 new_vel = (enemy.getBody().velocity * (1.0F - T)) + (DESIRED_VEL * T);
|
||||
const float NEW_SPEED = new_vel.length();
|
||||
if (NEW_SPEED > 0.0F) {
|
||||
new_vel = new_vel * (SPEED / NEW_SPEED);
|
||||
}
|
||||
enemy.getBody().velocity = new_vel;
|
||||
}
|
||||
|
||||
void moveChase(Enemy& enemy, float delta_time) {
|
||||
const Vec2* ship_pos = findNearestShipPosition(enemy);
|
||||
if (ship_pos == nullptr) {
|
||||
return;
|
||||
}
|
||||
const Vec2 TO_SHIP = *ship_pos - enemy.getCenter();
|
||||
const float DIST = TO_SHIP.length();
|
||||
if (DIST <= 0.0F) {
|
||||
return;
|
||||
}
|
||||
steerTowards(enemy, TO_SHIP / DIST, enemy.getConfig().ai.movement.chase_strength, delta_time);
|
||||
}
|
||||
|
||||
void moveFlee(Enemy& enemy, float delta_time) {
|
||||
const Vec2* ship_pos = findNearestShipPosition(enemy);
|
||||
if (ship_pos == nullptr) {
|
||||
return;
|
||||
}
|
||||
const Vec2 AWAY = enemy.getCenter() - *ship_pos;
|
||||
const float DIST = AWAY.length();
|
||||
if (DIST <= 0.0F) {
|
||||
return;
|
||||
}
|
||||
steerTowards(enemy, AWAY / DIST, enemy.getConfig().ai.movement.flee_strength, delta_time);
|
||||
}
|
||||
|
||||
// RECTILINEAR_PROXIMITY: rectilini (cap modificació a velocity); boost
|
||||
// de rotació visual quan distància al ship < proximity_distance. Còpia
|
||||
// literal del legacy Enemy::behaviorPinwheel.
|
||||
void moveRectilinearProximity(Enemy& enemy, float /*delta_time*/) {
|
||||
const auto& mv = enemy.getConfig().ai.movement;
|
||||
const Vec2* ship_pos = enemy.getShipPosition();
|
||||
const Vec2* ship_pos = findNearestShipPosition(enemy);
|
||||
if (ship_pos == nullptr) {
|
||||
return;
|
||||
}
|
||||
@@ -106,6 +173,9 @@ namespace Systems::EnemyAi {
|
||||
}
|
||||
switch (enemy.getConfig().ai.movement.type) {
|
||||
case MovementType::ZIGZAG:
|
||||
case MovementType::WANDER:
|
||||
// WANDER reusa la mecànica de canvi de direcció probabilístic;
|
||||
// l'única diferència és semàntica i el tunning dels paràmetres.
|
||||
moveZigzag(enemy, delta_time);
|
||||
break;
|
||||
case MovementType::TRACKING:
|
||||
@@ -114,6 +184,12 @@ namespace Systems::EnemyAi {
|
||||
case MovementType::RECTILINEAR_PROXIMITY:
|
||||
moveRectilinearProximity(enemy, delta_time);
|
||||
break;
|
||||
case MovementType::CHASE:
|
||||
moveChase(enemy, delta_time);
|
||||
break;
|
||||
case MovementType::FLEE:
|
||||
moveFlee(enemy, delta_time);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user