사례 연구: ‘Pong’을 만들어보자

처음 쓰는 컬럼을 이런 진부한 설명으로 시작하고 싶지는 않았지만, 어쩔 수 없을 것 같다. ‘Pong’은 게임 역사상 처음으로 흥행에 성공한 게임이다. 게임 자체는 지금도 오락실 등에서 볼 수 있는 아이스하키 게임과 비슷하다. 두 명의 사람이 각자 단순한 컨트롤러를 사용하여 상대방 쪽으로 공을 넘겨 통과시키면 이기는 게임이다.

그림1

▲(Pong, 출처: 위키피디아)

하고 많은 게임 중에 이 게임을 선택한 이유는, 실시간 동기화를 구현하기 좋은 게임이라고 판단했기 때문이다. 다르게 말하자면 이 게임은 재미있고, 구현하기도 쉬워 보인다. 그렇다면 어떤 게임이 실시간 게임으로 만들기 쉬운지 어떻게 판단해야 할까? 이 컬럼은 이에 대한 부분을 다룰 예정이다. 추가로 사례 연구를 통하여 어떤 식으로 동기화를 만들어 가는지, 그 꼼수와 우회로 이루어지는 향연에 있어서 전채의 맛을 살짝 느끼는 것도 목표로 삼는다.

실시간 멀티플레이를 구현하기 위해 가장 먼저 고려해야 할 부분 중 하나는 바로 무엇을 동기화 할 것인가 하는 것이다. ‘Pong’은 아마도 화면을 돌아다니는 ‘공’, 그리고 플레이어가 공을 튕겨내기 위해 움직이는 ‘막대’ 부분이 될 것이다. 이 컬럼에서는 공을 어떻게 동기화 할 것인가에 대하여 집중적으로 이야기할 것이다.

공은 게임의 시작부터 끝까지 계속 움직이는 물체이다. 이는 동기화 해야 하는 정보가 쉬지 않고 변한다는 것을 의미한다. 현재의 좌표, 속도, 벽이나 막대에 충돌했는지 끊임없이 파악하고 동기화 해야 한다. 보편적으로 이렇게 끊임없이 변화하는 데이터를 가진 객체를 동기화 하는 것은 매우 어려운 일이다.

하지만 ‘Pong’은 물리 엔진을 사용할 수 있는 게임이다. 어느 한 쪽 막대에 공이 충돌했을 때의 좌표와 속도만 동기화 하면, 같은 환경에서 같은 물리 엔진임을 활용해, 이후의 진행을 추가 작업 없이 자동으로 똑같이 맞출 수 있다. 실시간 게임을 만들 때 동기화 해야 하는 정보와 순간을 최소화하는 것은 매우 중요하다. ‘Pong’은 이를 극단적으로 줄일 수 있기 때문에 상당히 구현하기 쉬운 편이다.

그림3

▲(직접 만든 멀티플레이 Pong의 모습, 양쪽의 위치가 조금은 다를 수 밖에 없다.)

 그러나 네트워크를 통한 데이터의 송수신은 시간을 소모한다. 특히 모바일 기기의 무선망은 최소 수십 밀리세컨드 이상의 지연 시간을 감안하는 것이 정석이다. 이 것도 국내는 아주 좋은 편이라 이 정도, 만약 미국이나 심지어 중국이라면 더 긴 시간이 걸린다고 보는 것이 맞다. 이런 상황에서 Player A의 공 정보를 아무리 빠르게 전송한다 한들, Player B의 정보가 맞춰지는 시간에는 차이가 생기기 마련이다.

그렇다면 어떻게 해야 할까? 가장 먼저 떠오르는 아이디어는, 양 쪽이 같은 정보를 공유할 때까지 잠시 기다리는 것이다. 흐름을 생각하면 다음과 같다.

  1. Player A > 서버 : ‘나의 공이 지금 좌표 (100,100)에 있다’
  2. 서버 > Player B : ‘A의 공이 지금 좌표 (100,100)에 있다고 한다’
  3. B > 서버 : ‘공의 좌표를 (100,100)으로 설정했다’
  4. 서버 > A : ‘B의 공 좌표가 업데이트 되었다’
  5. 이제 다시 A의 공 좌표를 업데이트 한다

여기서 센스 있는 개발자 분들은 바로 ‘어 이러면 안되지 않나?’하고 생각하실 것이다. 맞다. 이렇게 끊임없이 정보를 갱신하는 객체, 특히 그게 바로 화면에 보이는 것은, 이렇게 여러 번 통신을 하면서 지연 시간이 길어지면 바로 플레이의 불편함(랙)으로 이어진다. 위와 같은 방식은 상대적으로 덜 활동적인 장르나 게임 시스템 내부의 처리가 턴제 형태로 이루어지는 게임에서 사용한다.

그렇다면 다시, 어떻게 해야 할까? 다음으로 할 수 있는 방법은 네트워크를 통해 수신하는 데이터에 보정을 가하는 방법이다. 네트워크의 지연 시간을 측정 혹은 추정하고, 그 만큼의 시간이 지난 후의 상태를 계산해서 ‘적절히’ 맞춰 놓는 식이다. 이 것은 실제로 자주 쓰이는 방법이지만, 경험이 많은 개발자라면 알 것이다. ‘적절히’라는건 개발자에게 세상에서 구현하기 제일 어려운 것 중 하나 이다. 마치 ‘플레이어가 느끼기에 적절한 난이도로 몬스터 AI를 만들어 주세요’ 같은 요구랄까..

이제 마지막으로, 그럼 어떻게 하면 좋을까? 실시간 게임을 만들어보지 않은 개발자라면 이 결론이 아주 어색할지도 모른다. 그건 바로 ‘굳이 딱 맞출 필요 없다’이기 때문이다. 어차피 물리 엔진이 계산해주는 공의 이동과 충돌은, 같은 위치에 같은 방향만 맞춰 놓는다면, 결국 같은 곳으로 움직이기 마련이다. 어느 한 쪽의 화면이 조금 늦을 수는 있지만, 최종적으로 보는 화면이 같아진다면 문제가 되지는 않는다.

하지만 여기서 구현을 마무리하면 문제가 생긴다. 바로 앞의 이야기를 뒤집는 것 같지만  ‘최종적으로 보는 화면이 같아’져야 한다는 것이 중요한 기준이 된다. 예를 들어 공의 속도가 매우 빨라진 상태에서 네트워크 지연 시간이 늘어나는 경우, A의 화면에서는 이미 승리한 상태로, B의 화면에서는 아직 공이 다가오는 상태로 보일 수 있다. 이런 상황을 방지하는 것을 보장할 필요가 있다. 단, 네트워크 지연 시간이 수백 밀리세컨드 이상 비정상적으로 계속 늘어나거나, 아예 끊기는 경우는 여기서 고려하지 않는다.  물론 이런 상황의 예외 처리도 게임의 상용화를 위해 꼭 필요하지만, 네트워크에 문제가 생긴 다른 플레이어를 기다릴 지, 패배 처리를 할지, AI를 투입할지 같은 식으로 기술보다는 게임 정책의 문제가 된다.

그렇다면 네트워크 지연 시간을 보정하는 것은 어떻게 접근해야 할까. 이 것 역시 개발자가 단독으로 처리하기 보다는 기획과 유기적으로 이야기를 나누며 진행하는 것이 더 좋은 결과물을 만드는 경향이 있다.

다시 ‘Pong’으로 돌아오자. 플레이어 A가 공을 보내 B가 반응 해야 하는 거리까지 도달하는 동안, 벽에 두 번 부딪히면서 가는 일반적인 상황을 생각해보자. 네트워크 전송이 제대로 이루어지고 있는지, 네트워크 지연 시간은 얼마나 되는 지를 확인하기 위해 벽에 충돌할 때에도 정보를 전송한다고 가정하면, 흐름은 다음과 같다.

  1. 공이 A의 막대에 충돌했을 때의 위치와 속도의 변화를 서버를 통해 B로 보냄
  2. B는 해당 정보를 물리 엔진에 적용
  3. 공이 벽에 충돌한 위치와 속도를 서버를 통해 B에 보냄
  4. B는 해당 정보를 물리 엔진에 적용
  5. 공이 벽에 충돌한 위치와 속도를 서버를 통해 B에 보냄
  6. B는 해당 정보를 물리 엔진에 적용
  7. 이제부터는 B가 충돌했을 때의 위치와 속도의 변화를 A에게 보냄

위 흐름에서 네트워크 지연은 모든 부분에서 발생할 가능성이 있다. 1)에서 상대적으로 높은 네트워크 지연이 발생하고, 3), 5)에서 점점 더 빠르게 도착하는 경우에는, 공이 벽에 충돌할 즈음에 순간 이동을 하여 예상보다 더 빠르게 움직이게 되고, 1)보다 3), 5)에서 네트워크 지연이 커지는 경우, 진행하던 공이 다시 벽으로 돌아가는 현상이 발생한다.

솔직히 말하자면, 실시간 게임에서 종종 순간 이동이 일어나는 것은 사실 심각한 문제는 아니다. 모든 실시간 게임은 일정 부분 이런 문제를 가지고 있고, ‘Pong’역시 마찬가지로 볼 수 있다. 하지만 잦은 순간 이동은 플레이를 불편하게 만든다. 좀 더 보기 좋게 만드는 방법은 없을까?

여기서 게임의 특성, 재미 요소와의 조율이 이루어진다. 네트워크 지연 시간으로 인한 순간 이동이 피할 수 없는 것이라면, 그건 가능한 플레이어가 막대로 반응하기 최대한 이전에 이루어지는 것이 좋다. 플레이어가 막대로 반응하기 바로 직전, 벽에 충돌하면서 보정이 일어나 순식간에 공이 앞으로 다가온다면 당연히 짜증이 날 수 밖에 없을 것이다.

이에 따라 B에서 1)은 정보가 날아오는 순간 바로 적용하고, 3)과 5)는 내용과 현재 공의 위치에 따라 큰 차이가 나는 것이 아니라면 적용을 하지 않고 넘어가는 것도 좋은 방법이다. 다시 말하자면, 어떤 상황에서는 정보의 업데이트를 최대한 빠르게 하기보다는 오히려 뒤로 미루는 것이 더 나은 게임성을 제공하기도 한다는 것이다. 아이러니하지 않은가. 이와 같이 실시간 게임의 동기화 정책은 기본적으로 기술적인 문제이지만, 결정은 개발자 뿐 아니라 기획자는 물론, 팀원 전체가 플레이를 통해 다듬어 가는 것이 가장 좋은 결과물을 만드는 길이다.

지금까지 나온 이야기를 세줄 요약한다면, 다음과 같이 할 수 있겠다.

  • 실시간 멀티 플레이 게임은 동기화 해야 하는 정보와 빈도가 적을 수록 만들기 쉽다.
  • 네트워크 지연 시간이 존재하기 때문에, 이론적으로 완벽한 동기화는 불가능하다.
  • 완벽한 동기화를 추구하기 보다는, 게임을 플레이하기 좋은 방향으로 접근해야 한다.

물론 이 외에도 개선해야 할 사항은 얼마든지 남아있다. 이는 꾸준한 고민과 테스트를 통해서 찾아내고 다듬는 작업이 된다. 그 것은 개발 초기에 발견할 수도 있고, 런칭 직전에나 떠오를 수도 있으며, 심지어 출시하고 꽤 긴 시간이 지난 이후에 알게 될 수도 있다. 시기와 상관 없이, 위의 큰 틀을 따른다면, 아마도 좋은 게임을 만들 수 있지 않을까 생각한다.

아이펀팩토리는 곧 위에서 설명한 ‘Pong’ 게임의 소스코드 전체를 공개할 예정이다. 또한 향후 개발자 세션을 통하여 어떤 식으로 구현했는지, 이 컬럼에서 언급하지 않는 막대는 어떻게 다루었는지, 추가로 최적화 할 수 있는 여지는 어디에 있는지도 함께 이야기 나누는 시간을 가질 예정이다.

또한 아이펀 엔진을 정식으로 사용한다면, 위와 같은 초기 아키텍팅에 대한 고민도 개발자와 함께 나눈다.

감사합니다.

아이펀팩토리 박근환 아이펀 엔진 테크니컬 디렉터

답글 남기기

댓글을 게시하려면 다음의 방법 중 하나를 사용하여 로그인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중