
오픈월드 거점 간 이동 시간과 몬스터 밀도 분포를 AI로 자동 평가하는 방법
플레이어 텔레메트리와 월드 그리드 데이터를 이용해 오픈월드 거점 간 이동 시간, 전투 중단율, 몬스터 위협 밀도를 계산하고 AI가 구간별 과밀·공백·지루함을 자동 판정하는 실전 기준을 정리합니다.
TL;DR
오픈월드의 이동 시간과 몬스터 밀도는 월드를 50m 단위 그리드로 나누고 플레이어 텔레메트리와 배치 데이터를 결합해 평가할 수 있습니다.
T_p50, T_p90, 전투 중단율, 위협 가중 밀도, 공간 자기상관을 점수화하면 AI가 과밀 구간·몬스터 공백·반복 조우를 자동으로 찾아냅니다.
오픈월드 거점 간 이동 시간은 왜 몬스터 밀도와 함께 봐야 하는가?
거점 간 이동 시간만 측정하면 플레이어가 길을 걷는 동안 어떤 경험을 했는지 알기 어렵습니다.
같은 3분의 이동이라도 몬스터와 한 번 긴장감 있게 마주치는 경우와 20초마다 반복 전투가 발생하는 경우의 체감은 크게 다릅니다.
따라서 평가 단위는 단순한 거리나 평균 시간이 아니라 다음 네 가지를 함께 사용해야 합니다.
| 평가 항목 | 핵심 질문 | 대표 지표 |
|---|---|---|
| 이동 시간 | 다음 거점까지 너무 멀거나 가까운가? | T_p50, T_p90, 경로 편차 |
| 전투 개입 | 이동 흐름이 전투로 적절히 끊기는가? | 전투 중단율, 분당 조우 횟수 |
| 몬스터 밀도 | 특정 구역에 위협이 과도하게 몰렸는가? | 위협 가중 밀도, 고밀도 셀 비율 |
| 공간 분포 | 조우가 반복적이거나 빈 구역이 생기는가? | 분산, 공간 자기상관, 연속 공백 길이 |
이 방식의 목표는 재미를 단일 숫자로 결정하는 것이 아닙니다.
자동 평가 시스템은 사람이 확인할 우선순위를 줄이고 디자이너가 플레이테스트를 시작할 지점을 빠르게 찾는 데 사용합니다.
거점 간 이동 시간은 어떻게 측정할까?
1. 실제 플레이 경로를 정규화한다
맵의 직선거리가 아니라 실제 플레이어가 이동한 경로를 사용해야 합니다.
내비게이션 메시, 지형 고저차, 폐쇄된 문, 퀘스트 상태에 따라 실제 이동 경로가 달라지기 때문입니다.
텔레메트리에는 최소한 다음 이벤트를 기록합니다.
HubEnter: 거점 진입 시각과 거점 IDHubLeave: 거점 이탈 시각과 거점 IDCombatStart,CombatEnd: 전투 시작과 종료FastTravelStart,FastTravelEnd: 빠른 이동 사용 여부QuestStateChanged: 경로 변경을 유발하는 퀘스트 상태PlayerPositionSample: 일정 주기의 위치 샘플
메뉴를 열었거나 로딩이 발생한 시간은 별도로 분리합니다.
플레이어가 실제로 이동한 체감 시간과 시스템 대기 시간을 섞으면 잘못된 결론이 나올 수 있습니다.
2. 평균 대신 p50과 p90을 사용한다
평균 이동 시간은 일부 플레이어의 길 잃음이나 비정상적인 탐험으로 쉽게 왜곡됩니다.
일반적인 경험은 중앙값인 T_p50으로 보고 길 찾기 실패나 지형 문제까지 포함한 상위 경험은 T_p90으로 확인합니다.
여기서 T_active는 실제 이동과 전투에 사용된 시간입니다.
거점 A에서 거점 B까지의 경로별로 T_active를 수집한 뒤 다음처럼 기준 구간과 비교합니다.
T_target: 해당 구간의 목표 이동 시간T_tolerance: 허용 오차P_interrupt: 전투나 제어 상실로 이동이 중단된 비율P_target: 해당 구간에서 허용하는 목표 중단율clamp: 결과를 0과 1 사이로 제한하는 함수
이 점수는 프로젝트의 설계 의도에 맞게 가중치를 조정해야 합니다.
상업용 데이터나 보편적인 정답을 대입하는 방식이 아니라 게임의 장르와 진행 단계에 맞춘 내부 기준을 먼저 정의해야 합니다.
몬스터 밀도 분포는 어떻게 수치화할까?
월드를 2차원 그리드로 나눈다
월드를 50m × 50m 셀로 분할하고 각 셀에 몬스터의 위협값을 누적합니다.
넓은 지역을 단순히 몬스터 개수로 세면 약한 몬스터 열 마리와 보스 한 마리를 같은 밀도로 처리하게 되므로 전투 위협을 가중해야 합니다.
예시 위협값은 다음처럼 정의할 수 있습니다.
| 몬스터 유형 | w_i 예시 | 설명 |
|---|---|---|
| 일반 몬스터 | 1.0 | 단독 조우의 기본 위협 |
| 정예 몬스터 | 3.0 | 높은 체력이나 특수 패턴 보유 |
| 원거리 지원형 | 1.5 | 직접 피해 외에 전투 난이도 증가 |
| 보스 또는 이벤트 몬스터 | 8.0 | 일반 이동 구간과 분리해 평가 |
셀의 위협 가중 밀도는 다음과 같이 계산합니다.
A_effective는 실제 이동 가능한 면적입니다. 호수, 절벽, 건물 내부처럼 해당 셀에 포함되지만 플레이어가 이동하지 않는 공간은 제외해야 합니다.
개수보다 조우 빈도와 연속 공백을 확인한다
밀도가 낮아도 주요 경로에 몬스터가 배치되어 있으면 체감 조우 빈도는 높을 수 있습니다.
반대로 밀도가 높아도 몬스터가 서로 멀리 떨어져 있으면 플레이어가 거의 만나지 않을 수 있습니다.
그러므로 다음 보조 지표를 함께 사용합니다.
EncounterRate: 이동 1분당 전투 시작 횟수ThreatPerMinute: 이동 1분당 처치 또는 회피해야 하는 위협값HotCellRatio: 기준 밀도의 1.5배를 초과하는 셀의 비율EmptyRun: 주요 경로에서 조우 없이 연속 이동한 거리RepeatEncounterRatio: 같은 종 또는 같은 스폰 그룹과 반복 조우한 비율
몬스터 밀도 분포는 평균보다 편차가 중요합니다.
평균 밀도가 적절해도 초반 길목에 고밀도 셀이 몰리거나 중간 구간이 지나치게 비어 있으면 플레이어 경험은 불안정해집니다.

공간 자기상관으로 몬스터 배치의 쏠림을 찾는 방법은?
인접한 셀의 밀도가 비슷한지 확인하려면 공간 자기상관을 사용할 수 있습니다.
실무에서는 Global Moran’s I나 Local Moran’s I를 적용해 고밀도 군집과 저밀도 군집을 분리합니다.
- 높은 양의 자기상관: 몬스터가 특정 지역에 군집됨
- 0에 가까운 값: 분포가 무작위에 가까움
- 음의 자기상관: 고밀도와 저밀도 셀이 교차하는 패턴
모든 군집이 문제는 아닙니다. 던전 입구, 보스 영역, 채집 지역처럼 의도된 군집도 있기 때문입니다.
따라서 셀에는 GameplayTag나 EncounterZoneType을 붙여 의도된 예외를 자동 평가에서 제외하거나 다른 기준으로 처리합니다.
예를 들어 다음 구역은 서로 다른 기준을 적용해야 합니다.
| 구역 유형 | 이동 시간 평가 | 밀도 평가 |
|---|---|---|
| 안전 거점 주변 | 짧은 이동 선호 | 위협값 상한을 낮게 설정 |
| 일반 탐험 지역 | 목표 시간과 편차 확인 | 균일한 조우 간격 확인 |
| 위험 지역 | 긴 이동 허용 | 높은 밀도와 반복 전투를 별도 평가 |
| 보스 접근로 | 전투 중단율보다 긴장 곡선 확인 | 이벤트 스폰을 일반 몬스터와 분리 |
AI 자동 평가는 어떤 구조로 구현할까?
AI 자동 평가는 세 계층으로 나누는 것이 안정적입니다.
- 수치 계산 계층: SQL, Python pandas 또는 게임 분석 파이프라인으로 지표를 계산합니다.
- 규칙 판정 계층: 구간별 목표와 허용 범위를 비교해 이상 유형을 분류합니다.
- 요약 계층: LLM이 수치와 판정 결과를 바탕으로 디자이너가 읽을 수 있는 리포트를 생성합니다.
LLM에 원시 로그 전체를 직접 전달하기보다 계산된 지표와 대표 샘플만 전달해야 합니다.
그래야 같은 입력에 대해 결과가 안정적이고 판정 근거도 추적할 수 있습니다.
flowchart TD
A[플레이어 텔레메트리 수집] --> B[경로와 이벤트 정규화]
B --> C[그리드별 이동 시간 계산]
B --> D[그리드별 위협 밀도 계산]
C --> E[규칙 기반 이상 판정]
D --> E
E --> F[AI 리포트와 우선순위 생성]
F --> G[디자이너 플레이테스트]
프로젝트 기준값을 먼저 만든다
자동 평가 전에 거점 등급, 플레이어 레벨, 이동 수단, 퀘스트 단계별로 목표값을 정의해야 합니다. 다음은 구현을 시작할 때 사용할 수 있는 예시입니다.
| 기준 | 예시값 | 판정 예시 |
|---|---|---|
핵심 거점 간 T_target | 180초 | T_p50이 135~225초면 정상 |
T_p90 상한 | 360초 | 초과 시 길 찾기 또는 지형 문제 의심 |
일반 지역 EncounterRate | 분당 0.5~1.2회 | 초과 시 전투 과밀 가능성 |
HotCellRatio | 15% 이하 | 초과 시 특정 구간 쏠림 가능성 |
EmptyRun | 250m 이하 | 초과 시 콘텐츠 공백 가능성 |
이 숫자는 보편적인 정답이 아니라 테스트용 초기값입니다.
실제 기준은 플레이테스트에서 이탈률, 회복 아이템 사용량, 경로 변경률, 빠른 이동 사용률과 함께 보정해야 합니다.
TypeScript로 구간 점수를 계산하는 예시
게임 클라이언트에서 직접 계산하기보다 분석 서버나 배치 작업에서 실행하는 편이 좋습니다.
아래 코드는 핵심 판정 로직을 단순화한 예시입니다.
type SegmentMetric = {
segmentId: string;
p50TravelSec: number;
p90TravelSec: number;
interruptRate: number;
encounterRate: number;
hotCellRatio: number;
emptyRunMeters: number;
};
type SegmentTarget = {
targetTravelSec: number;
travelToleranceSec: number;
targetInterruptRate: number;
maxP90TravelSec: number;
minEncounterRate: number;
maxEncounterRate: number;
maxHotCellRatio: number;
maxEmptyRunMeters: number;
};
const clamp = (value: number, min: number, max: number) =>
Math.min(Math.max(value, min), max);
function evaluateSegment(metric: SegmentMetric, target: SegmentTarget) {
const travelScore = clamp(
1 -
Math.abs(metric.p50TravelSec - target.targetTravelSec) /
target.travelToleranceSec,
0,
1,
);
const interruptionScore = clamp(
1 - metric.interruptRate / target.targetInterruptRate,
0,
1,
);
const travelRisk = metric.p90TravelSec > target.maxP90TravelSec;
const encounterRisk =
metric.encounterRate < target.minEncounterRate ||
metric.encounterRate > target.maxEncounterRate;
const densityRisk =
metric.hotCellRatio > target.maxHotCellRatio ||
metric.emptyRunMeters > target.maxEmptyRunMeters;
const score = 0.6 * travelScore + 0.4 * interruptionScore;
return {
segmentId: metric.segmentId,
score: Number(score.toFixed(3)),
labels: [
...(travelRisk ? ["long-tail-travel"] : []),
...(encounterRisk ? ["encounter-rate-outlier"] : []),
...(densityRisk ? ["density-distribution-outlier"] : []),
],
};
}
실제 서비스에서는 구간별 플레이어 표본 수를 함께 반환해야 합니다.
표본이 10명뿐인 구간의 T_p90은 표본이 1,000명인 구간보다 신뢰도가 낮기 때문입니다.
최소 표본 수 미달 시에는 정상 또는 문제로 단정하지 않고 insufficient-data 상태로 분리하는 것이 안전합니다.
문제 구간을 자동으로 찾는 3단계 절차
1단계. 주요 경로를 정의한다
거점 간 모든 경로를 동일하게 평가하지 말고 다음 조건을 만족하는 경로부터 등록합니다.
- 메인 퀘스트가 최소 한 번 통과하는 경로
- 플레이어가 빠른 이동을 해금하기 전 사용하는 경로
- 상점, 제작소, 던전 입구를 연결하는 경로
- 플레이어 사망이나 퀘스트 실패가 자주 발생하는 경로
경로는 HubA-HubB-MainQuest처럼 고유 ID를 부여해 버전별로 추적합니다.
2단계. 이상 유형을 분류한다
AI가 생성하는 문장은 수치 판정 이후에 만들어야 합니다. 먼저 다음과 같은 고정 라벨을 부여합니다.
| 라벨 | 조건 예시 | 디자이너 확인 항목 |
|---|---|---|
too-short | T_p50이 목표보다 크게 낮음 | 탐험 가치와 지역 규모 |
too-long-tail | T_p90만 비정상적으로 높음 | 길 찾기, 지형, 충돌 |
combat-cluster | 특정 셀의 HotCellRatio가 높음 | 스폰 반경과 시야 중첩 |
content-gap | EmptyRun이 기준 초과 | 랜드마크, 채집, 이벤트 |
repetition | RepeatEncounterRatio가 높음 | 몬스터 종류와 스폰 주기 |
insufficient-data | 분석 표본 수 부족 | 추가 플레이 세션 필요 |
3단계. 리포트 우선순위를 정한다
모든 이상을 같은 순서로 수정하면 작업 효율이 떨어집니다. 다음 우선순위 점수를 사용할 수 있습니다.
Severity: 목표값에서 얼마나 벗어났는지PlayerExposure: 해당 구간을 경험한 플레이어 비율Confidence: 표본 수와 데이터 품질을 반영한 신뢰도
예를 들어 고밀도 셀이 존재해도 해당 지역을 거의 방문하지 않는다면 우선순위가 낮을 수 있습니다.
반대로 작은 이상이라도 모든 플레이어가 초반에 경험한다면 먼저 확인해야 합니다.
AI 판정 결과를 디자인 의사결정으로 연결하는 방법
자동 리포트는 다음 형식으로 고정하면 실행 가능한 결과를 얻기 쉽습니다.
구간 ID: Hub_A_to_Hub_B
판정: too-long-tail, combat-cluster
신뢰도: 0.87
근거:
- T_p50 192초로 목표 180초에 근접
- T_p90 428초로 상한 360초 초과
- 3번 셀의 위협 밀도 2.1배
- 해당 구간 이용자의 68%가 3번 셀에서 전투 중단
권장 확인:
- 3번 셀의 스폰 반경과 원거리 몬스터 중첩
- 우회로의 내비게이션 메시 연결 상태
- 메인 퀘스트 단계별 스폰 규칙
여기서 중요한 점은 AI가 자동으로 몬스터를 삭제하거나 거점 위치를 변경하지 않도록 하는 것입니다.
월드 배치와 퀘스트 흐름은 의도된 연출을 포함하므로 자동 시스템은 문제 후보와 근거만 제시하고 최종 변경은 디자이너가 결정해야 합니다.
자주 묻는 질문 FAQ
이동 시간은 평균으로 계산하면 안 되나요?
평균만 사용하면 일부 비정상 경로가 전체 결과를 왜곡할 수 있습니다.
일반적인 경험은 p50, 느린 경험과 길 찾기 문제는 p90으로 나누어 보는 것이 좋습니다.
몬스터 수가 적으면 밀도가 낮은 것인가요?
반드시 그렇지는 않습니다. 보스와 정예 몬스터는 일반 몬스터보다 높은 위협 가중치를 적용해야 하며 실제 이동 가능한 면적과 주요 경로를 함께 고려해야 합니다.
생성형 AI가 원시 로그를 직접 분석해도 되나요?
가능하면 피하는 편이 좋습니다. SQL이나 Python으로 지표와 이상 라벨을 먼저 계산한 뒤 생성형 AI에는 구조화된 결과를 전달해야 재현성과 판정 근거를 확보할 수 있습니다.
정리
오픈월드 거점 간 이동 시간과 몬스터 밀도 분포의 적정성은 하나의 평균값으로 판단할 수 없습니다.
T_p50과 T_p90으로 이동 경험의 중심과 꼬리를 분리하고 50m 그리드의 위협 가중 밀도와 EncounterRate, HotCellRatio, EmptyRun을 함께 분석해야 합니다.
가장 실용적인 자동 평가 구조는 수치 계산, 규칙 기반 라벨링, AI 요약의 3단계입니다.
이 구조를 사용하면 AI가 재미를 대신 판단하는 것이 아니라 플레이테스트에서 먼저 확인해야 할 과밀·공백·반복·길 찾기 문제를 일관된 기준으로 찾아낼 수 있습니다.


