6 분 소요


Game Networking Demystified, Part I: State vs. Input을 정리한 글입니다.

이 포스트는 게임 네트워킹에 대해 소개하는 글입니다.


상태 vs 입력

네트워크 기반 멀티 플레이는 모든 플레이어를 동일한 게임 상태로 유지해야 합니다.
이를 위해 무엇을 보낼 것인가? 에 대한 다음 두 가지의 접근 방식이 있습니다:

  • 상태를 전송하는 방식
  • 입력을 전송하는 방식

이 방식은 게임을 네트워크에서 동작하도록 설계할 때 가장 먼저 결정해야 합니다.


상태 전송

먼저, 게임 상태를 보내는 방법이 있습니다.
전형적인 예를 들어보겠습니다:

  • 플레이어 A권한있는 노드(authoritative node)MoveTo(1, 2) 를 보냅니다.
  • 권한있는 노드가 요청을 수신하고 모든 플레이어에게 A 가 현재 (1, 2)에
    있다고 알립니다.
  • 플레이어 A는 응답을 받고 (1, 2)로 이동합니다.
    다른 플레이어도 모두 동일한 정보를 받고 화면의 플레이어 A를 (1, 2)로 이동합니다.


권한있는 노드는 대부분 서버가 담당하지만, 호스트 라는 동일한 플레이어가
서버 코드를 실행하는 방법도 일반적입니다.
물론 별도로 구성된 실제 서버에서 서버 코드를 실행할 수도 있습니다.
이 경우 서버는 게임의 “헤드리스” 버전을 실행합니다.



입력 전송

다음으로, 게임 입력을 보내는 방법이 있습니다.
예를 들면 다음과 같습니다:

  • 플레이어 A는 모든 플레이어에게 MoveTo(1, 2)를 보냅니다.
  • A는 모든 플레이어로부터 승인을 받고 (1, 2)로 이동합니다.
    다른 모든 플레이어의 화면에서 플레이어 A는 (1, 2)로 이동합니다.


상태 전송과 비교하여 가장 큰 차이점은 A의 게임 상태(위치)를 보내는 대신 A의 입력(이동)을
보내는 것입니다.

상태 전송에서 상태의 계산은 권한있는 노드(호스트 또는 서버)가 수행하고, 다른 모든 플레이어는
그것을 궁극적인 진실로 받아들입니다.

하지만, 입력 전송에서 상태 계산은 모든 플레이어가 각자 수행하기 때문에, 모든 플레이어가
동일한 게임 상태를 갖기 위해서는 게임 로직이 Deterministic 해야 됩니다.
즉, 동일한 입력이 주어지면 동일한 게임 상태가 되어야 합니다.

이를 Deterministic Lockstep 라고 부르기도 하는데, Lockstep 기법과 함께 사용되기도 해서
혼동될 수 있지만, Deterministic 은 단계를 잠그지(lock step) 않아도 됩니다.

Lockstep 은 단계를 잠근다는 의미입니다.


실제로 많은 모바일 게임들은 모바일 네트워크 문제로 단계를 고정하지 않습니다.
락 스텝에 대해서는 나중에 논의하겠습니다.



모호한 서버

입력 전송은 peer-to-peer 모델에서 주로 사용합니다.
최근에는 릴레이 노드를 사용하여 모든 플레이어에 입력을 전달하기도 합니다.
이것을 릴레이 서버라고도 부르지만 서버라는 용어는 모호합니다.
정확하게 말하면, 물리적인 서버가 아닌 권한있는 노드(authoritative node) 를 의미합니다.

혼란스러울 수 있지만 간단한 규칙이 있습니다. 모든 클라이언트 및 서버에 대한 개념은 일단 잊겠습니다.
중요한 것은 통신 모델(peer-to-peer, server-client 또는 릴레이)에 관한 것이 아니라
게임 상태의 동기화입니다.

게임 상태를 동기화하기 위해 전송하는 데이터가 상태인지 입력인지만 구분하면 됩니다.



어떤 방법을 사용할까요?

앞서 언급했듯이 이 질문에 대한 답은 상황에 따라 다릅니다.
다음은 간단한 비교입니다.

  상태 전송 입력 전송
네트워크 트래픽 일반적으로 높음 일반적으로 낮음
재연결 상대적으로 쉽습니다.
플레이어는 권한있는 노드에서 최신 게임 상태를 받으면 됩니다.
상대적으로 어렵습니다.
모든 입력을 저장하려면 릴레이 노드가 필요합니다.
플레이어가 다시 연결되면 모든 입력이 다시 전송되어 플레이어 쪽에서 재생됩니다.
중계하는 데이터 사이즈 크다 작다
치트 상대적으로 어렵다.
권한있는 노드가 물리적 서버라면 더욱 어렵다.
상대적으로 쉽다.
Deterministic 로직 필요하지 않음 필요함
전형적인 장르 MMO RTS




Deterministic

DeterministicLockstep 은 종종 함께 나오지만, 실제로 개념은 다릅니다.
입력만 전송 하려면 게임 로직이 Deterministic 이어야 하지만,
게임 로직이 Deterministric 할 경우에는 입력 전송 동기화 모델만 사용하지는 않습니다.
상태 전송 모델을 사용하는 게임들도 Deterministic 로직을 사용하는 경우가 많습니다.


Deterministic 이란?

게임 로직이 Deterministic 이라는 것은 동일한 입력 시퀀스가 주어지면 모든 머신에서
동일한 게임 상태를 만든다는 의미입니다.

즉, 똑같은 입력으로 게임을 다시 플레이하면 똑같은 게임 결과를 얻을 수 있습니다.
Deterministic 은 일반적으로 네트워킹에 유리하지만, 만들기 어렵습니다.



어려운 Deterministic

Deterministic 게임 로직을 구현하기 위한 일반적인 과제들은 다음과 같습니다.

  • Floating point: 컴퓨터마다 부동 소수점 계산이 다르게 나올 수 있습니다.
    따라서 Deterministic 게임 로직을 위해서는 부동 소수점 연산을 조심해야 합니다.
    하지만 부동 소수점은 게임 엔진, 미들웨어 및 라이브러리의 기본 숫자 타입이기 때문에
    결코 쉬운 일이 아닙니다.
    로직 내에서의 계산에서 고정 소수점 라이브러리를 사용할 수도 있습니다.

  • Deterministic random: 게임 세션이 시작될 때 시드를 맞추면 됩니다.

  • 정렬된 데이터 구조: 예를 들어 대부분의 언어에서 해시(또는 맵 또는 사전) 내의
    아이템 순서는 보장되지 않습니다.
    따라서 해시를 통한 루핑(looping)은 신중하게 검토해야 하며 SortedDictionary 와 같은
    컨테이너를 사용해야 합니다.
    이 문제는 매우 미묘하고 추적하기 어렵습니다.

  • 실행 순서: 많은 게임 엔진의 경우 업데이트 콜백 순서는 Deterministic 하지 않습니다.
    예를 들어 Unity에서는 업데이트 순서가 보장되지 않으므로 게임 로직을 다른 Monobehavior
    분산해야 합니다.
    또 일반적으로 Unity 코루틴은 피해야 합니다.

  • Physics: 대부분의 물리 엔진은 부동 소수점 연산 및 내부 아키텍쳐로 Deterministic 하지
    않기 때문에 주의해서 사용해야 합니다.

  • 로직과 뷰의 분리: 일반적으로 로직이 뷰 레이어에 의존하지 않아야 합니다.




Lockstep

앞에서는 Deterministic 에 대해서 설명했고 이제 이와 유사한 Lockstep 에 대해서 설명하겠습니다.


Lockstep 이란?

Lockstep 이란 모든 클라이언트의 입력을 받은 후에만 게임 로직을 진행되기 때문에,
모든 클라이언트가 동일한 게임 상태를 보고 있다는 의미입니다.

이것은 게임 로직을 턴 기반 게임과 비슷하게 만듭니다.
모든 플레이어가 End Turn 버튼을 눌러 게임이 다음 턴으로 들어가는 턴 기반 게임에서,
각 턴이 1/60 초(또는 현실적으로 1/10 초)동안 지속된다면 이것이 Lockstep 입니다.



LAN의 멀티 플레이어

초기의 멀티 플레이어 게임들은 대부분의 네트워크가 LAN 기반이었기 때문에 비교적 지연 시간이 낮고
안정적이었습니다. 이것은 락 스텝 모델에 적합하여 우리는 N 턴에 수신된 모든 입력이 N+2 턴에
실행되도록 예약할 수 있으며, 네트워크 왕복이 N+1 에서 완료될 것으로 예상할 수 있었습니다.
게임 로직이 100ms 마다 실행된다는 것은 LAN에서 매우 합리적인 기대치입니다.


단점은 한 플레이어의 네트워크가 기한 내에 완료되지 않으면 모든 플레이어의 게임이 일시 중지되고
느린 플레이어를 기다린다는 것입니다.
따라서 모든 플레이어의 쾌적한 플레이는 가장 느린 플레이어에 달려 있습니다.


Lockstep 및 “State 대 Input”

입력 전송 + Lockstep + Deterministic logic 은 매우 인기있는 조합입니다.
하지만, 상태 전송 모델에서도 Lockstep 을 사용할 수 있습니다.

실제로 일부 게임에서 그렇게 하고 있습니다:

권위있는 노드가 모든 입력을 수신하고 처리한 후에만 게임 상태를 전송한다.
이 방법의 장점은 공정성 입니다. 즉, 모든 선수들이 동등한 입장입니다.



지연 시간(Latency)

인터넷에서 Lockstep을 사용할 때 가장 쉬운 방법은 입력 처리 지연을 늘리는 것입니다.
예를 들면, N 턴에 수신된 입력은 N+6 턴에 예약하는 방법입니다.
이 방법으로 게임은 “지연”이 있는 느낌이 들 수 있습니다.
일부 프레젠테이션 레이어 트릭을 통해 숨길 수 있는 몇 가지 방법이 있습니다.


예를 들어 플레이어가 능력을 시전할 때 시전하기 전에 시전 애니메이션을 추가할 수 있습니다.
애니메이션은 즉시 재생되지만 게임 상태를 변경하는 실제 데미지는 나중에 실행됩니다.
네트워크 지연 시간에 따라 지연을 동적으로 조정할 수도 있습니다.
즉, 네트워크가 안좋은 플레이어가 더 일찍 메시지를 보내어 모든 플레이어가 동일한 지연 시간을
받는 것입니다.

실제 게임 상태가 나중에 변경되는 동안 프리젠테이션 레이어(시각 효과, 입자, 애니메이션)를 활용해
플레이어의 행동에 직접 “피드백”을 제공한다는 아이디어입니다.
그러나 게임 장르에 따라 적용하지 못할 수도 있습니다.
또한, 만약 네트워크에 임의의 스파이크가 있다면, 이것은 제대로 동작하지 않습니다.
이것은 모바일 인터넷에서는 일반적입니다.



스파이크

I/O 스파이크란 분산 컴퓨팅 시스템에 영향을 미칠 수 있는 네트워크 수요(Demand)의 극심한 변화입니다.


시각적 트릭은 스파이크에 도움이 되지 않습니다.
Lockstep이 한 플레이어의 스파이크를 모든 플레이어에게 퍼뜨리기 때문입니다.
이것은 특히 모바일 게임에서 문제가 됩니다.
이를 위한 한 가지 해결책은 시간 제한을 설정하는 것 입니다.
앞으로 틱하기 전에 모든 플레이어의 입력을 기다리는 대신 권위있는 노드(또는 권위있는 틱 코디네이터)
설정된 타임 아웃(예: 100ms)에 앞으로 틱합니다.
턴에 대한 플레이어의 입력이 누락된 경우 게임 로직은 기본적으로 이 턴에 대한 입력 없음 으로 처리합니다.
이 경우 랙이 있는 플레이어만 랙을 경험하고 다른 모든 플레이어는 영향을 받지 않습니다.

입력 전송 모델의 틱 코디네이터는 일반적으로 릴레이 서버이고, 상태 전송 모드의 경우는
권한있는 노드 입니다.




네트워킹 동기화 방식

네트워킹을 동기화하기 위해서 주로 다음 두 가지 방법을 사용합니다.

delay 방식

입력 후 바로 시작하지 않고, Delay를 주고 실행하는 방법입니다.

delay 방식.



rollback 방식

입력이 발생한 시점으로 상태를 Rollback 한 후 실행하는 방법입니다.

rollback 방식.




Summary

네트워크 게임 동기화를 위해 2가지의 데이터를 전송할 수 있습니다.

  • 상태 전송
  • 입력 전송


입력 전송 방식이 주고 받는 데이터가 작지만,
클라이언트가 Deterministic 로직을 처리할 수 있어야 합니다.
재연결과 같은 경우에는 마지막 상태 전송
재연결 이후에 진행한 입력 전송이 필요할 수 있습니다.

Deterministic 이란:
Deterministic 로직이란, 입력이 같으면, 동일한 상태가 되는 로직을 의미합니다.
Lockstep이란, 모든 사용자로부터 입력을 받은 후 로직을 진행하여 동일한 상태를 유지하는 방식을 의미합니다.




참고자료

Tucker의 게임 네트워킹의 이해

댓글남기기