들어가며
이번 포스트에서는 네트워크 게임에서 주로 언급 되는 '동기화'라는 개념에 대해서 알아 보도록 하겠다.
게임에서의 동기화에 대해 이야기하기 위해 먼저 '동기화(syncronization)'에 정의해 보도록 하자. 교과서에서 나올 법한 말로 정의하자면 '동기화 그룹 내 개체들의 상태 변화와 이벤트 발생 순서를 모든 개체들이 동일하게 인지 할수 있게 하는 작업' 정도로 정의할 수 있다. 그리고 일반적으로 게임에서는 '실시간'이라는 조건이 추가된다. 실시간이라 하면 최대 지연 시간(transmission delay)이 일정 시간 이하로 제한 된다는 것을 의미한다.
동기화 그룹 내 개체들의 상태 변화와 이벤트 발생 순서를 모든 개체들이 동일하게 인지 할수 있게 하는 작업
오늘 포스트를 통해 생각해 볼 것들은 게임에서 클라이언트간 동기화를 하려면 무엇을 해야 할까이다. 이야기에 앞서 먼저 상황을 가정해 보도록하자.
- 하나의 던전에 A, B, C 세개의 클라이언트가 접속 해있다. 여기서 '던전'이 동기화 그룹이다.
- 경과 시간 0ms 에 클라이언트 A가 좌표 (0, 0)에서 (0, 1)로 초속 1로이동한다.
- 경과 시간 2ms 에 클라이언트 B가 몬스터1에게 공격을 한다. 하지만 클라언트 B의 네트워크 속도는 매우 느리다.
- 경과 시간 3 ms 에 클라이언트 C가 몬스터1에게 공격을 한다. 클라이언 C는 좋은 네트워크 환경에 있다.
시간 0 ms 부터 3 ms사이에 위와 같은 이벤트들이 발생 했다. 이 상황을 가지고 동기화에 대해서 하나씩 살펴 보자.
위치 동기화
- 경과 시간 0ms 에 클라이언트 A가 좌표 (0, 0)에서 (0, 1)로 초속 1로이동한다.
클라이언트들의 서로간의 위치를 동기화 시켜주기 위해서 가장 간단한 방법은 매 프레임 마다 모든 클라이언트 들에게 현재 위치를 공유하면 된다(현실적으로 매 프레임 마다 동기화 패킷을 보낸다는 것은 현실적으로 거의 불가능 하므로 일정 시간 또는 일정 프레임 마다 동기화 패킷을 전송하는 방법을 주로 사용한다).
하지만 메시지가 네트워크를 통해 다른클라이언트들에게 전달 되는 동안 저송 지연(transmission delay)은 피할 수 없는 물리적 한계이므로, 현재 도착한 위치 정보는 네트워크 지연 만큼의 전 상태다. 그래서 지연 시간을 고려해서 얼마만큼의 상태가 변했는지 예측해서 표시해 줘야 할 필요가 있다. 이것을 '예측'이라고 한다. 예측을 하기 위해서는 네트워크 지연 시간을 알아야 하며 현재 위치와 속도, 앞으로 변하게 될 가속도를 서로 동일한 방식으로 계산 할 수 있어야 한다.
예를 들어 클라이언트 A와 B의 평균 네트워크 지연이 100ms일 경우 A가 현재 위치 (0, 0)에서 (0, 1) 위치로 초당 1의 시간으로 움직이고 있다는 정보를 보내면, B에서 이동 메시지를 받은 시점은 이미 100 ms 가 지난 상태이므로 클라이언트 A의 움직임을 좌표 (0, 0)에서 부터가 아니라 100ms가 지난 이후 즉, (0, 0.1) 정도로 예측한다.
그리고 '예측'이란 언제나 틀릴수 있다. 예를 들어 A는 (0, 1)로 이동 하려 했으나, 이동 중간에 공격을 받았다거나, 장애물에 부딫히는 등 여러가지 이유로 예측했던 위치와 다른 현상이 발생 할 수 있다. 가장 간단한 해별 방법은 가장 최신의 정보를 받는대로 갱신해주면 된다. 하지만 예측한 위치와 신규 갱신된 위치의 차이가 크다면 캐릭터가 갑자기 텔레포트 하는 것 같은 현상이 보이기도 한다. 이런 현상을 줄이기 위해 사용되는 개념이 '보간(interpolation)' 이다.
유니티에서 Physics.Simulate를 이용한 클라이언트 예측에 관련된 예제는 [여기]를 참고한다.
보간의 방식은 아래 링크들을 참고하자 :
- Cubic-spline : https://www.gamedev.net/resources/_/technical/multiplayer-and-network-programming/defeating-lag-with-cubic-splines-r914
- cubic hemite interpolation(spline) : http://adnoctum.tistory.com/147
위치 동기화에서 필요한 항목
- 현재 위치
- 이동 방향
- 이동 속도 및 가속도
가속도의 경우 계산하기 너무 어렵다면 동기화 주기를 짧게하여 게임을 덜 어색하게 보이게 하는 방법도 있다
- 현재 상태, 바라 보고 있는 방향, 애니메이션 프레임
- 평균 전송 지연 시간
이벤트 동기화
공격 판정, 피격판정, 이동 시작, 방향 전환 등등의 이벤트 발생 순서가 게임의 결과에 영향을 미치는 것을 의미한다. 즉, 위치 동기화와 같이 순서와 상관 없이 동일한 결과를 내는 것이 아닌 발생 순서에 따라 결과가 달라지는 것들에 대한 동기화를 말한다.
앞에서 이야기한 상황을 다시 살펴 보자.
- 경과 시간 2ms 에 클라이언트 B가 몬스터1에게 공격을 한다. 하지만 클라언트 B의 네트워크 속도는 매우 느리다.
- 경과 시간 3 ms 에 클라이언트 C가 몬스터1에게 공격을 한다. 클라이언 C는 좋은 네트워크 환경에 있다.
클라이언트 간 이벤트 동기화가 제대로 지켜지지 않는 경우 경과 시간 2ms에 발생한 클라이언트 B의 몬스터1에 대한 공격과 3ms에 발생한 클라이언트 C의 공격이 각 클라이언트 마다 아래와 같이 다르게 보일 수 있다 :
- 클라이언트 A 시점 : (실제로 B가 먼저 때렸지만 전송 지연 때문에 메시지가 늦게 도착 했으므로) C가 먼저 때림
- 클라이언트 B 시점 : 클라이언트 B가 먼저 때림
- 클라이언트 C 시점 : 클라이언트 C가 먼저 때림
위와 같이 각 클라이언트 다른 화면을 재상한다. 만일 공격에 대한 넉백 판정이라도 있다면 몬스터는 각 클라이언트 마다 서로 다른 방향으로 날아가는 것으로 보일 것이고, 몬스터의 HP가 얼마 남지 않았다면 각 클라이언트 마다 몬스터를 처리한 캐릭터는 서로 다르게 보일 것이다.
위와 같은 이유로 모든 클라이언트들은 동일한 이벤트 발생 순서를 볼수 있어야 하는데 이를 이벤트 동기화라고 한다.
여기서 우리가 주목해야 할 점은 실제 이벤트 발생 순서는 그렇게 중요하지 않다는 것이다. 위의 예에서 B가 먼저 때렸다고 판단하든, C가 먼저 때렸다고 판단하든 그 자체는 별로 중요한 것이 아니다. 설령 실제 늦게 움직였던 C가 먼저 때렸다고 판단하더라도 모든 클라이언트 A와 B, C는 C가 먼저 때린 것으로 보여 주어야 하고, 반대일 경우도 모든 클라이언트는 같은 결과를 볼 수 있어야 한다.
이벤트 동기화 방식은 Lamport algoritim(http://kukuta.tistory.com/143)과 같은 total oredered multicasting을 사용 할 수도 있고 중앙 집중형 동기화 제어 컴포넌트를 하나 두고 모든 동기화를 해당 컴포넌트에게 맡겨 버리는 방식이 있다.
total oredered multicasting 과 같은 방식이 장애에 강하고 로드를 분산 시킬수 있다는 장점이 있긴 하지만 모든 동기화 그룹내에 모든 멤버들에게 브로드캐스팅하고 응답을 받아야 하므로 동기화 가능 최소 시간이 가장 늦은 클라이언트 기준으로 잡히는 단점이 있다. 게임에서는 반응 속도가 중요하므로 중앙집중형을 선택하는 것도 좋을 수 있다.