feat(enemy): migrar el moviment dels enemics a un sistema d'IA declaratiu
This commit is contained in:
1 parent
410955de3c
commit
61e40e88f4
8 files changed
+379
-95
No files matched your search
@@ -0,0 +1,120 @@
|
||||
// enemy_ai_system.cpp - Implementació del dispatcher de moviment d'enemics
|
||||
// © 2026 JailDesigner
|
||||
|
||||
#include "game/systems/enemy_ai_system.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "core/types.hpp"
|
||||
#include "game/constants.hpp"
|
||||
#include "game/entities/enemy.hpp"
|
||||
#include "game/entities/enemy_ai.hpp"
|
||||
#include "game/entities/enemy_config.hpp"
|
||||
|
||||
namespace Systems::EnemyAi {
|
||||
|
||||
namespace {
|
||||
|
||||
auto randFloat01() -> float {
|
||||
return static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX);
|
||||
}
|
||||
|
||||
auto velocityToAngle(const Vec2& velocity) -> float {
|
||||
if (velocity.lengthSquared() < 0.0001F) {
|
||||
return 0.0F;
|
||||
}
|
||||
return std::atan2(velocity.y, velocity.x) + (Constants::PI / 2.0F);
|
||||
}
|
||||
|
||||
// ZIGZAG: canvi de direcció probabilístic. Còpia literal del legacy
|
||||
// Enemy::behaviorPentagon.
|
||||
void moveZigzag(Enemy& enemy, float delta_time) {
|
||||
const auto& mv = enemy.getConfig().ai.movement;
|
||||
EnemyAiState& state = enemy.getAiState();
|
||||
state.direction_change_timer += delta_time;
|
||||
|
||||
if (randFloat01() < mv.zigzag_prob_per_second * delta_time) {
|
||||
const Vec2 VEL = enemy.getBody().velocity;
|
||||
const float CURRENT_ANGLE = velocityToAngle(VEL);
|
||||
const float DELTA = randFloat01() * mv.angle_change_max;
|
||||
const float NEW_ANGLE = CURRENT_ANGLE + ((std::rand() % 2 == 0) ? DELTA : -DELTA);
|
||||
const float SPEED = VEL.length();
|
||||
enemy.setVelocityFromAngle(NEW_ANGLE, SPEED);
|
||||
state.direction_change_timer = 0.0F;
|
||||
}
|
||||
}
|
||||
|
||||
// TRACKING: cada N segons, interpola la velocitat actual cap a la
|
||||
// direcció del ship mantenint la mateixa magnitud. Còpia literal del
|
||||
// legacy Enemy::behaviorSquare.
|
||||
void moveTracking(Enemy& enemy, float delta_time) {
|
||||
const auto& mv = enemy.getConfig().ai.movement;
|
||||
EnemyAiState& state = enemy.getAiState();
|
||||
state.tracking_timer += delta_time;
|
||||
|
||||
const Vec2* ship_pos = enemy.getShipPosition();
|
||||
if (state.tracking_timer < mv.tracking_interval || ship_pos == nullptr) {
|
||||
return;
|
||||
}
|
||||
state.tracking_timer = 0.0F;
|
||||
|
||||
const Vec2 TO_SHIP = *ship_pos - enemy.getCenter();
|
||||
const float DIST = TO_SHIP.length();
|
||||
if (DIST <= 0.0F) {
|
||||
return;
|
||||
}
|
||||
const Vec2 DESIRED_DIR = TO_SHIP / DIST;
|
||||
const float SPEED = enemy.getBody().velocity.length();
|
||||
const Vec2 DESIRED_VEL = DESIRED_DIR * SPEED;
|
||||
const float STRENGTH = state.tracking_strength;
|
||||
|
||||
Vec2 new_vel = (enemy.getBody().velocity * (1.0F - STRENGTH)) +
|
||||
(DESIRED_VEL * STRENGTH);
|
||||
|
||||
const float NEW_SPEED = new_vel.length();
|
||||
if (NEW_SPEED > 0.0F) {
|
||||
new_vel = new_vel * (SPEED / NEW_SPEED);
|
||||
}
|
||||
enemy.getBody().velocity = new_vel;
|
||||
}
|
||||
|
||||
// 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();
|
||||
if (ship_pos == nullptr) {
|
||||
return;
|
||||
}
|
||||
const Vec2 TO_SHIP = *ship_pos - enemy.getCenter();
|
||||
const float DIST = TO_SHIP.length();
|
||||
const float BASE = enemy.getRotationBase();
|
||||
if (DIST < mv.proximity_distance) {
|
||||
enemy.setRotationDelta(BASE * mv.rotation_proximity_multiplier);
|
||||
} else {
|
||||
enemy.setRotationDelta(BASE);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void tick(Enemy& enemy, float delta_time) {
|
||||
if (!enemy.isActive() || enemy.isWounded()) {
|
||||
return;
|
||||
}
|
||||
switch (enemy.getConfig().ai.movement.type) {
|
||||
case MovementType::ZIGZAG:
|
||||
moveZigzag(enemy, delta_time);
|
||||
break;
|
||||
case MovementType::TRACKING:
|
||||
moveTracking(enemy, delta_time);
|
||||
break;
|
||||
case MovementType::RECTILINEAR_PROXIMITY:
|
||||
moveRectilinearProximity(enemy, delta_time);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Systems::EnemyAi
|
||||
@@ -0,0 +1,23 @@
|
||||
// enemy_ai_system.hpp - Executa la primitiva de moviment d'un enemic
|
||||
// © 2026 JailDesigner
|
||||
//
|
||||
// Llegeix `enemy.getConfig().ai.movement` i aplica la primitiva corresponent
|
||||
// (ZIGZAG, TRACKING, RECTILINEAR_PROXIMITY...). El sistema reemplaça el switch
|
||||
// per type que vivia dins Enemy::update.
|
||||
//
|
||||
// A Fase C s'ampliarà per dispatchar també `ai.tick` (accions periòdiques tipus
|
||||
// SHOOT), moment en què caldrà passar el Systems::Collision::Context per
|
||||
// accedir a les bales i als ships.
|
||||
|
||||
#pragma once
|
||||
|
||||
class Enemy;
|
||||
|
||||
namespace Systems::EnemyAi {
|
||||
|
||||
// Aplica la primitiva de moviment activa de l'enemic durant aquest frame.
|
||||
// No fa res si l'enemic està actualment wounded (manté el comportament
|
||||
// legacy del switch original).
|
||||
void tick(Enemy& enemy, float delta_time);
|
||||
|
||||
} // namespace Systems::EnemyAi
|
||||
Reference in new issue
Block a user