그냥 게임개발자

[UE5] 8장 - AI 이동 BehaviorTree로 구현(C++) 본문

UE5_Tutorial1

[UE5] 8장 - AI 이동 BehaviorTree로 구현(C++)

sudoju 2023. 10. 1. 21:25

1. Blackboard, BehaviorTree 생성

2. AIController ➡ BehaviorTree로 구현

 

 

1. Blackboard, BehaviorTree 생성

생성

그 다음 AI가 이동할 Pos를 담을 Vector키를 생성하자.

필자는 키 이름을 "PatrolPos"라고 지정했다.

 

Vector키 생성

2. AIController ➡ BehaviorTree로 구현

우리는 MyAIController에서 BlackBoard와 BehaviorTree라는 변수를 만들어서 생성해줄 것이다.

 

MyAIController.h

	UPROPERTY()
	class UBehaviorTree* BehaviorTree;

	UPROPERTY()
	class UBlackboardData* BlackBoardData;

MyAIController.cpp

#include "FirstProject/AI/MyAIController.h"
#include "NavigationSystem.h"
#include "Blueprint/AIBlueprintHelperLibrary.h"
#include "BehaviorTree\BehaviorTree.h"
#include "BehaviorTree\BlackboardData.h"
#include "BehaviorTree\BlackboardComponent.h"

AMyAIController::AMyAIController()
{
	// BT 생성
	static ConstructorHelpers::FObjectFinder<UBehaviorTree> BT(TEXT("/Script/AIModule.BehaviorTree'/Game/AI/BT_MyCharacter.BT_MyCharacter'"));

	if (BT.Succeeded())
	{
		BehaviorTree = BT.Object;
	}

	// BD 생성
	static ConstructorHelpers::FObjectFinder<UBlackboardData> BD(TEXT("/Script/AIModule.BlackboardData'/Game/AI/BB_MyCharacter.BB_MyCharacter'"));

	if (BD.Succeeded())
	{
		BlackBoardData = BD.Object;
	}
}

우리가 만들었던 Blackboard와 BehaivorTree주소를 입력해주면 된다.

 

이제 OnPossess, OnUnPossess함수를 수정해주자.

MyAIController.cpp

void AMyAIController::OnPossess(APawn* InPawn)
{
	Super::OnPossess(InPawn);
	// GetWorld()->GetTimerManager().SetTimer(TimerHandle, this, &AMyAIController::RandomMove, 1.5f, true);

	UBlackboardComponent* BlackboardComp = Blackboard.Get();
	// 블랙보드를 사용하고
	if (UseBlackboard(BlackBoardData, BlackboardComp))
	{
		UE_LOG(LogTemp, Warning, TEXT("Success"));
		// 비헤이비어 트리를 사용한다는 것이다.
		if (RunBehaviorTree(BehaviorTree))
		{
			// TODO
			UE_LOG(LogTemp, Warning, TEXT("Success"));
		}
	}
}

void AMyAIController::OnUnPossess()
{
	Super::OnUnPossess();

	// 핸들을 주기마다 실행할 이벤트에서 빼는 역할
	//GetWorld()->GetTimerManager().ClearTimer(TimerHandle);
}

 

우리가 전에 사용했던 TimerHandle은 이제 사용하지 않고 BehaivorTree를 이용할 것이다.

 

그럼 사실 RandomMove도 다른 곳에 추가해줘야한다.

 

그렇기에 우리는 Task를 사용할 것이다.

BTTaskNode클래스를 생성하자.

BTTaskNode생성

이름은 대충 마음에 든것으로 설정하면 된다.(필자는 여전히 "BTTask_FindPatrolPos"라고 했다.)

이제 이 태스크노드를 실행하는 함수를 만들어야 한다.

 

BTTask_FindPatrolPos.h

UCLASS()
class FIRSTPROJECT_API UBTTask_FindPartolPos : public UBTTaskNode
{
	GENERATED_BODY()

public:
	UBTTask_FindPartolPos();

	// 태스크를 실행하는 함수
	virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
	
};

BTTask_FindPatrolPos.cpp

// MyAIController에서 사용하는 h파일을 다 가져왔다.
#include "FirstProject/AI/MyAIController.h"
#include "NavigationSystem.h"
#include "Blueprint/AIBlueprintHelperLibrary.h"
#include "BehaviorTree\BehaviorTree.h"
#include "BehaviorTree\BlackboardData.h"
#include "BehaviorTree\BlackboardComponent.h"


UBTTask_FindPartolPos::UBTTask_FindPartolPos()
{
	NodeName = TEXT("FindPatrolPos");
}

EBTNodeResult::Type UBTTask_FindPartolPos::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
	// 이 함수는 성공이 되면 다음 함수로 넘어가지만 성공이 아니라면 다음 함수로 넘어가지 않기 때문에
	// 성공여부를 반환시켜야 하는 함수이다.
	EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
	UE_LOG(LogTemp, Warning, TEXT("Succees"));
	// AI의 폰을 가져와서
	auto CurrentPawn = OwnerComp.GetAIOwner()->GetPawn();
	UE_LOG(LogTemp, Warning, TEXT("Succees"));
	if (CurrentPawn == nullptr)
	{
		UE_LOG(LogTemp, Warning, TEXT("Failed"));
		return EBTNodeResult::Failed;
	}

	// 네비게이션 시스템을 가져온다.
	UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetNavigationSystem(GetWorld());
	if (NavSystem == nullptr)
	{
		UE_LOG(LogTemp, Warning, TEXT("Failed"));
		return EBTNodeResult::Failed;
	}

	UE_LOG(LogTemp, Warning, TEXT("Succees"));
	FNavLocation RandomLocation;		// 네비 기반으로 좌표찍어놓을 변수
	// ZeroVector중심으로 500거리의 내 랜덤한 위치를 RandomLocation에 찍어놓는데에 성공하면
	if (NavSystem->GetRandomPointInNavigableRadius(FVector::ZeroVector, 500.f, RandomLocation))
	{
		// AI 한테 찍은 좌표로 이동시키라고 명령
		// UAIBlueprintHelperLibrary::SimpleMoveToLocation(this, RandomLocation);
		OwnerComp.GetBlackboardComponent()->SetValueAsVector(FName(TEXT("PatrolPos")), RandomLocation.Location);
		return EBTNodeResult::Succeeded;
	}
	return EBTNodeResult::Failed;
}

자 이렇게 완성이 되었으면 이제 BehaviorTree를 작성하러 가보자.

BehaviorTree

1초마다 이 Sequence를 계속 실행한다.

근데 중요한 점은 Sequence는 순서대로 실행이 되는데 오류가 생기면 그 다음 Task는 실행하지 않고 멈춘다고 한다.

일단 우리는 1초마다 기다려서(Wait) 랜덤 포지션을 찾고(우리가 만든 FindPatrolPos) 그 다음 MoveTo(PatrolPos의 위치로 움직인다)를 실행 할 것이다.

 

FindPatrolPos가 보이지 않는다면 빌드해서 찾아보자.

물론 BTTask_FindPatrolPos랑도 똑같이 맞추어야 이 이름으로 생성이 된다.

자 이제 빌드하고 실행해보자.

BehaviorTree를 이용한 AI

잘 된다.

후.. 어렵다..어려워...

※아 필자는 참고로 Sequence가 아닌 Selector를 사용해서 AI가 움직이지 않자 무엇이 문제인지 하면서 보고있었다....

꼭 Selector가 아닌 Sequence로 해야 한다.