멈추지 않고 끈질기게
[Unreal] 최종 프로젝트 10일차 - EQS 가이드 따라하기 / NPC 시야 구현 본문
※ 해당 포스팅은 개인의 공부 정리용 글입니다. 틀린 내용이 있다면 추후 수정될 수 있습니다.
※ 해당 포스팅은 청년취업사관학교 교육 과정의 최종 프로젝트에 관한 내용을 포함하고 있습니다.
※ 해당 포스팅은 Unreal 5.4.1 버전을 기준으로 작성되었습니다.
0. 서론
슬슬 NPC 행동패턴을 고도화하는 작업에 들어가려고 합니다. 강사님께서 추천하신 EQS를 넣어보기 위해 가이드를 따라해보며 학습하였는데, 다소 이슈가 발생하여 우선 EQS를 배제한 형태로 BT를 수정하였습니다.
1. EQS 가이드 따라하기
NPC 행동 패턴을 좀 더 고도화하기 전에, 강사님이 언급하신 EQS(Environment Query System)를 가이드를 따라하며 사용해보기로 했습니다.
해당 퀵스타터 가이드의 내용은 플레이어를 추적하는 행동 패턴 구현이었는데, 지금 프로젝트에서도 NPC가 호감도가 높은 상태에서 플레이어를 발견하면 다가오는 식으로 사용할 수 있을 것으로 보여 따라해보기로 했습니다. 기존의 BT에서 대화중 대기는 최우선순위로 유지하고, 그 다음 우선순위로 플레이어를 발견하면 다가오는 식으로 구현하기로 했습니다.
좌측부터 대화중 / 플레이어 탐지 / 특정 위치 이동 / 주변 순찰 순으로 정렬하였으며, 플레이어 탐지 부분에 EQS 노드를 넣기로 했습니다.
2. NPC 시야 구현
위 스타터 가이드에 NPC 시야를 구현하는 부분까지 포함되어 있었습니다. AIPerception은 아직 사용해보지 않았던 참이라, 좋은 기회라고 생각하고 같이 구현하였습니다. 가이드는 블루프린트 위주로 되어 있었지만, Sense Config를 등록하는 부분 외에는 C++로 처리할 수 있어 보여 C++로 작성했습니다.
// 헤더
UPROPERTY(EditDefaultsOnly)
TObjectPtr<class UAIPerceptionComponent> SightComp;
FTimerHandle SightHandle;
// cpp
#define TIME_LIMIT 3.0f
ANPCController::ANPCController()
{
/* 기타 내용들 */
SightComp = CreateDefaultSubobject<UAIPerceptionComponent>(TEXT("SightComp"));
}
void ANPCController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
SightComp->OnTargetPerceptionUpdated.AddDynamic(this, &ANPCController::OnSightUpdated);
/* 기타 내용들 */
}
void ANPCController::OnSightUpdated(AActor* Actor, FAIStimulus Stimulus)
{
if(false == Actor->IsA<ASTTCharacter>())
{
return;
}
if(Stimulus.WasSuccessfullySensed())
{
ACharacter* NPC = CastChecked<ACharacter>(GetPawn());
NPC->GetCharacterMovement()->MaxWalkSpeed = FastWalkSpeed;
BBComp->SetValueAsObject(KEY_PLAYER, Actor);
BBComp->SetValueAsBool(KEY_PLAYER_IN_SIGHT, true);
GetWorldTimerManager().ClearTimer(SightHandle);
}
else
{
GetWorldTimerManager().SetTimer(SightHandle, this, &ANPCController::OnLostPlayer, TIME_LIMIT, false);
}
}
void ANPCController::OnLostPlayer()
{
ACharacter* NPC = CastChecked<ACharacter>(GetPawn());
NPC->GetCharacterMovement()->MaxWalkSpeed = NormalWalkSpeed;
BBComp->SetValueAsObject(KEY_PLAYER, nullptr);
BBComp->SetValueAsBool(KEY_PLAYER_IN_SIGHT, false);
}
NPC용 AI Controller에 AIPerceptionComponent를 추가하고, OnTargetPerceptionUpdated에 플레이어를 성공적으로 찾았다면 BlackboardComponent의 플레이어 관련 키 값을 설정하고, 초기화 타이머가 돌고 있다면 해제하도록 했습니다. 플레이어를 놓친 경우에는 타이머를 걸어놓고, 타이머가 실행되면 플레이어 관련 키를 초기화하도록 했습니다.
그런데 가이드를 따라하며 만든 EQS 노드가 플레이어 위치가 아닌 본인(NPC)의 위치를 저장하는 이슈가 발생하였습니다. 가이드를 몇번 다시 읽어봤지만 다른 부분은 없는 것 같았습니다. 아무래도 Trace 하는 부분에서 자기 자신을 탐색하는 것으로 보였는데, 검색해보니 이를 막는 직접적인 방법은 없는 것으로 보였습니다. 이 부분은 좀 더 EQS를 공부하고 난 다음에 사용할 수 있을 것으로 보여서, 일단 NPC Controller에서 갱신한 플레이어를 쫓아가도록 수정해두었습니다.
3. 기타(NPC 열거형 분류)
그 외에 사소한 수정 내용으로는 NPC를 열거형으로 분류하도록 수정했습니다. NPCName이 웹통신에서 어떤 페르소나를 불러올지 정하는 중요한 변수인데, 아직까지 디테일 창에서 직접 입력하도록 되어 있었습니다. 따라서 열거형을 별도로 선언해놓고, BeginPlay()에서 선택한 열거형에 맞는 이름을 가지도록 구현했습니다.
// 헤더
UENUM()
enum class ENPCType : uint8
{
Mira = 0,
Junho,
Chunsik
};
// cpp
ANPCBase::ANPCBase()
{
/* 기타 내용들 */
NPCNameMap.Add(UEnum::GetValueAsString(ENPCType::Mira), TEXT("미라"));
NPCNameMap.Add(UEnum::GetValueAsString(ENPCType::Junho), TEXT("이준호"));
NPCNameMap.Add(UEnum::GetValueAsString(ENPCType::Chunsik), TEXT("이춘식"));
}
void ANPCBase::BeginPlay()
{
Super::BeginPlay();
/* 기타 내용들 */
NPCName = NPCNameMap[UEnum::GetValueAsString(NPCType)];
}
열거형 타입 변수를 NPCType을 EditAnywhere로 선언해놓고, 열거형 이름과 실제 NPC 이름을 매핑하여 TMap에 저장해두었습니다. BeginPlay()에서 해당 NPCType의 이름을 TMap을 통해 가져온 다음에 NPCName을 초기화하도록 했습니다. NPC 이름을 한글로 정하다보니 직접 열거형 이름으로 쓸 수 없어 한단계 거쳐가는 형태가 되었습니다.
다만 지금도 생성자에서 TMap에 일일히 집어넣는 형태로, 일단은 임시로 작성하였습니다. 추후 NPC 종류가 늘어나면 테이블 형태로 관리하도록 수정해야겠습니다.
'포트폴리오' 카테고리의 다른 글
[Unreal] 최종 프로젝트 12일차 - 식물 성장 기능 머지 / NPC 행동 패턴 고도화 (0) | 2024.05.28 |
---|---|
[Unreal] 최종 프로젝트 11일차 - STT & 채팅 기능 머지 / NPC 인사 구현 (0) | 2024.05.27 |
[Unreal] 최종 프로젝트 9일차 - 파일 경로 수정, 에셋 탐색 (0) | 2024.05.23 |
[Unreal] 최종 프로젝트 8일차 - 대화 로직 재구성 / 페르소나 생성 자동화 (0) | 2024.05.22 |
[Unreal] 최종 프로젝트 7일차 - 감정 애니메이션 / 대화 로직 재구성 (0) | 2024.05.21 |