- 정리
- 행동 트리 모델의 구현
- 블랙보드의 설정
- 내비게이션 메시의 설정
- 인터페이스를 활용한 AI와 캐릭터 간의 분리 설계
- 일반 테스크와 지연 테스크의 제작과 활용
- 서비스와 데코레이터의 제작과 활용
- 행동 트리 모델의 구현
- 각각의 행동 및 액션을 언리얼 엔진에서는 테스크(Task)라고 한다.
- 이 것을 구현하기 위해서는 이동할 목적지에 대한 데이터를 어딘가에 저장을 해둬야 하는데, 이 것을 위해 언리얼 엔진은 블랙보드라는 것을 제공하고 있다.
- 동적으로 계속해서 생성되는 맵을 가졌기 때문에 Navigation Mesh에 대한 속성을 편집해줄 필요가 있다.
- 기본적으로 Navigation Mesh는 Runtime Generation이 Static으로 설정되어 있는데, 이를 Dynamic으로 변경해준다.
- 새롭게 생성되는 섹션에 대해서도 NaviMesh를 사용할 수 있게 된다.
#pragma once
#define BBKEY_HOMEPOS TEXT("HomePos")
#define BBKEY_PATROLPOS TEXT("PatrolPos")
#define BBKEY_TARGET TEXT("Target")
- ABAI.h
- 블랙보드에서 사용할 변수들을 전처리문에서 define해주어 사용하는게 편리하다.
#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();
// BTTaskNode에 있는 ExecuteTask 오버라이드
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
};
- BTTask_FindPatrolPos.h
#include "AI/BTTask_FindPatrolPos.h"
#include "ABAI.h"
#include "AI/ABAIController.h"
#include "NavigationSystem.h"
#include "BehaviorTree/BlackboardComponent.h"
UBTTask_FindPatrolPos::UBTTask_FindPatrolPos()
{
}
// BTTaskNode에 있는 ExecuteTask 오버라이드
EBTNodeResult::Type UBTTask_FindPatrolPos::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
// BehaviorTree를 소유한 컴포넌트의 Owner를 GetAIOwner로 가져올 수 있다. 이는 AI 컨트롤러를 상속받은 클래스의 인스턴스가 될 것
// 이것이 어떤 폰을 빙의 하고 있다면, GetPawn을 통해서 가져올 수 있다.
APawn* ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
if (nullptr == ControllingPawn)
{
return EBTNodeResult::Failed;
}
// NavigationSystem 가져오기
// GetNavigationSystem에 월드값 넣어주기. ControllingPawn이 위치한 곳의 월드
UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetNavigationSystem(ControllingPawn->GetWorld());
if (nullptr == NavSystem)
{
return EBTNodeResult::Failed;
}
// 블랙보드 값 가져오기
// Origin은 블랙보드의 HomePos값을 가져오기
FVector Origin = OwnerComp.GetBlackboardComponent()->GetValueAsVector(BBKEY_HOMEPOS);
FNavLocation NextPatrolPos;
// NavigationSystem에서 랜덤하게 Point 값을 가져오는 것 (시작위치, 반경, PointLocation을 저장해줄 변수)
if (NavSystem->GetRandomPointInNavigableRadius(Origin, 500.0f, NextPatrolPos))
{
// 블랙보드의 PatrolPos를 NextPatrolPos.Location값으로 넘겨주기
// 그러면 성공
OwnerComp.GetBlackboardComponent()->SetValueAsVector(BBKEY_PATROLPOS, NextPatrolPos.Location);
return EBTNodeResult::Succeeded;
}
return EBTNodeResult::Failed;
}
- BTTask_FindPatrolPos.cpp
- NPC는 3s +-1s의 Wait 행동을 한 뒤, NaviMesh가 유효한 범위내에서의 Origin(NPC Pawn의 Location)에서 반경 500.0f의 Radius내에서 랜덤하게 PatrolPos를 설정하여 MoveTo 행동을 하게 된다.
- 하드코딩으로 BehaviorTree에 필요한 데이터들을 사용하는게 아닌 Interface를 통해서 NPC에서부터 간접적으로 필요한 값을 얻어오도록 구조를 변경해보자.
#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()
};
/**
*
*/
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:
// NPC가 의무적으로 가져야 될 기본적인 데이터들을 얻어올 수 있도록 함수들 선언
virtual float GetAIPatrolRadius() = 0;
virtual float GetAIDetectRange() = 0;
virtual float GetAIAttackRange() = 0;
virtual float GetAITurnSpeed() = 0;
};
- ABCharacterAIInterface.h
- NPC가 의무적으로 가져야 될 기본적인 데이터들을 얻어올 수 있도록 함수를 선언해준다.
// ...
#include "Interface/ABCharacterAIInterface.h" // AIInterface
#include "ABCharacterNonPlayer.generated.h"
// Config 폴더의 DefaultArenaBattle.ini를 사용해서 불러들이겠다는 의미
UCLASS(config=ArenaBattle)
class ARENABATTLE_API AABCharacterNonPlayer : public AABCharacterBase, public IABCharacterAIInterface
{
GENERATED_BODY()
// ...
// AI Section
protected:
virtual float GetAIPatrolRadius() override;
virtual float GetAIDetectRange() override;
virtual float GetAIAttackRange() override;
virtual float GetAITurnSpeed() override;
};
- ABCharacterNonPlayer.h
// ABCharacterAIInterface 부분
float AABCharacterNonPlayer::GetAIPatrolRadius()
{
return 500.0f;
}
float AABCharacterNonPlayer::GetAIDetectRange()
{
return 0.0f;
}
float AABCharacterNonPlayer::GetAIAttackRange()
{
return 0.0f;
}
float AABCharacterNonPlayer::GetAITurnSpeed()
{
return 0.0f;
}
- ABCharacterNonPlayer.cpp
- ABCharacterAIInterface를 상속받아서 함수들을 오버라이드하고 NPC를 통해 값을 가져올 수 있도록 구성해준다.
// ...
#include "Interface/ABCharacterAIInterface.h"
// BTTaskNode에 있는 ExecuteTask 오버라이드
EBTNodeResult::Type UBTTask_FindPatrolPos::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
// .
// NPC를 통해서 필요한 데이터를 받아오기 위함
IABCharacterAIInterface* AIPawn = Cast<IABCharacterAIInterface>(ControllingPawn);
if (nullptr == AIPawn)
{
return EBTNodeResult::Failed;
}
// 블랙보드 값 가져오기
// Origin은 블랙보드의 HomePos값을 가져오기
FVector Origin = OwnerComp.GetBlackboardComponent()->GetValueAsVector(BBKEY_HOMEPOS);
float PatrolRadius = AIPawn->GetAIPatrolRadius();
FNavLocation NextPatrolPos;
// NavigationSystem에서 랜덤하게 Point 값을 가져오는 것 (시작위치, 반경, PointLocation을 저장해줄 변수)
// 데이터들을 하드 코딩이 아닌 NPC로부터 받아오도록 변경 (500.0f -> PatrolRadius)
if (NavSystem->GetRandomPointInNavigableRadius(Origin, PatrolRadius, NextPatrolPos))
{
// 블랙보드의 PatrolPos를 NextPatrolPos.Location값으로 넘겨주기
// 그러면 성공
OwnerComp.GetBlackboardComponent()->SetValueAsVector(BBKEY_PATROLPOS, NextPatrolPos.Location);
return EBTNodeResult::Succeeded;
}
return EBTNodeResult::Failed;
}
- BTTask_FindPatrolPos.cpp
- Interface를 통해서 AIPawn이라는 변수를 만들어 NPC를 통해 데이터를 받아오도록 캐스팅해준다.
- 이후, 하드 코딩으로 500.0f으로 Radius를 설정한 부분을 PatrolRadius로 변경해준다.
- 현재의 BehaviorTree에서 플레이어를 감지하는 기능을 추가해준다.
- 감지하는 기능은 NPC가 상시적/언제든지 진행할 수 있어야하기 때문에 정찰중에 항상 진행할 수 있도록 서비스 노드를 추가해 주는 것이 좋다.
- 서비스 노드를 C++ 클래스로 추가해보도록 하자.
- BTService라는 클래스가 있다.
- BTService 클래스는 생성자에서 지정된 Interval 마다 Tick으로 행동을 체크한다.
- 서비스 노드를 C++ 클래스로 추가해보도록 하자.
#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:
// Tick노드
virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
};
- BTService_Detect.h
#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"
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);
// OwnerComp를 사용해서 Pawn 가져오기
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();
// 감지되는 플레이어가 다수라고 가정했을때 MultiByChannel, 결과값은 TArray로 들어오게된다.
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)
{
// Pawn을 가져와서 플레이어인지 확인하기
APawn* Pawn = Cast<APawn>(OverlapResult.GetActor());
if (Pawn && Pawn->GetController()->IsPlayerController())
{
// 플레이어라면 해당 Pawn을 Target으로 설정하고 DrawDebug를 하도록
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
- 플레이어가 감지되었을 경우 쫓아가서 공격하는 기능을 추가하도록 하자.
- Selector와 언리얼 기본 Decorator인 BlackBoard를 사용하여 Target이 설정되어 있을 때에만 왼쪽 셀렉터 컴포짓이 수행되도록 하고, Target이 설정되어 있지 않으면 오른쪽 시퀀스 컴포짓이 수행되도록 해준다.
- 또한, 오른쪽 컴포짓이 진행되는 동안에 Target을 감지하게 되면 현재 진행하고 있는 컴포짓을 중단하고 바로 Target을 쫓아갈 수 있도록 Flow Control>Notify Observer와 Observer aborts를 설정해준다.
- Notify Observer - On Value Change
- 값이 변경될 때
- Observer aborts - Self
- 현재 진행하고 있는 컴포짓 중단
- Notify Observer - On Value Change
- 공격을 해야하는지, 아니면 쫓아가야 되는지에 대한 판단을 수행하기 위해서 BTDecorator 노드를 사용해서 Target이 공격범위 내에 있는지에 대해 확인하는 노드 추가하기 (BTDecorator 클래스 생성)
- 범위 내에 있다면 공격하는 태스크 추가 (BTTaskNode 클래스 생성)
- 범위 내에 있는지 판단하기 위한 UBTDecorator_AttackInRange 추가 (BTDecorator 클래스 생성)
#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:
// Decorator의 경우 CalculateRawConditionValue라는 함수를 구현 해줘야 한다.
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()
{
// Decorator 이름
NodeName = TEXT("CanAttack");
}
bool UBTDecorator_AttackInRange::CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const
{
bool bResult = Super::CalculateRawConditionValue(OwnerComp, NodeMemory);
// Pawn을 가져오기
APawn* ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
if (nullptr == ControllingPawn)
{
return false;
}
// Pawn으로부터 Interface 가져오기
IABCharacterAIInterface* AIPawn = Cast<IABCharacterAIInterface>(ControllingPawn);
if (nullptr == AIPawn)
{
return false;
}
// Target값을 가져오기
APawn* Target = Cast<APawn>(OwnerComp.GetBlackboardComponent()->GetValueAsObject(BBKEY_TARGET));
if (nullptr == Target)
{
return false;
}
// Target까지의 거리를 구하기
// AttackRange안에 Target이 있다면 true
float DistanceToTarget = ControllingPawn->GetDistanceTo(Target);
float AttackRangeWithRadius = AIPawn->GetAIAttackRange();
bResult = (DistanceToTarget <= AttackRangeWithRadius);
return bResult;
}
- BTDecorator_AttackInRange.cpp
- 다른 BehaviorTree와 비슷하게 Pawn을 가져오고, Interface를 가져와준다.
- Target(Player)이 감지와 더불어 공격범위내에 존재하는지 파악하기 위해 Target값을 가져와준다.
- ABCharacterAIInterface에서 AttackRange에 대한 데이터를 가져와 주고, GetDistanceTo함수를 통해 Target까지의 거리를 불러와 비교하여 bool값으로 리턴해준다.
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "ABCharacterAIInterface.generated.h"
// 공격이 끝났는지를 알려주기위한 델리게이트
DECLARE_DELEGATE(FAICharacterAttackFinished);
class ARENABATTLE_API IABCharacterAIInterface
{
// ...
// 캐릭터(NPC)에게 넘겨주기 위한 델리게이트 함수
virtual void SetAIAttackDelegate(const FAICharacterAttackFinished& InOnAttackFinished) = 0;
// AI의 공격을 구현
// 공격의 경우에는 바로 끝나는 액션이 아니다. 공격 시작 -> 몽타주 재생 -> 몽타주가 끝나야지만 공격이 끝났다라고 할 수 있다.
virtual void AttackByAI() = 0;
};
- IABCharacterAIInterface.h
#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
#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);
// AIController가 가진 Pawn을 획득
APawn* ControllingPawn = Cast<APawn>(OwnerComp.GetAIOwner()->GetPawn());
if (nullptr == ControllingPawn)
{
return EBTNodeResult::Failed;
}
// Interface를 통해 공격 명령
IABCharacterAIInterface* AIPawn = Cast<IABCharacterAIInterface>(ControllingPawn);
if (nullptr == AIPawn)
{
return EBTNodeResult::Failed;
}
// 공격이 시작하기 전 델리게이트를 바인딩해준다.
// 지연 테스크
FAICharacterAttackFinished OnAttackFinished;
OnAttackFinished.BindLambda(
[&]()
{
// 람다식을 통해 Task가 끝나면 Succeeded를 해준다.
FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
}
);
// 델리게이트 바인딩
AIPawn->SetAIAttackDelegate(OnAttackFinished);
// 공격의 경우에는 바로 끝나는 액션이 아니다. 공격 시작 -> 몽타주 재생 -> 몽타주가 끝나야지만 공격이 끝났다라고 할 수 있다.
// AttackByAI를 통해 공격을 시작해주고 Result는 Inprogress로 설정해준 다음, 공격이 끝난 이후에 Succeded 값으로 반환해주면 된다.
AIPawn->AttackByAI();
return EBTNodeResult::InProgress;
}
- BTTask_Attack.cpp
- 공격의 경우에는 바로 끝나는 액션이 아니다. 공격 시작 -> 몽타주 재생 -> 몽타주가 끝나야지만 공격이 끝났다라고 할 수 있다.
- AttackByAI를 통해 공격을 시작해주고 Result는 Inprogress로 설정해준 다음, 공격이 끝난 이후에 Succeded 값으로 반환해주면 된다.
- ABCharacterAIInterface에서 공격이 끝났는지를 알려주기 위한 델리게이트를 만들어주고 캐릭터에게 넘겨주기 위한 델리게이트 함수와 AI의 공격을 위한 함수들을 만들어 준다.
virtual void NotifyComboActionEnd(); // 몽타주 없이 공격이 끝났는지를 파악하기 위한 함수
- ABCharacterBase.h
// ...
// 콤보 끝
void AABCharacterBase::ComboActionEnd(UAnimMontage* TargetMontage, bool IsProperlyEnded)
{
// Assertion 함수를 사용해서 몽타주가 끝날땐 CurrentCombo가 절때 0이 될 수 없으니, 검증 + 0이 나오면 에러
ensure(CurrentCombo != 0);
CurrentCombo = 0;
GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Walking); // 다시 움직임 가능하게 변경
// 콤보가 끝났을때 호출
NotifyComboActionEnd();
}
// 콤보가 끝났을때 호출
void AABCharacterBase::NotifyComboActionEnd()
{
}
// ...
- ABCharacterBase.cpp
- 기본 ABCharacterBase 클래스에 NPC의 콤보공격이 끝났을때 호출되는 가상함수를 만들어주고 콤보공격이 끝나게 되면 호출될 수 있도록 구성해준다.
class ARENABATTLE_API AABCharacterNonPlayer : public AABCharacterBase, public IABCharacterAIInterface
{
// ...
// 델리게이트를 담을 변수
FAICharacterAttackFinished OnAttackFinished;
// ABCharacterBase에 있는 콤보가 끝났을때 호출하는 함수 override
virtual void NotifyComboActionEnd() override;
};
}
- ABCharacterNonPlayer.h
void AABCharacterNonPlayer::SetAIAttackDelegate(const FAICharacterAttackFinished& InOnAttackFinished)
{
OnAttackFinished = InOnAttackFinished;
}
void AABCharacterNonPlayer::AttackByAI()
{
ProcessComboCommand();
}
// 콤보 공격이 끝났을때 해당 함수가 호출됨
void AABCharacterNonPlayer::NotifyComboActionEnd()
{
Super::NotifyComboActionEnd();
// 콤보 공격이 끝났을 때 OnAttackFinished 함수 실행
OnAttackFinished.ExecuteIfBound();
}
- ABCharacterNonPlayer.cpp
- NPC의 공격이 끝났을때를 알려주기 위한 델리게이트를 담을 변수를 설정해주고, ABCharacterBase에 있는 콤보 공격이 끝났을 때 호출되는 함수인 NotifyComboActionEnd()함수를 override해주며 델리게이트 함수인 OnAttackFinished함수를 실행해준다.
- 그렇게되면 BTTask_Attack 클래스에서 람다로 바인딩된 함수가 실행되며, Result는 Succeded로 변경될 것이다.
- Target이 존재하면 (Player), Selector를 통해 공격범위내에 있는지 판단하고, 공격 범위에 있다면 Attack, 그렇지 않으면 추격하는 행동 트리를 구성한다.
- Detect했다면 다시 파악하도록 서비스도 추가해주었다.
- 하지만 이 경우 추적하면서 공격 범위안에 들어오면 어느정도는 맞겠지만, NPC의 공격 범위안에 들어오더라도 해당 방향으로 회전하지는 않을 것이다.
- 이를 해결하기위해 Target방향으로 Turn을 하는 Task를 구현해주자. (BTTaskNode 클래스 생성)
#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
#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);
// Pawn 가져오기
APawn* ControllingPawn = Cast<APawn>(OwnerComp.GetAIOwner()->GetPawn());
if (nullptr == ControllingPawn)
{
return EBTNodeResult::Failed;
}
// Target으로 설정된 Pawn 가져오기
APawn* TargetPawn = Cast<APawn>(OwnerComp.GetBlackboardComponent()->GetValueAsObject(BBKEY_TARGET));
if (nullptr == TargetPawn)
{
return EBTNodeResult::Failed;
}
// AIPawn 가져오기
IABCharacterAIInterface* AIPawn = Cast<IABCharacterAIInterface>(ControllingPawn);
if (nullptr == AIPawn)
{
return EBTNodeResult::Failed;
}
// AI TurnSpeed 가져와서 바라봐야하는 Vector를 구한뒤, Pawn(NPC)을 TurnSpeed의 속도로 로테이션 시켜준다.
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
- 기본적인 생성자와 Pawn(NPC), Target을 가져오는 ExecuteTask를 구성해준 뒤, 회전시켜주는 로직을 구성해준다.
- BehaviorTree에서 공격 부분은 Selector가 아닌 Simple Parallel 컴포짓으로 변경해준뒤, 두 행동 모두 실행하도록 해준다. 다만, 메인 행동은 Attack으로 동작할 수 있도록 해준다.
해당 포스트는 인프런의 <이득우의 언리얼 프로그래밍 Part2 -언리얼 게임 프레임웍의 이해>
강의를 수강하고 정리한 내용입니다.
이득우의 언리얼 프로그래밍 Part3 - 네트웍 멀티플레이 프레임웍의 이해 | 이득우 - 인프런
이득우 | 또 하나의 언리얼 엔진이라고도 불리는 네트웍 멀티플레이어 프레임웍을 학습합니다. 네트웍 멀티플레이어 게임을 제작할 때 반드시 알아야 하는 주요 개념, 내부 동작 원리, 최적화
www.inflearn.com
'공부 > 이득우의 언리얼 프로그래밍' 카테고리의 다른 글
[Study] Part 2 - 게임플로우 다듬기 (14/15) (0) | 2024.06.24 |
---|---|
[Study] Part 2 - 헤드업디스플레이의 구현 (13/15) (0) | 2024.06.21 |
[Study] Part 2 - 행동 트리 모델의 이해 (11/15) (0) | 2024.06.19 |
[Study] Part 2 - 게임데이터 관리 (10/15) (0) | 2024.06.18 |
[Study] Part 2 - 무한맵의 제작 (9/15) (0) | 2024.06.17 |