공부용 이모저모
UE4 - AI컨트롤러와 비헤이비어 트리 본문
AIController와 내비게이션 시스템
NPC - 플레이어가 조종하지 않지만 스스로 행동하는 캐릭터를 의미
언리얼 엔진은 AI로 NPC를 조종하도록 AI 컨트롤러를 제공

ABCharacter이 AI를 들고있도록 코드를 수정한다.
ABCharacter.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "ABCharacter.h"
...
#include "ABAIController.h"
// Sets default values
AABCharacter::AABCharacter()
{
...
AIControllerClass = AABAIController::StaticClass();
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
}
여기서 AIControllerClass와 AutoPossessAI는 Pawn이 기본으로 갖고있는 변수이다.

AIController을 이용해 플레이어를 쫒아다니도록 수정한다.
주인공을 쫒아다니기 위해 내비게이션 메시를 깔기로 한다.




ABAIController이 빙의한 폰에 목적지(컨트롤러가 조작하는 플레이어의 위치)를 알려줘 3초마다 이동하는 AI를 구현한다
우선 쫒아오기전에 랜덤으로 이동하는 캐릭터 AI 부터 구현하기로 한다
GetRandomPointInNavigableRadius : 이동가능한 내비매쉬 목적지를 랜덤으로 갖고온다
SimpleMoveToLocation : 목적지로 폰을 이동시킨다
UE4 4.20 버전 기준으로 NavigationSystem 클래스가 NavigationSystemV1로 바뀌었으므로 4.20이후 버전 기준으로
기재
ArenaBattle.Build.cs
// Fill out your copyright notice in the Description page of Project Settings.
using UnrealBuildTool;
public class ArenaBattle : ModuleRules
{
public ArenaBattle(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "UMG", "NavigationSystem"});
PrivateDependencyModuleNames.AddRange(new string[] { });
// Uncomment if you are using Slate UI
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
// Uncomment if you are using online features
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
}
}
ABAIController.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "ABAIController.h"
#include "NavigationSystem.h"
#include "Blueprint/AIBlueprintHelperLibrary.h"
AABAIController::AABAIController()
{
//3초마다 호출한다
RepeatInterval = 3.0f;
}
void AABAIController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
//타이머,3초마다 OnRepeatTimer 호출
GetWorld()->GetTimerManager().SetTimer(RepeatTimerHandle, this, &AABAIController::OnRepeatTimer, RepeatInterval, true);
}
void AABAIController::OnUnPossess()
{
Super::OnUnPossess();
GetWorld()->GetTimerManager().ClearTimer(RepeatTimerHandle);
}
void AABAIController::OnRepeatTimer()
{
auto CurrentPawn = GetPawn();
if (nullptr == CurrentPawn)
return;
//월드상에 배치된 내비메쉬를 얻어온다
UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetNavigationSystem(GetWorld());
if (nullptr == NavSystem)
return;
FNavLocation NextLocation;
if (NavSystem->GetRandomPointInNavigableRadius(FVector::ZeroVector, 500.0f, NextLocation))
{
//목표 위치 로그를 표기한다.
UAIBlueprintHelperLibrary::SimpleMoveToLocation(this, NextLocation.Location);
UE_LOG(LogTemp, Warning, TEXT("Next Location: %s"), *NextLocation.Location.ToString());
}
}
ABAIController.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "ArenaBattle.h"
#include "AIController.h"
#include "ABAIController.generated.h"
UCLASS()
class ARENABATTLE_API AABAIController : public AAIController
{
GENERATED_BODY()
public:
AABAIController();
virtual void OnPossess(APawn* InPawn) override;
virtual void OnUnPossess() override;
private:
void OnRepeatTimer();
FTimerHandle RepeatTimerHandle; //타이머
float RepeatInterval; //타이머 주기
};

비헤이비어 트리 시스템
AI의 행동패턴을 보다 체계적으로 설계하기 위해 언리얼에서 제공하는 시스템
비헤이비어(NPC가 해야할 행동)를 분석하고 우선순위가 높은 행동부터 NPC가 실행하도록 해준다.


블랙 보드 : 인공지능의 판단에 사용하는 데이터의 집합.NPC의 의사 결정은 블랙보드에 있는 데이터를 기반으로 진행.
비헤이비어 트리 : 블랙보드 데이터에 기반해 설계한 비헤이비어 트리의 정보를 저장한 애셋.

Wait는 말그대로, 폰에게 지정 시간동안 대기하라는 명령을 내린다.
반드시 Composite node를 통해서만 AI가 실행되는데, 대표적으로 Selector,Sequence 두 종류가 존재한다.
Sequence는 연결된 태스크가 False가 나올때까지 계속 실행시킨다.

ArenaBattle.Build.cs
// Fill out your copyright notice in the Description page of Project Settings.
using UnrealBuildTool;
public class ArenaBattle : ModuleRules
{
public ArenaBattle(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "UMG", "NavigationSystem", "AIModule"});
PrivateDependencyModuleNames.AddRange(new string[] { });
// Uncomment if you are using Slate UI
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
// Uncomment if you are using online features
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
}
}
ABAIController.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "ArenaBattle.h"
#include "AIController.h"
#include "ABAIController.generated.h"
UCLASS()
class ARENABATTLE_API AABAIController : public AAIController
{
GENERATED_BODY()
public:
AABAIController();
virtual void OnPossess(APawn* InPawn) override;
private:
UPROPERTY()
class UBehaviorTree* BTAsset;
UPROPERTY()
class UBlackboardData* BBAsset;
};
AABAIController.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "ABAIController.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BlackboardData.h"
AABAIController::AABAIController()
{
static ConstructorHelpers::FObjectFinder<UBlackboardData> BBObject(TEXT("/Game/Book/AI/BB_ABCharacter.BB_ABCharacter"));
if (BBObject.Succeeded())
BBAsset = BBObject.Object;
static ConstructorHelpers::FObjectFinder<UBehaviorTree> BTObject(TEXT("/Game/Book/AI/BT_ABCharacter.BT_ABCharacter"));
if (BTObject.Succeeded())
BTAsset = BTObject.Object;
}
void AABAIController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
if (UseBlackboard(BBAsset, Blackboard))
{
if (!RunBehaviorTree(BTAsset))
UE_LOG(LogTemp, Warning, TEXT("AIController couldn't run behavior tree!"));
}
}


HomePos : 캐릭터 생성위치
PatrolPos : 매번 이동할 위치
ABAIController.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "ABAIController.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BlackboardData.h"
#include "BehaviorTree/BlackboardComponent.h"
const FName AABAIController::HomePosKey(TEXT("HomePos"));
const FName AABAIController::PatrolPosKey(TEXT("PatrolPos"));
AABAIController::AABAIController()
{
static ConstructorHelpers::FObjectFinder<UBlackboardData> BBObject(TEXT("/Game/Book/AI/BB_ABCharacter.BB_ABCharacter"));
if (BBObject.Succeeded())
BBAsset = BBObject.Object;
static ConstructorHelpers::FObjectFinder<UBehaviorTree> BTObject(TEXT("/Game/Book/AI/BT_ABCharacter.BT_ABCharacter"));
if (BTObject.Succeeded())
BTAsset = BTObject.Object;
}
void AABAIController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
if (UseBlackboard(BBAsset, Blackboard))
{
//현재 로케이션 값을 블랙보드의 HpomePosKey에 저장시킨다
Blackboard->SetValueAsVector(HomePosKey, InPawn->GetActorLocation());
if (!RunBehaviorTree(BTAsset))
UE_LOG(LogTemp, Warning, TEXT("AIController couldn't run behavior tree!"));
}
}
ABAIController.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "ArenaBattle.h"
#include "AIController.h"
#include "ABAIController.generated.h"
UCLASS()
class ARENABATTLE_API AABAIController : public AAIController
{
GENERATED_BODY()
public:
AABAIController();
virtual void OnPossess(APawn* InPawn) override;
static const FName HomePosKey;
static const FName PatrolPosKey;
private:
UPROPERTY()
class UBehaviorTree* BTAsset;
UPROPERTY()
class UBlackboardData* BBAsset;
};

ArenaBattle.Build.cs
// Fill out your copyright notice in the Description page of Project Settings.
using UnrealBuildTool;
public class ArenaBattle : ModuleRules
{
public ArenaBattle(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "UMG", "NavigationSystem", "AIModule", "GamePlayTasks"});
PrivateDependencyModuleNames.AddRange(new string[] { });
// Uncomment if you are using Slate UI
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
// Uncomment if you are using online features
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
// To include OnlineSubsystemSteam, dadd it to the plugins section in your uproject file with the Enabled attribute set to true
}
}
GameplayTasks를 선언해야한다.

비헤이비어트리에서 블랙보드에 직접 값을 쓰도록 설계하기 위해 TaskNode를 만든다.
비헤이비어트리는 태스크를 실행할때마다 ExecuteTask라는 함수를 실행시킨다.
ExecuteTask 함수는 아래중 하나의 값을 반환한다.
Aborted : 태스크 실행중 중단됨(실패)
Failed : 수행했으나 실패함
Succeded : 수행하고 성공했다
InProgress : 태스크를 수행하고있다.실행 결과는 추후 알려줆.
함수의 실행결과에 따라, 다음 태스크로 수행할지, 중단할지가 결정됨.
시퀸스 컴포짓의 경우 태스크가 실패할때까지 계속하는 성질을 지니고있음.
ExecuteTask 함수에서 다음 정찰지점을 찾는 로직을 구현 하고, 실행결과를 반환하도록 만들어본다.
*태스크의 이름을 다른 이름으로 표시하고 싶으면 NodeName속성을 다른값으로 바꾸면 됨
BTTask_FindPatrolPos.h
// 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.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "BTTask_FindPatrolPos.h"
#include "ABAIController.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "NavigationSystem.h"
UBTTask_FindPatrolPos::UBTTask_FindPatrolPos()
{
NodeName = TEXT("FindPatrolPos");
}
EBTNodeResult::Type UBTTask_FindPatrolPos::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
auto ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
if (nullptr == ControllingPawn)
{
return EBTNodeResult::Failed;
}
//4.20 버전부터 UNavigationSystem은 UNavigationSystemV1로 바뀌었다.
UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetNavigationSystem(ControllingPawn->GetWorld());
//내비 매쉬 못찾으면 False
if (nullptr == NavSystem)
return EBTNodeResult::Failed;
FVector Origin = OwnerComp.GetBlackboardComponent()->GetValueAsVector(AABAIController::HomePosKey);
FNavLocation NextPatrol;
if (NavSystem->GetRandomPointInNavigableRadius(FVector::ZeroVector, 500.0f, NextPatrol))
{
OwnerComp.GetBlackboardComponent()->SetValueAsVector(AABAIController::PatrolPosKey, NextPatrol.Location);
return EBTNodeResult::Succeeded;
}
return EBTNodeResult::Failed;
}



이번엔 플레이어를 쫒아다니도록 메커니즘을 조정한다.

주인공을 발견시 추격하고, 아닐시 정찰을 시켜야하므로 Selector 컴포짓을 사용해 로직을 확장한다.
추격을 더 우선권을 주고, 블랙보드의 Target을 향해 이동하도록 한다.


BTService_Detect.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "ArenaBattle.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.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "BTService_Detect.h"
#include "ABAIController.h"
#include "ABCharacter.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "DrawDebugHelpers.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;
UWorld* World = ControllingPawn->GetWorld();
FVector Center = ControllingPawn->GetActorLocation();
float DetectRadius = 600.0f;
if (nullptr == World) return;
TArray<FOverlapResult> OverlapResults;
FCollisionQueryParams CollisionQueryParam(NAME_None, false, ControllingPawn);
bool bResult = World->OverlapMultiByChannel(
OverlapResults,
Center,
FQuat::Identity,
ECollisionChannel::ECC_GameTraceChannel2,
FCollisionShape::MakeSphere(DetectRadius),
CollisionQueryParam
);
DrawDebugSphere(World, Center, DetectRadius, 16, FColor::Red, false, 0.2f);
}

AI가 캐릭터를 포착할경우, 그게 우리가 조종하는 캐릭터인지 확인하기위해 컨트롤러를 체크할 필요가 있다.
플레이어를 찾을경우 Target값을 플레이어로, 없으면 null을 넣는다.
ABAIController.h
static const FName TargetKey; 추가
ABAIController.cpp
const FName AABAIController::TargetKey(TEXT("Target")); 추가
BTService_Detect.cpp
void UBTService_Detect::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
...
if (bResult)
{
for (auto OverlapResult : OverlapResults)
{
AABCharacter* ABCharacter = Cast<AABCharacter>(OverlapResult.GetActor());
//컨트롤러가 플레이어의 컨트롤러인지
if (ABCharacter && ABCharacter->GetController()->IsPlayerController())
{
OwnerComp.GetBlackboardComponent()->SetValueAsObject(AABAIController::TargetKey, ABCharacter);
//반경을 보여준다
DrawDebugSphere(World, Center, DetectRadius, 16, FColor::Green, false, 0.2f);
//
DrawDebugPoint(World, ABCharacter->GetActorLocation(), 10.0f, FColor::Blue, false, 0.2f);
DrawDebugLine(World, ControllingPawn->GetActorLocation(), ABCharacter->GetActorLocation(), FColor::Blue, false, 0.2f);
return;
}
}
}
else
{
//null로 설정해준다.
OwnerComp.GetBlackboardComponent()->SetValueAsObject(AABAIController::TargetKey, nullptr);
}
DrawDebugSphere(World, Center, DetectRadius, 16, FColor::Red, false, 0.2f);
}

다음은 이동방향에 따라 회전도 자연스럽게 바뀌도록 코드를 좀 더 추가한다
ABCharacter.h
enum class EControlMode
{
GTA,
DIABLO,
NPC //NPC용 모드를 추가
};
...
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
virtual void PostInitializeComponents() override;
virtual float TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, AActor* DamageCauser) override;
virtual void PossessedBy(AController* NewController) override;
...
ABCharacter.cpp
void AABCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
CurveTimeLine.TickTimeline(DeltaTime);
SpringArm->TargetArmLength = FMath::FInterpTo(SpringArm->TargetArmLength, ArmLengthTo, DeltaTime, ArmLengthSpeed);
switch (CurrentControlMode)
{
case EControlMode::GTA:
break;
case EControlMode::DIABLO:
SpringArm->SetRelativeRotation(FMath::RInterpTo(SpringArm->GetRelativeRotation(), ArmRotationTo, DeltaTime, ArmRotationSpeed));
if (DirectionToMove.SizeSquared() > 0.0f)
{
GetController()->SetControlRotation(FRotationMatrix::MakeFromX(DirectionToMove).Rotator());
AddMovementInput(DirectionToMove);
}
break;
case EControlMode::NPC: //NPC모드 추가
bUseControllerRotationYaw = false;
GetCharacterMovement()->bUseControllerDesiredRotation = false;
GetCharacterMovement()->bOrientRotationToMovement = true;
GetCharacterMovement()->RotationRate = FRotator(0.0f, 480.0f, 0.0f);
break;
}
}
...
void AABCharacter::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
if (IsPlayerControlled())
{
//플레이어는 디아블로 시점
CurrentControlMode = EControlMode::DIABLO;
SetControlMode();
GetCharacterMovement()->MaxWalkSpeed = 600.0f;
}
else
{
CurrentControlMode = EControlMode::NPC;
SetControlMode();
//NPC를 주인공이 떨칠수 있게 속도를 조절한다
GetCharacterMovement()->MaxWalkSpeed = 300.0f
}
}




NPC 공격기능 추가
왼쪽 추격로직을 발전시켜서, 플레이어를 따라잡으면 공격하는 가능을 추가한다.
첫번째 행동은 거리로 구분하기 때문에, 한번더 분기가 필요하다.


BTDecorator_IsInAttackRange.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "ArenaBattle.h"
#include "BehaviorTree/BTDecorator.h"
#include "BTDecorator_IsInAttackRange.generated.h"
/**
*
*/
UCLASS()
class ARENABATTLE_API UBTDecorator_IsInAttackRange : public UBTDecorator
{
GENERATED_BODY()
public:
UBTDecorator_IsInAttackRange();
protected:
virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
const override;
};
BTDecorator_IsInAttackRange.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#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;
}





BTTask_Attack.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "ArenaBattle.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;
protected:
virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
private:
bool IsAttacking = false;
};
BTTask_Attack.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "BTTask_Attack.h"
#include "ABAIController.h"
#include "ABCharacter.h"
UBTTask_Attack::UBTTask_Attack()
{
//틱기능 활성화. Finish를 계속 체크한다.
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이 AttackEnd Delegate를 호출하면 IsAttacking을 false로
ABCharacter->OnAttackEnd.AddLambda([this]()->void {
IsAttacking = false;
});
//일단 InProgress에서 머물게한다.공격이 끝나기 전까지 계속 지연시켜준다.
return EBTNodeResult::InProgress;
}
//매 틱마다 공격태스크가 끝났는지를 체크한다.
void UBTTask_Attack::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickTask(OwnerComp, NodeMemory, DeltaSeconds);
//OnAttackEnd Delegate가 호출되면 Attacking이 false 되므로
if (!IsAttacking)
{
//공격 태스크가 끝났음을 알려준다
FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
}
}
ABCharacter.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "Components/TimelineComponent.h"
#include "ArenaBattle.h"
#include "GameFramework/Character.h"
#include "ABCharacter.generated.h"
enum class EControlMode
{
GTA,
DIABLO,
NPC //NPC용 모드를 추가
};
DECLARE_MULTICAST_DELEGATE(FOnAttackEndDelegate);
...
public:
//Called every frame
...
void Attack(); //public으로 바꿔준다
FOnAttackEndDelegate OnAttackEnd;
...
ABCharacter.cpp
void AABCharacter::OnAttackMontageEnded(UAnimMontage* Montage, bool bInterruped)
{
ABCHECK(IsAttacking);
IsAttacking = false;
AttackEndComboState();
OnAttackEnd.Broadcast(); //AttackEnd의 BroadCast호출
}


공격 할 때, 타겟을 향해 회전하면서 공격하도록 하고싶다.
BTTaskNode로 TurnToTarget을 추가한다

BTTask_TurnToTarget.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "ArenaBattle.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.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#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)
{
EBTNodeResult::Type Result = 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 LockVector = Target->GetActorLocation() - ABCharacter->GetActorLocation();
LockVector.Z = 0.0f;
FRotator TargetRot = FRotationMatrix::MakeFromX(LockVector).Rotator(); //방향벡터로부터 각도를 얻어낸다(내적?)
//각도만큼 2초에 걸쳐서 회전시킨다.
ABCharacter->SetActorRotation(FMath::RInterpTo(ABCharacter->GetActorRotation(), TargetRot, GetWorld()->GetDeltaSeconds(), 2.0f));
return EBTNodeResult::Succeeded;
}



'UE4 - C++' 카테고리의 다른 글
| UE4 - 게임데이터와 UI 위젯 (0) | 2021.11.04 |
|---|---|
| UE4 - 아이템 상자와 무기 제작 (0) | 2021.10.28 |
| UE4 - 충돌 설정과 데미지 전달 (0) | 2021.09.30 |
| UE4 - 잔상 효과 만들기(SPAWN/TIME LINE/POSEABLEMESH) (0) | 2021.09.24 |
| UE4 - 애니메이션 몽타주 (0) | 2021.09.09 |