블랙보드와 비헤이비어 트리 둘다 필요하므로 만들어준다.

 

블랙보드는 애님 인스턴스를 먼저 만들고 애니메이션 재생과 관련된 데이터를 몰아넣고 매 프레임마다 해당 데이터들을 이용해서 애니메이션을 재생하는데 사용하던 개념과 비슷하다.

 

저번까지는 빙의 시 타이머를 등록하여 랜덤한 이동을 테스트 해봤는데 비헤이비어 트리를 이용하면 이런 부분이 필요가 없어진다.

 

// MyAIController.h
private:
    UPROPERTY()
    class UBehaviorTree* BehaviorTree;

    UPROPERTY()
    class UBlackboardData* BlackboardData;
    
// MyAIController.cpp
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BlackboardData.h"
#include "BehaviorTree/BlackboardComponent.h"

AMyAIController::AMyAIController()
{
    static ConstructorHelpers::FObjectFinder<UBehaviorTree> BT(TEXT("BehaviorTree'/Game/AI/BT_MyCharacter.BT_MyCharacter'"));
    if (BT.Succeeded())
    {
        BehaviorTree = BT.Object;
    }

    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);

    // 블랙보드 사용
    if (UseBlackboard(BlackboardData, Blackboard))
    {
        // 비헤이비어 트리를 실행
        if (RunBehaviorTree(BehaviorTree))
        {
            // Log
        }
    }
}

 

아직까지는 비헤이비어 트리를 작성한게 딱히 없기때문에 아무런 행동도 하지 않지만 실제로는 적용되고 있다.

 

 

Task는 저것들이 전부가 아니라 기본적으로 제공되는 것들이고 당연히 커스텀으로 만들어서 사용할 수도 있다.

최상단의 Find Patrol Pos는 커스텀으로 만든 것이다.

 

BTTaskNode를 상속받은 클래스를 만들어서 필요한 기능을 안에 구현하면 된다.

 

// BTTask_FindPatrolPos.h
UCLASS()
class TESTUNREALENGINE_API UBTTask_FindPatrolPos : public UBTTaskNode
{
    GENERATED_BODY()

public:
    UBTTask_FindPatrolPos();

    virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
};

// BTTask_FindPatrolPos.cpp
#include "NavigationSystem.h"
#include "Blueprint/AIBlueprintHelperLibrary.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BlackboardData.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "AIController.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 CurrentPawn = OwnerComp.GetAIOwner()->GetPawn();

    if (CurrentPawn == nullptr)
        return EBTNodeResult::Failed;

    // 월드의 내비게이션 시스템을 가져온다
    UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetNavigationSystem(GetWorld());

    if (NavSystem == nullptr)
        return EBTNodeResult::Failed;

    FNavLocation RandomLocation;

    // 월드의 내비게이션 범위중 원점(첫번째 매개변수)을 기준으로 반지름 만큼의 범위를 한정지어
    // 랜덤한 좌표를 가져온다(RandomLocation에 저장됨)
    if (NavSystem->GetRandomPointInNavigableRadius(FVector::ZeroVector, 500.f, OUT RandomLocation))
    {
        // 해당 액터를 RandomLocation으로 이동
        OwnerComp.GetBlackboardComponent()->SetValueAsVector(FName(TEXT("PatrolPos")), RandomLocation.Location);
        return EBTNodeResult::Succeeded;
    }

    return EBTNodeResult::Failed;
}

 

ExecuteTask는 딱 보기에도 태스크의 실행이다.

 

3초간 랜덤한 위치로 이동하는 구현부를 이쪽으로 옮겨오면서 코드 수정이 이루어졌다.

원래 직접 이동시키는 함수가 있었으나 시퀀스를 이용할 것이기 때문에 랜덤한 좌표를 계산하는것만 가져오도록 한다.

 

비헤이비어 트리에서 시퀀스에 등록된 노드들은 각 노드별로 성공시 순차적으로 실행되지만 중간에 실패하면 이후의 노드들의 실행이 중단된다.

그렇기 때문에 매번 중요한 곳에서 체크할 때마다 Failed를 반환해 주어야 한다.

 

블랙보드에 등록된 키에 값을 넣어주려면 SetValue~~ 함수를 이용해서 블랙보드에 만들어 준 키 이름과 동일한 것을 입력하고 어떤 값을 넘겨줄지를 두번째 인자로 넘겨주면 된다.

 

블랙보드에 키를 만들어주고

 

트리를 위와 같이 구성하면 5초 대기 후 좌표를 구해서 해당 좌표로 이동하는 것을 반복하게 된다.

 

[정리]

 

  • 컴포짓 : 셀렉터, 시퀀스, 심플 페러렐 노드가 있다.
  • 셀렉터 : 자식 노드중 하나라도 성공하면 나머지 자식 노드의 실행을 중단한다. 실패하면 다음 자식 노드를 실행한다.
  • 시퀀스 : 자식 노드중 하나라도 실패하면 나머지 자식 노드의 실행을 중단한다. 성공하면 다음 자식 노드를 실행한다.
  • 심플 페러렐 : 전체 노드 트리와 동시에 하나의 태스크를 실행할 수 있다.
  • BTTaskNode : 비헤이비어 트리에 커스텀 노드를 추가할 수 있다.

'언리얼 엔진 > 언리얼 엔진4 입문' 카테고리의 다른 글

[UE4 입문] 샘플 분석  (0) 2022.09.12
[UE4 입문] Behavior Tree #2  (0) 2022.09.12
[UE4 입문] AI Controller  (0) 2022.09.11
[UE4 입문] UI 실습  (0) 2022.09.03
[UE4 입문] 스탯 매니저  (0) 2022.09.02

+ Recent posts