
이전 프로젝트에서는 Vision Mesh를 직접 만들어 시야를 구현했었다.
![]() | ![]() |
|---|
Vision Mesh의 오버랩을 이용해 시야에 들어왔는지를 확인하였고, 장애물 때문에 시야가 가리는 지를 Line Trace + Timer 기반으로 계속 검사하는 방식이었다.
하지만 이번 프로젝트는 수십 ~ 수백 마리의 적이 동시에 등장하는 디펜스 게임이라 성능을 더 중요하게 봐야 했다.
특히 Vision Mesh 같은 Custom Mesh는 Sphere나 Box Collision보다 훨씬 많은 정점과 면으로 구성되어 있어서 충돌 판정 비용이 비쌌다.
게다가 적이 움직일 때마다 Vision Mesh도 같이 이동 및 회전해야 하므로 물리 연산 비용도 계속 발생했다.
그래서 이번에는 구조를 단순하게 변경했다.적 중심에 Sphere Collision만 배치하고, Overlap된 대상 중 시야각 내부에 있는지만 내적으로 판정하도록 구성했다.
// BaseEnemyCharacter.cpp
bool ABaseEnemyCharacter::IsInFieldOfView(AActor* Target, float FOVAngle)
{
float DotProduct = FVector::DotProduct(Forward, DirectionToTarget);
return DotProduct >= FMath::Cos(FMath::DegreesToRadians(FOVAngle / 2));
}
다만 Overlap만 사용하면 문제가 있었다.
처음에는 시야각 밖이라 감지되지 않았던 대상이, 이후 시야각 안으로 들어와도 Overlap 이벤트는 다시 발생하지 않았기 때문이다.
그래서 Overlap 시 감지 후보 배열에 저장해두고, Timer를 통해 주기적으로 시야각 내부인지 다시 검사하도록 수정했다.
즉:
역할만 담당하도록 분리한 것이다.
void ABaseEnemyCharacter::OnDetectionSphereBeginOverlap(/**/)
{
NearbyActors.Add(OtherActor);
}
void ABaseEnemyCharacter::OnDetectionSphereEndOverlap(/**/)
{
NearbyActors.Remove(OtherActor);
}
void ABaseEnemyCharacter::SenseNearbyActors()
{
float SenseAngle = BaseEnemyDataAsset->SenseAngle;
for (AActor* NearbyActor : NearbyActors)
{
if (IsValid(NearbyActor) && IsInFieldOfView(NearbyActor, SenseAngle))
{
// 타겟을 인식 //
}
}
}
AI Perception도 고민했지만, 현재 프로젝트에서는 단순 시야각 판정만 필요했고, 대량 AI 환경에서 더 가벼운 구조가 중요하다고 판단해 Collision + 내적 기반 방식으로 구현했다.

적 유형마다 종족 유형마다 서로 다른 타겟 우선순위를 가지도록 어그로 수치를 도입했다.
UENUM(BlueprintType)
enum class ETargetPriorityType : uint8
{
Ignore UMETA(DisplayName = "Ignore (0)"),
High UMETA(DisplayName = "High (1)"),
Medium UMETA(DisplayName = "Medium (2)"),
Low UMETA(DisplayName = "Low (3)"),
Max UMETA(Hidden)
};
예를 들어:
Player : High
Core : Medium
Building : Ignore
이라면 플레이어를 가장 우선적으로 추적하고, 건물은 아예 공격하지 않는 종족이 된다.
이 어그로 수치와 능력치 배율, 색상을 조합해서 Tribe를 구성했다.
예를 들어 같은 Red Tribe라면 Larvae, Spikan처럼 몬스터 종류가 달라도:
을 공유하도록 설계했다.

그리고 SpawnTable에 종족값을 넣어줌으로써 해당 종족으로 스폰하게끔 하였다.
