서버 주도 이동으로 전환
OpenMMO의 이동 처리를 서버 중심으로 통합했습니다. 클릭·키보드·탑승 이동과 계단, 가구 상호작용에서 캐릭터의 위치·높이·층을 서버가 결정합니다. 클라이언트는 입력을 보내고 승인된 이동을 표시합니다.
기존 구조와 변경 범위
기존에는 클라이언트가 경로를 계산하고 캐릭터를 움직이는 동안 서버도 별도의 이동 큐를 처리했습니다. 프레임 정지, 경로 교체, 충돌 판단 차이로 양쪽의 진행 상태가 어긋날 수 있었고, 이를 맞추기 위한 경로 재연결과 위치 보정이 필요했습니다. 키보드 이동에서는 큐 교체가 가속 상태에도 영향을 주었습니다.
이번 전환에서는 브라우저의 로컬 길찾기와 이동 계산, 키보드 위치 전송을 제거했습니다. 서버의 기존 이동 큐, 속도 재검증, 경로 재연결, 위치 재동기화 메시지와 확인 응답도 새 이동 처리로 대체했습니다.
AI 캐릭터를 구동하는 agent-client에서도 예측 위치와 이동 타이머를 제거했습니다. 서버의 승인과 진행 응답으로 위치를 갱신하며, 목적지 선택과 문 열기 등의 행동은 계속 담당합니다.
이동 요청과 갱신
클릭 이동 요청에는 목적지의 XZ 좌표와 요청 번호를 보냅니다. 서버는 현재 위치에서 경로를 구하고, 위치·높이·층·속도·서버 시각을 포함한 응답을 보냅니다. 클라이언트는 승인된 경로를 받은 뒤 이동을 표시합니다.
| 처리 | 클라이언트 | 서버 |
|---|---|---|
| 클릭 이동 | 목적지와 요청 번호 전송 | 현재 위치에서 길찾기, 경로 승인 |
| 키보드 이동 | 방향·전후진·회전·달리기 입력 전송 | 가속·속도·회전·충돌 계산 |
| 높이와 층 | 승인된 상태 표시 | 지형·계단·다리의 연결에 따라 결정 |
| 전투와 상호작용 | 행동 요청과 애니메이션 표시 | 판정 시점의 위치로 거리와 상태 확인 |
서버의 이동 갱신은 200ms 간격입니다. 기본 달리기 속도는 4.5m/s이므로 갱신 사이에 약 0.9m를 이동합니다. 충돌 격자 한 칸이 1m이니, 달릴 때 대략 한 칸을 이동할 때마다 갱신하는 셈입니다. 클라이언트는 그 사이의 움직임을 프레임마다 표시합니다.
첫 경로 응답은 200ms 갱신 주기를 기다리지 않습니다. 입력을 받으면 빈도와 부하 제한 안에서 탐색하고, 결과를 검증한 뒤 바로 보냅니다. 이후 위치는 승인 시점부터 실제로 경과한 시간에 따라 계산합니다.
공격이나 상호작용 요청이 이동 갱신 사이에 들어오면, 해당 캐릭터의 위치를 요청 처리 시점까지 갱신한 뒤 거리와 상태를 판정합니다.
요청 번호와 목표 교체
요청 번호(request_id)는 클라이언트가 관리하는 32비트 부호 없는 정수(u32)입니다. 새 목적지·방향 변경·정지 요청마다 1, 2, 3, …처럼 하나씩 늘립니다. 서버는 응답에도 같은 번호를 넣습니다.
예를 들어 A, B, C로 이동하는 요청에 각각 1, 2, 3번을 붙이면, C로 목표를 바꾼 뒤 도착한 1번 응답이 이전 입력의 결과임을 구분할 수 있습니다. 서버는 오래된 탐색 결과를 적용하지 않고, 클라이언트도 현재 요청과 맞지 않는 응답을 무시합니다. 정지 요청은 대기 목표와 이전 탐색 결과를 무효화합니다.
같은 키보드 방향을 유지하는 갱신에는 기존 번호를 재사용합니다. 번호가 최댓값에 도달하면 다음에는 0으로 순환하며, 서버도 이 순환을 고려해 새 요청인지 비교합니다.
캐릭터마다 현재 이동 계획, 실행 중인 탐색 하나, 최신 대기 목표 하나를 관리합니다. 새 목표는 기존 계획을 대체하고, 대기 목표도 최신 것으로 교체합니다.
목적지에 도달할 수 없지만 부분 경로가 있으면 그 끝까지 이동합니다. 이때는 원래 목표에 도착한 것으로 처리하지 않습니다. 승인 후 문이 닫히거나 장애물이 생기면 실제 이동 구간을 다시 검사해 멈춥니다.
높이·층·가구 처리
일반 이동 요청에는 클라이언트가 계산한 높이나 목표 층을 넣지 않습니다. 서버가 현재 이동면과 연결된 계단을 기준으로 목적지를 해석합니다. 계단 높이는 진행 위치에 따라 계산하고, 층은 전환 지점을 통과할 때 바꿉니다. 다리 위와 아래도 현재 이동면을 기준으로 구분합니다.
브라우저의 별도 던전 층 판정은 제거했습니다. 집 내부 표시에도 서버 경로의 절대 높이를 사용해 계단 중간이나 층 연결 지점에서 멈췄을 때 표시가 유지되도록 했습니다.
의자와 침대의 진입·이탈 위치도 서버가 계산합니다. 가구 ID로 배치를 조회하고 거리·층·점유·장애물을 확인한 뒤 위치와 회전을 적용합니다. 일어날 때는 주변의 이동 가능한 위치와 높이를 선택합니다. 브라우저는 승인된 위치에 맞춰 애니메이션을 표시합니다.
문을 닫기 전에는 주변 캐릭터의 이동을 현재 시각까지 반영하고 몸이 문에 걸리는지 확인합니다. 문이 닫힌 뒤의 이동에는 변경된 충돌 상태를 적용합니다.
키보드 이동
첫 입력과 입력 변경은 즉시 보내고, 같은 입력은 1초마다 유지 여부를 알립니다. 서버는 같은 요청의 갱신을 받으면 유효 시간만 연장하며, 3초 동안 갱신되지 않은 입력은 만료시킵니다.
서버는 가속·회전·충돌을 계산해 앞으로 이동할 짧은 구간을 보냅니다. 클라이언트는 갱신된 경로를 서버 시각의 차이에 따라 같은 재생 시간축에 연결합니다. 패킷 수신 간격이 달라질 때마다 재생 기준이 바뀌어 움직임이 끊기는 현상을 줄이기 위한 처리입니다.
키를 놓으면 정지 요청을 보내고, 응답을 받으면 서버가 확정한 정지 위치로 화면 표시를 짧게 보간합니다. 대기 중에도 승인된 구간을 넘어서 이동하지 않습니다. 늦은 정지 응답이 이미 시작한 공격 애니메이션을 중단하는 문제도 수정했습니다.
경로 탐색과 동기화
실제 맵 데이터를 사용한 로컬 경로 탐색 시험에서는 작업자 4개로 짧은 경로의 혼합 요청을 초당 5,000건 처리했습니다. 여러 던전 층을 관통하는 긴 경로에서는 같은 작업자 수로 대기열이 포화됐습니다.
현재 탐색 제한은 동시 실행 4개, 대기 128개, 대기 기한 100ms입니다. 캐릭터별 후속 탐색의 시작 간격도 제한하고 최신 목표만 남깁니다.
초기 서버 주도 이동 구현에서는 전체 이동 상태를 보호하는 락 때문에 높이 데이터 읽기와 이동 구간 계산이 다른 캐릭터의 입력을 지연시켰습니다. 클라이언트에 보낼 앞으로의 이동 구간 계산을 락 밖으로 옮기고, 전송 전에 입력·위치·지형 상태가 여전히 유효한지 확인하도록 바꿨습니다.
이동 상태의 락은 캐릭터별로 나눴습니다. 각 캐릭터의 이동·텔레포트·가구 상호작용은 같은 캐릭터 락으로 순서를 맞춥니다. 문과 가구처럼 주변 이동에도 영향을 주는 처리는 지역 락을 함께 사용합니다.
| 공간 | 지역 락의 단위 |
|---|---|
| 지상 | 128m × 128m 구역 |
| 던전 | 입구 ID로 구분한 던전 하나의 모든 층 |
지상 구역을 나누면 문 변경 때문에 기다리는 이동의 범위를 제한할 수 있습니다. 구역이 크면 대기 범위가 넓어지고, 작으면 이동할 때 확인할 경계와 획득할 락이 늘어납니다. 현재는 128m × 128m를 관리 단위로 사용합니다.
던전의 모든 층을 묶으면 계단으로 층을 오가는 이동과 문 변경을 같은 락으로 보호할 수 있어 층 사이의 동기화가 단순해집니다. 입구 연결을 보호하기 위해 던전의 XZ 범위와 겹치는 지상 이동도 해당 던전 락을 함께 사용합니다. 이 구성에서는 한 층의 문 변경이 다른 층의 이동도 잠시 기다리게 할 수 있습니다.
일반 이동은 지역의 읽기 락을 사용하므로 같은 지역에서도 동시에 진행할 수 있습니다. 문 상태 변경과 가구 점유는 쓰기 락을 사용해 관련 이동과 순서를 맞춥니다. 구역 경계를 넘는 이동은 몸 반경을 포함한 경로 범위의 구역들을 함께 잠그며, 교착을 피하도록 항상 정해진 순서로 락을 획득합니다.
측정 결과
로컬에서 5,000명의 이동을 처리하면서 별도 캐릭터의 입력 처리 시간을 측정했습니다. Ryzen 9 9950X3D에서 서버 release 빌드를 사용했고, 아래는 높이 캐시가 준비된 키보드 보행 조건의 결과입니다.
각 시험을 세 번 실행해 얻은 p95 값의 중앙값을 비교했습니다. p95는 측정값의 95%가 그 값 이하였다는 뜻입니다.
| 항목 | 전역 락 사용 | 캐릭터·지역별 분리 후 |
|---|---|---|
| 입력 유지 갱신 처리 | 55.80ms | 1.33ms |
| 방향 변경 처리 | 68.11ms | 1.60ms |
| 정지 처리 | 67.68ms | 1.50ms |
| 전체 이동 틱 처리 | 68.27ms | 77.20ms |
입력 처리 지연은 줄었고 전체 이동 틱 시간은 늘었습니다. 캐릭터별 락과 지역 확인 비용이 추가됐으며, 측정값에는 모든 락 획득·해제를 기록하는 테스트 계측 비용도 포함됩니다.
이 시험에는 실제 네트워크 송신, 주변 플레이어에게 보내는 전체 전파 비용, 전투·AI 등이 포함되지 않았습니다. 실제 동시 접속 5,000명의 처리 성능이나 입력부터 화면 반응까지의 지연을 측정한 결과는 아닙니다.
검증과 남은 확인
탐색 중 목표 교체와 정지, 승인 후 문 닫힘, 계단에서의 정지·반전, 지형 높이 변경을 회귀 테스트로 확인했습니다. 클라이언트에는 프레임 정지와 응답 지연·단절을 넣어 확인했습니다.
서버에서는 이동 계산 중 발생한 회복이나 피해를 덮어쓰지 않도록 위치·회전·층만 반영합니다. 동시 상태 변경이 유지되는지, 느린 캐릭터의 처리가 같은 지역의 다른 캐릭터를 막지 않는지도 테스트했습니다.
현재는 서버 승인 후 출발하므로 새 입력의 반응에 통신 왕복 시간이 영향을 줍니다. 승인 전 출발하는 클라이언트 예측은 구현하지 않았습니다. 실제 브라우저 조작과 한국·미국 회선에서의 반응을 추가로 확인하고, 필요한 경우 예측 출발과 보정을 검토할 예정입니다.
설계와 측정 조건은 서버 주도 플레이어 이동 문서에 있습니다. 초기 설계 논의와 이전 측정 결과도 포함합니다.