AIController 설정
BlackBoard와 Behavior Tree를 사용하기 위해서는
AIController 블루프린트 클래스에 Blackboard와 BehaviorTree를 지정해야 한다
또한, Blackboard Data Asset도 지정해야 한다
Blackboard에서 사용할 Data들을 쉽게 관리하기 위해
Blackboard Data Asset을 사용한다
Blackboard key
Blackboard에는 특정 유형의 데이터를 저장하고
이를 BehaviorTree에서 활용할 수 있다
블랙보드에서 > New Key > 원하는 타입을 지정해 추가할 수 있다
Blackboard Component 연동 및 SetValue()
그리고 이러한 Blackboard key를 cpp에서 활용하기 위해서는
AIController 클래스에서 블랙보드 key를 초기화해 주고
BlackboardDataAsset을 BlackboardComponent로 설정하고
BlackboardComponent을 불러와서 SetValueAsVector()와 같은 함수를 통해 설정이 가능하다
// SAIController.h
...
class STUDYPROJECT_API ASAIController : public AAIController
{
...
public:
...
static const FName StartPatrolPositionKey;
static const FName EndPatrolPositionKey;
private:
...
};
// SAIController.cpp
...
int32 ASAIController::ShowAIDebug(0);
const FName ASAIController::StartPatrolPositionKey(TEXT("StartPatrolPosition"));
const FName ASAIController::EndPatrolPositionKey(TEXT("EndPatrolPosition"));
...
void ASAIController::BeginAI(APawn* InPawn)
{
...
if (IsValid(BlackboardComponent) == true)
{
if (UseBlackboard(BlackboardDataAsset, BlackboardComponent) == true)
{
...
BlackboardComponent->SetValueAsVector(StartPatrolPositionKey, InPawn->GetActorLocation());
if (ShowAIDebug == 1)
{
...
}
}
}
}
...
GetBlackBoardComponent()
Task, Service와 같은 기능을 C++ 클래스로 구현해서 사용하는 경우에는
아래와 같이 OwnerComp.GetBlackBoardComponent()를 통해 가져와서 사용하면 된다
// 사용 예시 Task
// BTTask_GetEndPatrolPosition.cpp
#include "AI/BTTask_GetEndPatrolPosition.h"
#include "Controller/SAIController.h"
#include "Character/SNonPlayerCharacter.h"
#include "NavigationSystem.h"
#include "BehaviorTree/BlackboardComponent.h"
UBTTask_GetEndPatrolPosition::UBTTask_GetEndPatrolPosition()
{
NodeName = TEXT("GetEndPatrolPosition"); // Behavior Tree에 보일 노드 이름.
}
EBTNodeResult::Type UBTTask_GetEndPatrolPosition::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
if (EBTNodeResult::Failed == Result)
{
return Result;
}
ASAIController* AIController = Cast<ASAIController>(OwnerComp.GetAIOwner());
checkf(IsValid(AIController) == true, TEXT("Invalid AIController"));
ASNonPlayerCharacter* NPC = Cast<ASNonPlayerCharacter>(AIController->GetPawn());
checkf(IsValid(NPC) == true, TEXT("Invalid NPC"));
UNavigationSystemV1* NavigationSystem = UNavigationSystemV1::GetNavigationSystem(NPC->GetWorld());
checkf(IsValid(NavigationSystem) == true, TEXT("Invalid NavigationSystem"));
FVector StartPatrolPosition = OwnerComp.GetBlackboardComponent()->GetValueAsVector(ASAIController::StartPatrolPositionKey);
FNavLocation EndPatrolLocation;
if (true == NavigationSystem->GetRandomPointInNavigableRadius(StartPatrolPosition, AIController->PatrolRadius, EndPatrolLocation))
{
OwnerComp.GetBlackboardComponent()->SetValueAsVector(ASAIController::EndPatrolPositionKey, EndPatrolLocation.Location);
return Result = EBTNodeResult::Succeeded;
}
return Result;
}
// 사용 예시 Service
// BTService_DetectPlayerCharacter.cpp
#include "AI/BTService_DetectPlayerCharacter.h"
#include "Controller/SAIController.h"
#include "Character/SNonPlayerCharacter.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Character/SCharacter.h"
UBTService_DetectPlayerCharacter::UBTService_DetectPlayerCharacter(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
NodeName = TEXT("DetectPlayerCharacter");
Interval = 1.f;
}
void UBTService_DetectPlayerCharacter::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
ASAIController* AIC = Cast<ASAIController>(OwnerComp.GetAIOwner());
if (IsValid(AIC) == true)
{
ASNonPlayerCharacter* NPC = Cast<ASNonPlayerCharacter>(AIC->GetPawn());
if (IsValid(NPC) == true)
{
UWorld* World = NPC->GetWorld();
if (IsValid(World) == true)
{
FVector CenterPosition = NPC->GetActorLocation();
float DetectRadius = 300.f;
TArray<FOverlapResult> OverlapResults;
FCollisionQueryParams CollisionQueryParams(NAME_None, false, NPC);
bool bResult = World->OverlapMultiByChannel(
OverlapResults,
CenterPosition,
FQuat::Identity,
ECollisionChannel::ECC_GameTraceChannel2,
FCollisionShape::MakeSphere(DetectRadius),
CollisionQueryParams
);
if (bResult == true)
{
for (auto const& OverlapResult : OverlapResults)
{
ASCharacter* PC = Cast<ASCharacter>(OverlapResult.GetActor());
if (IsValid(PC) == true)
{
if (PC->GetController()->IsPlayerController() == true)
{
OwnerComp.GetBlackboardComponent()->SetValueAsObject(ASAIController::TargetActorKey, PC);
if (ASAIController::ShowAIDebug == 1)
{
UKismetSystemLibrary::PrintString(this, FString::Printf(TEXT("Detected!")));
DrawDebugSphere(World, CenterPosition, DetectRadius, 16, FColor::Red, false, 0.5f);
DrawDebugPoint(World, PC->GetActorLocation(), 10.f, FColor::Red, false, 0.5f);
DrawDebugLine(World, NPC->GetActorLocation(), PC->GetActorLocation(), FColor::Red, false, 0.5f, 0u, 3.f);
}
}
else
{
OwnerComp.GetBlackboardComponent()->SetValueAsObject(ASAIController::TargetActorKey, nullptr);
if (ASAIController::ShowAIDebug == 1)
{
DrawDebugSphere(World, CenterPosition, DetectRadius, 16, FColor::Green, false, 0.5f);
}
}
}
}
}
else
{
OwnerComp.GetBlackboardComponent()->SetValueAsObject(ASAIController::TargetActorKey, nullptr);
}
if (ASAIController::ShowAIDebug == 1)
{
DrawDebugSphere(World, CenterPosition, DetectRadius, 16, FColor::Green, false, 0.5f);
}
}
}
}
}
'게임 엔진 > [코드조선] 언리얼' 카테고리의 다른 글
언리얼 Game Data - GameInstance (0) | 2024.05.17 |
---|---|
언리얼 AI - Decorator (0) | 2024.05.16 |
언리얼 AI - Behavior Tree, Blackboard (0) | 2024.05.16 |
언리얼 AI - AIController (0) | 2024.05.16 |
언리얼 Damage Framework - TakeDamage 함수 (0) | 2024.05.15 |