TIL_084 : AI Blackboard/Behavior Tree

김펭귄·2025년 12월 18일

Today What I Learned (TIL)

목록 보기
84/93

1. AI 블랙보드

  • AI Behavior Tree와 관련하여 칠판같이 정보를 저장하고 사용하는 곳

  • 좌측의 키는 변수 같은것으로 원하는 키(변수)를 저장

  • 각 키에 대해 우측 디테일 패널에서 설정

    • Entry Name : 이름
    • Category : 키들을 범주화 할 수 있는 카테고리
    • Description : 키 설명
    • key type : 키 자료형
    • Default value : 초기화 값
    • Base class : base class 계열만 받도록 설정
    • Instance synced : 체크하면 다른 ai 인스턴스끼리 해당 키값을 공유
    • Parent : 상속받을 부모 블랙보드

1.1. AI 컨트롤러에 블랙보드 등록

// MyAIController.h
UCLASS()
class MYAI_API AMyAIControllerAIController : public AAIController
{
	GENERATED_BODY()
    
public:
	FORCEINLINE UBlackboardComponent* GetBlackboardComp() const 
    { return BlackboardComp; }

protected:
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AI")
	UBlackboardComponent* BlackboardComp;
};
// MyAIController.cpp
#include "BehaviorTree/BlackboardComponent.h"

AMyAIController::AMyAIController()
{
	// ... //
	BlackboardComp = CreateDefaultSubobject<UBlackboardComponent>(TEXT("BB"));
}

void AMyAIController::BeginPlay()
{
	Super::BeginPlay();

	if (BlackboardComp)
	{
		BlackboardComp->SetValueAsBool(TEXT("CanSeeTarget"), false);
		BlackboardComp->SetValueAsBool(TEXT("IsInvestigating"), false); 
	}
}
  • 다른 컴포넌트처럼 속성으로 가지고, 생성자에서 생성

  • SetValueAs~(키이름, 값) : 블랙보드에서 만든 키에 값 설정하는 함수

  • 컨트롤러 블루프린트에서 BlackBoard 연결

1.2. Perception과 연동

void AMyAIController::OnPerceptionUpdated(AActor* Actor, FAIStimulus Stimulus)
{
	if (Actor->IsA<AMyCharacter>() == false || !BlackboardComp)
		return;

	if (Stimulus.WasSuccessfullySensed())
	{
		BlackboardComp->SetValueAsObject(TEXT("TargetActor"), Actor);
		BlackboardComp->SetValueAsBool(TEXT("CanSeeTarget"), true);
		BlackboardComp->SetValueAsVector(TEXT("TargetLastKnownLocation"), 
        										Actor->GetActorLocation());
		BlackboardComp->SetValueAsBool(TEXT("IsInvestigating"), false);
	}
	else
	{
		BlackboardComp->SetValueAsBool(TEXT("CanSeeTarget"), false);
		BlackboardComp->SetValueAsBool(TEXT("IsInvestigating"), true);
	}
}
  • 인지 상태가 바뀌면 불리는 OnPerceptionUpdated함수를 통해 블랙보드에서 값을 수정

2. Behavior Tree

  • AI의 행동을 결정해줄 트리

  • Node로 크게 Selector, Sequence, Simple Parallel이 존재

2.1. Selector

  • 해당 자식 노드들을 왼쪽에서부터 오른쪽으로 실행

  • if-else문처럼 첫 번재 자식 노드가 실패하면 오른쪽 노드로 넘어감

  • 자식 노드 실행 중 성공하면 오른쪽으로 더 안가고 다시 자기자신으로 돌아옴

  • 자식 노드 중 하나라도 성공하면 selector도 성공한거고, 자식들이 다 실패하면 본인도 실패한 것

2.2. Sequence

  • 마찬가지로 자식노드를 왼쪽에서 오른쪽으로 실행

  • 자식 노드가 성공하면 그 다음 오른쪽 자식도 실행함

  • 실행하다 실패하면, 오른쪽으로 더 안가고 자기 자신으로 돌아옴

  • 모든 자식들이 다 성공해야 본인도 성공인거고, 하나라도 실패하면 실패

2.3. Simple Parallel

  • 왼쪽 보라색이 main task, 오른쪽 회색이 background

  • main task를 실행시키면서 동시에 background 브랜치를 동시에 실행

  • 디테일 창에 Finish Mode관련하여 2가지 옵션이 존재

    • Intermediate : 메인 성공 시 즉시 백그라운드도 종료
    • Delayed : 메인 성공해도 백그라운드가 끝날때까지 기다림

2.4. 블랙보드와 연결

  • 우클릭하여 연결
  • 이 Decorator가 조건을 걸어주는 역할. 여러 개도 가능

  • 디테일 패널

    • Notify Observer : On Result Change로 하면, 값이 바뀔 때 Notify됨.
      On Value Change이면, 동일한 값으로 바뀌어도 Notify 됨
    • Observer aborts : 데코레이터 조건이 바뀌어 Task 멈춰야 할 때 어떻게 할 것인지
      • None : Task 마저 수행
      • Self: 현재 브랜치(자신의 하위)만 중단
      • Lower Priority: 우선순위 낮은(오른쪽) 브랜치 중단
      • Both: Self + Lower Priority 모두 중단
    • Key Query : Is Set이면 블랙보드 키에 값이 설정되어 있어야 참을 반환.
      Is Not Set이면 값이 없거나 false이거나 초기화 되어 있으면 참 반환
  • 하고 조건이 참이면 Task를 실행

3. 컨트롤러에 연결

// MyController.h
UPROPERTY(EditDefaultsOnly, Category = "AI")
UBehaviorTree* BehaviorTreeAsset;

// MyController.cpp
void AMyAIController::BeginPlay()
{
	Super::BeginPlay();
	RunBehaviorTree(BehaviorTreeAsset);
}
  • Behavior Tree를 처음 시작해줄 함수가 필요함. RunBehaviorTree함수를 통해 시작

  • 컨트롤러에 연결

3.1. AI Behavior Tree 상태 확인

  • PIE로 실행 후, Behavior Tree를 켜놓으면 실시간으로 현재 AI상태를 확인 가능
아직 플레이어 감지 못 해 ROOT에 위치플레이어 감지 하여 이후 노드들 실행

4. 원하는 Task 만들기

  • BTTaskNode를 상속받아 원하는 커스텀 Task를 생성 가능

// header
#pragma once

#include "CoreMinimal.h"
#include "BehaviorTree/BTTaskNode.h"
#include "BTTask_FindRandomLocation.generated.h"

UCLASS()
class SPARTAAI_API UBTTask_FindRandomLocation : public UBTTaskNode
{
	GENERATED_BODY()

public:
    UBTTask_FindRandomLocation();

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

    // 결과를 저장할 Blackboard 키
    UPROPERTY(EditAnywhere, Category = "Blackboard")
    struct FBlackboardKeySelector LocationKey;

    UPROPERTY(EditAnywhere, Category = "Search", meta = (ClampMin = "100.0"))
    float SearchRadius = 1000.0f;
};
  • 사진과 같이 새로운 Task가 생성됨
디테일 창에 구현한 속성들 존재.
// cpp
#include "Course/BTTask_FindRandomLocation.h"
#include "BehaviorTree/BehaviorTreeComponent.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "AIController.h"
#include "NavigationSystem.h"

UBTTask_FindRandomLocation::UBTTask_FindRandomLocation()
{
	// 해당 Node 이름 설정
    NodeName = TEXT("Find Random Location");

    // 벡터값을 저장가능한 블랙보드의 키들만 Location Key에 필터링됨
    LocationKey.AddVectorFilter(this, 
    							GET_MEMBER_NAME_CHECKED(UBTTask_FindRandomLocation, 
                                					    LocationKey));
}

EBTNodeResult::Type UBTTask_FindRandomLocation::ExecuteTask(/**/)
{
    // 컨트롤러, Pawn, 네비게이션 시스템 가져오기
    AAIController* AIController = OwnerComp.GetAIOwner();
    if (!AIController) return EBTNodeResult::Failed;
    APawn* MyPawn = AIController->GetPawn();
    if (!MyPawn) return EBTNodeResult::Failed;
    UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetCurrent(GetWorld());
    if (!NavSystem) return EBTNodeResult::Failed;

    // 랜덤 위치 찾기
    FNavLocation RandomLocation;
    bool bFound = NavSystem->GetRandomReachablePointInRadius(
        MyPawn->GetActorLocation(),  
        SearchRadius,                
        RandomLocation               // 결과를 여기에 저장
    );

    // Blackboard 키에 저장
    UBlackboardComponent* BlackboardComp = OwnerComp.GetBlackboardComponent();
    if (BlackboardComp)
    {
        // 디테일 창에서 설정한 Location Key 이름을 이용해 저장
        BlackboardComp->SetValueAsVector(LocationKey.SelectedKeyName, 
            								 RandomLocation.Location);
        return EBTNodeResult::Succeeded;  
    }

    return EBTNodeResult::Failed;  
}

4.1. 최종 추적/순찰 Behavior Tree

profile
반갑습니다

0개의 댓글