멈추지 않고 끈질기게
[Unreal] 최종 프로젝트 5일차 - 타임 플로우 / NPC 행동 패턴 본문
※ 해당 포스팅은 개인의 공부 정리용 글입니다. 틀린 내용이 있다면 추후 수정될 수 있습니다.
※ 해당 포스팅은 청년취업사관학교 교육 과정의 최종 프로젝트에 관한 내용을 포함하고 있습니다.
※ 해당 포스팅은 Unreal 5.4.1 버전을 기준으로 작성되었습니다.
0. 서론
오늘은 어제 틀만 잡아놓은 NPC 행동 패턴을 작성하기로 했습니다. 그런데 구성하다보니 시간에 따른 행동을 처리해야 할 필요가 생겼습니다(ex. 10시 되면 특정 위치로 이동). 그래서 우선 게임모드에서 시간 및 날짜 흐름을 작성하고, 일괄적으로 처리하는 방법에 대해 고민하였습니다.
1. 시간 및 날짜 흐름
우선 기획 내용상 하루를 약 5분 정도로 잡고, 저녁시간이 되면 집으로 돌아가서 다음 날짜로 넘어가기로 했으므로 하루를 300초로 잡았습니다. 한시간을 30초로 잡으면 10분당 5초로 깔끔하게 나눠지므로, 일단 하루는 오전 8시부터 오후 6시의 10시간으로 만들기로 했습니다.
#define TEN_MINUTES 10
#define SIXTY_MINUTES 60
#define START_HOUR 8
#define END_HOUR 18
#define TIME_UPDATE_INTERVAL 5.0f
void AFarmLifeGameMode::StartTime()
{
GetWorldTimerManager().UnPauseTimer(TimerHandle);
Paused = false;
}
void AFarmLifeGameMode::StopTime()
{
GetWorldTimerManager().PauseTimer(TimerHandle);
Paused = true;
}
void AFarmLifeGameMode::UpdateMinutes()
{
Minutes += TEN_MINUTES;
if(Minutes == SIXTY_MINUTES)
{
UpdateHours();
}
TimerUI->UpdateTimerUI(Date, Hours, Minutes);
}
void AFarmLifeGameMode::UpdateHours()
{
Minutes = 0;
++Hours;
// 시간 업데이트 일괄처리
if(Hours == END_HOUR)
{
UpdateDate();
}
}
void AFarmLifeGameMode::UpdateDate()
{
StopTime();
// 대화중이라면 업데이트 보류
if(ConversationUI->IsVisible())
{
return;
}
Hours = START_HOUR;
Minutes = 0;
++Date;
SunLight->SetActorRotation(SunBeginRot);
// 날짜 업데이트 일괄처리
TimerUI->UpdateTimerUI(Date, Hours, Minutes);
}
시간 및 날짜 흐름까지는 큰 어려움이 없지만, 가장 고민한 부분은 시간 및 날짜 갱신 타이밍에 이벤트를 호출하는 방식이었습니다. 시간 흐름을 관리하는 게임모드에서 호출하는 것이 가장 적절해 보이지만, 시간 및 날짜 갱신이 필요한 객체가 한둘이 아니라 아니라 일일히 게임모드에서 들고있는 것은 그다지 바람직하지 않아 보였습니다.
문득 떠오른 것은 UGameplayStatics의 GetAllActorsOfClass() 함수였습니다. 그러나 갱신이 필요한 클래스가 플레이어, NPC, 작물 등 다양하므로 이 또한 적절치 않아보였습니다. 그러나 비슷한 이름의 함수 중에 GetAllActorsWithInterface()라는 함수가 있다는 것을 알게 되었고, 인터페이스로 구현하는 방법을 떠올리게 되었습니다.
// 시간 갱신 인터페이스
class VICTROYSHIP_API IHourUpdate
{
GENERATED_BODY()
// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
virtual void OnHourUpdated(int32 NewHour) = 0;
};
// 날짜 갱신 인터페이스
class VICTROYSHIP_API IDateUpdate
{
GENERATED_BODY()
// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
virtual void OnDateUpdated(int32 NewDate) = 0;
};
void AFarmLifeGameMode::UpdateHours()
{
Minutes = 0;
++Hours;
// 시간 업데이트 일괄처리
TArray<AActor*> OutActors;
UGameplayStatics::GetAllActorsWithInterface(GetWorld(), UHourUpdate::StaticClass(), OutActors);
for(AActor* Actor : OutActors)
{
IHourUpdate* HU = Cast<IHourUpdate>(Actor);
HU->OnHourUpdated(Hours);
}
if(Hours == END_HOUR)
{
UpdateDate();
}
}
void AFarmLifeGameMode::UpdateDate()
{
StopTime();
// 대화중이라면 업데이트 보류
if(ConversationUI->IsVisible())
{
return;
}
Hours = START_HOUR;
Minutes = 0;
++Date;
SunLight->SetActorRotation(SunBeginRot);
// 날짜 업데이트 일괄처리
TArray<AActor*> OutActors;
UGameplayStatics::GetAllActorsWithInterface(GetWorld(), UDateUpdate::StaticClass(), OutActors);
for (AActor* Actor : OutActors)
{
IDateUpdate* DU = Cast<IDateUpdate>(Actor);
DU->OnDateUpdated(Date);
}
TimerUI->UpdateTimerUI(Date, Hours, Minutes);
}
위와 같이 시간 업데이트 함수와 날짜 업데이트 함수에서 IHourUpdate, IDateUpdate를 상속받은 액터들을 긁어와서 일괄적으로 갱신 로직을 실행하도록 하였습니다. 간단하게 로그를 찍어보니 문제없이 동작하는 것으로 보였습니다. 다음 회의 때 팀원분들께 시간, 날짜 갱신 로직은 해당 인터페이스를 상속받아서 구현하도록 요청드려야겠습니다.
2. NPC 행동 패턴 구현
지난번에 짠 틀에서 커스텀 노드의 내용을 추가하였습니다. 우선 대화중인 경우 맨 왼쪽의 대기하는 로직을 최우선적으로 실행하도록 데코레이션을 작성하였습니다.
bool UBTDecorator_InConversation::CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const
{
bool SuperResult = Super::CalculateRawConditionValue(OwnerComp, NodeMemory);
UBlackboardComponent* BBComp = OwnerComp.GetBlackboardComponent();
if(BBComp)
{
return BBComp->GetValueAsBool(KEY_IN_CONV);
}
return SuperResult;
}
그리고 특정 위치로 이동하는 행동이 끝나면 이동 상태를 해제하는 태스크를 추가하였습니다.
EBTNodeResult::Type UBTTask_MoveComplete::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
UBlackboardComponent* BBComp = OwnerComp.GetBlackboardComponent();
if (BBComp)
{
BBComp->SetValueAsBool(KEY_IS_MOVING, false);
return EBTNodeResult::Succeeded;
}
return Result;
}
이 외의 경우에는 기본적으로 현재 위치 기준 일정 반경 내를 이동하고 멈추기를 반복하도록 구현했습니다. 다음에 이동할 위치를 정하는 태스크는 NavSystem을 이용하여 다음과 같이 작성하였습니다.
EBTNodeResult::Type UBTTask_SetTargetLoc::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
APawn* OwnerPawn = OwnerComp.GetAIOwner()->GetPawn();
if(nullptr == OwnerPawn)
{
return EBTNodeResult::Failed;
}
UBlackboardComponent* BBComp = OwnerComp.GetBlackboardComponent();
if(nullptr == BBComp)
{
return EBTNodeResult::Failed;
}
UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetNavigationSystem(GetWorld());
if (nullptr == NavSystem)
{
return EBTNodeResult::Failed;
}
const FVector Origin = OwnerPawn->GetActorLocation();
float PatrolRadius = BBComp->GetValueAsFloat(KEY_PATROL_DIST);
FNavLocation NextNav;
if (NavSystem->GetRandomPointInNavigableRadius(Origin, PatrolRadius, NextNav))
{
BBComp->SetValueAsVector(KEY_TARGET_LOC, NextNav.Location);
return EBTNodeResult::Succeeded;
}
return Result;
}
테스트해보니 대화할 때 플레이어를 바라보지 않으니 어색하여 대화 로직의 최우선 작업으로 플레이어쪽을 바라보는 내용을 추가하였고, 특정 좌표 이동 및 일반 이동 중에는 대화 시 바로 상태를 갱신하도록 Blackboard 데코레이터를 추가하였습니다. 최종적으로 다음과 같은 모습으로 나왔습니다.
물론 확정 사항은 아니고, NPC 종류를 추가하면서 페르소나에 맞는 행동 패턴을 추가할 예정입니다.
3. NPC 애니메이션 추가
그리고 애니메이션 작업은 원래 알파때 몰아서 하려고 했으나, 대화 시 통신 딜레이 동안에 덜 부자연스럽게 하기 위한 처리도 들어가야 하기 때문에 우선적으로 간단하게 애니메이션을 추가하였습니다.
// NPCBase
void ANPCBase::StartConversation()
{
if(ACharacter* Player = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0))
{
NPCController->StartConversation();
AnimInstance->PlayMontage_Conv();
}
}
//////////////////////////////////////////////////////////////////
// NPCAnimInstance
void UNPCAnimInstance::PlayMontage_Conv()
{
StopAllMontages(0);
Montage_Play(Montage_Conv, 1.0f);
}
'포트폴리오' 카테고리의 다른 글
[Unreal] 최종 프로젝트 7일차 - 감정 애니메이션 / 대화 로직 재구성 (0) | 2024.05.21 |
---|---|
[Unreal] 최종 프로젝트 6일차 - 목소리 분류 / 식물 상호작용 / 웹 통신 수정 (0) | 2024.05.20 |
[Unreal] 최종 프로젝트 2일차 - wav 파일 런타임에 출력하기 (0) | 2024.05.13 |
[Unreal] 최종 프로젝트 1일차 - STT 구현 (1) | 2024.05.11 |
[Unreal][공지] 최종 프로젝트 개발 일지 관련 (0) | 2024.05.03 |