오늘은 네브매쉬와 BehaviorTree(BT)를 이용해 탐색하는 NPC를 만들도록 하겠다
먼저 엔진 좌측 상단 Get Content -> Volumes -> NavMeshBoundsVolume 을 맵으로 드래그해 네브매쉬를 추가해준다
네브 매쉬의 Location 은 (0, 0, 0), Brush Setting 을 넉넉하게 (10000, 10000, 500) 으로 잡고 p 를 눌러 범위를 확인한다
그려지는 네브매쉬 범위
블랙보드와 행동트리는 npc가 해야 할 행동을 분석하고 구조로 설계하는 기법이다
블랙보드
인공지능의 판단에 사용되는 데이터 집합
npc의 의사 결정은 블랙보드에 있는 데이터를 기반으로 진행된다
행동트리
블랙보드 데이터에 기반해 설계한 정보를 저장한 애셋
행동트리와 블랙보드를 각각 생성해준다
블랙보드에 들어가 원래 캐릭터가 있는 위치의 key값 Homepos
와 다음에 이동할 위치 값 PatrolPos
key 값을 만들자
만들어둔 트리와 블랙보드는 잠시 두고, 이제 스크립팅을 하자
먼저 ArenaBattle.Build.cs 파일에 추가로 NavigationSystem
, AIModule
, GameplayTasks
를 추가해주자
다음은 AIController 를 상속받는 ABAIController 클래스를 생성해준다
ABAIController.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "ArenaBattle.h"
#include "AIController.h"
#include "ABAIController.generated.h"
/**
*
*/
UCLASS()
class ARENABATTLE_API AABAIController : public AAIController
{
GENERATED_BODY()
public:
AABAIController();
virtual void OnPossess(APawn* InPawn) override;
static const FName HomePosKey;
static const FName PatrolPosKey;
private:
UPROPERTY()
class UBehaviorTree* BTAsset;
UPROPERTY()
class UBlackboardData* BBAsset;
};
ABAIController.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "ABAIController.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BlackboardData.h"
#include "BehaviorTree/BlackboardComponent.h"
const FName AABAIController::HomePosKey(TEXT("HomePos"));
const FName AABAIController::PatrolPosKey(TEXT("PatrolPos"));
AABAIController::AABAIController()
{
static ConstructorHelpers::FObjectFinder<UBlackboardData> BBObject
(TEXT("/Game/Book/AI/BB_ABCharacter.BB_ABCharacter"));
if (BBObject.Succeeded())
BBAsset = BBObject.Object;
static ConstructorHelpers::FObjectFinder<UBehaviorTree> BTObject
(TEXT("/Game/Book/AI/BT_ABCharacter.BT_ABCharacter"));
if (BTObject.Succeeded())
BTAsset = BTObject.Object;
}
void AABAIController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
UBlackboardComponent* BlackboardComponent = Blackboard;
if (UseBlackboard(BBAsset, BlackboardComponent))
{
Blackboard->SetValueAsVector(HomePosKey, InPawn->GetActorLocation());
}
}
다음으로 ABCharacter에 AIController 를 설정해준다
// Fill out your copyright notice in the Description page of Project Settings.
#include "ABCharacter.h"
#include "ABAnimInstance.h"
#include "DrawDebugHelpers.h"
#include "ABWeapon.h"
#include "ABCharacterStatComponent.h"
#include "Components/WidgetComponent.h"
#include "ABCharacterWidget.h"
#include "ABAIController.h"
// Sets default values
AABCharacter::AABCharacter()
{
...
AIControllerClass = AABAIController::StaticClass();
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
}
행동트리에 블랙보드 값을 사용하게 하기 위해서는 Task가 쓰인다
BTTaskNode 를 부모로 하는 C++ 클래스 BTTask_FindPatrolPos 를 생성한다
행동트리는 Task 를 실행할때 ExecuteTask 함수를 실행한다
이 함수는 Aborted
, Failed
, Suceeded
, InProgress
4가지 상태로 반환된다
BTTask_FindPatrolPos.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTTaskNode.h"
#include "BTTask_FindPatrolPos.generated.h"
/**
*
*/
UCLASS()
class ARENABATTLE_API UBTTask_FindPatrolPos : public UBTTaskNode
{
GENERATED_BODY()
public:
UBTTask_FindPatrolPos();
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
};
BTTask_FindPatrolPos.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "BTTask_FindPatrolPos.h"
#include "ABAIController.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "NavigationSystem.h"
UBTTask_FindPatrolPos::UBTTask_FindPatrolPos()
{
NodeName = TEXT("FindPatrolPos");
}
EBTNodeResult::Type UBTTask_FindPatrolPos::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
auto ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
if (nullptr == ControllingPawn)
return EBTNodeResult::Failed;
UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetNavigationSystem(ControllingPawn->GetWorld());
if (nullptr == NavSystem)
return EBTNodeResult::Failed;
FVector Origin = OwnerComp.GetBlackboardComponent()->GetValueAsVector(AABAIController::HomePosKey);
FNavLocation NextPatrol;
if (NavSystem->GetRandomPointInNavigableRadius(Origin, 500.0f, NextPatrol))
{
OwnerComp.GetBlackboardComponent()->SetValueAsVector(AABAIController::PatrolPosKey, NextPatrol.Location);
return EBTNodeResult::Succeeded;
}
return EBTNodeResult::Failed;
}
Task 에서 NavSystem 이 다음 목표를 잡았을때만 성공으로 리턴하게끔 설정해준다
행동트리에 시퀀스를 제작해주고, 시퀀스는 각각 Wait
, FindPatrolPos
, MoveTo
3가지 기능을 연결해주자
Wait
의 시간을 3초로 두었는데, 3초간 대기 후 FindPatrolPos
를 통해 구현해둔 클래스에서 다음 좌표를 잡고,
MoveTo
에서 그 위치로 이동하게 될 것이다
이때 MoveTo
의 블랙보드를 PatrolPos
로 바꾸는것을 잊지 말자
마지막으로 결과를 테스트 해보기 전, 움직일 AI 캐릭터의 설정이 제대로 되어 있는지 확인하자
잘 되어있다면 실행하여 탐색하는 npc 를 볼 수 있다
이때 행동트리 또한 시퀀스에 맞게 동작하는 것을 볼 수 있다