기존에 플레이어에게 다가오는 AI의 로직을 SetFocus와 MoveToActor만을 사용해서 구현을 했었다.
이렇게 구현을 하니 다수의 Enemy가 플레이어를 쫒아올때 한줄로 줄을 서서 쫒아오게 되었다.
이를 좀 더 퍼져서 플레이어를 추격하도록 하기 위해 AIController를 조금 수정하였다.
float acceptanceRadius = 130.0f;
float avoidanceRadius = 300.0f;
float avoidanceStrength = 1.5f;
float lastEnemyScan = 0.0f;
float enemyScanInterval = 0.2f;
FVector AvoidanceVector;
void EnemyScan();
헤더파일에 위와 같은 변수를 추가해주었다.
acceptanceRadius는 AI객체가 이동할 때 목표 지점과 얼마나 떨어지면 도착을 한 것으로 판단할 것인지에 나타낸다.
avoidanceRadius는 AI객체 주변 반경을 나타낸다. 이를 통해 반경 내 겹치는 객체를 찾아낼 것이다.
avoidanceStrenth는 겹치는 객체와 벌어지는 강도를 나타낸다. 값이 높을수록 객체들은 더 멀리 서로 떨어지게 될 것이다.
lastEnemyScan과 enemyScanInterval은 겹치는 AI객체가 있는지 탐색하는 함수의 호출을 조절한다.
AvoidanceVector는 최종적으로 목적지에 더해질 변수로, 벌어질 거리를 저장하는 Vector이다.
void AAIController_Common::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
if (PlayerPawn) {
if (ControlledPawn->bIsActive && !ControlledPawn->bIsEnemyDie) {
SetFocus(PlayerPawn);
lastEnemyScan += DeltaSeconds;
if (!bIsAttack) {
FVector Destination = PlayerPawn->GetActorLocation();
FVector CurrentLocation = ControlledPawn->GetActorLocation();
if (lastEnemyScan >= enemyScanInterval)
{
EnemyScan();
lastEnemyScan = 0.0f;
}
FVector AdjustedDestination = Destination + AvoidanceVector;
MoveToLocation(AdjustedDestination, acceptanceRadius);
float distance = FVector::Distance(this->GetPawn()->GetActorLocation(), PlayerPawn->GetActorLocation());
if (distance < 250.0f)
{
bIsAttack = true;
FTimerHandle AttackTimerHandle;
FTimerDelegate AttackCD = FTimerDelegate::CreateLambda([this]() { bIsAttack = false; });
GetWorld()->GetTimerManager().SetTimer(AttackTimerHandle, AttackCD, attackDelay, false);
ControlledPawn->Attack();
}
}
}
else {
ClearFocus(EAIFocusPriority::Gameplay);
}
}
else {
ClearFocus(EAIFocusPriority::Gameplay);
}
}
먼저 Tick함수 내에서 객체의 동작을 제어한다.
객체가 공격을 하고있지 않는 상태라면 Player의 위치를 토대로 목적지를 설정한다.
이때 저장한 Interval마다 주변 객체를 탐색하고, AvoidanceVector값을 조정하여 최종적으로 MoveToLocation을 통해 목적지를 설정한다.
void AAIController_Common::EnemyScan()
{
if (ControlledPawn)
{
FVector CurrentLocation = ControlledPawn->GetActorLocation();
TArray<AActor*> OverlappingEnemies;
UKismetSystemLibrary::SphereOverlapActors(GetWorld(), CurrentLocation, avoidanceRadius, TArray<TEnumAsByte<EObjectTypeQuery>>(), ABaseEnemy_Common::StaticClass(), TArray<AActor*>(), OverlappingEnemies);
AvoidanceVector = FVector::ZeroVector;
for (AActor* Actor : OverlappingEnemies)
{
if (Actor != ControlledPawn)
{
FVector ToOther = CurrentLocation - Actor->GetActorLocation();
float DistanceToOther = ToOther.Size();
if (DistanceToOther < avoidanceRadius)
{
AvoidanceVector += ToOther.GetSafeNormal() * (avoidanceRadius - DistanceToOther) * avoidanceStrength;
}
}
}
}
}
주변 객체를 탐색하는 함수는 다음과 같다.
ShphereOverlapActors함수를 통해 주변에 Overlap되는 Actor를 탐색한다.
이때 Overlap된 Actor가 존재한다면 해당 Actor와 자신과의 거리를 계산한다.
해당 거리에 avoidanceStrength값 즉, 거리를 벌릴 강도를 곱해주어 최종 목적지 Location값에 더해줄 AvoidanceVector값을 구해준다.
이러한 방식을 통해 다수의 AI객체가 플레이어를 추격할 때, 서로 적절한 거리를 벌리며 넓게 추격하게 된다.