

BTTaskNode를 상속받은 BTTask_FindPatrolPos 노드 생성
컴파일 에러가 발생하는데, 행동 트리 모듈을 추가하려면 AI에 관련된 모듈을 추가해줘야함.
"NavigationSystem", "AIModule", "GameplayTasks"
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTTaskNode.h"
#include "BTTask_FindPatrolPos.generated.h"
/**
*
*/
UCLASS()
class ARENABATTLE_API UBTTask_FindPatrolPos : public UBTTaskNode
{
GENERATED_BODY()
public:
UBTTask_FindPatrolPos();
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
};
BTTask_FindPatrolPos.h
// Fill out your copyright notice in the Description page of Project Settings.
#include "AI/BTTask_FindPatrolPos.h"
#include "ABAI.h"
#include "AIController.h"
#include "NavigationSystem.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Interface/ABCharacterAIInterface.h"
UBTTask_FindPatrolPos::UBTTask_FindPatrolPos()
{
}
EBTNodeResult::Type UBTTask_FindPatrolPos::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
APawn* ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
if (nullptr == ControllingPawn)
{
return EBTNodeResult::Failed;
}
UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetNavigationSystem(ControllingPawn->GetWorld());
if (nullptr == NavSystem)
{
return EBTNodeResult::Failed;
}
IABCharacterAIInterface* AIPawn = Cast<IABCharacterAIInterface>(ControllingPawn);
if (nullptr == AIPawn)
{
return EBTNodeResult::Failed;
}
FVector Origin = OwnerComp.GetBlackboardComponent()->GetValueAsVector(BBKEY_HOMEPOS);
float PatrolRadius = AIPawn->GetAIPatrolRadius();
FNavLocation NextPatrolPos;
if (NavSystem->GetRandomPointInNavigableRadius(Origin, PatrolRadius, NextPatrolPos))
{
OwnerComp.GetBlackboardComponent()->SetValueAsVector(BBKEY_PATROLPOS, NextPatrolPos.Location);
return EBTNodeResult::Succeeded;
}
return EBTNodeResult::Failed;
}
BTTask_FindPatrolPos.cpp

ABCharacterAIInterface 인터페이스 생성
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "ABCharacterAIInterface.generated.h"
// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UABCharacterAIInterface : public UInterface
{
GENERATED_BODY()
};
DECLARE_DELEGATE(FAICharacterAttackFinished);
/**
*
*/
class ARENABATTLE_API IABCharacterAIInterface
{
GENERATED_BODY()
// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
virtual float GetAIPatrolRadius() = 0;
virtual float GetAIDetectRange() = 0;
virtual float GetAIAttackRange() = 0;
virtual float GetAITurnSpeed() = 0;
virtual void SetAIAttackDelegate(const FAICharacterAttackFinished& InOnAttackFinished) = 0;
virtual void AttackByAI() = 0;
};
ABCharacterAIInterface.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Character/ABCharacterBase.h"
#include "Engine/StreamableManager.h"
#include "Interface/ABCharacterAIInterface.h"
#include "ABCharacterNonPlayer.generated.h"
/**
*
*/
UCLASS(config=ArenaBattle)
class ARENABATTLE_API AABCharacterNonPlayer : public AABCharacterBase, public IABCharacterAIInterface
{
GENERATED_BODY()
public:
AABCharacterNonPlayer();
protected:
virtual void PostInitializeComponents() override;
protected:
void SetDead() override;
void NPCMeshLoadCompleted();
UPROPERTY(config)
TArray<FSoftObjectPath> NPCMeshes;
TSharedPtr<FStreamableHandle> NPCMeshHandle;
// AI Section
protected:
virtual float GetAIPatrolRadius() override;
virtual float GetAIDetectRange() override;
virtual float GetAIAttackRange() override;
virtual float GetAITurnSpeed() override;
virtual void SetAIAttackDelegate(const FAICharacterAttackFinished& InOnAttackFinished) override;
virtual void AttackByAI() override;
FAICharacterAttackFinished OnAttackFinished;
virtual void NotifyComboActionEnd() override;
};
ABCharacterNonPlayer.h

BTService를 상속받은 BTService_Detect 노드 생성
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTService.h"
#include "BTService_Detect.generated.h"
/**
*
*/
UCLASS()
class ARENABATTLE_API UBTService_Detect : public UBTService
{
GENERATED_BODY()
public:
UBTService_Detect();
protected:
virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
};
BTService_Detect.h
// Fill out your copyright notice in the Description page of Project Settings.
#include "AI/BTService_Detect.h"
#include "ABAI.h"
#include "AIController.h"
#include "Interface/ABCharacterAIInterface.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Physics/ABCollision.h"
#include "DrawDebugHelpers.h"
#include "Engine/OverlapResult.h"
UBTService_Detect::UBTService_Detect()
{
NodeName = TEXT("Detect");
Interval = 1.0f;
}
void UBTService_Detect::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
APawn* ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
if (nullptr == ControllingPawn)
{
return;
}
FVector Center = ControllingPawn->GetActorLocation();
UWorld* World = ControllingPawn->GetWorld();
if (nullptr == World)
{
return;
}
IABCharacterAIInterface* AIPawn = Cast<IABCharacterAIInterface>(ControllingPawn);
if (nullptr == AIPawn)
{
return;
}
float DetectRadius = AIPawn->GetAIDetectRange();
TArray<FOverlapResult> OverlapResults;
FCollisionQueryParams CollisionQueryParam(SCENE_QUERY_STAT(Detect), false, ControllingPawn);
bool bResult = World->OverlapMultiByChannel(
OverlapResults,
Center,
FQuat::Identity,
CCHANNEL_ABACTION,
FCollisionShape::MakeSphere(DetectRadius),
CollisionQueryParam
);
if (bResult)
{
for (auto const& OverlapResult : OverlapResults)
{
APawn* Pawn = Cast<APawn>(OverlapResult.GetActor());
if (Pawn && Pawn->GetController()->IsPlayerController())
{
OwnerComp.GetBlackboardComponent()->SetValueAsObject(BBKEY_TARGET, Pawn);
DrawDebugSphere(World, Center, DetectRadius, 16, FColor::Green, false, 0.2f);
DrawDebugPoint(World, Pawn->GetActorLocation(), 10.0f, FColor::Green, false, 0.2f);
DrawDebugLine(World, ControllingPawn->GetActorLocation(), Pawn->GetActorLocation(), FColor::Green, false, 0.27f);
return;
}
}
}
OwnerComp.GetBlackboardComponent()->SetValueAsObject(BBKEY_TARGET, nullptr);
DrawDebugSphere(World, Center, DetectRadius, 16, FColor::Red, false, 0.2f);
}
BTService_Detect.cpp


BTDecorator를 상속받은 BTDecorator_AttackInRange 노드와
BTTask를 상속받은 BTTask_Attack 노드 생성
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTDecorator.h"
#include "BTDecorator_AttackInRange.generated.h"
/**
*
*/
UCLASS()
class ARENABATTLE_API UBTDecorator_AttackInRange : public UBTDecorator
{
GENERATED_BODY()
public:
UBTDecorator_AttackInRange();
protected:
virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override;
};
BTDecorator_AttackInRange.h
// Fill out your copyright notice in the Description page of Project Settings.
#include "AI/BTDecorator_AttackInRange.h"
#include "ABAI.h"
#include "AIController.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Interface/ABCharacterAIInterface.h"
UBTDecorator_AttackInRange::UBTDecorator_AttackInRange()
{
NodeName = TEXT("CanAttack");
}
bool UBTDecorator_AttackInRange::CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const
{
bool bResult = Super::CalculateRawConditionValue(OwnerComp, NodeMemory);
APawn* ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
if (nullptr == ControllingPawn)
{
return false;
}
IABCharacterAIInterface* AIPawn = Cast<IABCharacterAIInterface>(ControllingPawn);
if (nullptr == AIPawn)
{
return false;
}
APawn* Target = Cast<APawn>(OwnerComp.GetBlackboardComponent()->GetValueAsObject(BBKEY_TARGET));
if (nullptr == Target)
{
return false;
}
float DistanceToTarget = ControllingPawn->GetDistanceTo(Target);
float AttackRangeWithRadius = AIPawn->GetAIAttackRange();
bResult = (DistanceToTarget <= AttackRangeWithRadius);
return bResult;
}
BTDecorator_AttackInRange.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTTaskNode.h"
#include "BTTask_Attack.generated.h"
/**
*
*/
UCLASS()
class ARENABATTLE_API UBTTask_Attack : public UBTTaskNode
{
GENERATED_BODY()
public:
UBTTask_Attack();
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
};
BTTask_Attack.h
// Fill out your copyright notice in the Description page of Project Settings.
#include "AI/BTTask_Attack.h"
#include "AIController.h"
#include "Interface/ABCharacterAIInterface.h"
UBTTask_Attack::UBTTask_Attack()
{
}
EBTNodeResult::Type UBTTask_Attack::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
APawn* ControllingPawn = Cast<APawn>(OwnerComp.GetAIOwner()->GetPawn());
if (nullptr == ControllingPawn)
{
return EBTNodeResult::Failed;
}
IABCharacterAIInterface* AIPawn = Cast<IABCharacterAIInterface>(ControllingPawn);
if (nullptr == AIPawn)
{
return EBTNodeResult::Failed;
}
FAICharacterAttackFinished OnAttackFinished;
OnAttackFinished.BindLambda(
[&]()
{
FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
}
);
AIPawn->SetAIAttackDelegate(OnAttackFinished);
AIPawn->AttackByAI();
return EBTNodeResult::InProgress;
}
BTTask_Attack.cpp

BTTask를 상속받은 BTTask_TurnToTarget 노드 생성
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTTaskNode.h"
#include "BTTask_TurnToTarget.generated.h"
/**
*
*/
UCLASS()
class ARENABATTLE_API UBTTask_TurnToTarget : public UBTTaskNode
{
GENERATED_BODY()
public:
UBTTask_TurnToTarget();
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
};
BTTask_TurnToTarget.h
// Fill out your copyright notice in the Description page of Project Settings.
#include "AI/BTTask_TurnToTarget.h"
#include "ABAI.h"
#include "AIController.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Interface/ABCharacterAIInterface.h"
UBTTask_TurnToTarget::UBTTask_TurnToTarget()
{
NodeName = TEXT("Turn");
}
EBTNodeResult::Type UBTTask_TurnToTarget::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
APawn* ControllingPawn = Cast<APawn>(OwnerComp.GetAIOwner()->GetPawn());
if (nullptr == ControllingPawn)
{
return EBTNodeResult::Failed;
}
APawn* TargetPawn = Cast<APawn>(OwnerComp.GetBlackboardComponent()->GetValueAsObject(BBKEY_TARGET));
if (nullptr == TargetPawn)
{
return EBTNodeResult::Failed;
}
IABCharacterAIInterface* AIPawn = Cast<IABCharacterAIInterface>(ControllingPawn);
if (nullptr == AIPawn)
{
return EBTNodeResult::Failed;
}
float TurnSpeed = AIPawn->GetAITurnSpeed();
FVector LookVector = TargetPawn->GetActorLocation() - ControllingPawn->GetActorLocation();
LookVector.Z = 0.0f;
FRotator TargetRot = FRotationMatrix::MakeFromX(LookVector).Rotator();
ControllingPawn->SetActorRotation(FMath::RInterpTo(ControllingPawn->GetActorRotation(), TargetRot, GetWorld()->GetDeltaSeconds(), TurnSpeed));
return EBTNodeResult::Succeeded;
}
BTTask_TurnToTarget.cpp


완성된 행동트리
이득우의 언리얼 프로그래밍 Part2 - 언리얼 게임 프레임웍의 이해 강의 | 이득우 - 인프런
이득우 | 대기업 현업자들이 수강하는 언리얼 C++ 프로그래밍 전문 과정입니다. 언리얼 C++ 프로그래밍을 사용해 핵&슬래시 로그라이크 게임 예제를 처음부터 끝까지 체계적으로 제작하는 방법을
www.inflearn.com
'게임 개발 > 언리얼 5' 카테고리의 다른 글
언리얼 5 - 게임 플로우 다듬기 (0) | 2024.12.19 |
---|---|
언리얼 5 - 헤드업디스플레이의 구현 (0) | 2024.12.18 |
언리얼5 - 행동트리 모델의 이해 (1) | 2024.12.15 |
언리얼 5 - 게임 데이터 관리 (1) | 2024.12.12 |
언리얼 5 - 무한맵의 제작 (0) | 2024.12.11 |