언리얼에는 AIController class가 있어 이를 이용해서 AI움직임을 구현할 수 있다.
// MyAIController.h
/.../
UCLASS()
class UNREALINTRODUCTION_API AMyAIController : public AAIController
{
GENERATED_BODY()
public:
AMyAIController();
// Pawn에 인칭이 고정될 때 호출
virtual void OnPossess(APawn* InPawn) override;
// 인칭이 풀릴 때 호출
virtual void OnUnPossess() override;
private:
void RandomMove();
private:
// deletgate의 타이머를 구분할 수 있는 핸들
FTimerHandle TimerHandle;
};
AIController 헤더에 Pawn에 시점이 고정되는 순간과 풀리는 순간에 호출되는 Possess함수들을 선언한다.
FTimerHandle을 이용해서 주기적으로 함수를 실행하는 Timer를 설정할 수 있는데, FTimerHandle을 찾아보면 동일한 delegate를 가진 timer를 구분할 수 있는 유일한 핸들이라고 하는데, 무슨 말인지 아직은 이해하지 못하겠다.
// MyAIController.cpp
#include "MyAIController.h"
#include "NavigationSystem.h" // UNavigationSystemV1을 위한 헤더
#include "Blueprint/AIBlueprintHelperLibrary.h"
/.../
void AMyAIController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
// RandomMove함수를 주기적으로 실행하는 Timer
GetWorld()->GetTimerManager().SetTimer(TimerHandle, this, &AMyAIController::RandomMove, 3.f, true);
}
void AMyAIController::OnUnPossess()
{
Super::OnUnPossess();
// TimerHandle에 설정된 함수 루틴이 clear
GetWorld()->GetTimerManager().ClearTimer(TimerHandle);
}
void AMyAIController::RandomMove()
{
auto CurrentPawn = GetPawn();
// Navigation system을 설정
UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetNavigationSystem(GetWorld());
if (NavSystem == nullptr)
return;
FNavLocation RandomLocation;
// 반경 내 임의의 점 좌표를 RandomLocation에 저장
if (NavSystem->GetRandomPointInNavigableRadius(FVector::ZeroVector, 500.f, RandomLocation))
{
// 이동함수
UAIBlueprintHelperLibrary::SimpleMoveToLocation(this, RandomLocation);
}
}
cpp파일에는 possess함수들에 Timer를 세팅하고 해제하는 로직과, Navigation System을 이용하여 갈 수 있는 임의의 위치로 이동하는 random move를 정의하였다.
navigation system은 엔진 버전마다 다르기도 하고 여러 버전이 있다는데, 나중에 찾아보는 것이 좋을 것 같다.
// MyCharacter.cpp
#include "MyAIController.h"
AMyCharacter::AMyCharacter()
{
/.../
// AMyAIController를 StaticClass로 지정
AIControllerClass = AMyAIController::StaticClass();
// possess되었을 때 AI가 실행되는 상황설정
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
}
Character cpp파일의 생성자에서 AIController를 StaticClass로 지정하고, AI가 possess될 때 실행관련 설정을 진행한다.
// probjectName.Build.cs
public class UnrealIntroduction : ModuleRules
{
public UnrealIntroduction(ReadOnlyTargetRules Target) : base(Target)
{
PublicDependencyModuleNames.AddRange(new string[] { ..., "NavigationSystem", "AIModule", "GameplayTasks" });
}
}
build c#파일에 NavigationSystem, AIModule, GameplayTasks를 추가했다.
NavigationSystem 모듈은 Navigation System을 이용하기 위해, AIModule은 블랙보드 및 Behavior Tree를 이용하기 위해, GameplayTasks는 BTTaskNode를 사용하기 위한 모듈이다.
이제 엔진에서 NavMesh를 설치하기 위해 '볼륨 → 내비 메시 바운드 볼륨'을 끌어다 Scene에 넣는다.
네비 메시의 구역을 설정한 뒤 키보드 P키를 통해 Navigation이 적용되는 구간을 초록색 바운더리로 확인할 수 있다.
이번에는 AI의 Behavior Tree 기능에 대해서 알아보자.
엔진의 콘텐츠 브라우저에서 '인공지능 → 블랙보드'로 블랙보드를, '인공지능 → 비헤이비어 트리'로 Behavior Tree를 생성할 수 있다.
비헤이비어 트리는 위 그림처럼, 루트에서 시작해서 각 행동들을 노드로 연결할 수 있다.
// MyAIController.h
UCLASS()
class UNREALINTRODUCTION_API AMyAIController : public AAIController
{
/.../
UPROPERTY()
class UBehaviorTree* BehaviorTree;
UPROPERTY()
class UBlackboardData* BlackboardData;
};
AIController 헤더에 블랙보드와 비헤이비어 트이 프로퍼티를 추가하고,
// MyAIController.cpp
// Behavior Tree 및 Blackboard 관련 헤더 추가
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BlackboardData.h"
#include "BehaviorTree/BlackboardComponent.h"
AMyAIController::AMyAIController()
{
// Behavior Tree 에셋 불러오기
static ConstructorHelpers::FObjectFinder<UBehaviorTree> BT(TEXT("BehaviorTree'/Game/AI/BT_MyCharacter.BT_MyCharacter'"));
if (BT.Succeeded()) {
BehaviorTree = BT.Object;
}
// Blackboard 에셋 불러오기
static ConstructorHelpers::FObjectFinder<UBlackboardData> BD(TEXT("BlackboardData'/Game/AI/BB_MyCharacter.BB_MyCharacter'"));
if (BD.Succeeded()) {
BlackboardData = BD.Object;
}
}
void AMyAIController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
//GetWorld()->GetTimerManager().SetTimer(TimerHandle, this, &AMyAIController::RandomMove, 3.f, true);
// Blackboard를 확인하고 Behavior Tree 실행
if (UseBlackboard(BlackboardData, Blackboard)) {
if (RunBehaviorTree(BehaviorTree)) {
}
}
}
위와같이 실행하는 코드를 작성하면
이렇게 Behavior Tree에서만든 동작이 실행하는 것을 확인할 수 있다.
만약 특정 위치로 이동하고 싶은 상황이라고 해보자.
그렇다면 먼저 Blackboard창에서 새키를 통해 vector key를 생성한다.
위 그림을 보면 MoveTo라는 요소를 추가하였고, MoveTo의 Blackboard Key가 위에서 만든 key로 설정되 있는 것을 볼 수 있다.
방금 생성한 key를 이용하기 위해 Behavior Tree관련 코드를 작성해보자.
엔진에서 BTTaskNode를 상속받는 class를 생성하고 다음과 같이 코드를 작성하자.
// BTTask_FindPatrolPos.h
UCLASS()
class UNREALINTRODUCTION_API UBTTask_FindPatrolPos : public UBTTaskNode
{
/.../
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
};
EBTNodeResult는 Behavior Tree에서 사용하는 데이터타입으로 보인다. Task를 실행한 결과에 대해 Type형식으로 반환하는 모양이다.
// BTTask_FindPatrolPos.cpp
#include "BTTask_FindPatrolPos.h"
// AIController.cpp의 헤더 추가
#include "MyAIController.h"
#include "NavigationSystem.h"
#include "Blueprint/AIBlueprintHelperLibrary.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BlackboardData.h"
#include "BehaviorTree/BlackboardComponent.h"
UBTTask_FindPatrolPos::UBTTask_FindPatrolPos()
{
// Behavior Tree에서 Node로 표시할 이름을 정한다.
NodeName = TEXT("FindPatrolPos");
}
EBTNodeResult::Type UBTTask_FindPatrolPos::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
// Pawn 가져오기
auto CurrentPawn = OwnerComp.GetAIOwner()->GetPawn();
if (CurrentPawn == nullptr) // 가져오기 실패의 경우 failed 반환
return EBTNodeResult::Failed;
UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetNavigationSystem(GetWorld());
if (NavSystem == nullptr)
return EBTNodeResult::Failed;
FNavLocation RandomLocation;
if (NavSystem->GetRandomPointInNavigableRadius(FVector::ZeroVector, 500.f, RandomLocation))
{
// blackboard에서 생성한 vector key에 random location을 value로 세팅
OwnerComp.GetBlackboardComponent()->SetValueAsVector(FName(TEXT("PatrolPos")), RandomLocation.Location);
return EBTNodeResult::Succeeded;
}
return EBTNodeResult::Failed;
}
생성자에서 엔진에서 Behavior Tree의 node로 표시할 이름을 정한다.
ExecuteTask함수에서 Pawn과 Navigation System을 가져오고 임의의 Location을 Blackboard key에 세팅하여 성공 실패 여부를 반환한다.
코드로 생성한 FindPatrolPos라는 Node를 2번째 Behavior로 추가하면,
기다렸다가 → 진행할 위치를 찾고 → 해당위치로 이동하는 로직을 실행한다.
'개발 · 컴퓨터공학' 카테고리의 다른 글
Learning Unreal 4 언리얼 공부일지 - UMG 사용해보기 (0) | 2022.02.08 |
---|---|
Learning Unreal 4 언리얼 공부일지 - AIController class와 Behaivor Tree 2 (0) | 2022.02.07 |
Learning Unreal 4 언리얼 공부일지 - class 삭제 or 이름 바꾸기 (0) | 2022.02.05 |
Learning Unreal 4 언리얼 공부일지 - Hp bar 만들기 (0) | 2022.02.02 |
Learning Unreal 4 언리얼 공부일지 - GameInstance로 데이터 만들어 사용해보기 (0) | 2022.01.30 |