Unreal Engine에서 Pawn은 직접 판단하지 않고, 보통 Controller에게 조종된다.
APawn
└─ ACharacter
AController
├─ APlayerController
└─ AAIController
| 구분 | 역할 |
|---|---|
Pawn / Character |
실제 월드에 존재하는 몸체 |
PlayerController |
플레이어 입력을 받아 Pawn 조종 |
AIController |
AI 로직으로 Pawn 조종 |
플레이어가 조종하는 캐릭터는 PlayerController가 붙고,
적 몬스터나 NPC처럼 스스로 움직이는 캐릭터는 AIController가 붙는다.
AI Controller
AI를 Character에서 AI Controller를 사용할 때 문제점
// Bad Example
void AWEnemyCharacter::Tick(float DeltaTime)
{
FindPlayer();
MoveToPlayer();
AttackIfClose();
}
- 책임 분리 실패 : Character가 이동, 애니메이션, 체력, 공격, 판단까지 모두 담당
- 재사용 어려움 : 같은 몸체에 다른 AI 패턴을 붙이기 힘듦
- 테스트 어려움 : AI 판단 로직만 따로 교체하거나 디버깅하기 어려움
- Behavior Tree 연동 불편 | UE의 AI 시스템은 Controller 중심으로 설계됨
// Good Example
EnemyCharacter
- 체력
- 피격
- 공격 애니메이션
- 충돌
- 이동 컴포넌트
- Mesh
EnemyAIController
- 플레이어 탐색
- 추적 판단
- 공격 판단
- Behavior Tree 실행
- Blackboard 값 갱신
Character는 몸체, AIController는 두뇌
AI Controller 역할
Pawn Possess
AI Controller는 특정 Pawn을 Possess해서 조종한다.OnPossess()는 AI Controller가 Enemy Character를 소유했을 때 호출된다.
void AWEnemyAIController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
// AI가 Pawn을 조종하기 시작했을 때 호출됨
// 아래와 같은 로직 수행하기 좋음
// Enemy Character 캐스팅
// Behavior Tree 실행
// Blackboard 초기값 설정
// Perception 설정
}
예시:
void AWEnemyAIController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
AWEnemyCharacter* Enemy = Cast<AWEnemyCharacter>(InPawn);
if (!Enemy)
{
return;
}
if (BehaviorTree)
{
RunBehaviorTree(BehaviorTree);
}
}
MoveTo
Pawn을 특정 위치나 Actor로 이동
MoveToActor(TargetActor);
MoveToLocation(TargetLocation);
예시:
void AWEnemyAIController::MoveToPlayer(AActor* PlayerActor)
{
if (!PlayerActor)
{
return;
}
MoveToActor(PlayerActor, 150.0f);
}
Behavior Tree 실행
RunBehaviorTree(BehaviorTreeAsset);
Blackboard 값 관리
AI Controller에서 Blackboard 값을 설정
Blackboard는 AI가 사용하는 공유 데이터 저장소.
| Key | Type | 의미 |
|---|---|---|
TargetActor |
Object | 추적할 대상 |
TargetLocation |
Vector | 이동할 위치 |
bCanSeePlayer |
Bool | 플레이어를 볼 수 있는지 |
bIsInAttackRange |
Bool | 공격 범위 안인지 |
PatrolLocation |
Vector | 순찰 위치 |
UBlackboardComponent* BlackboardComp = GetBlackboardComponent();
if (BlackboardComp)
{
BlackboardComp->SetValueAsObject(TEXT("TargetActor"), PlayerActor);
BlackboardComp->SetValueAsBool(TEXT("bCanSeePlayer"), true);
}
Behavior Tree는 이 Blackboard 값을 보고 다음 행동을 결정.
AI Controller 생성 예시
AI Controller 클래스
WEnemyAIController.h
#pragma once
#include "CoreMinimal.h"
#include "AIController.h"
#include "WEnemyAIController.generated.h"
class UBehaviorTree;
UCLASS()
class YOURPROJECT_API AWEnemyAIController : public AAIController
{
GENERATED_BODY()
public:
AWEnemyAIController();
protected:
virtual void OnPossess(APawn* InPawn) override;
protected:
UPROPERTY(EditDefaultsOnly, Category = "AI")
TObjectPtr<UBehaviorTree> BehaviorTree;
};
WEnemyAIController.cpp
#include "WEnemyAIController.h"
#include "BehaviorTree/BehaviorTree.h"
AWEnemyAIController::AWEnemyAIController()
{
}
void AWEnemyAIController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
if (!BehaviorTree)
{
return;
}
RunBehaviorTree(BehaviorTree);
}
Character에 AI Controller 지정
AWEnemyCharacter::AWEnemyCharacter()
{
AIControllerClass = AWEnemyAIController::StaticClass();
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
}
| 설정 | 의미 |
|---|---|
AIControllerClass |
이 Character를 조종할 AI Controller 클래스 |
AutoPossessAI |
언제 자동으로 AI Controller가 붙을지 설정 |
AutoPossessAI 옵션
AutoPossessAI는 AI Controller가 언제 Pawn을 자동 Possess할지 결정할 수 있음
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
| 옵션 | 설명 |
|---|---|
Disabled |
자동 Possess 안 함 |
PlacedInWorld |
레벨에 배치된 Pawn만 자동 Possess |
Spawned |
런타임에 Spawn된 Pawn만 자동 Possess |
PlacedInWorldOrSpawned |
배치된 Pawn과 Spawn된 Pawn 모두 자동 Possess |
PlacedInWorldOrSpawned으로 하면 레벨에 직접 배치한 적도 동작하고,
런타임에 SpawnActor로 생성한 적도 동작한다.
AI Perception
AI Perception은 AI가 시야, 청각 등으로 대상을 감지하는 시스템이다.
AI Controller에 UAIPerceptionComponent를 붙여서 사용한다.
UPROPERTY(VisibleAnywhere, Category = "AI")
TObjectPtr<UAIPerceptionComponent> PerceptionComponent;
시야 감지는 UAISenseConfig_Sight를 사용한다.
UPROPERTY()
TObjectPtr<UAISenseConfig_Sight> SightConfig;
AIController
└─ AIPerceptionComponent
└─ SightConfig
├─ SightRadius
├─ LoseSightRadius
├─ PeripheralVisionAngleDegrees
└─ DetectionByAffiliation
예시:
AWEnemyAIController::AWEnemyAIController()
{
PerceptionComponent = CreateDefaultSubobject<UAIPerceptionComponent>(TEXT("PerceptionComponent"));
SightConfig = CreateDefaultSubobject<UAISenseConfig_Sight>(TEXT("SightConfig"));
SightConfig->SightRadius = 1500.0f;
SightConfig->LoseSightRadius = 1800.0f;
SightConfig->PeripheralVisionAngleDegrees = 70.0f;
SightConfig->DetectionByAffiliation.bDetectEnemies = true;
SightConfig->DetectionByAffiliation.bDetectFriendlies = true;
SightConfig->DetectionByAffiliation.bDetectNeutrals = true;
PerceptionComponent->ConfigureSense(*SightConfig);
PerceptionComponent->SetDominantSense(SightConfig->GetSenseImplementation());
}
감지 이벤트 받기 예시:
PerceptionComponent->OnTargetPerceptionUpdated.AddDynamic(
this,
&AWEnemyAIController::OnTargetPerceptionUpdated
);
AI Controller Tick
UE AI 구조에서는 보통 Tick에 모든 판단을 넣지 않는 편이 좋음
장점:
- 구현이 단순함
- 작은 프로젝트에서는 빠르게 만들 수 있음
단점:
- AI 수가 많아지면 비용 증가
- 판단 로직이 복잡해질수록 유지보수 어려움
- Behavior Tree 디버깅 기능 활용 어려움
Tick 기반 AI
void AWEnemyAIController::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
FindPlayer();
MoveToPlayer();
CheckAttackRange();
}
Behavior Tree 기반 AI
AIController
→ BehaviorTree
→ Blackboard
→ Task / Decorator / Service
장점:
- 상태와 행동을 시각적으로 관리 가능
- 디버깅이 편함
- AI 패턴을 확장하기 쉬움
- 여러 적이 같은 Tree를 공유 가능
단점:
- 처음 구조 잡는 시간이 필요함
- Task, Decorator, Service 클래스가 늘어남
AI Controller에서는 상태전환, 명령, 판단과 같은 처리를 주로 진행한다.
10근접 적 AI 예시 구조
사용자님의 Iron Wagon 같은 구조를 기준으로 보면,
마차를 따라오는 근접 적은 다음처럼 구성할 수 있습니다.
AWMeleeEnemyCharacter
- 체력
- 이동 속도
- 공격 애니메이션
- 공격 판정
- 마차 부위 공격 함수
AWEnemyAIController
- 마차 추적
- 공격 가능한 거리인지 판단
- 공격 대상 부위 Blackboard에 저장
- Behavior Tree 실행
Behavior Tree
- 죽었는가?
- 마차가 보이는가?
- 공격 범위인가?
- 공격
- 아니면 추적
Blackboard 예시:
| Key | Type | 설명 |
|---|---|---|
TargetWagon |
Object | 추적할 마차 |
TargetWagonPart |
Object | 공격할 마차 부위 |
bIsInAttackRange |
Bool | 공격 거리 여부 |
bIsDead |
Bool | 사망 여부 |
Behavior Tree 흐름:
Selector
├─ Sequence: IsDead == true
│ └─ StopAI
│
├─ Sequence: IsInAttackRange == true
│ └─ AttackWagonPart
│
└─ Sequence
└─ MoveTo TargetWagon
기본 구현 흐름
// WEnemyAIController.h
#pragma once
#include "CoreMinimal.h"
#include "AIController.h"
#include "WEnemyAIController.generated.h"
class UBehaviorTree;
class UBlackboardComponent;
UCLASS()
class IRONWAGON_API AWEnemyAIController : public AAIController
{
GENERATED_BODY()
public:
AWEnemyAIController();
protected:
virtual void OnPossess(APawn* InPawn) override;
public:
void SetTargetActor(AActor* NewTarget);
protected:
UPROPERTY(EditDefaultsOnly, Category = "AI")
TObjectPtr<UBehaviorTree> BehaviorTree;
};
// WEnemyAIController.cpp
#include "WEnemyAIController.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BlackboardComponent.h"
AWEnemyAIController::AWEnemyAIController()
{
}
void AWEnemyAIController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
if (!BehaviorTree)
{
return;
}
RunBehaviorTree(BehaviorTree);
}
void AWEnemyAIController::SetTargetActor(AActor* NewTarget)
{
UBlackboardComponent* BlackboardComp = GetBlackboardComponent();
if (!BlackboardComp)
{
return;
}
BlackboardComp->SetValueAsObject(TEXT("TargetActor"), NewTarget);
}
// WEnemyCharacter.cpp
#include "WEnemyCharacter.h"
#include "WEnemyAIController.h"
AWEnemyCharacter::AWEnemyCharacter()
{
AIControllerClass = AWEnemyAIController::StaticClass();
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
}
Spawn할 때 주의할 점
런타임에 적을 생성할 때는 AutoPossessAI 설정이 중요합니다.
AWEnemyCharacter* Enemy = GetWorld()->SpawnActor<AWEnemyCharacter>(
EnemyClass,
SpawnLocation,
SpawnRotation
);
// 아래 설정이 이루어져야함.
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
Enemy->SpawnDefaultController();
SpawnActor 후 AI가 움직이지 않는다면 보통 다음을 확인해야 합니다.
- AIControllerClass가 설정되어 있는가?
- AutoPossessAI가 Spawned 또는 PlacedInWorldOrSpawned인가?
- NavMeshBoundsVolume이 배치되어 있는가?
- Behavior Tree가 실행되고 있는가?
- Blackboard Key 이름이 일치하는가?
- MoveTo 목표가 NavMesh 위에 있는가?
AI Controller, NavMesh
MoveToActor, MoveToLocation은 기본적으로 Navigation System을 사용한다.
AI가 이동하려면 월드에 NavMeshBoundsVolume이 있어야한다.
AI Controller 디버깅
작은따옴표' 키를 누르면 AI Debugger가 열린다.
- 현재 Possess된 Pawn
- AI Controller
- Behavior Tree 상태
- Blackboard 값
- MoveTo 상태
- Perception 정보
- NavMesh 관련 정보
또한 콘솔 명령으로도 확인 가능.
showdebug ai
NavMesh는 P 키로 확인
요약
AI Controller는 Unreal Engine 5에서 AI 캐릭터의 두뇌 역할을 하는 클래스.
Character는 몸체
AI Controller는 판단
Behavior Tree는 행동 흐름
Blackboard는 AI 기억 공간
NavMesh는 이동 가능 영역
Enemy Character 생성
→ AIControllerClass 지정
→ AutoPossessAI 설정
→ AI Controller가 Pawn Possess
→ Behavior Tree 실행
→ Blackboard 값 기반으로 이동/공격
→ Character가 실제 행동 수행
Character, Component, Behavior Tree, Blackboard와 역할을 분리하는 것이 좋음.
'TIL' 카테고리의 다른 글
| Clean Code (0) | 2026.06.09 |
|---|---|
| Unreal EQS(Environment Query System) (0) | 2026.06.08 |
| ProjectFT 시작, 초기 설정 (0) | 2026.06.04 |
| Unreal Component (0) | 2026.06.02 |
| TEXT RPG(DIABL5) 발표하며 회고... (0) | 2026.05.29 |