Add AI components and systems for enhanced enemy behavior and pathfinding
This commit is contained in:
130
GameCore/AI/AIPerceptionSystem.cs
Normal file
130
GameCore/AI/AIPerceptionSystem.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using GameCore.ECS;
|
||||
using GameCore.ECS.Interfaces;
|
||||
using GameCore.Math;
|
||||
using GameCore.Movement;
|
||||
using GameCore.Physics;
|
||||
using GameCore.Player;
|
||||
|
||||
namespace GameCore.AI;
|
||||
|
||||
public class AIPerceptionSystem : ISystem
|
||||
{
|
||||
private readonly Vector3 _aiEyeOffset = new(0f, 1.5f, 0f);
|
||||
private readonly Vector3 _playerCenterOffset = new(0f, 1.0f, 0f);
|
||||
|
||||
public void Update(World world, float deltaTime)
|
||||
{
|
||||
var playerEntities = world.GetEntitiesWith<PlayerComponent>().ToList();
|
||||
if (!playerEntities.Any())
|
||||
{
|
||||
world.Logger.Warn("No player entity found for AI perception.");
|
||||
return;
|
||||
}
|
||||
|
||||
var playerEntity = playerEntities.First();
|
||||
var playerPos = world.GetComponent<PositionComponent>(playerEntity);
|
||||
if (playerPos == null)
|
||||
{
|
||||
world.Logger.Warn("No player position found for AI perception.");
|
||||
return;
|
||||
}
|
||||
|
||||
var aiEntities = world.GetEntitiesWith<AIComponent>();
|
||||
foreach (var entity in aiEntities)
|
||||
{
|
||||
if (world.GetComponent<PlayerComponent>(entity) != null)
|
||||
{
|
||||
world.Logger.Warn("AI Perception System: Skipping player entity.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var ai = world.GetComponent<AIComponent>(entity);
|
||||
var aiPos = world.GetComponent<PositionComponent>(entity);
|
||||
var aiRot = world.GetComponent<RotationComponent>(entity);
|
||||
if (ai == null || aiPos == null || aiRot == null)
|
||||
{
|
||||
world.Logger.Warn("AI Perception System: Skipping AI perception. Missing components.");
|
||||
continue;
|
||||
}
|
||||
|
||||
ai.StateTimer += deltaTime;
|
||||
|
||||
var aiEyePos = aiPos.Position + _aiEyeOffset;
|
||||
var playerCenterPos = playerPos.Position + _playerCenterOffset;
|
||||
|
||||
var vectorToPlayer = playerCenterPos - aiEyePos;
|
||||
var distanceToPlayer = vectorToPlayer.Length();
|
||||
var directionToPlayer = vectorToPlayer.Normalize();
|
||||
|
||||
if (distanceToPlayer < ai.SightRange &&
|
||||
IsInFieldOfView(world, ai, aiRot, directionToPlayer) &&
|
||||
HasLineOfSight(world, aiEyePos, playerCenterPos, entity))
|
||||
{
|
||||
ai.Target = playerEntity;
|
||||
ai.LastKnownTargetPosition = playerPos.Position;
|
||||
ai.StateTimer = 0.0f;
|
||||
|
||||
var attackRangeBuffer = ai.AttackRange * ai.AttackRangeBufferPercent;
|
||||
if (distanceToPlayer <= ai.AttackRange)
|
||||
{
|
||||
if (ai.CurrentState != AIState.Attack)
|
||||
{
|
||||
ai.CurrentState = AIState.Attack;
|
||||
world.Logger.Debug($"AI Entity {entity.Id} switching to Attack state.");
|
||||
}
|
||||
}
|
||||
else if (distanceToPlayer > ai.AttackRange + attackRangeBuffer)
|
||||
{
|
||||
if (ai.CurrentState != AIState.Chase)
|
||||
{
|
||||
ai.CurrentState = AIState.Chase;
|
||||
world.Logger.Debug($"AI Entity {entity.Id} switching to Chase state.");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ai.Target != null)
|
||||
{
|
||||
if (ai.StateTimer > ai.ChaseGiveUpTime)
|
||||
{
|
||||
ai.Target = null;
|
||||
ai.CurrentState = world.GetComponent<PatrolComponent>(entity) != null
|
||||
? AIState.Patrol
|
||||
: AIState.Idle;
|
||||
world.Logger.Debug($"AI Entity {entity.Id} lost target, switching to {ai.CurrentState} state.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ai.CurrentState = AIState.Chase;
|
||||
world.Logger.Debug($"AI Entity {entity.Id} continuing to chase last known target position.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ai.CurrentState = world.GetComponent<PatrolComponent>(entity) != null
|
||||
? AIState.Patrol
|
||||
: AIState.Idle;
|
||||
world.Logger.Debug($"AI Entity {entity.Id} has no target, switching to {ai.CurrentState} state.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasLineOfSight(World world, Vector3 from, Vector3 to, Entity owner)
|
||||
{
|
||||
var hit = world.WorldQuery.Raycast(from, to, owner);
|
||||
return hit.DidHit && hit.HitEntity.HasValue && world.GetComponent<PlayerComponent>(hit.HitEntity.Value) != null;
|
||||
}
|
||||
|
||||
private bool IsInFieldOfView(World world, AIComponent ai, RotationComponent aiRot, Vector3 directionToPlayer)
|
||||
{
|
||||
var aiForward = world.WorldQuery.RotateVectorByYaw(new Vector3(0f, 0f, 1f), aiRot.Rotation.Y);
|
||||
|
||||
var dotProduct = Vector3.Dot(aiForward, directionToPlayer);
|
||||
var fovRadians = ai.FieldOfView * (float)System.Math.PI / 180f;
|
||||
var fovThreshold = (float)System.Math.Cos(fovRadians / 2f);
|
||||
|
||||
return dotProduct >= fovThreshold;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user