[UE5] Lag Compensation, Server Side Rewind

kkado·2024년 7월 26일
0

UE5

목록 보기
49/61

Lag Compensation의 필요성

멀티플레이어 환경의 게임은 필연적으로 지연 시간이 발생한다. 클라이언트에서 서버로 요청을 보내는 시간, 서버가 그 요청을 처리하고 다시 클라이언트에게 응답을 보내는 시간 등은 반드시 발생하기 마련이다.

그렇기에 멀티플레이어 게임은 이 지연 시간을 핸들링할 수 있는 방안을 필수적으로 구현해야 한다. 그렇지 않으면 높은 지연 시간을 가진 플레이어의 플레이 경험이 극도로 나빠질 것이다. 이 지연 문제는 특히 액션 게임에서 더더욱 그 해결책의 필요성이 대두되는데, 캐릭터가 실시간으로 매우 바쁘게 움직이며 또 매우 짧은 시간에 상대를 포착하고 공격을 가하기 때문이다.

게임의 지연 시간을 일정 부분 해결할 수 있는 방안에는 Client Side Prediction이 있고, Server Side Rewind가 있다.

예시

예를 들어, 한 클라이언트가 저격총으로 움직이는 상대방을 조준하고 쏘는 상황을 가정해 볼 수 있다.

. . . ㅇ . . . .

대충 이렇게 생긴 일자형 길이 있다고 하고, O이 위치한 지점을 플레이어가 저격총으로 저격 대기하고 있다고 하자.

이제 상대 캐릭터가 움직인다. 이 움직임은 다음과 같은 과정으로 일어난다.

  • 상대 플레이어가 이동 명령을 서버에게 요청함
  • 이동 요청을 받은 서버는 '실제로' 플레이어를 이동시킴 (이 때 '실제로' 라고 함은 실제 게임 플레이 환경을 의미함. 실질적 게임은 오직 서버에서만 진행되고 이것이 곧 authorized된 상태)
  • 플레이어의 이동한 새 위치 정보가 모든 클라이언트에 복제되며, 곧이어 모든 클라이언트의 화면에서도 그 플레이어 캐릭터가 이동한 것으로 보임

여기서 두 번의 서버-클라이언트 통신이 일어난다. 먼저 상대 플레이어가 서버에 내리는 이동 요청, 그리고 이 이동 요청을 처리한 결과로 새로운 위치 정보가 복제 되는 과정.

그렇다면, 서버-클라이언트 사이의 단방향 통신에 발생하는 시간을 Single Trip Time (이하 STT) 라고 하고, 이를 예컨대 50ms라고 하면,

상대 캐릭터가 게임상에서 실제로 움직여지는 것은 상대 플레이어가 키를 누른지 50ms 이후이고,
상대 플레이어와 나를 비롯한 모든 플레이어가 '저 캐릭터의 움직임' 을 보는 것은 게임상에서 실제로 움직인지 50ms 이후이다.

즉, 내가 저격총으로 쪼고 있는 지점에 상대 캐릭터가 보이는 시점에 실제 게임에서는 그 캐릭터는 그 지점을 이미 지나쳤다. 정확히는 50ms 전에 그 위치에 있었다. 그리고 내가 이 플레이어를 인식하고 총을 발사하면, 그 발사 입력은 또다시 50ms 뒤에 서버에 도착하게 된다. 즉 총알이 도착했을 시점, 캐릭터는 이미 100ms만큼 앞선 위치에 있다.

그렇다면 지연 시간이 발생하는 환경에서는, 상대방의 이동을 예측해서 리드샷을 쏘지 않는 한 이동하고 있는 상대방 캐릭터를 절대 맞출 수 없다. 내가 보고 있는 상대방 캐릭터의 위치는 이미 과거의 위치이기 때문이다.

이론

따라서 서버에서는 공격 요청이 들어오면 '시간을 뒤로 되감아서 처리' 할 필요가 있다.

나 : 서버님아 저 이 시간에 상대방 맞췄어요.
서버 : 진짜 그런지 한번 확인해 볼게. 기달

즉 클라이언트는 요청을 보내는 시점의 현재 시간을 기록해서 요청을 날린다. 서버는 이를 받아서 '그 캐릭터가 이 시간에 그 지점에 있었는지' 를 확인한 후, 만약 맞다면 공격이 성공한 것으로 처리할 수 있다. 이 과정을 서버가 시간을 되감아 처리한다는 뜻으로 Server side rewind이라고 한다.

그렇다면 서버는 어떤 식으로 시간을 되감을까.

리그오브레전드의 '에코' 라는 캐릭터는 자신을 따라다니는, 즉 자신의 과거 발자취인 잔상을 얻게 되고, 스킬을 사용하면 이 지점으로 이동할 수 있다. 이 매커니즘과 정확히 일치한다. 서버는 일정 시간만큼의 직전 위치 정보를 계속해서 저장하고 있다. 물론 매 ms마다의 정보를 모두 가질 수는 없을 것이고 게임의 틱(프레임)마다, 60프레임이라고 하면 약 0.016초(16ms)마다의 위치 정보를 기록하고 있을 것이다.

이 위치 정보는 게임플레이 내내 끊임없이 삭제되고 추가된다. 즉 앞뒤로 모두 삽입/삭제 연산을 수행할 수 있는 덱 또는 double linked list 형태의 자료구조에 저장하는 것이 좋을 것이다.

만약 서버가 16ms마다 이동 정보를 기록하고 있다고 가정하면, 현재 시간이 200ms라고 했을 때 184ms, 168ms, 152ms... 이런 식으로 과거 데이터를 쭉 가지고 있을 것인데, 클라이언트가 꼭 이 ms에 딱 맞게 요청을 보낼 것이라는 보장은 없다. 사실 대부분의 요청이 그렇다. 즉 190ms, 160ms 이런 시간에도 요청을 보낼 수 있을 것이다. 만약 190ms 시점에 공격이 가해졌다고 요청이 들어온다면, 가장 근접한 앞뒤의 위치 정보를 가지고 보간을 거침으로써 그 위치를 산출할 수 있다. 가령 184ms와 200ms 두 시점의 위치 정보를 6/16만큼 보간하면 190ms 시점의 위치 정보를 대략적으로 (등속 운동하고 있을 경우엔 정확하게) 알아낼 수 있을 것이다.

단점

그러나, Server Side Rewind에는 부작용 또는 단점이 있다. 바로 상대방 유저로 하여금 불합리함을 느끼게 할 수 있다는 것이다.

공격을 당하는 상대방 유저의 입장에서 생각해 보자. 이 유저의 컨트롤 정보는 실제 서버보다 한 STT만큼 앞서있다. 왜냐 하면 그 컨트롤 정보가 서버에 도달하기까지 1 STT가 소요되기 때문이다. 그리고 2 STT 후에 이 이동 정보가 상대방 유저 및 다른 모든 유저들에게 보이게 된다. 그리고 이 이동을 시작하는 지점을 A 지점이라고 하자.

그리고 공격은 A + 2STT에 정확히 발생하며, 서버에 A + 3STT에 도달하여, 그 유효한 타격은 A + 4STT에 전체 클라이언트에 전파된다. 이 때 플레이어는 공격에 대한 타격을 받게 된다. (그리고 이 순간에도 지속적으로 캐릭터는 움직이고 있다)

만약 이 캐릭터가 계속해서 움직이고 있었다면, 2STT만큼 더 멀리 이동했음에도 그 공격을 맞는 것처럼 보이게 된다.
이 캐릭터가 그 사이에 장애물에 엄폐했거나 코너를 돌았다면? 장애물에 엄폐하고 나서 공격을 당하는 것처럼 보일 것이다. 만약 렉이 더 심한 환경이라면 이 체감은 더 커질 수 있다.

즉, 너무 지연 시간이 긴 환경에서는 부작용이 발생하기 때문에 Server Side Rewind는 일정 수준까지의 렉만을 커버하는 것이 바람직하다.

profile
울면안돼 쫄면안돼 냉면됩니다

0개의 댓글