저번 포스팅에서는 적의 이동을 타겟포인트로 지정해주었다.

하지만 그렇게 되면 내가 이동하려는 타겟포인트의 위치들을 다 지정해줘야하고 생성되는 액터와 리소스 관리가 굉장히 복잡해지고 귀찮게 될 것이다..

 

그래서 오늘은 랜덤으로 이동가능한 범위 내에서 적이 순찰을 도는 부분을 구현하려고 한다.

 

UNavigationSystemV1

오늘 사용한 것 중에 가장 중요한 것은 UNavigationSystemV1이다.

월드 내에서 활성화된 내비게이션 시스템을 가져온다.

그리고 이동가능한 범위를 검색할 수 있게 해준다.

 

 

GetRandomPointInNavigableRadius()

다음으로는 GetRandomPointInNavigableRadius()이다.

매개변수로는

  • 랜덤 위치를 찾을 기준이 되는 중심점
  • 반경
  • 랜덤 위치 찾았는지 여부

이렇게 들어간다.

이 함수가 true를 반환하게되면 맵 안에서 성공적으로 적 AI가 이동가능한 랜덤 위치를 찾았다고 판단할 수 있다,

 

 

 

NavModifierVolume

이미지를 보면 지나갈 수 없는 영역이 너무 작아서 적이 저기를 지나가다가 큐브와 적의 메쉬가 끼여서 적이 멈춰버리는 현상이 발생했다.

그래서 갈 수 없는 영역의 반지름을 키우고 싶어서 찾아보다가 NavModifierVolume이라는 것을 발견했다.

저 빈 네모 테두리가 NavModifierVolume이고, 큐브 옆에 작게 빈 영역이 Navmesh로 Baking된 영역이다.

처음에는 두 영역이 똑같이보여서 NavModifierVolume도 AI가 가지 못하게 막는 볼륨인 줄 알았지만 그게 아니었다.

 

NavModifierVolume은 이동가능한 영역을 판별할 때의 우선순위를 지정할 수 있게 해주는 것이다. 

이게 무슨 말이냐면 만약 맵에 갈 수 있는 영역이 이 NavModifierVolume 밖에 없을 때, Null로 지정해도 AI가 그 영역으로 다니기는 한다는 소리이다.

 

디테일 패널에서 Area Class 설정을 살펴보면 여러 설정들이 나오는데 각각 우선 순위를 지정할 수 있다.

Null이면 가장 낮게, Obstacle이면 높게 이런 식으로 설정된다고한다.

 

설정 별로 비교해보면 더 잘보일건데 다음과 같다.

 

- NavArea_Null

 

  • 역할: 해당 영역은 내비게이션 메시 생성 시 완전히 무시(제외)
  • 우선순위: 비용이 매우 높게(거의 무한대에 가까운 값) 설정되어 있어, 다른 영역보다 항상 우선 적용된다.
    즉, 겹치는 영역 중 하나라도 NavArea_Null이 있다면 해당 영역은 네비게이션이 불가능해진다.

 

-  NavArea_Default

 

  • 역할: 특별한 수정 없이 기본 네비게이션 영역으로 사용된다.
  • 비용/우선순위: 기본 비용이 1로 설정되어 있어, 특별히 비용을 조정하지 않은 경우 이 영역이 적용된다.

 

 - NavArea_LowHeight

 

  • 역할: 낮은 높이 제한으로 인해 통과가 어려운 영역에 사용된다
  • 비용/우선순위: 기본적으로 NavArea_Default보다 높은 비용(예: 5 또는 그 이상)으로 설정되어, 경로 탐색 시 회피하려는 성향을 갖게 된다.

 

- NavArea_Obstacle

 

 
  • 역할: AI가 장애물로 인식하여 경로 탐색 시 반드시 회피한다. 즉,  AI가 절대로 통과하지 않고 우회해야 하는 영역으로 설정된다
  • 비용/우선순위: 비용이 매우 높게 설정되어, 경로 계산 시 해당 영역을 피하는 우선순위가 가장 높다. 그리고 AI가 장애물을 우회하는 경로를 선택하도록 유도한다.
 

 

**결론**

(우선순위 높음 -> 낮음)
NavArea_Obstacle -> NavArea_Null -> NavArea_LowHeight -> NavArea_Default 

 

 

 

MoveToLocation()

그리고 위치로 이동하는 함수는 MoveToActor라는 함수를 사용했었는데, 어쨋건 이 Actor를 참조해도 내부에서는 Actor의 좌표를 가져와서 그 곳으로 이동하기 때문에 MoveToLocation도 있을거라 생각해서 검색해보았다.

 

역시! 있었다!

 

사용할 때는 파라미터는 MoveToActor()와 같지만 0번째를 Actor에서 Vector로 변경해주면 된다.

void AMeleeEnemyAIController::MoveToCurrentPatrolPoint()
{
	float PatrolRadius = 2000.0f;
	AMeleeEnemyCharacter* EnemyCharacter = Cast<AMeleeEnemyCharacter>(GetPawn());
	
	if (!EnemyCharacter) return;
	else EnemyCharacter->GetCharacterMovement()->MaxWalkSpeed = 700.0f;

	FVector CurrentLocation = EnemyCharacter->GetActorLocation();
	FNavLocation RandomNavLocation;
	UNavigationSystemV1* NavSys = UNavigationSystemV1::GetCurrent(GetWorld());
	if (NavSys && NavSys->GetRandomPointInNavigableRadius(CurrentLocation, PatrolRadius, RandomNavLocation))
	{
		MoveToLocation(
			RandomNavLocation.Location,
			5.0f,
			true,
			true,
			false,
			true,
			nullptr,
			true
		);
	}
}

 

아 그리고 원래는 이동 스피드가 없었는데 적이 너무 빠르게 움직이는 것 같아서 조금 천천히 움직이게 하고 싶었다.

찾아보니까 CharacterMovement를 가져와서 속도를 조절할 수 있는 부분이 있길래 700.0f로 설정해주었다.

 

이 부분은 순찰 중에는 느린 속도로 다니다가 플레이어를 발견했을 때는 조금 더 높여주는 방식으로 하면 더 자연스러운 추격이 될 것 같다.

 

void AMeleeEnemyAIController::OnMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult& Result)
{
	Super::OnMoveCompleted(RequestID, Result);
	
	if (Result.Code == EPathFollowingResult::Success)
	{
		FTimerHandle TimerHandle;
		GetWorld()->GetTimerManager().SetTimer(TimerHandle, this, &AMeleeEnemyAIController::MoveToCurrentPatrolPoint, 0.2f, false);
	}
}

 

그리고 OnMoveCompleted라는 함수를 이용해서 타겟 지점에 도착했으면 0.2초 멈춰주는 부분을 만들었다.

이건 타이머를 활용했고, 타겟에 도착후 다시 이동하는 방식으로 플레이어를 발견하기 전까지 계속 움직여주도록 설계했다.

 

 

결과

 

 

 

마지막 말

NameshVolume을 랜덤 범위로 잡으니까 너무 큰 느낌이 들었다.

스폰할 수 있는 볼륨을 만들어 그 안에서 랜덤으로 움직이던가, Trigger Volume이라는 것이 있던데 둘 중 어떤 방식으로 랜덤 범위를 효율적으로 설정할 수 있을지 고민해봐야겠다.

 

 

트러블슈팅

원인 : 아마도.. 라이더를 사용하다가(언리얼 에디터 내에서도 라이더 쓴다고 설정 마친 상태)

디버깅 상태로 켜놓은 상태에서 비주얼스튜디오 업데이트를 했다.

업데이트 도중에 msBuild 머시기라는 안내창이 떴지만 별 신경 안쓰고 0.2초만에 닫기를 눌렀고...

그리고 비주얼 스튜디오를 기본 편집기로 바꾸고 다시 디버깅하니까 해당 오류창 발생했다.

 

해결방법 : 초간단 ㅎㅎ..

 

언리얼 프로젝트에서 내 솔루션을 시작 프로젝트로 다시 설정해주니까 잘 작동했다.. !...ㅎ

(혹시 그런 분은 없겠지만 이런 상황에서 시간을 많이 소비하지 않았으면 하는 마음에서 바보같은 오류였지만 공유한다..)