Fast-Paced Multiplayer (파트 II) : 클라이언트 측 예측 및 서버 조정

소개

이 시리즈의 첫 번째 기사에서는 서버에 입력을 보내고 서버가 보낼 때 업데이트 된 게임 상태를 렌더링하는 신뢰할 수있는 서버와 바보 클라이언트가 있는 클라이언트 - 서버 모델을 살펴 보았습니다.

이 계획을 간단하게 구현하면 사용자 명령과 화면 변경 간의 지연이 발생합니다. 예를 들어 플레이어가 오른쪽 화살표 키를 누르면 캐릭터가 움직이기 전에 0.5 초가 걸립니다. 이는 클라이언트 입력이 먼저 서버로 이동해야하고 서버가 입력을 처리하고 새 게임 상태를 계산해야하며 업데이트 된 게임 상태가 클라이언트에 다시 도달해야하기 때문입니다.

Effect of network delays.

인터넷과 같이 지연이 10 분의 1 초가 될 수있는 네트워크 환경에서는 게임이 응답이 좋지 않거나 최악의 경우 재생할 수 없게 될 수 있습니다. 이 기사에서는 이러한 문제를 최소화하거나 제거하는 방법을 찾아 볼 것입니다.

클라이언트 측 예측

부정 행위자가 있지만 대부분의 경우 게임 서버는 비 부정한 클라이언트와 그 특정 시간에 부정 행위를하지 않는 속임수 클라이언트로부터 유효한 요청을 처리하고 있습니다. 이것은 수신 된 대부분의 입력이 유효 함을 의미하며 예상대로 게임 상태를 업데이트합니다. 즉, 캐릭터가 (10, 10)에 있고 오른쪽 화살표 키를 누르면 (11, 10)에 끝납니다.

우리는 이것을 우리의 장점으로 사용할 수 있습니다. 게임 세계가 충분히 결정적이라면 (즉, 게임 상태와 입력 집합이 주어지면 결과는 완전히 예측 가능합니다),

우리가 100ms 지연을 가지고 있고 한 사각형에서 다음 사각형으로 움직이는 캐릭터의 애니메이션이 100ms 걸린다 고 가정 해 봅시다. 순진한 구현을 사용하면 전체 동작에 200 밀리 초가 걸립니다.
Network delay + animation.

세계는 결정 론적이기 때문에 우리가 서버에 보내는 입력이 성공적으로 실행될 것이라고 가정 할 수 있습니다. 이 가정 하에서, 클라이언트는 입력이 처리 된 후에 게임 세계의 상태를 예측할 수 있으며, 대부분의 경우 이것이 정확할 것입니다.
Animation plays while the server confirms the action.
서버가 여전히 신뢰할 수 있는 반면 플레이어의 동작과 결과는 절대적으로 지연되지 않습니다 (해킹 된 클라이언트가 잘못된 입력을 보내면 화면에 원하는대로 렌더링 할 수는 있지만 상태에 영향을주지는 않습니다. 다른 플레이어가 보는 것입니다).

동기화 문제

위의 예에서 모든 것을 올바르게 작동하도록 숫자를 신중하게 선택했습니다. 그러나 약간 수정 된 시나리오를 고려하십시오. 서버에 250ms 지연이 있고 정사각형에서 다음 서버로 이동하는 데 100ms가 걸린다고 가정 해 봅시다. 플레이어가 오른쪽 키를 두 번 연속 눌러 2 개의 정사각형을 오른쪽으로 이동하려고합니다.

지금까지의 기술을 사용하면 다음과 같이됩니다.
Predicted state and authoritative state mismatch.
새로운 게임 상태가 도착하면 t = 250ms에서 재미있는 문제가 발생합니다. 클라이언트의 예상 상태는 x = 12이지만 서버는 새 게임 상태가 x = 11이라고 말합니다. 서버가 신뢰할 수 있기 때문에 클라이언트는 문자를 x = 11로 다시 이동해야합니다. 그런 다음 새 서버 상태가 도착합니다 t = 350에서 x = 12라고 말하면 문자가 다시 앞으로 점프합니다.

플레이어의 관점에서 그는 오른쪽 화살표 키를 두 번 눌렀습니다. 캐릭터는 오른쪽으로 두 칸을 옮기고 50ms 동안 그곳에 서서 왼쪽으로 한 칸 스퀘어를 뛰어 올라 100ms 동안 그 자리에 서서 오른쪽으로 한 칸 스퀘어를 뛰어 넘었습니다. 물론 이것은 받아 들일 수 없는 것입니다.

서버 조정

이 문제를 해결하는 열쇠는 클라이언트가 현재 게임 세계를보고 있다는 것을 인식하는 것입니다. 그러나 지연으로 인해 서버에서 가져 오는 업데이트는 실제로 과거의 게임 상태입니다. 서버가 업데이트 된 게임 상태를 보낼 때까지는 클라이언트가 보낸 모든 명령을 처리하지 않았습니다.

하지만 이 문제를 해결하는 것은 그리 어렵지 않습니다. 먼저, 클라이언트는 각 요청에 일련 번호를 추가합니다. 예에서 첫 번째 키 누름은 요청 # 1이고 두 번째 키 누름은 요청 # 2입니다. 그런 다음 서버가 응답하면 처리 된 마지막 입력의 순서 번호가 포함됩니다.
Client-side prediction + server reconciliation.
이제 t = 250에서 서버는 "귀하의 요청 # 1, 귀하의 위치는 x = 11"까지 본 것을 기반으로합니다. 서버가 신뢰할 수 있기 때문에 x = 11에 문자 위치를 설정합니다. 이제 클라이언트가 서버에 보내는 요청의 복사본을 보관한다고 가정 해 봅시다. 새로운 게임 상태에 따라 서버는 이미 요청 # 1을 처리 했으므로 해당 복사본을 삭제할 수 있습니다. 그러나 서버가 여전히 요청 # 2를 처리 한 결과를 되돌려 보내야 한다는 것도 알고 있습니다. 따라서 클라이언트 측 예측을 다시 적용하면 클라이언트는 서버가 보낸 마지막 신뢰할 수 있는 상태와 서버가 아직 처리하지 않은 입력을 기반으로 게임의 "현재"상태를 계산할 수 있습니다.

따라서 t = 250에서 클라이언트는 "x = 11, 마지막으로 처리 된 요청 = # 1"을 얻습니다. 전송 된 입력 사본을 최대 # 1 개까지 버리지 만 서버에 의해 확인되지 않은 # 2 사본을 보유합니다. 서버가 전송 한 내용 (x = 11)으로 내부 게임 상태를 업데이트 한 다음 서버에서 아직 볼 수없는 입력 (이 경우 입력 # 2, "오른쪽으로 이동")을 적용합니다. 최종 결과는 x = 12이며 올바른 값입니다.

예제를 계속 진행하면 t = 350에서 새로운 게임 상태가 서버에서 도착합니다. 이번에는 "x = 12, 마지막으로 처리 된 요청 = 2"라고 표시됩니다. 이 시점에서 클라이언트는 # 2까지의 모든 입력을 버리고 x = 12로 상태를 업데이트합니다. 재생할 입력이 처리되지 않으므로 처리가 올바른 결과와 함께 끝납니다.

잡동사니

위에 논의 된 예는 움직임을 의미하지만, 같은 원리가 거의 모든 것에 적용될 수 있습니다. 예를 들어 턴 기반 전투 게임에서 플레이어가 다른 캐릭터를 공격하면 피와 완료된 피해를 나타내는 숫자를 표시 할 수 있지만 실제로 서버가 말하기 전까지는 캐릭터의 상태를 업데이트해서는 안됩니다.

게임 상태의 복잡성으로 인해 쉽게 되돌릴 수있는 것은 아니기 때문에 클라이언트의 게임 상태에서 상태가 0 이하로 떨어지더라도 서버가 그렇게 말할 때까지 캐릭터를 죽이지 않도록 할 수 있습니다 (다른 캐릭터가 치명적인 공격을 받기 바로 전에 구급 상자가 있지만 서버가 아직 말하지 않았습니까?)

이것은 우리에게 흥미로운 점을 안겨줍니다. 세계가 완전히 결정적이고 클라이언트가 전혀 속임수를 쓰지 않는다고하더라도, 클라이언트가 예측 한 상태와 서버가 보낸 상태가 화해 후에 일치하지 않을 가능성은 여전히 ​​있습니다. 시나리오는 단일 플레이어에서 설명한 것처럼 불가능하지만 여러 플레이어가 한 번에 서버에 연결될 때 쉽게 실행될 수 있습니다. 이것은 다음 기사의 주제가 될 것입니다.

개요

신뢰할 수있는 서버를 사용할 때는 서버가 실제로 입력을 처리 할 때까지 기다리는 동안 플레이어에게 응답 성을 부여해야합니다. 이를 위해 클라이언트는 입력 결과를 시뮬레이션합니다. 업데이트 된 서버 상태가 도착하면 예측 된 클라이언트 상태가 업데이트 된 상태와 클라이언트가 보낸 입력에서 다시 계산되지만 서버가 승인하지 않았습니다.

댓글 없음:

댓글 쓰기