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