컴퓨터 및 게임 관련 전공인이 아닌 문외한 일반인이 언리얼 엔진을 배우면서 정리한 내용입니다.
그날그날 배웠던 내용을 제가 나중에 보기 위해 정리한 것으로, 지금 당장 보기에 부족한 점이 아주아주 많고 추후에 수정이 될 수도 있습니다.
오탈자 및 잘못 기재된 내용 지적, 부족한 내용 설명은 언제든지 환영입니다.
본글은 언리얼 엔진 5.5.4 버전 영문판을 기준으로 합니다.
----------------------------------------------------------------------------------------------------------------
33일차 학습 내용
- 적 AIController
적 AIController

Behavior Tree (이하 "BT" 라고 칭함) 를 실행하면 위와 같은 창이 뜬다.
여기서 BP를 작성하듯이 우클릭을 하여 노드들을 연결해줄 수 있다.
우클릭을 하면 보이는 "Sequence" 는 모든 자식 노드들을 순차대로 실행하며, 하나라도 실패하면 전체가 실패한 것으로 간주되어 이하 노드는 실행되지가 않는다.
즉, 모든 노드가 성공해야 성공으로 간주되는 것이다.
그와는 대조적으로 "Selector" 는 모든 자식 노드들 중 하나라도 성공하면 전체가 성공한 것으로 판정하며, 성공한 하나의 자식 노드 중 하나만 실행한다.
BT에 대한 자세한 정보는 아래의 api를 참고하면 좋다.
https://dev.epicgames.com/documentation/ko-kr/unreal-engine/behavior-trees-in-unreal-engine
우선은 "Sequence" 를 생성하여 간단하게 AI 조종을 해보도록 하겠다.

위 그림과 같이 노드들을 배치하고 연결해준다.
Sequence를 생성한 뒤 Task에서 검색하면 "Move To" 와 "Wait" 노드를 배치할 수 있다.
위의 BT는 지정한 위치로 움직인 뒤 지정한 시간만큼 기다린 후에 다시 지정한 위치로 움직이고 기다리는 것을 반복하는 것이다.
다만 여기서는 지정한 위치가 한 곳으로 고정되어있기에 처음 한 번만 움직이고 더 이상 움직이는 않고, 명령 자체는 지속적으로 내려진다.
그리고 위에서 지정한 "SelfActor" 는 자기 자신을 가리키기에 실행 시 그 자리에 그대로 있게 된다.

Blackboard (이하 "BB" 라고 칭함) 로 들어가면 데이터를 생성해줄 수 있다.
위치로 지정되어있는 "SelfActor" 가 Object 형식으로 되어있는 것을 확인할 수 있고, 우리는 우선 적 개체를 플레이어의 위치로 이동 시켜주기 위해 "PlayerActor" 라는 이름의 Object 형식의 데이터를 생성해준다.

하지만 BT로 이동해서 확인해보면, "Move To" 노드에 위에서 생성한 "PlayerActor" 데이터를 입력할 수 없는 것을 확인할 수 있다.
데이터 형식이 맞지 않아서 입력할 수 없는 것이다.


BB로 이동하여 데이터 형식을 Vector로 바꿔주면 위 그림과 같이 "Move To" 노드에 "PlayerActor" 데이터가 입력이 가능하게 되는 것을 확인할 수 있다.
Task 마다 인식하는 자료형이 다르다는 것을 알 수 있고, "Move To" 노드에 들어가는 데이터 형식은 Vector라는 것을 알 수 있다.

그리고 "Wait" 노드에서 "Wait Time" 및 "Random Duration" 을 통해 대기 시간 및 대기 시간 범위를 조정해줄 수 있다.

마지막으로 Vector에 임의의 위치값을 입력해준다.

게임을 실행해보면 입력한 위치로 이동하고 대기한 뒤, 다시 이동하고 대기하고, 이 과정을 계속 반복하는 것을 확인할 수 있다.
"PlayerActor" 벡터는 플레이어의 위치로 이동하기 위해 만들어준 데이터이기에 임의의 Vector 값이 아닌 플레이어의 위치값을 넣어줘야하는데, 이 과정은 아래에서 코드를 입력할 때 해주도록 하겠다.
여기에 플레이어의 위치로 이동한 뒤 원래 있던 위치로 이동하기 위한 Vector 데이터를 추가해주도록 하겠다.


위 그림과 같이 BB에서 "DefaultVector" 라는 이름의 데이터를 생성해주고, Sequence 노드 아래에 Task를 추가해준다.
"Move To" 노드에서 "DefaultVector" 를 지정해주는 것도 잊어버리지 않도록 한다.
그리고 아래와 같이 코드를 작성해준다.
NormalZombie.h
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
UCLASS()
class MYPROJECT_API ANormalZombie : public AZombieBase
{
GENERATED_BODY()
.
.
.
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI", meta= (AllowPrivateAccess = "true", MakeEditWidget = "true"))
FVector ZombieMoveVector = FVector(0, 0, 0); // AllowPrivateAccess: C++ 변수가 private로 선언되어도 접근 가능하게 허용 / MakeEditWidget: 에디터에서 이 변수를 3D 공간에 시각적으로 표시
};
|
cs |
NormalZombie.cpp
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#include "Kismet/GameplayStatics.h" // 추가
#include "BehaviorTree/BehaviorTree.h" // 추가
#include "BehaviorTree/BlackboardComponent.h" // 추가
.
.
.
void ANormalZombie::BeginPlay()
{
Super::BeginPlay();
AZombieAIController* AIController = Cast<AZombieAIController>(GetController());
UBlackboardComponent* BB = AIController->GetBlackboardComponent();
if (BB)
{
APawn* playerActor = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
BB->SetValueAsVector(TEXT("PlayerActor"), playerActor->GetActorLocation()); // 플레이어 위치로 이동
BB->SetValueAsVector(TEXT("DefaultVector"), GetActorLocation()); // 첫 위치로 이동
}
}
|
cs |
BB에 있는 "PlayerActor" 에는 플레이어의 위치를 지정해주었고, "DefaultVector" 에는 적이 처음으로 있던 위치를 지정해주었다.
그리고 AIController 쪽이 아닌 NormalZombie에서 코드를 추가해준 이유는 추후에 플레이어와 자기자신의 위치만이 아닌 레벨의 무작위 장소로 이동시키기 위해서는, C++ 코드 작성을 통해 NormalZombie에서 추가해줘야 좀비 별로 위치를 설정해주는 것이 더 편하기 때문이다.
컴파일 후 게임을 실행시켜보면 적이 플레이어의 위치와 처음 위치를 대기 시간 후에 왔다갔다하는 것을 확인할 수 있다.
여기서 우리가 지정해준 무작위의 위치로 이동하도록 코드를 추가해보도록 하겠다.
아래와 같이 코드를 추가해준다.
NormalZombie.cpp
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
void ANormalZombie::BeginPlay()
{
Super::BeginPlay();
AZombieAIController* AIController = Cast<AZombieAIController>(GetController());
if (AIController) // 예외 처리를 해줘서 안전하게 해줌
{
UBlackboardComponent* BB = AIController->GetBlackboardComponent();
if (BB)
{
APawn* playerActor = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
BB->SetValueAsVector(TEXT("PlayerActor"), playerActor->GetActorLocation());
BB->SetValueAsVector(TEXT("DefaultVector"), GetActorLocation());
BB->SetValueAsVector(TEXT("RandomVector"), ZombieMoveVector); // RandomVector에 지정해준 위치로
}
}
}
|
cs |
코드의 안정성을 위해 예외 처리를 추가로 해주었고, BB에 있는 "RandomVector" 에 아까 생성한 "ZombieMoveVector" 위치를 지정해주었다.

컴파일 후 각 개체 별 적의 BP를 열고 확인해보면 위 그림과 같이 'ZombieMoveVector' 변수가 추가된 것을 확인할 수 있다.

레벨로 가서 레벨에 배치한 각 개체 별 적을 클릭해보면, 위 그림과 같이 'ZombieMoveVector' 가 가시화되어 레벨 상에서 움직일 위치를 지정해줄 수도 있다.
'ZombieMoveVector' 를 레벨 상 원하는 위치에 두고 게임을 실행해보면, 적이 처음 있던 위치에서 플레이어가 있던 위치로 이동하고 위에서 우리가 지정해준 위치로 움직이고 이 세 과정을 대기 시간과 함께 반복하는 것을 확인할 수 있다.
추가로 아래와 같이 코드를 입력해준다.
NormalZombie.cpp
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
void ANormalZombie::BeginPlay()
{
Super::BeginPlay();
AZombieAIController* AIController = Cast<AZombieAIController>(GetController());
if (AIController)
{
UBlackboardComponent* BB = AIController->GetBlackboardComponent();
if (BB)
{
FVector WorldMoveVector = GetActorTransform().TransformPosition(ZombieMoveVector); // 로컬 -> 월드 좌표로 변환
FVector LocalMoveVector = GetActorTransform().TransformPosition(ZombieMoveVector); // 월드 -> 로컬 좌표로 변환
FVector WorldMoveVector = ZombieMoveVector; // 월드 좌표로 사용
APawn* playerActor = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
BB->SetValueAsVector(TEXT("PlayerActor"), playerActor->GetActorLocation());
BB->SetValueAsVector(TEXT("DefaultVector"), GetActorLocation());
BB->SetValueAsVector(TEXT("RandomVector"), ^WorldMoveVector^);
}
}
}
|
cs |
위에서 추가한 FVector 코드는 하나씩만 활성화시키고 나머지는 주석처리를 해줘야하고, "RandomVector" 옆의 '^^' 사이에는 활성화된 FVector 옆의 변수를 넣어줘야한다.
만약 첫번째 줄의 FVector 코드를 사용할 경우, '^^' 사이에 "WorldMoveActor" 를 입력해줘야한다.
*오늘까지 작성한 코드 정리 (아래 2가지만 수정)
NormalZombie.h
|
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
|
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "ZombieBase.h"
#include "NormalZombie.generated.h"
/**
*
*/
UCLASS()
class MYPROJECT_API ANormalZombie : public AZombieBase
{
GENERATED_BODY()
public:
ANormalZombie();
private:
virtual void BeginPlay() override;
public:
void InitAI();
public:
// AllowPrivateAccess: C++ 변수가 private로 선언되어도 접근 가능하게 허용 / MakeEditWidget: 에디터에서 이 변수를 3D 공간에 시각적으로 표시
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI", meta= (AllowPrivateAccess = "true", MakeEditWidget = "true"))
FVector ZombieMoveVector = FVector(0, 0, 0);
};
|
cs |
NormalZombie.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
|
// Fill out your copyright notice in the Description page of Project Settings.
#include "NormalZombie.h"
#include "ZombieAnimInstance.h" // 추가
#include "AIController.h"
#include "ZombieAIController.h"
#include "Kismet/GameplayStatics.h" // 추가
#include "BehaviorTree/BehaviorTree.h" // 추가
#include "BehaviorTree/BlackboardComponent.h" // 추가
ANormalZombie::ANormalZombie()
{
InitAI();
}
void ANormalZombie::BeginPlay()
{
Super::BeginPlay();
AZombieAIController* AIController = Cast<AZombieAIController>(GetController());
if (AIController)
{
UBlackboardComponent* BB = AIController->GetBlackboardComponent();
if (BB)
{
//FVector WorldMoveVector = GetActorTransform().TransformPosition(ZombieMoveVector); // 로컬 -> 월드 좌표로 변환
FVector LocalMoveVector = GetActorTransform().TransformPosition(ZombieMoveVector); // 월드 -> 로컬 좌표로 변환
//FVector WorldMoveVector = ZombieMoveVector; // 월드 좌표로 사용
APawn* playerActor = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
BB->SetValueAsVector(TEXT("PlayerActor"), playerActor->GetActorLocation());
BB->SetValueAsVector(TEXT("DefaultVector"), GetActorLocation());
BB->SetValueAsVector(TEXT("RandomVector"), LocalMoveVector);
}
}
}
void ANormalZombie::InitAI()
{
AIControllerClass = AZombieAIController::StaticClass();
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
}
|
cs |
'Unreal Engine > GCC Class (5.5.4)' 카테고리의 다른 글
| Unreal Engine 5 배워보기 - 35일차 (0) | 2025.05.13 |
|---|---|
| Unreal Engine 5 배워보기 - 34일차 (0) | 2025.05.12 |
| Unreal Engine 5 배워보기 - 32일차 (0) | 2025.05.08 |
| Unreal Engine 5 배워보기 - 31일차 (0) | 2025.05.07 |
| Unreal Engine 5 배워보기 - 30일차 (0) | 2025.04.30 |