늦게 도착한 총알은 어디를 겨눌까요? Source의 지연 보상
화면에서 맞았는데 서버에서는 빗나가는 이유
움직이는 상대를 조준해 발사해도 입력이 서버에 도착할 때는 상대가 다른 위치에 있을 수 있습니다. 서버의 현재 위치만 검사하면 플레이어가 본 장면과 명중 판정이 어긋납니다. Valve의 Source SDK에는 이 차이를 줄이기 위해 다른 플레이어의 과거 상태를 잠시 복원하는 지연 보상 코드가 공개돼 있습니다.
여기서는 공식 저장소의 Half-Life 2 멀티플레이어 코드와 공통 서버 구현을 살펴봅니다. 현재 모든 Valve 게임이 같은 방식으로 동작한다는 뜻은 아닙니다.
판정할 때만 과거로 돌아갑니다
서버는 플레이어별로 시뮬레이션 시각, 위치, 각도, 충돌 크기와 애니메이션 상태를 기록합니다. 같은 시각의 기록은 중복 추가하지 않고 오래된 기록은 제거합니다.
발사 함수는 보상 시작, 실제 발사 처리, 보상 종료 순서로 실행됩니다. 아래는 hl2mp_player.cpp의 CHL2MP_Player::FireBullets에서 가져온 호출이며, 가운데 무기 피해량 설정 등은 생략했습니다.
lagcompensation->StartLagCompensation( this, this->GetCurrentCommand() );
// ...
BaseClass::FireBullets( modinfo );
// ...
lagcompensation->FinishLagCompensation( this );
핵심은 게임 전체를 과거부터 다시 실행하는 것이 아닙니다. 명중 검사에 필요한 플레이어 상태를 되돌려 판정한 뒤 복원합니다.
클라이언트가 보낸 시각을 그대로 믿지는 않습니다
player_lagcompensation.cpp의 StartLagCompensation은 네트워크 지연과 보간 시간을 고려하고 보상 시간의 상한을 적용합니다. 입력의 틱에서 보간 틱을 뺀 목표 시각과 서버가 계산한 보정량의 차이가 0.2초를 넘으면 지연 기반 목표 틱으로 바꿉니다. 공개 코드에서 sv_maxunlag의 기본값과 상한은 1초입니다.
BacktrackPlayer는 목표 시각을 사이에 둔 두 기록을 찾으면 위치 등을 보간합니다. 죽은 상태나 순간이동으로 간주되는 큰 위치 변화는 별도 경계로 처리합니다. 종료 단계에도 충돌과 중간 위치 변화를 고려하는 복원 처리가 있습니다.
작은 실험에서는 복원까지 확인합니다
직접 시험하려면 이동하는 표적 하나와 서버 판정용 레이 하나부터 만듭니다. 고정 간격으로 표적의 위치와 시각을 저장하고, 동일한 발사 입력에 인위적인 지연을 더합니다. 현재 좌표로 검사하는 경우와 기록에서 복원한 좌표로 검사하는 경우를 같은 이동 경로에서 비교합니다.
기록할 항목은 발사 시각, 서버 도착 시각, 선택된 과거 시각, 두 방식의 명중 여부입니다. 특히 검사 직후 표적이 현재 위치로 돌아오는지 확인해야 합니다. 오래된 요청, 기록 사이의 시각, 순간이동 직전과 직후도 따로 시험하면 경계를 찾기 쉽습니다.
이 절차는 독자가 수행할 비교 실험 제안이며, 여기서 실행해 얻은 결과는 없습니다. 지연 보상은 통신 지연을 없애지 않습니다. 사수에게 보인 과거 장면을 인정하는 만큼 피격자 화면과의 차이도 남습니다.
출처
- Valve 공식 Source SDK · 발사 처리, hl2mp_player.cpp 640~659행
- Valve 공식 Source SDK · 기록·보정·복원, player_lagcompensation.cpp
- 코드 발췌: © Valve Corporation. 구현 설명에 필요한 호출 부분만 인용했습니다.

댓글
이 글에 대한 생각을 남겨 주세요.