Behavior Tree를 다루는 강의 파트에서 겪은 일이다.
원거리 공격이 가능한 적은 현재 추적 중인 캐릭터와의 거리가 600정도 될 때까지 다가간다. 그 다음 EQS를 실행, 캐릭터를 Trace할 수 있는 위치를 추려낸 뒤 자신과 가장 가까운 위치 1개를 뽑아내 그 위치로 간다.
그런데 여기서 문제가 생겼다. EQS가 Querier의 위치(몬스터의 현재 위치)를 반환하지 않아 계속 1칸씩 이동하는 것이다.
디버깅해본 결과
그런데도 왜 EQS는 1칸 옆 자리를 반환하는가?
30분 정도 붙잡고 디버깅해본 결과, 문제는 Trace Channel에 있었다.
EQS의 Trace Test는 원하는 Trace Channel로 원하는 객체를 검출해내는 과정이 아니다. 목표인 객체는 Context를 통해 이미 주어진 상태고, 그 객체로부터 선을 그어(Trace) 사이에 장애물이 있는지 판단하는 과정이다.
즉, 위 설정은 Context에 할당된 EQS_PlayerContext가 이미 Player Character 객체를 넘겨준 상태고, Visibility Channel로 트레이스를 해서 사이에 장애물이 있는지 판단하는 중이다. 그런데 몬스터들의 메쉬와 콜리전이 Visibility에 대해 Block Response를 갖고 있다.
EQS의 Generate로 생성된 위치에서 Player Character를 향해 Visibility Channel로 Trace를 시작하면, 현재 Querier의 위치는 당연하게도 Querier(Enemy 객체)의 메쉬로 콜리전에 의해 가로막혀 Player Character에게까지 닿을 수 없는 상황이었다.
해결을 위해 CharacterBase에서 메쉬와 콜리전 모두 Visibility에 대한 Response를 Ignore로 변경했다. 그리고 Ally와 Enemy Channel을 새로 선언하고, Player Character와 Enemy Character가 각각 Block Response를 갖도록 했다. 이제 EQS는 Querier의 위치에서도 Player Character를 찾을 수 있게 되었다.
현업에서도 사용하는 방식인지가 궁금해 AI들에게 물어봤더니, 현업에서도 자주 쓰는 패턴이라고 답변했다.
FVector CameraLocation = PlayerCameraManager->GetCameraLocation();
FHitResult VisibilityHit;
FHitResult EnemyHit;
GetHitResultUnderCursor(ECC_Visibility, false, VisibilityHit);
GetHitResultUnderCursor(ECC_Enemy, false, EnemyHit);
if (VisibilityHit.bBlockingHit != EnemyHit.bBlockingHit)
{
CursorHit = VisibilityHit.bBlockingHit ? VisibilityHit : EnemyHit;
}
else
{
float VisibilityHitDist = FVector::Dist(CameraLocation, VisibilityHit.ImpactPoint);
float EnemyHitDist = FVector::Dist(CameraLocation, nemyHit.ImpactPoint);
CursorHit = VisibilityHitDist < EnemyHitDist ? VisibilityHit : EnemyHit;
}
커서 아래에 적이 있는지 검출하는 로직도 수정했다.