이번글에서는 저번 글에 이어서,
AI 관련된 Decorator와 Task를 추가해볼거다.
Decorator는 대강 예를 들자면, 공격범위 내에 들어왔나 안왔나를 체크한다 뭐 이런 동작을 비헤이비어 트리에 넣기 위해 만들어 주는 거라고 보면 된다.
Task는 뜻 그대로 뭔가 일을 수행하는 거고, 예를 들면 공격을 한다 같은 것이 있다. Task도 역시 비헤이비어 트리에 넣기 위해 만들어 줄거다.
먼저 Decorator를 만들어 보자.

위의 사진을 참고하여 데코레이터 생성.
이후 헤더 파일에 아래 소스 추가.
public:
UMyBTDecorator_IsInAttackRange();
protected:
virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override;
cpp파일에 아래 소스 추가.
#include "MyAIController.h"
#include "MyCharacter.h"
#include "BehaviorTree/BlackboardComponent.h"
UMyBTDecorator_IsInAttackRange::UMyBTDecorator_isInAttackRange()
{
NodeName = TEXT("CanAttack");
}
bool UMyBTDecorator_IsInAttackRange::CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const
{
bool bResult = Super::CalculateRawConditionValue(OwnerComp, NodeMemory);
auto ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
if (nullptr == ControllingPawn)
return false;
//FName TargetKey = TEXT("PlayerTarget");
auto Target = Cast<AMyCharacter>(OwnerComp.GetBlackboardComponent()->GetValueAsObject(AMyAIController::TargetKey));
if (nullptr == Target)
return false;
float Distance = Target->GetDistanceTo(ControllingPawn);
return Distance <= 200.0f;
}
위의 소스는
auto Target = Cast<AMyCharacter>(OwnerComp.GetBlackboardComponent()->GetValueAsObject(AMyAIController::TargetKey));
이 부분이 핵심인데, GetValueAsObject에 블랙보드에 있는 Target이 연결되게끔 추가해주면 된다.
AMyAIController::TargetKey
그리고 ControllingPawn이 현재 내가 조종 중인 캐릭터가 되고,
해당 캐릭터가 200 안으로 들어오면 True를 리턴하는 데코레이터가 되겠다.
이제 컴파일 다시 해보고
비헤이비어 트리를 수정해주자.

데코레이터에 내가 추가한 Is in Attack Range가 추가된 걸 볼 수 있다.

위 처럼 역조건을 체크해주면 False일 때 True가 된다고 보면 된다.
비헤이비어 트리를 아래와 같이 수정하고 플레이 한 후, 범위 안에 들어가면 CanAttack쪽이 True인 것을 확인 할 수 있다.

그러면 이제 CanAttack일 때, 공격을 하도록 Task를 추가해보자.

먼저 BTTaskNode 선택하여 클래스 생성.
헤더에 아래 소스 추가.
public:
UMyBTTaskNode_Attack();
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
protected:
virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
cpp에 아래 소스 추가.
#include "MyAIController.h"
#include "MyCharacter.h"
UMyBTTaskNode_Attack::UMyBTTaskNode_Attack()
{
bNotifyTick = true; // 틱 함수 호출
}
EBTNodeResult::Type UMyBTTaskNode_Attack::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
if (GEngine)
GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, TEXT("UMyBTTaskNode_Attack::ExecuteTask"));
auto MyC = Cast<AMyCharacter>(OwnerComp.GetAIOwner()->GetCharacter());
if (nullptr == MyC)
return EBTNodeResult::Failed;
if (MyC->CurrentHP > 0)
MyC->Attack();
return EBTNodeResult::InProgress; // 대기
}
void UMyBTTaskNode_Attack::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickTask(OwnerComp, NodeMemory, DeltaSeconds);
if (GEngine)
GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, TEXT("UMyBTTaskNode_Attack::TickTask"));
FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
}
여기서의 핵심은
if (MyC->CurrentHP > 0)
MyC->Attack();
이 부분인데, 딱 보면 알겠지만, 현재 HP가 0보다 크면 Attack 하게끔 하는 소스이다.
여기까지 하고 컴파일하면

위와 같이 Attack이 추가되있다.
이제 베헤이비어 트리에 Attack을 추가해보자.

여기까지 하고 플레이하면 공격범위 내로 들어가면 적이 공격하는걸 확인할 수 있다.
이제 마지막으로 자동으로 움직이도록 추가해보자.
먼저 Task를 하나 더 추가해준다.
그리고 헤더에 소수 추가,
public:
UMyBTTaskNode_FindPatrolPos();
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
cpp에 소스 추가.
#include "MyAIController.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "NavigationSystem.h"
UMyBTTaskNode_FindPatrolPos::UMyBTTaskNode_FindPatrolPos()
{
NodeName = TEXT("FindPatrolPos");
}
EBTNodeResult::Type UMyBTTaskNode_FindPatrolPos::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
auto ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
if (nullptr == ControllingPawn)
return EBTNodeResult::Failed;
auto NavSystem = UNavigationSystemV1::GetNavigationSystem(ControllingPawn->GetWorld());
if (nullptr == NavSystem)
return EBTNodeResult::Failed;
FVector Origin = OwnerComp.GetBlackboardComponent()->GetValueAsVector(AMyAIController::HomePosKey);
FNavLocation NextPatrol;
if (NavSystem->GetRandomPointInNavigableRadius(Origin, 500.0f, NextPatrol)) // 500.f 반경에 아무 값이나 리턴해줌, 다만 네비 메쉬가 있어야함.
{
OwnerComp.GetBlackboardComponent()->SetValueAsVector(AMyAIController::PatrolPosKey, NextPatrol.Location); // vertor 값 셋팅
return EBTNodeResult::Succeeded;
}
return EBTNodeResult::Failed;
}
해당 소스는 기존에 봐왔던 것들이 거의 다라서 딱히 설명할 만한 건 없는 것 같다.
여기까지 하고 컴파일하면
Task에 FindPatrolPos가 추가되어 있을거다.
최종적인 비헤이비어 트리는 아래와 같다.

이대로 하고 실행해보면,
여기서 또 한가지 문제가 발생했는데,
범위 밖으로 벗어나면 다시 시작위치로 돌아가야하는데 안돌아간다.
원인은 TargetDetect와 NotDetect에

관찰자 노티파이를 OnVlueChange로 바꿔주고, 관찰자 중단을 Self로 해줘야 한다.
이제 다시 실행해보면,
범위에서 벗어나면 다시 원래자리로 돌아가는 걸 볼 수 있다.
여기까지가 내가 생각하는 언리얼엔진 개발에 필요한 기초부분이다.
이후에 추가적으로 작업을 해서
포트폴리오를 만들 건데,
이 부분도 한번 작업하면서 글로 남겨볼까 한다.
여기까지 봐주신 분이 있으실진 모르겠지만,
궁금하신점 있으시면 댓글 달아주시면 답변 하겠습니다.
여기까지의 전체 소스는 밑의 주소에 올려두었습니다.
jungjin-lee90/https-github.com-jungjin-lee90-Unreal5: 언리얼공
GitHub - jungjin-lee90/https-github.com-jungjin-lee90-Unreal5: 언리얼공
언리얼공. Contribute to jungjin-lee90/https-github.com-jungjin-lee90-Unreal5 development by creating an account on GitHub.
github.com
감사합니다.
'언리얼엔진' 카테고리의 다른 글
언리얼 엔진 활용하기 - 12. AI 추가2 - 블랙보드와 비헤이비어 트리. (0) | 2023.02.27 |
---|---|
언리얼 엔진 활용하기 - 11. AI 추가 (0) | 2023.02.27 |
언리얼 엔진 활용하기 - 10. 캐릭터 위젯 추가하기2 (0) | 2023.02.27 |
언리얼 엔진 활용하기 - 9. 캐릭터 위젯 추가하기 (0) | 2023.02.24 |
언리얼 엔진 활용하기 - 8. 이펙트 추가하기 (0) | 2023.02.20 |