AI 에이전트 몬스터 어그로 분산과 유저 둘러싸기 협동 패턴 설계
AI 에이전트 몬스터가 한 지점에 뭉치거나 무한 추적하는 문제를 줄이는 어그로 분산·Surrounding 협동 패턴을 점수식, 상태 전이, 밸런싱 파라미터로 설계합니다.

핵심 요약
몬스터 협동 전투에서 중요한 것은 모든 AI가 가장 높은 위협도를 가진 유저에게 곧바로 달려드는 것이 아니라 제한된 인원만 교전하고 나머지는 공격 기회와 공간을 분담하게 만드는 것이다. 이를 위해 Threat Score에 역할별 가중치와 거리·혼잡 패널티를 더하고 목표 주변의 원형 슬롯을 예약하는 Surrounding 시스템을 함께 사용한다.
이 글의 목표는 플레이어를 억지로 가두는 AI가 아니라 회피와 위치 선정으로 대응할 여지를 남기는 협동 전투 패턴을 만드는 것이다.
몬스터가 한 유저에게 뭉치는 문제는 왜 발생하는가?
단순한 어그로 설계의 한 예는 피해량, 회복량, 도발 수치를 위협도 테이블에 누적한 뒤 최고 점수의 유저를 목표로 선택하는 방식이다. 다수 몬스터가 같은 규칙을 사용할 때 다음 상황을 점검한다.
- 여러 몬스터가 같은 목표와 경로를 선택해 이동을 방해하는가?
- 공격 반경 안의 과밀 때문에 일부 근접 몬스터가 공격하지 못하는가?
- 원거리 몬스터의 집중 공격이 설계한 동시 피해량을 넘는가?
- 탱커가 있는 파티에서도 후방 유저가 위치와 행동을 선택할 여지가 있는가?
따라서 목표 선택과 이동 목적지를 분리해야 한다. 목표 선택은 ‘누구에게 압박을 줄 것인가’를 결정하고 Surrounding은 ‘어느 위치에서 그 압박을 줄 것인가’를 결정한다.
어그로 분산은 어떻게 설계할까?
1. 기본 위협도와 전술 점수를 분리한다
Base Threat는 전투 행위에서 발생한 누적 위협도다. 피해량, 회복량, 군중 제어, 도발 등을 반영한다. 그러나 실제 목표 선택에는 현재 전술 상황을 반영한 Target Score를 사용한다.
각 항의 의미는 다음과 같다.
| 항목 | 목적 | 예시 |
|---|---|---|
BaseThreat | 전투 기여에 대한 기본 반응 | 피해량, 회복량, 도발 누적 |
RoleBonus | 몬스터 역할에 맞는 타깃 선호 | 암살자는 후방 딜러에 가산점 |
DistancePenalty | 지나친 추격 방지 | 목표가 추적 한계를 넘으면 감점 |
CrowdPenalty | 같은 유저 쏠림 방지 | 이미 추적 중인 몬스터 수에 비례해 감점 |
OpportunityBonus | 순간 전술 기회 반영 | 방어막이 없거나 고립된 유저에 제한적 가산점 |
CrowdPenalty는 어그로 분산의 핵심이다. 예를 들어 특정 유저를 현재 추적하는 몬스터 수를 AssignedCount라고 할 때 다음처럼 계산할 수 있다.
Capacity는 이 예시에서 혼잡 패널티를 적용하기 시작하는 배정 수다. 2~3기나 4~6기 같은 값은 튜닝 후보이며 일반적인 정답이 아니다. 평가 중인 몬스터를 제외한 배정 수에 이번 배정 1기를 더해 AssignedCount를 계산하고, 중앙 조정자가 배정 결과를 순차적으로 반영하면 동시 평가에 따른 중복 배정을 줄일 수 있다. 이 패널티는 상한 초과를 금지하지 않으므로 하드 제한이 필요하면 별도로 적용한다.
2. 목표 고정 시간과 교체 임계값을 둔다
점수가 매 프레임 바뀌면 몬스터가 목표를 계속 교체하는 현상이 생긴다. 이를 막으려면 TargetLockDuration과 RetargetThreshold를 함께 둔다.
- 목표를 선택한 뒤
TargetLockDuration동안은 현재 목표를 유지한다. - 새 후보의 점수가 현재 목표보다
RetargetThreshold이상 높을 때만 교체한다. - 현재 목표가 사망, 비가시 상태, 추적 불가 거리 초과 상태라면 즉시 예외 처리한다.
예를 들어 0.8초의 고정 시간과 max(최소 점수 차이, abs(현재 점수) × 0.2)의 교체 임계값부터 테스트할 수 있다. 패널티 때문에 점수가 음수가 될 수 있으므로 새 점수 > 현재 점수 × 1.2만으로 판단하면 더 나쁜 목표로 바뀔 수 있다. 고정 시간은 몬스터 역할에 맞춰 조정한다.
3. 몬스터 역할마다 분산 규칙을 다르게 한다
모든 몬스터에 같은 분산 규칙을 적용하면 전투가 산만해질 수 있다. 역할별 목표 정책을 명시하는 편이 좋다.
| 몬스터 역할 | 주요 목표 | 분산 정책 |
|---|---|---|
| 브루저 | 전열 유지 | 탱커 우선, 최대 배정 수를 낮게 제한 |
| 스커미셔 | 빈 공간 공략 | 측면 슬롯 우선, 고립 유저에 제한적으로 전환 |
| 암살자 | 후방 압박 | 후방 유저 최대 1~2기만 배정 |
| 원거리 | 사선 확보 | 목표보다 사격 위치를 먼저 평가 |
| 지원형 | 아군 보호 | 아군 체력·위험도 기반으로 행동 결정 |

Surrounding 패턴은 무엇이며 어떻게 구현할까?
Surrounding은 목표 캐릭터 주위에 공격 위치를 미리 정의하고 몬스터가 서로 다른 위치를 점유하도록 하는 패턴이다. 핵심은 ‘포위’ 자체가 아니라 공격 가능한 위치를 예약하고 과밀을 제어하는 것이다.
원형 슬롯을 생성한다
대상 위치를 P, 몬스터의 유효 공격 거리와 충돌 반경을 고려한 포위 반경을 R, 슬롯 수를 N이라고 하자. 각 슬롯의 월드 좌표는 다음과 같이 계산할 수 있다.
여기서 θ는 파티 이동 방향이나 전투 시작 방향에 따라 회전시키는 오프셋이다. 고정된 각도만 사용하면 몬스터 배치가 지나치게 규칙적으로 보일 수 있다.
이 식은 2D 평면의 좌표다. Unity의 Y-up 3D 공간에서는 두 성분을 X·Z에 배치하고 높이 Y는 내비게이션 표면에서 구한다. R > 0, 정수 N >= 2를 가정한다.
슬롯 수는 단순히 많을수록 좋지 않다. 근접 공격자의 충돌 지름을 D, 슬롯 간 최소 여유를 M으로 잡으면 원주 길이를 기준으로 대략 다음 범위에서 시작할 수 있다.
원주 기준 식은 근사값이다. 실제 인접 슬롯 중심 간 거리는 현의 길이 2R sin(π/N)이므로 이 값이 D + M 이상인지 확인해야 한다. 내비게이션 표면으로 보정한 뒤에는 슬롯 간 거리도 다시 검사한다.
예를 들어 반경 2.4m, 충돌 지름 0.7m, 여유 0.25m라면 근사 상한은 15개이고, 이때 인접 거리도 약 0.998m로 필요한 0.95m 이상이다. 활성 슬롯 6~8개는 공격 애니메이션과 회피 공간을 평가하기 위한 별도의 튜닝 후보다.
flowchart TD
A[0.25초마다 전술 갱신] --> B[후보 유저의 Target Score 계산]
B --> C[목표 선택 또는 유지]
C --> D[목표 주변 슬롯 생성·갱신]
D --> E{예약 가능한 슬롯이 있는가?}
E -- 예 --> F[가장 낮은 비용의 슬롯 예약]
E -- 아니오 --> G[대기·측면 이동·다른 유저 압박]
F --> H[이동 후 공격 거리와 시야 확인]
G --> A
H --> A
슬롯 예약 비용을 계산한다
가장 가까운 슬롯만 선택하면 모든 몬스터가 같은 쪽에서 접근할 수 있다. 슬롯마다 비용을 계산해 가장 낮은 비용의 위치를 선택한다.
Distance: 몬스터와 슬롯 사이의 이동 비용이다. 가능하면 단순 직선거리 대신 NavMesh 경로 길이를 사용한다.AnglePenalty: 이미 점유된 슬롯과 지나치게 가까운 각도면 비용을 높인다.OccupancyPenalty: 다른 몬스터가 예약·점유한 슬롯은 후보에서 제외한다. 해제 예정 슬롯 등을 추가로 비교할 때만 비용을 사용한다.NavmeshPenalty: NavMesh 위에 없거나 경로가 막힌 슬롯을 제외한다.
Unity 6에서는 NavMesh.SamplePosition으로 후보를 보정할 수 있지만, 이 함수는 장애물을 고려하지 않으며 다른 층의 점을 반환할 수도 있다. 에이전트 유형·영역·높이 차이를 확인하고 NavMesh.CalculatePath의 반환값뿐 아니라 path.status == NavMeshPathStatus.PathComplete도 검사한다. 반환값이 true여도 부분 경로일 수 있다. Godot 4에서는 NavigationServer3D.map_get_closest_point 등으로 후보를 보정한 뒤 실제 경로와 목표 도달 여부를 확인한다.
예약 만료와 이탈 규칙을 만든다
슬롯 예약은 영구적이면 안 된다. 몬스터가 넉백되거나 경로를 잃으면 다른 몬스터가 해당 자리를 사용해야 한다.
ReservationTimeout: 예약 후 일정 시간 안에 슬롯 반경에 진입하지 못하면 해제한다.OccupyRadius: 슬롯 중심에서 이 거리 안에 들어오면 점유 상태로 바꾼다.ReleaseDistance: 목표와 너무 멀어지거나 목표가 바뀌면 해제한다.PathFailCount: 연속 경로 실패 횟수가 임계값에 도달하면 새 슬롯을 탐색한다.
예약 갱신 주기는 0.2~0.5초를 초기 테스트 값으로 두고 반응 지연과 경로 질의 비용을 측정해 조정한다. 예약 해제와 목표 무효화 같은 사건은 정기 갱신과 별도로 처리한다.
유저를 완전히 가두지 않으려면 무엇을 제한해야 할까?
Surrounding의 실패 사례는 몬스터가 원형으로 빈틈없이 서서 플레이어가 이동하거나 회피할 수 없는 경우다. 이 문제는 AI의 영리함보다 게임의 공정성을 먼저 고려해 해결해야 한다.
탈출 구간을 의도적으로 남긴다
다음 규칙 중 하나 이상을 적용한다.
OpenArcAngle을 60°~120°로 설정해 포위 원에 비어 있는 구간을 만든다.- 동일 목표에 배정되는 근접 몬스터 수를
MeleeEngageCap으로 제한한다. - 플레이어가 대시, 구르기, 넉백 스킬을 사용한 직후에는 재포위 계산에
ReformDelay를 적용한다. - 벽, 절벽, 좁은 문처럼 지형이 이미 이동을 제한하는 곳에서는 활성 슬롯 수를 줄인다.
특히 3번은 중요하다. 유저가 비용을 지불해 탈출한 직후 몬스터가 즉시 새 원을 만들면 회피 스킬의 체감 가치가 사라진다.
어그로 분산과 Surrounding 밸런싱 체크리스트
전투 난이도는 어떤 지표로 검증할까?
테스트에서는 처치 시간만 보지 말고 플레이어의 선택지가 실제로 유지되는지 함께 측정한다.
| 지표 | 확인할 문제 | 권장 관찰 방법 |
|---|---|---|
| 동시 공격자 수 | 피격이 불가피한 상황 | 근접·원거리 공격 시작 이벤트 집계 |
| 포위 완성 시간 | 압박이 너무 빠른 문제 | 첫 인지부터 슬롯 점유까지 측정 |
| 탈출 성공률 | 회피 수단 무력화 | 대시·구르기 후 3초간 재피격 여부 |
| 목표 전환 횟수 | AI가 불안정하게 보이는 문제 | 몬스터당 분당 Retarget 횟수 |
| 경로 실패율 | 지형과 패턴의 충돌 | 슬롯 예약 대비 경로 실패 비율 |
피해량만으로는 플레이어가 공격을 읽고 피할 수 있었는지 판단하기 어렵다. 로그 분석과 플레이테스트에서 경고 시간, 탈출 경로, 카메라 시야를 함께 확인한다.
자주 묻는 질문 (FAQ)
모든 몬스터가 각자 다른 유저를 노리게 하면 더 좋은가?
아니다. 전투의 초점이 사라지고 탱커나 군중 제어의 역할도 약해질 수 있다. 기본 위협도는 유지하되 CrowdPenalty와 역할별 배정 상한으로 과도한 쏠림만 줄이는 방식이 안정적이다.
Surrounding 슬롯은 몇 개가 적절한가?
몬스터 충돌 크기, 공격 범위, 지형에 따라 다르다. 이 글의 테스트 후보는 플레이어 한 명당 활성 슬롯 6~8개이며, 실제 동시 공격자 수는 별도의 상한으로 조정한다.
NavMesh가 없는 위치에 슬롯이 생성되면 어떻게 해야 하는가?
후보 좌표를 NavMesh에 투영한 뒤 유효 경로를 확인해야 한다. 실패한 슬롯은 이번 갱신에서 제외하고 연속 실패한 몬스터는 대기나 측면 이동 상태로 전환한다.
정리
어그로 분산은 최고 위협도만 바라보는 목표 선택에 혼잡 비용과 역할 규칙을 추가하는 설계다. Surrounding은 그 목표 주변의 공격 위치를 슬롯으로 관리해 몬스터 간 충돌과 중복 이동을 줄인다.
두 시스템은 반드시 탈출 구간, 재포위 지연, 동시 공격자 제한과 함께 설계해야 한다. 플레이어가 협동 AI의 압박을 읽고 대응할 수 있을 때, 포위는 답답한 군중 제어가 아니라 전술적인 전투 경험이 된다.



