Behavior Tree에서 sequence 노드를 실행하기 전에 Selector를 이용할 수 있다.
Sequence의 경우 진행 도중 문제가 생겨 실패하면 다음 노드로 진행할 수 없지만, Selector의 자식 노드들 중 하나가 실패하면 다음 자식 노드를 실행하는 기능이 있어 실패시 대처가 가능하다.
Selector를 이용하기 위해 BTService를 상속받는 클래스를 생성하자.
// BTService_SearchTarget.h
UCLASS()
class UNREALINTRODUCTION_API UBTService_SearchTarget : public UBTService
{
GENERATED_BODY()
public:
UBTService_SearchTarget();
// UBTService에 있는 TickNode 함수를 오버라이딩
virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
};
헤더에서 TickNode라는 함수를 오버라이딩하여 선언하고,
// BTService_SearchTarget.cpp
#include "BTService_SearchTarget.h"
// 헤더 추가
#include "MyAIController.h"
#include "MyCharacter.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "DrawDebugHelpers.h"
UBTService_SearchTarget::UBTService_SearchTarget()
{
NodeName = TEXT("SearchTarget");
Interval = 1.0f;
}
// 주기적으로 실행하는 Node
void UBTService_SearchTarget::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
// 각종 정보가 담긴 OwnerComp를 가져옴
auto CurrentPawn = OwnerComp.GetAIOwner()->GetPawn();
if (CurrentPawn == nullptr)
return;
UWorld* World = CurrentPawn->GetWorld();
FVector Center = CurrentPawn->GetActorLocation();
float SearchRadius = 500.f;
if (World == nullptr)
return;
// 충돌 테스트 결과 저장
TArray<FOverlapResult> OverlapResults;
FCollisionQueryParams QueryParams(NAME_None, false, CurrentPawn);
// 충돌 체크하여 bool형태로 저장
bool bResult = World->OverlapMultiByChannel(
OverlapResults,
Center,
FQuat::Identity,
ECollisionChannel::ECC_GameTraceChannel2,
FCollisionShape::MakeSphere(SearchRadius),
QueryParams);
if (bResult)
{
// overlap을 확인하고
for (auto& OverlapResult : OverlapResults)
{
AMyCharacter* MyCharacter = Cast<AMyCharacter>(OverlapResult.GetActor());
if (MyCharacter && MyCharacter->GetController()->IsPlayerController())
{
// overlap 된 Actor를 Target변수로 black board로 전달
OwnerComp.GetBlackboardComponent()->SetValueAsObject(FName(TEXT("Target")), MyCharacter);
DrawDebugSphere(World, Center, SearchRadius, 16, FColor::Green, false, 0.2f);
return;
}
}
DrawDebugSphere(World, Center, SearchRadius, 16, FColor::Red, false, 0.2f);
}
else
{
OwnerComp.GetBlackboardComponent()->SetValueAsObject(FName(TEXT("Target")), nullptr);
DrawDebugSphere(World, Center, SearchRadius, 16, FColor::Red, false, 0.2f);
}
}
cpp파일에서 TickNode함수안에 충돌 체크 및 충돌한 Actor객체를 Target변수로 전달하도록 정의한다.
이 Target을 통해 블랙보드에서 충돌된 캐릭터의 정보를 가져올 수 있다.
블랙보드에 새 키로 Object 자료형인 Target을 생성하고, 디테일에서 Base Class를 캐릭터의 클래스로 지정하였다.
작성한 BTService 클래스는 Behavior Tree에서 'Selector 우클릭 → 서비스 추가'를 통해 삽입할 수 있다.
위 사진처럼 'Selector → 데코레이터추가'를 통해 조건을 줄 수 있다.
위 그림처럼 Behavior Tree를 구성한 다음 필요한 Blackboard key를 Target으로 설정하면 범위 안에 캐릭터가 들어오면 AI가 접근한다.
위에서는 BTService 클래스를 만들어서 Selector에 넣었지만, 이번에는 BTDecorator 클래스를 상속하여 생성해보자.
// BTDecorator_CanAttack.h
UCLASS()
class UNREALINTRODUCTION_API UBTDecorator_CanAttack : public UBTDecorator
{
GENERATED_BODY()
public:
UBTDecorator_CanAttack();
virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override;
};
CanAttack이라는 BTDecorater 클래스를 생성하고, 헤더에 CanculateRawConditionValue함수를 오버라이드 한다.
// BTDecorator_CanAttack.cpp
// 헤더 추가
#include "MyAIController.h"
#include "MyCharacter.h"
#include "BehaviorTree/BlackboardComponent.h"
// 생성자 - 노드 이름 정의
UBTDecorator_CanAttack::UBTDecorator_CanAttack()
{
NodeName = TEXT("CanAttack");
}
bool UBTDecorator_CanAttack::CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const
{
bool bResult = Super::CalculateRawConditionValue(OwnerComp, NodeMemory);
auto CurrentPawn = OwnerComp.GetAIOwner()->GetPawn();
if (CurrentPawn == nullptr)
return false;
// Target으로 catch 한 값을 가져옴
auto Target = Cast<AMyCharacter>(OwnerComp.GetBlackboardComponent()->GetValueAsObject(FName(TEXT("Target"))));
if (Target == nullptr)
return false;
// Target과 거리 계산
return bResult && Target->GetDistanceTo(CurrentPawn) <= 200.f;
}
cpp파일에서는 선언한 CanculateRawConditionValue함우세 Target을 가져와서 그 거리를 계산하는 코드를 작성한다.
이렇게 생성한 BTDecorater를 Behavior Tree에서 데코레이터로 추가할 수 있다.
이번에는 BTTaskNode 클래스를 상속한 클래스를 만들어보자.
// BTTask_Attack.h
UCLASS()
class UNREALINTRODUCTION_API UBTTask_Attack : public UBTTaskNode
{
GENERATED_BODY()
public:
UBTTask_Attack();
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
// udpate 함수
virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
private:
bool bIsAttacking = false;
};
BTTask_Attack 헤더에는 ExecuteTask와 TickTask로 실행함수 및 update함수를 선언한다.
// BTTask_Attack.cpp
#include "BTTask_Attack.h"
// 헤더 추가
#include "MyAIController.h"
#include "MyCharacter.h"
#include "BehaviorTree/BlackboardComponent.h"
UBTTask_Attack::UBTTask_Attack()
{
bNotifyTick = true;
}
EBTNodeResult::Type UBTTask_Attack::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
// 상위 부모 호출, 오버라이딩의 기본
EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
auto MyCharacter = Cast<AMyCharacter>(OwnerComp.GetAIOwner()->GetPawn());
if (MyCharacter == nullptr)
return EBTNodeResult::Failed;
MyCharacter->Attack();
bIsAttacking = true;
// OnAttackEnd 함수에 람다(무명)함수로 정의
MyCharacter->OnAttackEnd.AddLambda([this]()
{
bIsAttacking = false;
});
return Result;
}
void UBTTask_Attack::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickTask(OwnerComp, NodeMemory, DeltaSeconds);
if (bIsAttacking == false)
// Behavior Tree 종료처리
FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
}
ExecuteTask에서 MyCharacter의 Attack함수를 호출하고, 향후 만들 OnAttackEnd라는 델리게이트에 bIsAttacking 변수에 false를 넣어 공격이 끝남을 알려주도록 한다.
TickTask에서는 FinishLatentTask를 통해 Behavior Tree를 종료처리한다.
공격이 끝남을 알려줄 수 있는 델리게이트를 만들기 위해 Character 클래스를 수정한다.
// MyCharacter.h
DECLARE_MULTICAST_DELEGATE(FOnAttackEnd);
UCLASS()
class UNREALINTRODUCTION_API AMyCharacter : public ACharacter
{
/.../
// 델리게이트 선언
FOnAttackEnd OnAttackEnd;
/.../
}
// MyCharacter.cpp
/.../
void AMyCharacter::OnAttackMontageEnded(UAnimMontage* Montage, bool bInterrupted)
{
IsAttacking = false;
// Attack이 끝남을 통보
OnAttackEnd.Broadcast();
}
/.../
OnAttackMontageEnded에서 Delegate Broadcast를 통해 bind된 함수 (위에서 람다함수로 처리한 부분)을 오브젝트가 호출하도록 한다.
최종적으로 CanAttack의 Sequence에 생성한 BTTaskNode Attack을 연결한다.
'개발 · 컴퓨터공학' 카테고리의 다른 글
Learning Unreal 4 언리얼 공부일지 - 클래스에 대해 [UObject, Actor, Pawn, Controller, Character] (0) | 2022.02.09 |
---|---|
Learning Unreal 4 언리얼 공부일지 - UMG 사용해보기 (0) | 2022.02.08 |
Learning Unreal 4 언리얼 공부일지 - AIController class와 Behaivor Tree (0) | 2022.02.06 |
Learning Unreal 4 언리얼 공부일지 - class 삭제 or 이름 바꾸기 (0) | 2022.02.05 |
Learning Unreal 4 언리얼 공부일지 - Hp bar 만들기 (0) | 2022.02.02 |