컴퓨터 및 게임 관련 전공인이 아닌 문외한 일반인이 언리얼 엔진을 배우면서 정리한 내용입니다.
그날그날 배웠던 내용을 제가 나중에 보기 위해 정리한 것으로, 지금 당장 보기에 부족한 점이 아주아주 많고 추후에 수정이 될 수도 있습니다.
오탈자 및 잘못 기재된 내용 지적, 부족한 내용 설명은 언제든지 환영입니다.
본글은 언리얼 엔진 5.5.4 버전 영문판을 기준으로 합니다.
----------------------------------------------------------------------------------------------------------------
39일차 학습 내용
- 적이 플레이어를 바라보도록 하는 기능 추가
- 추적하는 대상의 좌표 실시간 업데이트 추가
*교육기관 컴퓨터 문제로 계속 작업해오던 프로젝트가 날아가서, 강사님의 프로젝트 파일을 받아 수업 참여(..)
적이 플레이어를 바라보도록 하는 기능 추가
지난 38일 차까지 코드를 작성하고 게임을 플레이 할 경우, 적이 플레이어를 추적하거나 공격할 때 플레이어를 정면으로 바라보고 있지 않는 문제가 있었다.
이번 시간에는 적이 플레이어를 바라보도록 수정해보도록 하겠다.
이 경우 적이 플레이어를 바라보게 할 Task를 새로 추가해줘야한다.
"BTServie_BlackboardBase" 형식의 새로운 C++ 클래스를 생성하고, 플레이어를 바라보게 할 Task이기에 "BTTask_SetFocus" 라고 명명해준다.
그리고 아래와 같이 코드를 작성해준다.
BTTask_SetFocus.h
|
1
2
3
4
5
6
7
8
9
10
11
|
UCLASS()
class SILENTHILL_API UBTTask_SetFocus : public UBTTask_BlackboardBase
{
GENERATED_BODY()
public:
UBTTask_SetFocus();
protected:
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
};
|
cs |
BTTask_SetFocus.cpp
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
#include "BTTask_SetFocus.h"
#include "AIController.h" // 추가
#include "BehaviorTree/BlackboardComponent.h" // 추가
UBTTask_SetFocus::UBTTask_SetFocus()
{
NodeName = "ZombieSetFocus";
bNotifyTick = false;
}
EBTNodeResult::Type UBTTask_SetFocus::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
//Super::ExecuteTask(OwnerComp, NodeMemory);
AAIController* AIController = OwnerComp.GetAIOwner();
if (!AIController) return EBTNodeResult::Failed; // 안전하게 예외처리 필수
UBlackboardComponent* BB = OwnerComp.GetBlackboardComponent();
if (!BB) return EBTNodeResult::Failed; // 안전하게 예외처리 필수
AActor* FocusTarget = Cast<AActor>(BB->GetValueAsObject(TEXT("FocusTarget"))); // 플레이어를 바라보도록 설정
if (!FocusTarget) return EBTNodeResult::Failed; // 안전하게 예외처리 필수
if (AIController)
{
AIController->SetFocus(FocusTarget);
return EBTNodeResult::Succeeded;
}
return EBTNodeResult::Failed;
}
|
cs |
여타 Task에 사용하는 코드와 크게 다르지 않기에, 다른 Task의 부분을 복사해서 붙여넣고 일부만 수정해주면 된다.
우리는 적이 플레이어를 공격할 때에만 플레이어를 바라보도록 하고 싶기에, "BTService_AttackRange.cpp" 로 가서 아래의 코드를 추가해준다.
BTService_AttackRange.cpp
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
void UBTService_AttackRange::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
.
.
.
if (DistanceToPlayer <= AttackRange)
{
if (AIController)
{
if (BB)
{
BB->SetValueAsBool(TEXT("AttackState"), true);
BB->SetValueAsVector(TEXT("PlayerActor"), PlayerPawn->GetActorLocation());
BB->SetValueAsObject(TEXT("FocusTarget"), PlayerPawn); // 추가
}
}
}
.
.
.
}
|
cs |
"FocusTarget" 이라는 BB 키를 생성해주고 해당 키에 플레이어를 대입해주었다.

컴파일 후 "FocusTarget" 이라는 새로운 Object 형식 BB 키 값을 코드에서 추가해줬기에, BB로 가서 Object 형식으로 같은 이름인 "FocusTarget" 의 키를 생성해준다.


플레이어를 공격할 때에만 플레이어를 바라보도록 설정하였기에, 공격 상태의 Sequence에서 플레이어를 바라보도록 "SetFocus" Task를 연결해주고, "SetFocus" Task의 BBkey를 "FocusTarget" 로 지정해준다.

게임을 실행해보면 플레이어를 추적한 뒤 플레이어를 바라보며 공격하는 것을 확인할 수 있다.
추적하는 대상의 좌표 실시간 업데이트 추가
위까지의 과정에서 어색한 점이 있다면 플레이어의 위치가 적이 플레이어를 추적하는 중에 변경될 경우, 적이 처음에 인식한 플레이어의 위치로 이동한 뒤에 다시 플레이어의 변경된 위치로 이동한다는 점이다.
이는 플레이어의 위치값이 실시간으로 업데이트 되지 않기에 일어나는 현상이다.

위 문제를 해결하려면 Move To 노드에서 "Observed Blackboard Value Tolerance" 를 true가 되도록 체크해주면 해결된다.
"Observed Blackboard Value Tolerance" 의 수치는 추적하는 대상의 위치 변경에 대한 최신화를 하기 위한 최소 이동 거리이다.
즉, 수치가 작을 수록 플레이어가 조금만 움직여도 바로 반영되는 것이다.

이렇게 해줄 경우, 플레이어가 계속 움직여도 자연스럽게 플레이어의 변경된 위치를 추적하는 것을 확인할 수 있다.
*오늘까지 작성한 코드 정리
BTService_AttackRange.cpp
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
// Fill out your copyright notice in the Description page of Project Settings.
#include "BTService_AttackRange.h"
#include "NormalZombie.h"
#include "Kismet/GameplayStatics.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "ZombieAIController.h"
#include "Engine/GameEngine.h"
UBTService_AttackRange::UBTService_AttackRange()
{
NodeName = TEXT("Attack Ranage Serivice");
Interval = 0.5f;
}
void UBTService_AttackRange::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
//GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Red, TEXT("PlayerAttack"));
APawn* PlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
AZombieAIController* AIController = Cast<AZombieAIController>(OwnerComp.GetAIOwner());
ANormalZombie* Zombie = Cast<ANormalZombie>(AIController ? AIController->GetPawn() : nullptr);
if (!PlayerPawn || !AIController) return;
const float DistanceToPlayer = FVector::Dist(Zombie->GetActorLocation(), PlayerPawn->GetActorLocation());
UBlackboardComponent* BB = AIController->GetBlackboardComponent();
const float AttackRange = 120.0f;
if (DistanceToPlayer <= AttackRange)
{
if (AIController)
{
if (BB)
{
BB->SetValueAsBool(TEXT("AttackState"), true);
BB->SetValueAsVector(TEXT("PlayerActor"), PlayerPawn->GetActorLocation());
BB->SetValueAsObject(TEXT("FocusTarget"), PlayerPawn);
}
}
}
else
{
if (AIController)
{
if (BB)
{
BB->SetValueAsBool(TEXT("AttackState"), false);
}
}
}
}
|
cs |
BTTask_SetFocus.h
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/Tasks/BTTask_BlackboardBase.h"
#include "BTTask_SetFocus.generated.h"
/**
*
*/
UCLASS()
class SILENTHILL_API UBTTask_SetFocus : public UBTTask_BlackboardBase
{
GENERATED_BODY()
public:
UBTTask_SetFocus();
protected:
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
};
|
cs |
BTTask_SetFocus.cpp
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
// Fill out your copyright notice in the Description page of Project Settings.
#include "BTTask_SetFocus.h"
#include "AIController.h"
#include "BehaviorTree/BlackboardComponent.h"
UBTTask_SetFocus::UBTTask_SetFocus()
{
NodeName = "ZombieSetFocus";
bNotifyTick = false;
}
EBTNodeResult::Type UBTTask_SetFocus::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
//Super::ExecuteTask(OwnerComp, NodeMemory);
AAIController* AIController = OwnerComp.GetAIOwner();
if (!AIController) return EBTNodeResult::Failed;
UBlackboardComponent* BB = OwnerComp.GetBlackboardComponent();
if (!BB) return EBTNodeResult::Failed;
AActor* FocusTarget = Cast<AActor>(BB->GetValueAsObject(TEXT("FocusTarget")));
if (!FocusTarget) return EBTNodeResult::Failed;
if (AIController)
{
AIController->SetFocus(FocusTarget);
return EBTNodeResult::Succeeded;
}
return EBTNodeResult::Failed;
}
|
cs |
'Unreal Engine > GCC Class (5.5.4)' 카테고리의 다른 글
| Unreal Engine 5 배워보기 - 41일차 (0) | 2025.05.21 |
|---|---|
| Unreal Engine 5 배워보기 - 40일차 (1) | 2025.05.20 |
| Unreal Engine 5 배워보기 - 38일차 (0) | 2025.05.16 |
| Unreal Engine 5 배워보기 - 37일차 (0) | 2025.05.15 |
| Unreal Engine 5 배워보기 - 36일차 (0) | 2025.05.14 |