멀티 플레이는 두 가지 방법으로 구현할 수 있다.
client-server
- 서버를 중간에 두고 두 클라이언트가 통신한다.
- 두 클라이언트의 동기화를 하는 것이 비교적 쉽다.
- 하지만 서버를 사용하므로 비용이 증가한다.
p2p
- 두 클라이언트가 직접 통신한다.
- 두 클라이언트를 동기화하는 작업을 해야한다.
- 서버를 거의 사용하지 않는다.
client-server로 멀티플레이 구현
처음에는 client-server로 구현을 하였다. 클라이언트는 서버에 자신의 입력 데이터(키보드)를 서버에 전송하고 게임의 연산은 서버에서 진행된다. 서버는 게임 내에 물체를 렌더링하는데 필요한 필수적인 데이터를 json형식으로 정리하여 클라이언트로 전송한다.
실행 결과는 느렸고 동시에 하나의 게임 밖에 할 수 없었다. 서버에서 게임에 관련된 연산을 하면서 1초에 30번씩 렌더링 정보를 클라이언트에 보내줘야했기 때문이다. 라즈베리파이로 실행했기 때문에 느렸을 수도 있다.
이 방법은 핵으로부터 비교적 안전하기 때문에, 여러 플레이어의 트래픽을 감당할 수 있는 대형 게임에서 주로 사용하는 방법이다. 나 같은 경우는 이를 처리할만한 서버를 갖지 않았고, 핵 문제가 발생해도 나에게 발생하는 손해는 없다. 따라서 p2p방식으로 방향을 바꾸게 되었다.
p2p로 멀티플레이 구현
p2p 플레이 방식은 대표적으로 슈퍼 피어방식과 동등한 피어 방식이 있다. 슈퍼 피어는 한명의 피어가 서버와 같은 역할을 하는 것이고, 동등한 피어는 게임 연산을 각자의 컴퓨터에서 하는 것이다. 슈퍼 피어는 호스트와 게스트 구현을 따로해야하며, 슈퍼 피어가 변조될 수 있다는 단점이 있다. 때문에 동등한 피어 방식으로 구현을 하였다.
WebRTC
WebRTC를 사용해 p2p 통신이 가능하도록 하였다. 데이터 채널을 사용해 서로에게 메시지를 보낸다. 두 피어를 매칭하기 위해서는 시그널링 서버가 필요한데 이는 사이드 프로젝트인 “WebRTC chat”과 동일한 서버를 사용하였다.
일관성 유지
게임 연산을 각자가 하기 때문에 연산의 일관성을 유지시켜야 한다. 아래 조건이 만족하면 일관성이 유지될 것으로 여긴다.
- 모든 브라우저에서 연산결과가 같다.: 모든 브라우저는 자바스크립트를 구현한 방식이 조금씩 다르다. 연산도 다를 수 있다고 생각했다. 특히 부동소수점 연산을 가장 걱정했다.
- 무작위 요소가 없다.: Math.random() 뿐만 아니라 게임의 외부적인 요소(시간 등)가 게임에 영향을 끼쳐서는 안된다.
- 두 사용자 입력과 그것의 처리가 동기적이다.: 사용자 서로의 입력이 같은 틱에 처리되어야 한다.
결과적으로 1번은 같은듯하고, 2번은 그런 요소을 넣지 않으면 된다. 문제는 3번이다.
입력 동기화
- 1~n틱 동안 각자의 입력 데이터를 수집한다.
- n+1~2n틱 동안 상대에게 송신 요청 메시지를 보낸다
- 상대에게 송신 요청 메시지를 받으면 입력 데이터를 송신한다.
- 2n틱에 상대에게 입력데이터를 받지 않았으면 게임을 멈춘다.
- 2n+1틱에 입력 데이터를 처리한다.
상대에게 보내는 메시지는 10자리의 문자열로 1~8자리는 입력, 9는 시간(틱), 10은 연산결과이다. 재송신 메시지는 2자리로 송신받아야할 시간을 담고있다. 이 방식의 단점은 두 클라이언트 중 더 느린 클라이언트의 속도로 게임이 진행된다는 것이다. 또한 입력에 대한 처리가 최대 2n틱 이후에 이루어진다는 것이다. 나는 n을 5로 잡아서 구현했으며, 내가 느끼기에는 쾌적했다.
연산 결과 불일치 인식
만약에 경우에 입력 동기화를 하였는데도 불구하고 두 피어에서 서로 연산한 결과가 다를 수도 있다. 이는 브라우저의 차이에서 발생할 수도 있고, 게임의 버전이 다르기 때문일 수도 있다. 이러한 문제는 인식은 할 수 있지만 해결은 할 수 없다고 생각한다. 입력 동기화가 되어있는데도, 한번 불일치가 발생하면 그 이후에도 불일치가 계속 발생할 것이다. 만약 불일치가 발생했을때 한 쪽 피어를 올바른 피어로 정하고 그것의 연산 결과를 다른 쪽에 보내 고치게하면 그것은 더 이상 동등 피어가 아니라 슈퍼 피어 방식이다. 그리고 동등피어 방식에서 얻는 이점도 사라질 것이다. 따라서 불일치가 발생하면 그것을 인식해서 게임을 끝내도록 하였다.
불일치를 인식하는 방법은 게임의 현상태(플레이어의 위치, 라이프 등)로 0~9사이에 키를 만들고 이를 입력메시지를 보낼때 같이 보내는 것이다. 이렇게 하면 상대방에서 이를 확인할 수 있고, 만약 다르다면 게임을 끝낼 수도 있다. 하지만 둘다 같은 키를 보내서는 안된다. 한 피어가 입력메시지를 받을 때 키를 확인하고 동일한 키를 상대에게 보내 게임이 정상적으로 진행되는 것처럼 속일 수 있기 때문이다. 따라서 두 피어의 키를 만드는 조합을 다르게 해야한다.