1. 다수 AI의 이동이 왜 어려운가
게임에서 단일 AI 캐릭터를 이동시키는 것은 NavMesh와 AIController만으로 충분합니다. 그러나 AI의 수가 수십 명을 넘어 수백, 수천 명 규모로 증가하면 전통적인 방식은 즉각적으로 한계를 드러냅니다.
첫 번째 문제는 회피(Avoidance) 입니다. NavMesh는 정적인 장애물을 우회하는 경로를 계산하지만, AI 캐릭터들은 서로를 동적 장애물로 인식하지 못합니다. 결과적으로 동일한 목표 지점으로 이동하는 여러 AI가 한 곳에 겹쳐 쌓이거나 서로 뚫고 지나가는 현상이 발생합니다. 이를 해결하려면 각 AI가 주변 에이전트의 속도와 위치를 실시간으로 인식하고 이동 궤적을 조정해야 합니다.
두 번째 문제는 성능 비용입니다. UE5의 AActor 기반 AI는 각자 독립적인 AIController, CharacterMovementComponent, 그리고 SkeletalMesh를 보유합니다. 이 구조는 풍부한 표현력을 제공하지만, 에이전트 수에 비례하여 CPU 비용이 급격히 증가합니다. 실제로 DetourCrowd의 기본 Max Agents 설정이 50으로 제한되어 있는 것만 봐도, 전통적인 Actor 기반 구조가 대규모 시뮬레이션에 적합하지 않음을 알 수 있습니다.
UE5는 이 문제를 두 단계의 기술 계층으로 해결합니다. 중규모(수십 명)에서는 DetourCrowd 시스템이 Actor 기반 AI를 그룹으로 묶어 집단 회피를 처리하고, 대규모(수백~수천 명)에서는 Mass Entity + ZoneGraph 조합이 데이터 지향 ECS 구조로 연산 부하를 수직으로 감소시킵니다.
2. 중규모 솔루션: DetourCrowd 시스템
2-1. DetourCrowd의 개념과 동작 원리
DetourCrowd는 오픈소스 네비게이션 라이브러리인 Recast & Detour를 기반으로 하며, UE5에서는 UCrowdManager 서브시스템이 이를 래핑하여 관리합니다. 핵심 아이디어는 개별 AI가 각자 경로를 계산하는 대신, 크라우드 매니저가 등록된 에이전트들의 이동을 일괄 처리하는 것입니다.
DetourCrowd는 두 가지 독립적인 회피 방식과 구별됩니다.
- RVO(Reciprocal Velocity Obstacles):
CharacterMovementComponent레벨에서 동작하며, 속도 벡터를 계산하여 충돌을 회피합니다. 구현이 간단하지만 군집 이동 품질이 DetourCrowd보다 낮습니다. - DetourCrowd:
UCrowdFollowingComponent레벨에서 동작하며, 에이전트 그룹의 조향(Steering)을 중앙화하여 더 자연스러운 군집 흐름을 만들어냅니다.
두 방식은 서로 독립적으로 설계되었으므로, 동일 에이전트에서 동시에 활성화하지 않아야 합니다.
2-2. C++로 DetourCrowd 활성화하기
AI가 DetourCrowd를 사용하도록 설정하는 가장 기본적인 방법은 AIController의 PathFollowingComponent를 UCrowdFollowingComponent로 교체하는 것입니다. 다음은 커스텀 AIController에서 이를 적용하는 방법입니다.
먼저 빌드 설정 파일에 필요한 모듈을 추가합니다.
// MyProject.Build.cs
PrivateDependencyModuleNames.AddRange(new string[]
{
"AIModule",
"NavigationSystem",
"DetourCrowd" // DetourCrowd 관련 클래스 사용에 필요
});
위와 같이 DetourCrowd 모듈을 명시하지 않으면 UCrowdFollowingComponent를 포함하는 헤더를 찾지 못합니다.
다음은 커스텀 AIController를 작성합니다.
// MyCrowdAIController.h
#pragma once
#include "CoreMinimal.h"
#include "AIController.h"
#include "MyCrowdAIController.generated.h"
UCLASS()
class MYPROJECT_API AMyCrowdAIController : public AAIController
{
GENERATED_BODY()
public:
AMyCrowdAIController(const FObjectInitializer& ObjectInitializer);
};
// MyCrowdAIController.cpp
#include "MyCrowdAIController.h"
#include "Navigation/CrowdFollowingComponent.h"
AMyCrowdAIController::AMyCrowdAIController(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer.SetDefaultSubobjectClass<UCrowdFollowingComponent>(
TEXT("PathFollowingComponent"))) // PathFollowingComponent를 CrowdFollowing으로 교체
{
}
위 코드에서 핵심은 FObjectInitializer::SetDefaultSubobjectClass<>() 호출입니다. 이 방식으로 기본 UPathFollowingComponent를 UCrowdFollowingComponent로 대체하면, 해당 AIController를 사용하는 모든 Pawn이 자동으로 DetourCrowd 시스템에 등록됩니다.
2-3. CrowdManager 설정 조정
DetourCrowd의 동작은 프로젝트 설정의 Crowd Manager 항목에서 세부 조정이 가능합니다. C++로 직접 접근하는 방법도 제공됩니다.
// 런타임에서 CrowdManager 설정에 접근
#include "Navigation/CrowdManager.h"
void AMyGameMode::ConfigureCrowdManager()
{
UCrowdManager* CrowdManager = UCrowdManager::GetCurrent(GetWorld());
if (!CrowdManager)
{
return;
}
FCrowdAvoidanceConfig AvoidanceConfig;
AvoidanceConfig.VelocityBias = 0.5f; // 현재 속도 방향 유지 선호도
AvoidanceConfig.DesiredVelocityWeight = 2.0f; // 목표 방향 가중치
AvoidanceConfig.CurrentVelocityWeight = 0.75f;
AvoidanceConfig.SideBiasWeight = 0.75f; // 측면 회피 선호도
// 0번 슬롯에 회피 설정 등록 (0~3 슬롯 사용 가능)
CrowdManager->SetAvoidanceConfig(0, AvoidanceConfig);
}
위 코드에서 VelocityBias가 높을수록 에이전트가 현재 이동 방향을 유지하려는 경향이 강해지고, DesiredVelocityWeight가 높을수록 목표 지점으로 적극적으로 이동합니다. 두 값의 균형이 군집 이동의 자연스러움을 결정합니다.
2-4. DetourCrowd의 한계
DetourCrowd는 기본 Max Agents가 50으로 설정되어 있습니다. 프로젝트 설정에서 이 값을 올릴 수 있지만, 등록된 에이전트 수가 한계를 초과하면 초과분은 단순히 크라우드 시뮬레이션에서 제외됩니다. 또한 에이전트 수가 증가함에 따라 CPU 비용이 비례하여 증가하기 때문에, 수백 명 이상의 AI를 처리하기에는 구조적으로 적합하지 않습니다. 이 지점부터 Mass Entity 시스템으로의 전환을 고려해야 합니다.
3. 대규모 솔루션: Mass Entity와 ZoneGraph
3-1. Mass Entity 시스템이란
Mass Entity는 UE5가 새롭게 도입한 데이터 지향 ECS(Entity Component System) 프레임워크입니다. City Sample 데모에서 수만 명의 보행자와 차량을 동시에 처리한 기반 기술이 바로 Mass Entity입니다.
Mass Entity의 핵심 개념은 다음과 같습니다.
- Entity: 정수 ID로 식별되는 경량 객체로, AActor보다 훨씬 적은 메모리를 사용합니다.
- Fragment: 각 Entity가 소유하는 데이터 단위입니다.
FMassFragment를 상속한 순수 데이터 구조체입니다. - Processor: Fragment를 읽고 쓰는 로직 단위입니다. 동일한 Fragment 조합을 가진 Entity들을 연속된 메모리 블록(Archetype)으로 묶어 SIMD 친화적으로 처리합니다.
- Trait: 여러 Fragment와 Processor의 조합을 재사용 가능한 단위로 묶은 설정 에셋입니다.
기존 Actor 기반 AI와의 근본적인 차이점은 메모리 레이아웃에 있습니다. Actor 기반 시스템에서 각 캐릭터의 데이터는 메모리 상에 분산되어 있지만, Mass Entity에서는 동일 Archetype에 속하는 모든 Entity의 Fragment 데이터가 연속된 메모리 배열에 저장됩니다. 이로 인해 캐시 효율이 극적으로 향상됩니다.
3-2. ZoneGraph: 대규모 이동 경로 정의
ZoneGraph는 레벨 안에서 AI가 따라 이동할 레인(Lane) 기반 경로 구조입니다. NavMesh가 넓은 영역의 이동 가능 면을 제공한다면, ZoneGraph는 보행로, 도로, 복도와 같이 의미 있는 흐름을 가진 경로를 레인으로 정의합니다.
ZoneGraph의 주요 특징은 다음과 같습니다.
- 에디터에서 스플라인으로 경로를 배치하고, Lane Profile로 레인 수와 태그를 지정합니다.
- 정적 태그와 동적 태그를 레인에 부여하여 Mass Entity가 조건에 맞는 레인을 선택하게 할 수 있습니다.
- NavMesh와 달리 경로 자체가 설계 의도를 내포하므로, AI의 이동이 훨씬 자연스럽고 예측 가능한 흐름을 따릅니다.
3-3. 플러그인 활성화 및 모듈 설정
Mass Entity와 ZoneGraph를 사용하려면 먼저 .uproject 파일과 Build.cs를 설정해야 합니다.
// MyProject.uproject - Plugins 배열에 추가
{
"Plugins": [
{ "Name": "MassEntity", "Enabled": true },
{ "Name": "MassGameplay", "Enabled": true },
{ "Name": "MassAI", "Enabled": true },
{ "Name": "MassCrowd", "Enabled": true },
{ "Name": "ZoneGraph", "Enabled": true }
]
}
// MyProject.Build.cs
PrivateDependencyModuleNames.AddRange(new string[]
{
"MassEntity",
"MassCommon",
"MassMovement",
"MassNavigation",
"MassAIBehavior",
"MassCrowd",
"MassRepresentation",
"MassSpawner",
"MassLOD",
"ZoneGraph",
"MassZoneGraphNavigation",
"StateTreeModule"
});
위 모듈 목록에서 MassZoneGraphNavigation은 Mass Entity가 ZoneGraph 레인을 인식하고 따라 이동하는 기능을 제공하며, StateTreeModule은 각 Entity의 행동 상태 머신을 담당합니다.
3-4. 커스텀 Fragment와 Processor 작성
Mass Entity 시스템에서 AI의 데이터와 로직을 직접 확장하려면 Fragment와 Processor를 정의합니다. 다음은 체력(Health) 상태를 추적하는 Fragment와, 그것을 처리하는 Processor 예제입니다.
먼저 Fragment를 정의합니다.
// MyCrowdFragment.h
#pragma once
#include "CoreMinimal.h"
#include "MassEntityTypes.h"
#include "MyCrowdFragment.generated.h"
USTRUCT()
struct MYPROJECT_API FMyCrowdHealthFragment : public FMassFragment
{
GENERATED_BODY()
float CurrentHealth = 100.0f; // 현재 체력
float MaxHealth = 100.0f;
};
다음은 해당 Fragment를 처리하는 Processor를 작성합니다.
// MyCrowdHealthProcessor.h
#pragma once
#include "CoreMinimal.h"
#include "MassProcessor.h"
#include "MyCrowdHealthProcessor.generated.h"
UCLASS()
class MYPROJECT_API UMyCrowdHealthProcessor : public UMassProcessor
{
GENERATED_BODY()
public:
UMyCrowdHealthProcessor();
protected:
virtual void ConfigureQueries() override;
virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override;
private:
FMassEntityQuery HealthQuery;
};
// MyCrowdHealthProcessor.cpp
#include "MyCrowdHealthProcessor.h"
#include "MyCrowdFragment.h"
#include "MassExecutionContext.h"
UMyCrowdHealthProcessor::UMyCrowdHealthProcessor()
{
// 이 Processor가 어느 처리 단계에서 실행될지 지정
ExecutionFlags = (int32)EProcessorExecutionFlags::All;
ProcessingPhase = EMassProcessingPhase::PrePhysics;
}
void UMyCrowdHealthProcessor::ConfigureQueries()
{
// FMyCrowdHealthFragment를 가진 Entity만 쿼리 대상으로 설정
HealthQuery.AddRequirement<FMyCrowdHealthFragment>(EMassFragmentAccess::ReadWrite);
HealthQuery.RegisterWithProcessor(*this);
}
void UMyCrowdHealthProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
// 동일 Archetype 단위로 일괄 처리 (캐시 친화적 루프)
HealthQuery.ForEachEntityChunk(EntityManager, Context,
[](FMassExecutionContext& ExecContext)
{
// 같은 Archetype의 HealthFragment 배열을 한 번에 가져옴
TArrayView<FMyCrowdHealthFragment> HealthList =
ExecContext.GetMutableFragmentView<FMyCrowdHealthFragment>();
const float DeltaTime = ExecContext.GetDeltaTimeSeconds();
for (FMyCrowdHealthFragment& Health : HealthList)
{
// 예: 시간이 지남에 따라 체력을 서서히 회복
Health.CurrentHealth = FMath::Min(
Health.CurrentHealth + 5.0f * DeltaTime,
Health.MaxHealth);
}
});
}
위 코드에서 ForEachEntityChunk가 핵심입니다. 동일 Archetype에 속하는 Entity들의 Fragment 데이터는 메모리상 연속으로 배치되어 있으므로, 이 루프는 캐시 미스 없이 수천 개의 Entity를 매 프레임 처리할 수 있습니다.
3-5. FMassMoveTargetFragment로 이동 목표 지정
Mass Entity가 특정 위치로 이동하도록 만들려면 FMassMoveTargetFragment를 설정합니다. ZoneGraph 레인을 사용하는 경우 Mass 내부 Processor가 자동으로 다음 웨이포인트를 갱신하지만, 커스텀 목표 이동이 필요한 경우 다음과 같이 직접 Fragment를 조작합니다.
// 특정 Entity에 이동 목표를 설정하는 함수
#include "MassNavigationFragments.h"
#include "MassCommonFragments.h"
void SetEntityMoveTarget(FMassEntityManager& EntityManager,
FMassEntityHandle EntityHandle,
const FVector& TargetLocation)
{
// Entity가 해당 Fragment를 가지고 있는지 확인 후 접근
FMassMoveTargetFragment& MoveTarget =
EntityManager.GetFragmentDataChecked<FMassMoveTargetFragment>(EntityHandle);
// 목표 위치와 이동 의도 설정
MoveTarget.Center = TargetLocation;
MoveTarget.Forward = FVector::ForwardVector;
MoveTarget.DistanceToGoal = 0.0f;
MoveTarget.SlackRadius = 50.0f; // 목표 지점 도달 판정 반경 (cm)
MoveTarget.CreateNewAction(EMassMovementAction::Move, *EntityManager.GetWorld());
}
위 코드에서 CreateNewAction을 호출할 때 EMassMovementAction::Move를 전달하면 Mass의 이동 Processor들이 해당 Entity를 목표 방향으로 조향하기 시작합니다.
3-6. ZoneGraph 레인 기반 이동 흐름
ZoneGraph와 Mass Entity를 연동하면 AI는 레인을 따라 자동으로 이동합니다. MassZoneGraphNavigation 모듈의 Processor들이 FZoneGraphShortPathFragment와 FMassMoveTargetFragment를 연동하여 레인 위의 다음 포인트를 지속적으로 갱신해줍니다.
Trait 에셋에서 아래 Trait들을 조합하면 ZoneGraph 레인 이동이 활성화됩니다.
| Trait 이름 | 역할 |
|---|---|
MassZoneGraphPathFollowingTrait |
ZoneGraph 레인을 따라 이동 목표를 갱신 |
MassObstacleAvoidanceTrait |
로컬 회피 연산 활성화 |
MassNavigationObstacleTrait |
이 Entity를 다른 Entity의 회피 대상으로 등록 |
MassCrowdVisualizationTrait |
애니메이션 및 렌더링 LOD 처리 |
에디터에서 MassEntityConfigAsset을 생성하고, 위 Trait을 추가한 뒤 MassSpawner 액터의 Entity Config에 연결하면 레벨 안의 ZoneGraph 레인을 따라 이동하는 군중이 완성됩니다.
4. 규모별 스케일 확장 전략
4-1. DetourCrowd → Mass+ZoneGraph 전환 기준
두 시스템은 대체 관계가 아니라 용도와 규모에 따라 선택하는 상호 보완적 도구입니다. 아래 기준을 참고하면 전환 시점을 판단하는 데 도움이 됩니다.
| 기준 | DetourCrowd | Mass + ZoneGraph |
|---|---|---|
| 에이전트 수 | 수백 ~ 수만 명 | |
| 에이전트 표현 | 완전한 Skeletal Mesh Actor | LOD 기반 경량 표현 (Niagara, AnimToTexture) |
| 행동 복잡도 | BehaviorTree, 전체 AI 스택 | StateTree, 단순~중간 행동 |
| 개별 상호작용 | 가능 | Smart Object를 통해 제한적 가능 |
| NavMesh 사용 | 필수 | 선택적 (ZoneGraph로 대체 가능) |
4-2. 하이브리드 구성: 두 시스템 함께 사용하기
플레이어 주변의 중요 NPC는 DetourCrowd 기반의 완전한 Actor AI로 운영하고, 원거리의 배경 군중은 Mass Entity로 처리하는 하이브리드 구성이 가장 현실적인 선택입니다. UE5의 Mass LOD 시스템은 카메라 거리에 따라 Entity의 시뮬레이션 품질을 자동으로 조정하므로, 먼 거리의 Mass Entity는 틱 빈도를 줄이거나 시각 표현을 단순화할 수 있습니다.
// Mass LOD Processor가 참조하는 LOD 설정 예시 (MassEntityConfigAsset의 Trait에서 조정)
// - High LOD (근거리): 완전한 Skeletal Mesh 애니메이션, 매 틱 이동 처리
// - Medium LOD(중거리): 단순화된 애니메이션(AnimToTexture), 틱 빈도 감소
// - Low LOD (원거리): Niagara 파티클 표현, 이동만 처리
// - Off LOD (컬링): 시뮬레이션 정지
// UMassVisualizationLODProcessor와 UMassSimulationLODProcessor가
// FMassVisualizationLODFragment를 기반으로 자동 LOD 전환을 처리합니다.
위 설명에서처럼 Mass의 LOD Processor들은 FMassVisualizationLODFragment를 참조하여 에이전트의 거리와 중요도에 따라 자동으로 시뮬레이션 품질과 렌더링 방식을 조정합니다.
5. 유니티 엔진과의 비교
유니티에서 이와 유사한 기능을 제공하는 것은 DOTS(Data-Oriented Technology Stack) 와 NavMesh Agent 조합입니다.
유니티의 NavMesh Agent는 UE5의 CharacterMovementComponent + UPathFollowingComponent 조합과 역할이 유사하며, 로컬 회피는 RVO 방식을 사용합니다. DetourCrowd에 직접 대응하는 유니티 내장 기능은 존재하지 않습니다. 군집 회피가 필요한 경우 Boids 알고리즘을 직접 구현하거나 서드파티 에셋을 활용해야 합니다.
대규모 시뮬레이션 측면에서 유니티 DOTS는 UE5 Mass Entity와 아키텍처 면에서 매우 유사합니다. ECS 개념(Entity, Component, System), 메모리 청크 기반 Archetype, Job System을 통한 병렬 처리 등 핵심 설계 철학이 같습니다. 다만 UE5의 Mass Entity는 ZoneGraph, StateTree, Smart Objects, MassLOD와 같이 게임 제작에 특화된 상위 시스템들과 긴밀하게 통합되어 있다는 점이 차이입니다. 유니티 DOTS에서 이와 동등한 수준의 통합 시스템을 구성하려면 상당한 직접 구현이 필요합니다.
6. 주의사항
6-1. DetourCrowd와 RVO를 동시에 사용하지 않아야 합니다.
UCrowdFollowingComponent를 활성화한 에이전트에 RVO도 함께 켜두면 두 회피 시스템이 충돌하여 에이전트가 떨리거나 비정상적인 궤적으로 이동합니다. CharacterMovementComponent의 bUseRVOAvoidance 설정이 비활성화되어 있는지 반드시 확인하시기 바랍니다.
6-2. DetourCrowd의 Max Agents 한계를 반드시 설정에서 확인해야 합니다.
기본값 50을 초과하는 에이전트는 크라우드 관리에서 조용히 제외되며, 별도의 에러 메시지가 출력되지 않습니다. 에이전트가 뭉치거나 회피가 작동하지 않는 경우 이 설정을 가장 먼저 점검하시기 바랍니다.
6-3. Mass Entity에서 Fragment 데이터에 Processor 외부에서 직접 접근하는 것은 위험합니다.
FMassEntityManager::GetFragmentDataChecked()를 게임 스레드 임의의 지점에서 호출하면 다른 Processor와의 Race Condition이 발생할 수 있습니다. Fragment 데이터의 읽기·쓰기는 반드시 UMassProcessor::Execute() 내부, 또는 FMassEntityManager::Defer() 메커니즘을 통한 Command Buffer를 사용해야 합니다.
6-4. ZoneGraph 레인을 런타임에 동적으로 변경하는 것은 제한적입니다.
ZoneGraph는 기본적으로 정적인 구조로 설계되어 있습니다. 레인의 동적 태그는 변경할 수 있지만, 레인의 형상 자체를 런타임에 수정하는 것은 지원되지 않습니다. 동적으로 변하는 이동 경로가 필요한 경우 NavMesh 기반 이동과 병행 사용을 검토하시기 바랍니다.
6-5. Mass Entity 시스템은 UE5.1 이후 안정화되고 있지만 여전히 활발히 변경되고 있습니다.
API가 엔진 버전 간에 변경되는 경우가 있으므로, 공식 MassSample 프로젝트와 엔진 소스 코드를 함께 참조하면서 버전별 변경 사항을 추적하시기 바랍니다.
이 글에서는 UE5에서 다수 AI의 이동을 처리하는 두 가지 핵심 기술인 DetourCrowd와 Mass Entity + ZoneGraph를 다루었습니다. 수십 명 규모의 전투 군중이나 NPC 집단은 UCrowdFollowingComponent를 통한 DetourCrowd 방식이 기존 AI 스택과의 호환성을 유지하면서 집단 회피를 손쉽게 적용할 수 있습니다. 수백 명 이상의 배경 군중이나 열린 세계의 교통 흐름처럼 규모 자체가 핵심 요건인 상황에서는 Mass Entity와 ZoneGraph 조합이 유일한 실용적 선택입니다. 두 시스템을 카메라 거리 기반 LOD 전략으로 결합하면, 표현력과 성능을 동시에 확보하는 대규모 AI 시뮬레이션을 구현할 수 있습니다.
'언리얼 엔진 5' 카테고리의 다른 글
| [UE5] UNavLocalGridManager - 로컬 내비게이션 그리드로 AI 경로 탐색 제어하기 (0) | 2026.03.28 |
|---|---|
| [UE5] NavLink - 단절된 NavMesh 구간을 연결하는 기술 (1) | 2026.03.23 |
| [UE5] AIController · MoveTo & NavigationQueryFilter 커스터마이징 (0) | 2026.03.22 |
| [UE5] NavMesh 런타임 생성, 업데이트 & 경로 탐색 알고리즘 (0) | 2026.03.21 |
| [UE5] 네비게이션 시스템 개요 및 NavMesh 기초 (0) | 2026.03.19 |