본문 바로가기
Unreal 게임 개발/Unreal Tool 활용

Unreal Behavior Tree 'Check Condition Only if Blackboard Changes'

by daisy0461 2024. 9. 23.

Behavior Tree의 Decorator를 Blueprint로 만든 것에서 CPP로 만들다가 발생한 이슈에 대한 풀이이다.

이 영상이 의도한대로 구현된 것이다.

 

일정거리 이상 멀어지면 바로 따라오도록 만들고 싶었다.

Decorator Blueprint

Blueprint로 만든 것은 이렇게 생겼다.

 

이걸 그대로 구현하고 싶어서 cpp에는 이렇게 만들었다.

#include "Enemy/EnemyAI/Decorator/BTD_IsWithInIdealRange.h"
#include "AIController.h"
#include "BehaviorTree/BlackboardComponent.h"

UBTD_IsWithInIdealRange::UBTD_IsWithInIdealRange()
{

}

bool UBTD_IsWithInIdealRange::CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const
{
    bool bResult = Super::CalculateRawConditionValue(OwnerComp, NodeMemory);

    APawn* ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
    if(!ControllingPawn) return true;
    
    UBlackboardComponent* BlackboardComp = OwnerComp.GetBlackboardComponent();
    if (!BlackboardComp) return true;

    UObject* AttackTargetObject = BlackboardComp->GetValueAsObject(AttackTargetKey.SelectedKeyName);
    AActor* AttackTargetActor = Cast<AActor>(AttackTargetObject);
    float IdealRange = BlackboardComp->GetValueAsFloat(IdealRangeKey.SelectedKeyName);
    AAIController* AIController = OwnerComp.GetAIOwner();

    float AttackTargetDistance = AttackTargetActor->GetDistanceTo(ControllingPawn);

    UE_LOG(LogTemp, Display, TEXT("BTD_IsWithInIdealRange : %s"), *AttackTargetActor->GetName());

    if((AttackTargetDistance - ErrorMarin) <= IdealRange){
        return false;
    }

    //return false;
    return true;
}

FString UBTD_IsWithInIdealRange::GetStaticDescription() const
{
    return FString::Printf(TEXT("\nAttackTargetKey: %s\nIdealRangeKey: %s"), *AttackTargetKey.SelectedKeyName.ToString(), *IdealRangeKey.SelectedKeyName.ToString());
}

 

근데 이상하게 흘러간다..

BehaviorTree가 이렇게 만들어져있는데 왼쪽 Sequnece를 다 돌고 온다.

당연히 Observer Obert는 Lower Priority이다.

이렇게까지 했는데 원하는 방식으로 진행되지 않아서 고민이었다.

 

이게 왜 이런 일이 발생하는지 계속 살펴보고 만져보다가

이런걸 발견했다.

이걸 Uncheck해서 Tick이 활성화되어 계속 검사했기 때문이다.

 

그래서 CPP를 이렇게 바꾸니까 해결됐다.

#include "Enemy/EnemyAI/Decorator/BTD_IsWithInIdealRange.h"
#include "AIController.h"
#include "BehaviorTree/BlackboardComponent.h"

UBTD_IsWithInIdealRange::UBTD_IsWithInIdealRange()
{
    bNotifyTick = true;
    bNotifyBecomeRelevant = true;
	bNotifyCeaseRelevant = true;
    LastConditionValue = false;
}

void UBTD_IsWithInIdealRange::OnBecomeRelevant(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
	Super::OnBecomeRelevant(OwnerComp, NodeMemory);
    bNotifyTick = true;
    UE_LOG(LogTemp, Display, TEXT("OnBecomeRelevant Call"));
}

void UBTD_IsWithInIdealRange::OnCeaseRelevant(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
	Super::OnCeaseRelevant(OwnerComp, NodeMemory);
    bNotifyTick = false;
    UE_LOG(LogTemp, Display, TEXT("OnCeaseRelevant Call"));
}

void UBTD_IsWithInIdealRange::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
    bool bConditionValue = CalculateRawConditionValue(OwnerComp, NodeMemory);

    if (bConditionValue != LastConditionValue) {
        OwnerComp.RequestExecution(this);
    }

    LastConditionValue = bConditionValue;
}

bool UBTD_IsWithInIdealRange::CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const
{
    bool bResult = Super::CalculateRawConditionValue(OwnerComp, NodeMemory);

    APawn* ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
    if(!ControllingPawn) return true;
    
    UBlackboardComponent* BlackboardComp = OwnerComp.GetBlackboardComponent();
    if (!BlackboardComp) return true;

    UObject* AttackTargetObject = BlackboardComp->GetValueAsObject(AttackTargetKey.SelectedKeyName);
    AActor* AttackTargetActor = Cast<AActor>(AttackTargetObject);
    float IdealRange = BlackboardComp->GetValueAsFloat(IdealRangeKey.SelectedKeyName);
    AAIController* AIController = OwnerComp.GetAIOwner();

    float AttackTargetDistance = AttackTargetActor->GetDistanceTo(ControllingPawn);

    if((AttackTargetDistance - ErrorMarin) <= IdealRange){
        return false;
    }

    return true;
}


FString UBTD_IsWithInIdealRange::GetStaticDescription() const
{
    return FString::Printf(TEXT("\nAttackTargetKey: %s\nIdealRangeKey: %s"), *AttackTargetKey.SelectedKeyName.ToString(), *IdealRangeKey.SelectedKeyName.ToString());
}

위 처럼 Tick Node를 활용해서 풀어냈다.

여기서 갑자기 추가된

OnBecomeRelevant, OnCeaseRelevant는 다음 링크에 들어가면 설명이 되어있다.

https://daisy0461.tistory.com/139