Behavior Tree는 행동패턴의 흐름을 GUI로 쉽게 볼 수 있는 블루프린트이다. 키값을 저장하는 Black Board와 함께 운용한다.
이 행동트리는 기획자나 디자이너도 함께 만지게 되므로 C++로 작성하지 말고 무조건 BP로 만드는게 좋다.
Detail 창의 BlackBoard Key값에 따라서 Wating Task를 실행할지 Task Combat을 실행할지 결정하고 Key값은 저번글에 있듯이 AI Perception여부를 통해서 변경된다(플레이어를 인식했다면 true, 인식 못했다면 false)
UNormalMonsterWatingTask::UNormalMonsterWatingTask()
{
bNotifyTick = true;
NodeName = TEXT("Normal Monster Waiting Task");
}
EBTNodeResult::Type UNormalMonsterWatingTask::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
ANormalAIController* AIController = Cast<ANormalAIController>(OwnerComp.GetAIOwner());
ANormalMonster* NormalMonster = Cast<ANormalMonster>(AIController->GetPawn());
if (!NormalMonster)
{
UE_LOG(LogTemp, Warning, TEXT("NormalMonster가 nullptr"));
return EBTNodeResult::Failed;
}
UBlackboardComponent* BlackboardComp = OwnerComp.GetBlackboardComponent();
if (!BlackboardComp)
{
UE_LOG(LogTemp, Error, TEXT("BlackboardComp가 nullptr"));
return EBTNodeResult::Failed;
}
return EBTNodeResult::InProgress; // Task 진행 중으로 설정
}
void UNormalMonsterWatingTask::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
UBlackboardComponent* BlackboardComp = OwnerComp.GetBlackboardComponent();
if (!BlackboardComp) return;
bool bPlayerDetected = BlackboardComp->GetValueAsBool(TEXT("PlayerDetectedKey"));
// UE_LOG(LogTemp, Warning, TEXT("TickTask 실행 중... PlayerDetectedKey = %s"),
// bPlayerDetected ? TEXT("true") : TEXT("false"));
if (bPlayerDetected)
{
FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
}
}
void UNormalMonsterWatingTask::UpdateWaiting(ANormalMonster* NormalMonster)
{
UAnimInstance* AnimInstance = NormalMonster->GetMesh()->GetAnimInstance();
UGrux_AnimInstance* GruxAnimInstance = Cast<UGrux_AnimInstance>(AnimInstance);
if (!GruxAnimInstance)
{
UE_LOG(LogTemp, Error, TEXT("GruxAnimInstance가 nullptr"));
return;
}
GruxAnimInstance->IsWaiting = true; // 시작 시 대기 상태로 변경
// 함수 내부에서 사용할 타이머 핸들 선언
FTimerHandle WaitingLoopTimerHandle;
// 일정 시간마다 반복 실행
NormalMonster->GetWorld()->GetTimerManager().SetTimer(
WaitingLoopTimerHandle,
[GruxAnimInstance]()
{
GruxAnimInstance->IsWaiting = !GruxAnimInstance->IsWaiting; // 상태 반전
UE_LOG(LogTemp, Warning, TEXT("애니메이션 블루프린트: IsWaiting 변경 → %s"),
GruxAnimInstance->IsWaiting ? TEXT("true") : TEXT("false"));
},
GruxAnimInstance->IsWaiting ? 9.0f : 7.0f, true
);
}
왼쪽트리의 WatingTask는 C++코드로 작성되었다.
대기상태에서의 애니메이션출력이나 기타 행동들을 위해서 Excute나 UpdateWating을 넣었는데 다 실패하는 바람에 Tick함수만 사용되는중이다. 대기하다가 적을 인식했을 경우 Task를 종료하게 된다.
오른쪽 Task는 블루프린트로 작성해보았다.
Task가 시작되면 Task가 적용될 Pawn을 결정한다. 결정이 됬다면 변수로 만들고 그 몬스터의 공격딜레이타임을 가져와 똑같이 변수로 만든다.
현재는 몬스터가 2종류밖에 없어 branch로 나눴지만 더 많은 종류의 몬스터가 하나의 Behavior Tree를 사용한다면 c++로 작성하는게 더 나을수도 있겠다.
추적, 공격할 Pawn을 정했으면 다음은 추적대상을 향해 이동할 차례이다.
Monster내부 Perception 로직을 통해서 받은 TargetActor를 캐스팅하고 Target을 향해 Pawn을 이동시킨다. 이후 대상을 향해서 몸을 회전시킨다.
공격할대상이 누군지에 대한 키값으로 Attack함수를 호출하고 Task를 종료한다. C++과 다르게 블루프린트는 Finish Execute가 호출된 이후 다시 반복하게 된다.
'UE5' 카테고리의 다른 글
언리얼 Monster AI 구현(4): 통신 (0) | 2025.04.22 |
---|---|
언리얼 Monster AI 구현(3): Animation (0) | 2025.04.22 |
언리얼 Monster AI 구현(1): 전투 (0) | 2025.04.15 |
C++ Behavior Tree (0) | 2025.04.08 |
AI(2): Behavior tree (0) | 2025.04.04 |