본문 바로가기
언리얼 C++ 게임 개발의 정석/11. AI 컨트롤러와 비헤어비어 트리

11.4 NCP 공격 모드

by ftftgop3 2024. 5. 4.

추격 모드에서 따라 잡으면 공격 하는 모드 추가

NCP 행동은 플레이어의 거리에 따라서 추격 공격 분기

셀렉터 컴포짓으로 추가 후 노드 정리

 

공격 범위 체크 데코레이터 생성 

블랙보드 값을 참조 하지 않고 플레이어가 공격 범위 내에 있는 판단

CalculateRawConditionValue() override 해서 원하는 조건 달성 했는지 판단

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

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

#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;
	//TargetKey 값으로 타켓 확인
	auto Target = Cast<AABCharacter>(OwnerComp.GetBlackboardComponent()->GetValueAsObject(AABAIController::TargetKey));
	if (nullptr == Target)
		return false;
	//200m 내에 있으면 true 리턴
	bResult = (Target->GetDistanceTo(ControllingPawn) <= 200.0f);
	return bResult;
}

 

비헤어비어 트리에 공격 범위 데코레이트 부착

공격 범위 데코레이트 동작 시 Wait 태스크 1.5 실행

반대편에는 공격 범위 데코레이트 추후 Inverse Condition 활성화  

공격 범위 내에 없으면 다시 추격 하게 설정

 

공격 태스크 생성

ExecuteTask() 태스크 동작 하며 공격 시작 애니메이션 동작

공격 애니메이션이 끝날 떄까지 대기 하기 위해 결과 값을 Inprogress 반환

 

공격 애니메이션 종료 시 태스크가 끝났다고 알려주는 FinishLatentTask 사용

노드의 Tick 기능 활성화 해 태스크 종료 명령을 내려준다

UCLASS()
class ARENABATTLE_API UBTTask_Attack : public UBTTaskNode
{
	GENERATED_BODY()
public:
	UBTTask_Attack();

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

protected:
	virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSecondes) override;

};
#include "BTTask_Attack.h"
#include "ABAIController.h"
#include "ABCharacter.h"

UBTTask_Attack::UBTTask_Attack()
{
	//Tick 활성화
	bNotifyTick = true;
	
}
//태스크 동작
EBTNodeResult::Type UBTTask_Attack::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
	EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
	// 지연 태스크로 동작 (주위 : FinishLatentTask 호출 하지 않으면 계속 태스트 동작)  
	return EBTNodeResult::InProgress; 
}

void UBTTask_Attack::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
	Super::TickTask(OwnerComp, NodeMemory, DeltaSeconds);
	//태스트 동작 끝
	FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
}

 

공격 태스트 실제 구현

캐릭터에 공격 명령 실행 후 공격 애니메이션 종료 시 태스트 종료 하게 구현

 

ABCharacter 변경

Attack() 함수를 public으로 변경

델리게이트 사용 해 캐릭터에서 공격 애니메이션 종료 시 델리게이트 실행 

DECLARE_MULTICAST_DELEGATE(FOnAttackEndDelegate);

UCLASS()
class ARENABATTLE_API AABCharacter : public ACharacter
{
	GENERATED_BODY()
    public:
    void Attack(); //public 변경
	FOnAttackEndDelegate OnAttackEnd;
}

//공격 애니메이션 종료 시 실행
void AABCharacter::OnAttackMontageEnded(UAnimMontage* Montage, bool bInterrupted)
{
	//IsAttacking, CurrentCombo > 0 일때 동작
	ABCHECK(IsAttacking);
	ABCHECK(CurrentCombo > 0);
	IsAttacking = false;
	AttackEndComboState();

	//델리게이트 실행
	OnAttackEnd.Broadcast();
}

BTTask_Attack 변경

ABCharacter의 OnattackEnd 델리게이트에 함수 등록

IsAttacking로 공격 끝나면 태스크 종료 

#include "ABAIController.h"
#include "ABCharacter.h"

UBTTask_Attack::UBTTask_Attack()
{
	//Tick 활성화
	bNotifyTick = true;
	IsAttacking = false;

}
//태스크 동작
EBTNodeResult::Type UBTTask_Attack::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
	EBTNodeResult::Type Result = 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;
		});

	// 지연 태스크로 동작 (주위 : FinishLatentTask 호출 하지 않으면 계속 태스트 동작)  
	return EBTNodeResult::InProgress; 
}

void UBTTask_Attack::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
	Super::TickTask(OwnerComp, NodeMemory, DeltaSeconds);
	if(!IsAttacking)
	//태스트 동작 끝
	FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
}

 

공격 시 회전 기능 추가

NPC 공격 시 플레이어가 뒤에 있어도 제자리에서 같은 곳을 공격 하는 현상 발생

공격 하면서 동시에 플레이어를 향해 회전 하는 기능 추가

 

BTTask_TurnToTarget 생성

블랙 보드의 Target 에 향해 일정한 속도로 회전 하는 FMath::RInterTo 함수 사용

UCLASS()
class ARENABATTLE_API UBTTask_TurnToTarget : public UBTTaskNode
{
	GENERATED_BODY()
	
public:
	UBTTask_TurnToTarget();
	virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
};

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

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

EBTNodeResult::Type UBTTaskNode_TurnToTarget::ExecuteTask(UBehaviorTreeComponent & OwnerComp, uint8 * NodeMemory)
{
	EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
	//현재 AI controller 에서 빙의 중인 Pawn객체를 캐스팅
	auto ABCharacter = Cast<AABCharacter>(OwnerComp.GetAIOwner()->GetPawn());
	if (nullptr == ABCharacter)
		return EBTNodeResult::Failed;
	//Target 값 로드
	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; //높이 값 제거

	//방향 벡터로 X축 기준 회전 행렬 생성( 캐릭터가 Target 방향으로 회전 행렬 생성)
	FRotator TargetRot = FRotationMatrix::MakeFromX(LookVector).Rotator();
	//RInterpTo 로 일정한 속도로 회전
	ABCharacter->SetActorRotation(FMath::RInterpTo(ABCharacter->GetActorRotation(), TargetRot, GetWorld()->GetDeltaSeconds(), 2.0f));

	return EBTNodeResult::Succeeded;
}

 

비헤이비어 트리에 심플 패러럴 컴포짓 대체

Attack 를 메인 태스크 로 지정, 회전을 보조 태스크로 지정

동시에 태스크 동작 하며 동작