- 먼저 분기를 위해 로직을 확장한다.
- 데코레이터 클래스는 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;
}
- 회전 태스크를 완성하면 공격로직에서 사용한 시퀸스 컴포짓을 심플 패러럴 컴포짓으로 대처 한다
'Unreal > Game 1 (C++)' 카테고리의 다른 글
20. 프로젝트이 설정과 무한 맵의 제작 2 (클래스 기본 객체) (0) | 2019.05.08 |
---|---|
20. 프로젝트이 설정과 무한 맵의 제작 1 (트리거+모듈) (0) | 2019.05.08 |
19. AI 컨트롤러와 행동트리 3 (추격) (0) | 2019.05.08 |
19. AI 컨트롤러와 행동트리 2 (정찰) + BTTask_ (1) | 2019.05.08 |
19. AI 컨트롤러와 행동트리 1 + 네비시스템 (0) | 2019.05.08 |