https://docs.coherence.io/learning-coherence/beginners-guide-to-networking
이 문서는 네트워크 게임이 어떻게 동작하는지 기본 컨셉을 설명합니다. 코드 예제나 사소한 디테일을 설명하지는 않습니다. 대신에 이 분야가 익숙하지 않은 사람들에게 네트워크를 고차원적인 관점에서 어떠한 문제가 발생하고 어떻게 해결하는지를 기술합니다. 이 정보는 Coherence SDK를 이해하는데 유용할 것이지만, 다른 네트워크 라이브러리에 적용할 수도 있을 것 입니다.
게임이 로컬머신에서 동작할때 게임을 모델링 하기 위한 많은 데이터가 있습니다. 애니메이션 state, 게임 오브젝트의 위치와 방향, AI 계산, 물리적 힘, 게임 플레이 관련 변수와 같은 것 들이 있습니다. 이러한 모든 데이터들을 통칭하여 state라고 합니다. 로컬에서만 동작하는 게임의 경우에도 상태를 효율적으로 업데이트하는 것은 어려운 문제입니다.
멀티플레이어 게임은 같은 게임 월드에서 함께 플레이하고 있다는 착각을 불러일으키기 위해 다른 플레이어에게 충분한 상태를 전송해야 합니다. 컴퓨터 네트워크는 대역폭이 제한되어 있으므로 전송되는 데이터의 양을 제한하는 것이 반드시 필요합니다.
일반적으로 상태를 동기화하는 방법에는 크게 두 가지가 있는데, input을 전송하거나 업데이트된 데이터 자체를 전송할 수 있습니다. 또한 이러한 접근 방식을 다양한 방식으로 혼합할 수도 있습니다. 이제 각 옵션에 대해 간략하게 설명하겠습니다.
일반적으로 게임 플레이어가 수행할 수 있는 여러 사전 정의된 입력이 있습니다(예: "점프", "실행", "activate"). input이 로컬 게임 state에 적용되면 현재 세션의 다른 모든 플레이어에게 동시에 전송되도록 할 수 있습니다. 각 플레이어가 정확히 동일한 상태에서 게임을 시작하고 모든 플레이어가 다른 플레이어와 정확히 동일한 input을 적용하도록 하면 게임 state가 각 플레이어에게 정확하게 동기화됩니다. 특정 유형의 게임에서는 이렇게 하면 전송해야 하는 많은 데이터를 절약할 수 있습니다.
좋은 예로 수백 개의 유닛이 있는 RTS 게임의 경우, 각 유닛의 위치 대신 마우스 클릭 좌표를 전송하는 것으로 충분할 수 있습니다. 물론 이를 위해서는 완벽하게 결정론적인(deterministic) 게임 로직이 필요하며, 이는 그 자체로 어려운 문제입니다.
또 다른 문제는 input이 조금이라도 불일치하면 플레이어의 로컬 게임 상태가 달라지기 시작한다는 것입니다. 이 접근 방식에 대해 자세히 알아보려면(그리고 몇 가지 문제를 해결하는 방법을 알아보려면) GGPO에 대한 문서를 참조하세요.
input을 전송하는 데 반드시 서버가 필요하지 않으므로 P2P 환경에서 사용하기에 좋은 모델입니다.
두 번째 방법은 업데이트된 데이터 자체를 전송하는 것입니다. 이 방법은 비용이 더 많이 들 수 있습니다(한 플레이어의 행동으로 많은 로컬 데이터가 변경될 수 있으며, 이를 다른 플레이어에게 전송해야 합니다). 이 방식의 장점은 게임 state가 조금 달라져도 보정이 가능 하다는 점 입니다.
이 개념을 일반적으로 eventual consistency 라고 합니다. 초기 state가 없어도 상관없기 때문에, player가 나중에 참여하는것을 허용하거나 게임 월드의 상태를 backup 하는 등의 기능을 더 쉽게 지원할 수 있습니다.
로컬에서 시뮬레이션을 수행한 다음 업데이트된 게임 state를 서버로 전송하는 것은 클라이언트이므로 client-authoritative(클라이언트 주도적)하다고 할 수 있습니다.
세 번째 옵션은 위의 두 가지 방식을 결합한 것으로, 클라이언트가 input을 전송하고 업데이트된 월드 데이터를 수신하는 방식입니다. 이를 위해서는 게임 로직을 실행할 수 있는 Simulator(게임서버)가 필요합니다. Simulator는 서버에서 수행되기 때문에 게임 개발자가 신뢰하는 프로그램으로, 클라이언트가 보낸 입력을 어떻게 적용하는지, 게임 로직을 가지고 있습니다.
이는 server-authoritative(서버 주도적) 방식으로, player는 시뮬레이션을 담당하지 않으며 게임 상태에 직접 영향을 줄 수 없습니다. 이는 여러 가지 의미가 있는데, 예를 들어 사용자 디바이스의 연산 부담을 서버로 이전 할 수 있습니다.
또한 client-authoritative를 방식에 input을 전송하는 결합할 수 있다는 점도 주목할 가치가 있습니다. 예를 들어, player가 게임 state의 덜 중요한 부분은 로컬에서 시뮬레이션하면서, 캐릭터 input은 중앙 서버로 보내 처리하도록 할 수 있습니다.
앞서 언급했듯이 게임에는 많은 데이터가 포함되어 있으며 네트워크를 통해 모든 데이터를 실시간으로 전송하는 것은 불가능합니다. input만 전송하는 것이 데이터 사용량 관점에서 가장 가벼운 선택이지만, 클라이언트에서 서버로 또는 그 반대로 게임 state를 전송해야 하는 경우가 많습니다. 이 두 경우 모두 몇 가지 최적화를 사용해야 합니다. 가장 중요한 것은 다음과 같습니다.
다른 플레이어가 게임 상태에 대해 알고 있는 정보를 추적하면 많은 데이터 전송을 피할 수 있는 경우가 많습니다. 예를 들어, 한 플레이어가 게임 오브젝트를 바닥에 떨어뜨리고 다른 참가자에게 새로운 위치를 전송할 수 있습니다. 만일 오브젝트가 움직이지 않는다면, 같은 위치를 계속 반복해서 전송할 필요는 없습니다. 이 간단한 아이디어는 Coherence 다른 유사한 네트워크 솔루션에서 광범위하게 사용되어 큰 효과를 발휘합니다.
게임에서는 짧은 시간 내에 많은 변화가 발생한다는 점을 인식하는 것이 중요합니다. 이러한 상황에서는 해당 변경 사항이 게임에서 얼마나 중요한지에 따라 우선순위를 정하고, 전송되지 않은 시간도 고려하는 것이 유용합니다. 즉, 전송되지 않은 '오래된' 변경 사항은 다른 변경 사항과 비교하여 중요도와 상대적 우선순위가 높아져 결국 전송될 수 있습니다.
마지막으로, 데이터 사용을 제한하는 주요 방법은 관심 영역이라고도 하는 각 클라이언트의 필요에 따라 관심 없는 정보를 제외하고 중요한 부분만 전송하는 것입니다. 가장 일반적으로는 위치 기반 쿼리의 형태를 취합니다. 이 쿼리는 특정 플레이어가 주변에 있는 오브젝트의 업데이트만 받도록 합니다. 멀리 떨어져 있는 것은 무시되며, 데이터를 전송할 필요가 없습니다. 근거리에 있는 객체는 전체 데이터, 원거리에 있는 객체는 부분적인 데이터 전송처럼 거리에 따라 차별적으로 전송할 수도 있습니다. 이러한 기법에 대해 자세히 알아보려면 Coherence 의 쿼리 및 Level of detail 문서를 참조하세요.
게임에는 많은 사용자가 있을 수 있으며, 이전 섹션에서 언급한 최적화를 용이하게 하려면 각 참가자가 게임 상태에 대해 알고 있는 정보(그리고 알고 싶어하는 정보)를 추적할 필요가 있습니다. 각 게임 클라이언트에 이러한 부담을 주는 대신 중앙 서버에서 이 부분을 처리하는 것이 좋습니다. Coherence에서는 이를 Replication Server 라고 합니다.
input만 전송하는 방식을 사용하는 경우에도 수신된 입력을 처리하고 게임 상태에 적용하며 새로운 게임 상태를 각 클라이언트에 전송하는 중앙 중재자도 있어야 합니다. Coherence에서는 게임 시뮬레이션(게임 관련 로직이 필요)은 Replication server와 연결된 Simulator가 처리합니다.
다양한 작업을 서로 다른 컴퓨터 또는 서로 다른 물리적 위치에서 서로 다른 프로그램에 의해 수행하는 이러한 분업화된 접근 방식은 사용자가 많은 게임의 확장에 도움이 될 수 있습니다.
온라인에서 다른 사람과 컴퓨터 게임을 하는 대부분의 사람들은 각 플레이어에게 동등한 조건으로 공정하게 게임을 하기를 원합니다.
클라이언트가 게임 상태의 업데이트를 바로 서버로 전송하는 client-authoritative한 게임의 경우 이러한 업데이트의 유효성을 검증 할 수 없으므로 문제가 됩니다. 능숙한 플레이어라면 클라이언트를 변형하여 게임 개발자가 설정한 특정 제한을 제거하는 것이 충분히 가능합니다.
예를 들어 게임 클라이언트가 적의 체력을 0으로 설정하는 업데이트를 보낼 수 있습니다. 이러한 노골적인 부정 행위를 방지하기 위해 권한 (흔히 '소유권'이라고도 함)이라는 개념을 도입하는 것이 유용합니다. 즉, Replication server는 게임 내 각 엔티티를 업데이트할 권한이 있는 클라이언트를 추적합니다. 권한이 없는 업데이트가 서버로 전송되면 거부되며 다른 참가자에게는 전송되지 않습니다.
클라이언트에서 input만 전송하는 게임의 경우 부정 행위 문제는 약간 다릅니다. input은 올바른 상황에서 적용되어야 하기 때문에 단순히 게임 상태를 불법적인 값으로 설정하는 것이 훨씬 더 어렵게됩니다. Authority가 플레이어가 권한이 없는 게임 객체에 대한 입력을 보내지 못하도록 하는 것입니다.
많은 경우 권한 이전을 허용하는 것이 유용합니다. 예를 들어 게임에서 마실 수 있는 마법의 물약이 있을 수 있습니다. 플레이어가 물약에 대한 권한을 가지고 있다면 물약을 옮기거나 마실 수 있고, 물약을 다시 채울 수도 있습니다. 그런 다음 다른 플레이어에게 물약을 주면 그 플레이어가 물약에 대한 권한을 갖게 되고 원래 플레이어는 더 이상 물약을 업데이트할 수 없게 됩니다.
클라이언트가 업데이트하는 것을 신뢰하지 않거나 잠재적으로 무거운 연산이 클라이언트에서 실행되는 것을 원하지 않는 특정 게임 객체의 경우 해당 객체에 대한 권한을 갖고 업데이트하는 전용 머신을 사용할 수도 있습니다( Simulator 참조).
네트워크를 통해 데이터를 전송하는 방법에는 여러 가지가 있습니다. 이를 프로토콜이라고 합니다. 속도가 가장 중요한 요소가 아닐 때는 TCP가 자주 사용됩니다. TCP에는 올바른 정보가 전송되었는지 확인하는 메커니즘이 있으며, 정보가 수신자에게 전송되는 도중에 손실된 경우 재전송을 합니다.
이 방식은 초당 수십번씩 시뮬레이션이 수행되고 빠르게 진행되는 게임에서는 잘 작동하지 않습니다. 손실된 패킷이 재전송되어 최종 주소에 도달했을 때는 이미 정보가 오래되었을 가능성이 높습니다.
따라서 게임에서는 TCP 대신 UDP를 사용하는 경우가 많습니다. UDP는 설계상 신뢰할 수 없지만 Coherence에서는 그 위에 신뢰성 계층을 추가합니다. 업데이트가 수신자에게 전달되지 않은 것으로 확인되면 해당 업데이트는 다시 전송되지만, 데이터에 대한 최근 변경 사항이 있는지 확인한 후에만 다시 전송됩니다. 이렇게 하면 각 플레이어가 게임 state에 대해 일관적으로 최신 정보를 얻을 가능성이 높아집니다.
한 컴퓨터에서 다른 컴퓨터로 데이터를 전송하는 데는 시간이 걸리며, 이를 피할 수 있는 방법은 없습니다. 네트워크 게임 프로그래머는 이 사실을 받아들이고 게임 로직에 대해 생각하는 방식을 바꿔야 합니다. 싱글플레이어 게임을 프로그래밍할 때(특히 단일 프로세서 스레드에서만 실행되는 경우) 게임 상태의 변경이 즉각적으로 발생한다고 가정할 수 있습니다. 하지만 네트워크 게임에서는 그렇지 않습니다.
즉, 네트워크 게임의 각 플레이어는 각자의 '평행 우주'에서 플레이하며 멀리 떨어져 있는 서로에게 영향을 미칩니다. 권한이 없는 데이터에 대한 업데이트는 불규칙하고 예측할 수 없는 방식으로 나타납니다. 따라서 순서를 벗어난 업데이트 및 기타 예기치 않은 상황을 수정하는 방어적 코딩 스타일을 사용하는 것이 좋습니다.
이러한 코딩 기법(이미 Coherence에 내장되어 있음)의 한 가지 예로 interpolation(보정)을 들 수 있습니다. interpolation은 다양한 알고리즘을 사용하여 이전 값을 기반으로 현재 값이 어떻게 될지 예측합니다. 이렇게 하면 시간이 지남에 따라 값이 부드럽게 보정되어 interpolation을 사용하지 않는 것보다 더 좋아 보이는 경우가 많습니다. 가장 좋은 예는 아마도 위치 보정일 것입니다. 물체가 특정 속도로 직선으로 움직이고 있었는데, 새 위치가 업데이트되지 않는다면 물체가 멈추지 않고 계속 움직일 것이라고 가정하는 것이 좋습니다.
이 글의 개념이 생소했다면, 이제는 네트워크 게임에 대해 좀 더 자신감을 가지셨기를 바랍니다. 네트워크은 때때로 까다로울 수 있지만, 제대로 작동하면 엄청나게 멋지고 재미있기도 합니다. Coherence 통해 여러분도 곧 그 지점에 도달할 수 있기를 바랍니다! Coherence 튜토리얼부터 시작해보시는 건 어떨까요?