• 먼저 분기를 위해 로직을 확장한다.
  • 데코레이터 클래스는 CalculateRawConditionValue 함수를 상속받아 원하는 조건이 달성됐는지 파악하도록 설계됐다. 이 함수는 const로 선언돼 데코레이터 클래스의 멤버 변수 값을 변경할 수 없다.

 

//h
UCLASS()
class ARENABATTLE_API UBTDecorator_IsInAttackRange : public UBTDecorator
{
    GENERATED_BODY()
public:
    UBTDecorator_IsInAttackRange();

protected:
    virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override;
};

//cpp
#include "BTDecorator_IsInAttackRange.h"
#include "ABAIController.h"
#include "ABCharacter.h"
#include "BehaviorTree/BlackboardComponent.h"

UBTDecorator_IsInAttackRange::UBTDecorator_IsInAttackRange()
{
    NodeName = TEXT("CanAttack");
}

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

    auto ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
    if (nullptr == ControllingPawn)
        return false;

    auto Target = Cast<AABCharacter>(OwnerComp.GetBlackboardComponent()->GetValueAsObject(AABAIController::TargetKey));

    if (nullptr == Target)
        return false;

    bResult = (Target->GetDistanceTo(ControllingPawn) <= 200.0f);
    return bResult;
}
  • 그리고 완성된 데코를 가장 왼쪾에 위치한 시퀸스 컴포짓에 부착한다
  • 우측은 반대 조건으로 부착한다(밑 사진에는 안들어 있음)

  • Wait 대신에 실레조 플레이어를 공격할 태스크를 생성한다
  • 공격 태스크는 공격 애니메이션이 끝날 떄까지 대기해야 하는 지연 태스크 이므로 ExecuteTask의 결과 값을 InProgress로 반환하고, 공격이 끝났을 떄 태스크가 끝났다고 알려줘야 한다.
  • 이를 알려주는 함수가 FinistLatentTask 이다. 이 함수를 호출하지 않으면 행동트리 시스템은 현재 태스크에 계속 머물어 있는다. 그래서 호출 할수 있도록 노드의 Tick 기능을 활성화하고 조건을 파악한 후 태스크 종료 명령을 내려줘야 한다
//h
UCLASS()
class ARENABATTLE_API UBTTask_Attack : public UBTTaskNode
{
    GENERATED_BODY()
public:
    UBTTask_Attack();

protected:

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

//cpp
#include "BTTask_Attack.h"

UBTTask_Attack::UBTTask_Attack()
{
    bNotifyTick = true; //틱 기능 활성화?

}

EBTNodeResult::Type UBTTask_Attack::ExecuteTask(UBehaviorTreeComponent & OwnerComp, uint8 * NodeMemory)
{
    Super::ExecuteTask(OwnerComp, NodeMemory);
    return EBTNodeResult::InProgress;
}

void UBTTask_Attack::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
    Super::TickTask(OwnerComp, NodeMemory, DeltaSeconds);
    FinishLatentTask(OwnerComp,EBTNodeResult::Succeeded);
}
  • 실제로 캐릭터에 공격 명령을 내리고, 공격이 끝난 시점을 파악해 태스크를 종료하도록 기능을 구현한다
  • AI 컨트롤러에서도 공격명령을 내릴 수 있도록 ABCharacter 클래스의 Attack 함수의 접근 권한을 public으로 변경한다 .
  • 플레이어의 공격이 종료되면 공격 태스크에서 해당 알림을 받을 수 있도록 델리게이트를 새로 선언하고 공격이 종료될 떄 이를 호출하는 로직을 캐릭터에 구현한다
  • 캐릭터의 델리게이트 설정이 완료되면 태스크에서 람다 함수를 해당 델리게이트에 등록하고 Tick 함수 로직에서 이를 파악해 FinishLatentTask 함수를 호출함으로써 태스크를 종료하도록 구현한다

 

//h
DECLARE_MULTICAST_DELEGATE(FOnAttackEndDelegate);

{
public:
    void Attack();
    FOnAttackEndDelegate OnAttackEnd;
}

//cpp
void AABCharacter::OnAttackMontageEnded(UAnimMontage * Montage, bool bInterrupted)
{
.....

    OnAttackEnd.Broadcast();
}
//h
UCLASS()
class ARENABATTLE_API UBTTask_Attack : public UBTTaskNode
{
    GENERATED_BODY()
public:
    UBTTask_Attack();

protected:

    virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent & OwnerComp, uint8 * NodeMemory) override;
    virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
private:
    bool IsAttacking = false;
};


//cpp
#include "BTTask_Attack.h"
#include "ABAIController.h"
#include "ABCharacter.h"

UBTTask_Attack::UBTTask_Attack()
{
    bNotifyTick = true; //틱 기능 활성화?
    IsAttacking = false;
}

EBTNodeResult::Type UBTTask_Attack::ExecuteTask(UBehaviorTreeComponent & OwnerComp, uint8 * NodeMemory)
{
    Super::ExecuteTask(OwnerComp, NodeMemory);

    auto ABCharacter = Cast<AABCharacter>(OwnerComp.GetAIOwner()->GetPawn());
    if (nullptr == ABCharacter)
        return EBTNodeResult::Failed;

    ABCharacter->Attack();
    IsAttacking = true;

    ABCharacter->OnAttackEnd.AddLambda([this]() ->void {IsAttacking = false; });

    return EBTNodeResult::InProgress;
}


void UBTTask_Attack::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
    Super::TickTask(OwnerComp, NodeMemory, DeltaSeconds);
    if (!IsAttacking)
    {
        FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
    }
}
  • 위 내용을 하고 기존 제일 왼쪽에 있던 태스크를 새로 만든 Attack로 교체한다
  • NPC가 플레이어를 공격할 떄 제자리에 정지하기 떄문에 플레이어가 NPC 뒤로 돌아가도 계속 같은 곳을 공격한다
  • 그래서 공격하면시 동시에 플레이러를 향해 회전하는 기능을 추가한다
  • 블랙보드의 Target으로 회전하는 태스크를 추가한다. BTTask_TurnToTarget을 만들고 일정한 속도로 회전하도록 FMath::RInterpTo 함수를 사용해 회전시키는 기능을 구현한다

 

//h
UCLASS()
class ARENABATTLE_API UBTTask_TurnToTarget : public UBTTaskNode
{
    GENERATED_BODY()

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

//cpp
#include "BTTask_TurnToTarget.h"
#include "ABAIController.h"
#include "ABCharacter.h"
#include "BehaviorTree/BlackboardComponent.h"


UBTTask_TurnToTarget::UBTTask_TurnToTarget()
{
    NodeName = TEXT("Turn");
}

EBTNodeResult::Type UBTTask_TurnToTarget::ExecuteTask(UBehaviorTreeComponent & OwnerComp, uint8 * NodeMemory)
{
    Super::ExecuteTask(OwnerComp, NodeMemory);

    auto ABCharacter = Cast<AABCharacter>(OwnerComp.GetAIOwner()->GetPawn());
    if (nullptr == ABCharacter)
    {
        return EBTNodeResult::Failed;
    }
    auto Target = Cast<AABCharacter>(OwnerComp.GetBlackboardComponent()->GetValueAsObject(AABAIController::TargetKey));
    if (nullptr == Target)
        return EBTNodeResult::Failed;

    FVector LookVector = Target->GetActorLocation() - ABCharacter->GetActorLocation();
    LookVector.Z = 0.0f;

    FRotator TargetRot = FRotationMatrix::MakeFromX(LookVector).Rotator();
    ABCharacter->SetActorRotation(FMath::RInterpTo(
        ABCharacter->GetActorRotation(),
        TargetRot,GetWorld()->GetDeltaSeconds(),
        2.0f));

    return EBTNodeResult::Succeeded;
}
  • 회전 태스크를 완성하면 공격로직에서 사용한 시퀸스 컴포짓을 심플 패러럴 컴포짓으로 대처 한다

+ Recent posts