NDC의 "〈카트라이더〉 0.001초 차이의 승부" 강의를 듣고 정리한 글입니다.
https://youtu.be/r4ZaolMQOzE?si=xiOBldRnOnUktjRf
클라이언트가 서버에게 먼저 이벤트를 요청(Server RPC), 서버가 이벤트를 받고 연산 후 클라이언트에게 되돌려준다(Multicast RPC).
미사일 아이템은 다음과 같은 4가지 상태를 갖는다.
공격자가 서버에게 미사일을 발사하도록 요청, 서버가 여러가지 조건을 확인하고 미사일을 발사한다.
무브먼트 컴포넌트에서는 사진처럼 딜레이가 발생할 수 있기 때문에, 이런 방식으로 사용하기가 쉽지 않다.
무브먼트는 인풋을 통해서 캐릭터를 움직일 수 있다. 전진 키를 눌렀을 때 카트가 앞으로 나아가는 식이다. 나아간 결과값을 클라이언트에게 전파해줄 수 있다.
내 입력을 서버에 알리고, 결과값만 받으면 서버에서 나온 결과값만 보게 되니까 모두 같은 화면을 보게 된다. 모두 같은 화면을 보는 것은 굉장한 이점이 될 수 있지만, 그 방법에는 한계점이 있다.
인풋을 서버에서 처리하게 되면 (한국에서 서버에 유저가 들어오는 속도를 보통 20ms로 가정하고 있다) 서버에 핑퐁됐을 시 40ms 정도의 딜레이가 발생할 수 있다. 해외에서 플레이하는 유저라면 10frame 이상의 차이도 발생할 수 있게 된다. 드리프트 등의 세밀한 조작은 1frame 차이로 결과가 크게 다르게 나온다.
따라서 모든 것을 서버에서 처리할 수는 없고, 일부는 클라이언트에서 처리를 해주어야 한다. Client Simulated model은, 클라이언트가 먼저 계산을 한 후에 계산된 결과를 초당 16번 서버에 보낸다. 서버는 그 결과값을 Validation 하여, 값을 검증한다.
P2P의 경우, 클라이언트가 클라이언트에게 시뮬레이트된 결과를 보내면 Validation이 없기 때문에 검증되지 않은 정보를 다른 클라이언트에게 보내게 될 수 있다.
Client Simulated model은 시뮬레이트된 결과를 서버에 보내고, 서버는 검증 후 다른 클라이언트에게 시뮬레이트된 결과를 보내주게 된다.
서버가 처리하는 방식과 클라이언트가 처리하는 방식의 다른점은, 클라이언트가 처리를 하기 때문에 클라이언트가 패킷을 보내기 전에 딜레이가 발생할 수 있다. 대표적인 버그로, 클라이언트 1의 화면에는 1의 카트가 2의 카트보다 빠르다고 되어 있는데, 클라이언트 2의 화면에는 클라이언트 1보다 2의 카트가 빠르다고 보일 수가 있다.
(실제로는 비슷한 위치에 있었음에도 불구하고)
하지만 0.001초로 승부가 갈리는 게임에서, 이렇게 동기화가 이루어지면 안된다.
상하로 클라이언트 1과 2를 나눈 샘플 프로젝트를 준비했다. 동시에 출발함에도, 서로 다른 위치에 존재하는 것처럼 보이는 것을 알 수 있다. 따라서, Extrapolation
이라는 기술을 사용해야 한다.
Extrapolation은 interpolation의 반대말로, 0과 1이 있을 때 2혹은 3과 같은 미래의 값을 유추하는 과정이다.
Extrapolation을 하기 위해서는 4가지 정보가 필요하며, 클라이언트는 서버에게 해당 정보를 보내주게 된다.
동기화를 위해 가장 중요한 것은, 서로의 시간을 맞추는 것이다. 클라이언트 1이 2에게 패킷을 보내는데, 만약 2시에 보냈고 2시 2분에 받았을 때 2분의 차이가 날 것 같지만 둘의 시계가 같다는 보장이 없다. 따라서, 서버의 시간을 받아 서버의 시간으로 비교하는 것이 중요하다.
언리얼 엔진4에서는 GameStateBase 클래스를 통해 서버가 각 클라이언트들에게 ReplicateWorldTimeSeconds를 초당 10frame으로 리플리케이션 해주고, 각자의 시계와 서버의 시계를 비교해 델타를 구하고 서버의 시간을 유추하고 있다.
그렇게 유추된 서버의 시간을 클라이언트 1은 Time Stamp를 찍어서 서버에 보내고, 클라이언트 2는 그 패킷을 받아 클라이언트 1과 2가 시간 차이가 얼마나 나는지 계산한다.
Current는 이전 프레임에 계산된 위치이다. 패킷을 받으면 Rep_Position이라는 패킷의 위치가 나올 것인데, 두 클라이언트의 레이턴시가 200ms라고 한다면 200ms 전의 위치일 것이다. 200ms 후인 현재의 위치를 알고 싶은 것이기 때문에, 패킷으로 받은 Linear Velocity와 아까 계산된 ExtrapolationDeltaSeconds를 곱해 타겟 포지션을 구한다. 타겟 포지션을 완전히 믿을 수 없기 때문에, 포지션 interpolation을 통해 New Location을 유추해내고 있다. 조절 파람의 경우, 카트의 속도가 얼마나 빠르고 정확도가 어느정도인지에 따라 조절해줄 수 있다.
Rotation, Velocity Extrapolation도 마찬가지이다. Position과 동일한 방식으로 유추한다.
이렇게 유추한 결과를 적용하면, 0.2초 전에 보낸 패킷도 같이 달리는 것처럼 볼 수 있게 된다.
언리얼 엔진4의 콘솔 명령어이다. 이걸 사용해 스트레스 테스트 진행이 가능하다.
200ms 정도 레이턴시가 있다면, Extrapolation이 미래를 예측하는 것이기 때문에 오차가 발생할 수 있다.
플레이어가 0.2초 후에 왼쪽 키를 누를지, 오른쪽 키를 누를지 알 수가 없기 때문이다. 혹은 UDP 통신이기 때문에 패킷 손실이 발생하여 Extrapolation이 종종 실패하기도 한다. 실제 값과 예측된 값이 오차가 크거나, 오류가 누적되었을 때 보간 없이 HardSnap을 발생시킨다.(텔레포트) 기존의 연산 값들을 버리고, 새로 연산된 값들을 바로 적용시킨다.
멀리 있는 유저는 더 과거의 모습을 보여주어도 크게 문제가 되지 않기 때문에 예측을 조금 덜 하고, 가까운 유저는 하드 스냅을 더 빈번하게 발생시켜 정확한 위치를 표현해주어야 한다. 카트라이더 내에서 하드 스냅의 조건의 경우, 캐릭터의 속도에 따라 다르게 주어진다.