행동트리 시스템

  • 행동트리는 NPC가 해야 할 행동을 분석하고 우선순위가 높은 행동부터 NPC가 실행할 수 있도록 트리 구조로 설계하는 기법
  • 블랙보드 애샛, 비헤이비어트리 에셋 을 생성
  • 위 2개를 C++ 코드를 사용할라면 'AIModule' 모듈을 추가해야한다
//////////////h
UCLASS()
class ARENABATTLE_API AABAIController : public AAIController
{
    GENERATED_BODY()

public:
    AABAIController();
    virtual void OnPossess(APawn* InPawn) override;

private:
    UPROPERTY()
    class UBehaviorTree* BTAsset;

    UPROPERTY()
    class UBlackboardData* BBAsset;
};

/////////////////cpp
#include "ABAIController.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BlackboardData.h"

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);
    if (UseBlackboard(BBAsset, Blackboard))
    {
        Blackboard->SetValueAsVector(HomePosKey, InPawn->GetActorLocation());
        if (!RunBehaviorTree(BTAsset))
        {
            ABLOG(Error, TEXT("AIController couldn't run behavior tree!"));
        }
    }
}
  • NPC 순찰기능을 구현하려면 2가지 데이터가 필요하다
  • NPC가 생성됐을 떄의 위치값, Vector 타입으로 키를 생성하고 HomePos
  • NPC가 순찰할 위치정보를 보관할 키, Vector 타입으로 PatroPos
  • 일단 블랙보드에 추가
  • 앞으로 관련 키 이름이 절대 변하지 않는다근 가정하에 static const 를 사용해 변수 초기 값을 지정함. 이렇게 선언하면 참조하기 편하지만, 하드코딩으로 값을 변경해야 하는 단점이 있다.
//h
UCLASS()
class ARENABATTLE_API AABAIController : public AAIController
{
...........
    static const FName HomePosKey;
    static const FName PatrolPosKey;
 }
///////////cpp
#include "BehaviorTree/BlackboardComponent.h"

const FName AABAIController::HomePosKey(TEXT("HomePos"));
const FName AABAIController::PatrolPosKey(TEXT("PatrolPos"));


void AABAIController::OnPossess(APawn* InPawn)
{
    Super::OnPossess(InPawn);
    if (UseBlackboard(BBAsset, Blackboard))
    {
        Blackboard->SetValueAsVector(HomePosKey, InPawn->GetActorLocation());
        if (!RunBehaviorTree(BTAsset))
        {
            ABLOG(Error, TEXT("AIController couldn't run behavior tree!"));
        }
    }
}
  • 사용하기 위해 모듈에 "GameplayTasks" 추가한다.
  • 다음으로 NPC가 이동할 위치인 PatrolPos 데이터를 생성해야 한다. 이는 순찰할 때마다 바뀌므로 테스트를 제작해 행동트리에서 블랙보드에 값을 쓰도록 설계하는 것이 좋다
  • BTTaskNode를 부모 클래스로 하는 BTTask_FindPatrolPos클래스를 생성한다
  • UI 에서 표현할 떄는 BTTask_ 접두사 부분이 자동으로 걸러진다

행동트리는 태스크를 실행할때, ExecuteTask라는 멤버 함수를 실행하고, 함수는 다음의 셋중 하나의 값을 반환한다
Aborted : 태스크 실행 중에 준단됐다. 결과적으로 실패 
Failed : 태스크를 수행했지만 실패 
Succeeded : 태스크를 성공적으로 수행 
InProgress : 대스크를 계속 수행하고 있다. 태크스의 실행 결과는 향후 얄려줌

* 그래서 ExeCuteTask 함수에서 다음 정찰 지점을 찾는 로직을 구현하고 바로 실행 결과를 반환라도록 구현한다. 태스크의 이름을 다른 이름으로 표시하고 싶다면 NodeName속성을 다른값으로 지정하면 된다

//h

//h
#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;
};


////cpp
#include "BTTask_FindPatrolPos.h"
#include "ABAIController.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "NavigationSystem.h"

UBTTask_FindPatrolPos::UBTTask_FindPatrolPos()
{
	NodeName = TEXT("KKB_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(FVector::ZeroVector, 500.0f, NextPatrol))
	{
		OwnerComp.GetBlackboardComponent()->SetValueAsVector(AABAIController::PatrolPosKey, NextPatrol.Location);
		return EBTNodeResult::Succeeded;
	}

	return EBTNodeResult::Failed;
}

 

Secter 가 아닌 Sequence (뻘짓 5시간)

+ Recent posts