E D R S I H C RSS
ID
Password
Join
일이란 시간 안에 할 수 있는 알맞은 정도를 목표로, 그리고 약간 빠듯하게 스케줄을 세워두면 잘 된다. -다케우치 히토시

 * 원문링크 : [http]http://www.gameis.org/Korean/Game_Intro/Game_Genre/OnlineGame.htm
  • 괜찮은 글이라 보기좋게 정리해둡니다. 원문이 좀 두서없이 되어있어서 약간 띄어쓰기만 했습니다.

Contents

1 로드 밸런싱(load balancing)
2 배틀넷(Battle.net)
3 Latency Masking
4 Rendevouz Positioning
5 충돌
6 불확정성 원리 - 정확한 값은 알 수 없다
7 조정
8 지연 허용 범위와 Dead Reckoning
8.1 지연 허용 범위는?
8.2 Dead Reckoning
9 DIS에서의 확장
10 Dead Reckoning의 적용
11 객체의 위치 예측
12 Jerk가 발생했을 때
13 Linear Smoothing
14 향상된 Smoothing
15 비표준적인 DIS 공식

1 로드 밸런싱(load balancing) #

MUG에서는 차후의 확장 등을 대비해 서버를 클러스터링 구조로 설계하는 경우가 많습니다. 이렇게 하면 초기에 접속자가 적을 때는 저렴하게 서버를 한대만 놓고 운영하다가 나중에 접속자가 증가함에 따라 같은 서버를 내부 네트워크로 묶어 동일한 역할을 하게 하면 서버의 용량이 증가되므로 편리하게 됩니다. 이때 데이터는 한곳의 DB 서버에서 모두 갖는 것이 동기화 작업에 유리하며, 각 클라이언트 접속 서버는 모두 같은 일을 하게 됩니다. 이런 경우 특정 서버에 부하가 집중되지 않도록 각 서버의 접속 인원 등을 파악해 각 서버에 골고루 부하를 분산시키는 기술을 로드 밸런싱(Load Balancing)이라 합니다. 국내 통신망 업체들도 모두 하나 이상의 동일한 작업을 하는 서버를 갖고 있지만 접속할 때마다 부하의 정도를 따져 각 클라이언트를 다른 서버에 접속하도록 하고 있습니다.

2 배틀넷(Battle.net) #

1995년 "디아블로"라는 뛰어난 게임이 등장했습니다. 별도의 서버가 존재하지는 않았지만 게임을 실행한 피어가 게임 서버가 될 수 있는 방식은 매우 획기적이었죠. 또한 블리자드의 배틀넷이라는 인터넷을 경유한 게임 서비스가 각광을 받아 대단한 히트를 기록했습니다. 현재는 소수의 인원이 서버와 클라이언트를 겸한 애플리케이션을 실행시켜 즐기는 게임의 형태를 대개 "네트워크 플레이" 혹은 "넷플"이라고 부르게되었습니다. 이러한 게임과 MUG를 간단히 비교해 보죠.

우선 전송되는 데이터의 용량 차이가 있습니다. MUG의 경우는 서버를 따로 두고 있으므로 좋은 환경과 빠른 전용선을 사용할 수 있어 전송되는 데이터가 다소 많아도 무리는 없습니다. 또한 서버는 항시 운용되고 있고 많은 사용자가 접속하므로 같은 데이터 양이라도 MUG의 서버에 유입되는 용량은 엄청나게 됩니다.

반면 "넷플"에서는 각 피어가 하나의 서버 역할을 하므로 쓸데없이 데이터가 많이 전송되면 서버 역할을 하는 피어에 엄청난 병목을 일으켜 게임 자체가 불가능하게 됩니다. 아마 블리자드도 이에 대한 고민을 많이 했을 것입니다. 즉 낮은 대역폭의 회선에 걸맞게 데이터를 적게 전송하는 방법이 없이는 디아블로와 같은 게임은 불가능하게 됩니다. 데이터를 압축해 전송하는 방법이 있었지만 이 역시 한계가 있었고 결국 채택된 방식이 "치명적이지 않은 수준의 오차를 허용하는 것"과 "오브젝트에 대한 데이터를 각 피어가 분산 저장하는 것"이었습니다.

특히 후자의 경우는 현재 군사용으로 많이 사용되는 DIS(Distributed Simulation)와 관련이 깊은데, DARPA에서 이를 연구하면서 발전된 것이 Dead Reckoning입니다. CPU의 파워가 부족하다든지, 장비의 설치문제라든지, 데이터를 여러 대의 시스템이 분산해 저장해야 하는 경우는 의외로 많습니다. 이때 가장 문제가 되는 것은 각 시스템이 갖는 객체(Entity)들을 동기화하는 것입니다. 냉전시대 군비경쟁이 심화되면서 급속히 발전한 DIS는 이렇게 피어 서버를 사용하기 때문에 데이터를 분산저장해야 하는 시스템에 매우 이상적으로 적용될 수 있었죠.

3 Latency Masking #

디아블로에서는 서버 역할을 하는 피어가 성능이 좋지 않을 경우 데이터를 몰아넣는 것은 불합리하다고 생각할 수 있습니다. 만약 MUG와 같이 한 서버에 데이터가 집중되면 매번 모든 클라이언트로부터 데이터를 전송 받아야 하고 (정확한 데이터는 서버만이 갖고 있으므로) 매번 요청이 있을 때마다 전송해 주어야 합니다. 이렇게 되면 서버에 엄청난 부하가 걸릴 뿐 아니라 네트워크상의 병목 현상 때문에 게임 자체가 불가능해집니다. 이런 방식은 별도의 서버를 운영하며 넓은 대역폭의 전용선을 제공할 수 있는 MUG에서나 가능한 방법입니다.

이를 해결하기 위해서는 각각에 데이터를 분산시켜 저장할 수밖에 없습니다. 물론 이중에도 게임을 만든 클라이언트 즉, 다른 클라이언트와 구분되는 게임의 호스트는 존재합니다. 호스트는 서버와 다르게 넷플에서 중요한 역할을 담당합니다. 그러나 호스트는 서버처럼 모든 데이터를 갖지 않으며 자신이 소유한 데이터와 소유자가 불분명한 데이터 즉, 모든 피어가 공통적으로 사용하는 데이터만을 저장하고 관리하게됩니다(예: 스타크에서의 중립생물).

각 피어는 자신이 소유한 데이터를 가지며 이를 저장하고 관리합니다. 다시 말하면 각 피어가 소유한 데이터가 가장 정확한 데이터가 되는 것 입니다. 이 모델에서는 자신의 데이터가 변경되면 피어는 이 변경사실을 모든 피어에게 멀티캐스트합니다. 호스트 역시 똑같은 하나의 피어로 참가하므로 다른 피어에게 데이터를 전송할 의무는 없습니다. CPU의 부하와 네트워크의 패킷이 각 클라이언트들에 거의 공평하게 나누어 가지게 되는 거죠.

이때 만약 10Mbps의 LAN상에서 플레이한다면 실제로 한대에서 플레이하는 것처럼 보일 정도로 데이터 교환이 빠르고, 서로의 입력을 교환하면 이 모델에서도 간단히 넷플이 가능하게 됩니다. 그러나 보통 전화접속이나 인터넷을 통한 게임을 시도하게 되므로 크든 작든 Latency가 생기며, 이는 게임의 중대한 방해요소가 됩니다. 따라서 치명적인 Latency를 처리하는 기술이 필요해졌고 이를 Latency Masking이라고 부릅니다. Dead Reckoning은 바로 이러한 Latency Masking의 대표적인 기술입니다.

4 Rendevouz Positioning #

각각의 피어는 자신이 갖지 않은 데이터에 대한 사본을 항상 유지시킬 의무가 있습니다. 이를 ghost라는 표현으로 쓰자면 ghost는 Dead Reckoning을 적용해 부드러운 움직임을 보여주나 실제 PDU의 데이터와 차이를 가질 수 있습니다. 그러나 동기화시 PDU와 ghost의 데이터에 차이가 있다고 ghost의 데이터를 전송된 PDU의 것으로 바로 바꾸면 안됩니다. 급격하게 움직이고 동기화가 늦는 경우일수록 ghost가 한 지점에서 다른 지점으로 점프하는 것처럼 보이는 현상이 많이 일어나게 됩니다. 이를 저크(jerk), 혹은 지터(jitter)라고 합니다. 저크를 막기 위해서 사용하는 방법은 여러 가지 있으나 Rendevouz Positioning이 가장 이해하기 쉬운 방법입니다. 이는 ghost와 PDU를 당장 동기화하지 않고 다음 동기화 시간까지 값이 같아지도록 맞추는 것을 말합니다.

예를 들어 현재 ghost의 좌표와 속도가 (100, 100) (10/sec, 20/sec)이고 전송된 PDU의 값은 (120, 130) (30/sec, 10/sec), 동기화는 1초씩일 경우 당장 ghost를 PDU에 맞추면 여러분의 객체는 순식간에 가로 20, 세로 30포인트를 점프하는 것처럼 보일 것입니다.

Rendevouz Positioning에서는 이렇게 하지않고 먼저 PDU가 다음 동기화시에 도달할 좌표를 구합니다. 좌표는 (150, 140)이 될 것입니다. 그러면 현재 좌표인 (100, 100)에서 다음 동기화시에 이 좌표에 도달하도록 속도값을 변경해줍니다. 변경한 속도값은 (50/sec, 40/sec)이 됩니다. 결론적으로 현재 전달된 속도는 (30/sec, 10/sec)이므로 가로, 세로 속도가 상당히 늘었지만 (100, 100)에서 (150, 140)까지 점프하지 않고 부드럽게 움직이는 모습을 보여줄 수 있습니다. 다음 동기화시에도 역시 같은 작업이 이루어집니다. 이는 간단하게 저크를 없애고 실제 객체와 ghost간의 차이를 줄일 수 있는 좋은 방법이지만 급격한 움직임은 표현되지 않고 항상 느리게 움직이는 것처럼 보이게되는 단점이 있습니. 하지만 duel과 같이 관성이 작용해 급가속이나 감속이 되지 않는 객체의 움직임에는 매우 좋은 효과를 줄 수 있습니다. 이 방법은 급격한 변화를 표현하지 못하고 속도의 변화가 크다는 점에서 개선의 여지가 있으며 이에 대한 해결방안은 뒤에서 소개하도록 하겠습니다.

5 충돌 #

자신의 데이터를 다른 피어에서 나타내려면 Rendevouz Positioning으로 간단히 해결할 수 있습니다. 그러나 이는 큰 문제가 아닙니다. 네트워크 게임에서 최대 문제는 바로 충돌입니다.

만약 서버에 모든 데이터가 몰려 있다면 객체끼리의 Interaction 역시 서버에서 이루어지므로 이 결과에 대해서는 이의를 제기할 수 없습니다(MUG의 경우). 언제나 서버에 있는 데이터가 가장 정확하기 때문이죠. 그런데 만약 자신이 소유한 어떤 객체와 소유하지 않은 객체간의 상호작용이 이루어졌다고 가정해 보죠.(배틀넷의 경우). 자신이 소유한 객체의 상태는 자신에게 있는 데이터가 가장 정확한 것이므로 이를 가지고 작업할 수 있습니다. 그런데 자신이 소유하지 않은(ghost만을 가지고 있는) 객체는 현재 상태를 정확하게 판단할 수 없습니다.

물론 정확히 알려면 이 객체의 소유주에게 질의를 보내 상태를 전달받으면 되지만, 문제는 데이터가 전달되는 동안 늦어져 이미 정확한 데이터가 아니게 된다는 것입니다. 보여주는 정도라면 latency를 측정하고 부정확하나마 이 값을 더하여 예측치를 계산할 수 있지만 질롯의 움직임이나 시즈의 무빙샷 등 정확한 조작에 의해 성패가 좌우되는 경우는 latency에 의해 결과가 달라지게 됩니다.

결국 이 정도의 차이는 무시할 수 있는 판정 시스템을 만들어야 하는데, 이 역시 경우에 따라 크게 달라지므로 딜레마가 아닐 수 없게 됩니다. 레인보우 6나 퀘이크 등 순간에 승패가 결정되는 게임을 많이 해본 사람은 서버에서 자신의 컴퓨터까지의 latency도 게임 결과에 중대한 영향을 미치는 요소라는 사실을 알 것이다. 이런 종류의 넷플에서 대개 서버쪽에서 플레이하는 사람 혹은 ping이 낮은 사람이 유리해지는 것은 어쩔 수 없는 부분입니다. 하지만 최대한 공평한 환경을 만들려고 노력은 해야되지 않을까요? 이런 불리한 점을 어떻게 개선할 수 있을까요?

네트워크 게임에서는 기본적으로는 플레이하는 상대끼리 계속 자신이 갖고 있는 PDU에 대한 정보를 전달하고 다른 클라이언트의 정보를 전달받게 됩니다. 문제는 전달받은 정보가 어떤 한 시점에 정확하다고 확신할 수 있는가입니다. 어떤 이벤트가 발생해 PDU의 상태가 바뀌면 해당 PDU를 소유한 클라이언트는 최대한 빨리 정보를 다른 클라이언트들에게 보내게 됩니다.

6 불확정성 원리 - 정확한 값은 알 수 없다 #

첫 번째, 지구에서만 통신하는 경우는 로컬 랜으로 연결해서 게임을 하는 경우에 비견할 수 있습니다. 이때 지연은 특별한 문제가 없는 한 10ms 이내이며 네트워크 게임도 초능력자가 아닌 보통 인간이 플레이하는 이상, 0.01초의 차이는 무의미하다고 단정해도 무리가 없습니다. 이때의 지연은 완전 무시됩니다.

두 번째, 태양과 지구가 교신하는 경우는 공중망(PSTN)을 거쳐 서로 접속한 상황에 비유할 수 있습니다. 이 경우 지연은 100ms 이상이며 동체 시력이 매우 좋은 사람은 미세한 차이점을 발견할 수 있을 정도로 증가합니다. 만약 선로의 상태가 나쁘거나 별도의 서버를 경유해 접속한다면 지연은 400~500ms까지 증가합니다. 이때는 필연적으로 실제 PDU의 데이터와 ghost 사이에 오차가 생기며 서로의 데이터에 차이가 없도록 여러 가지 예측시스템을 사용하게 됩니다. 그러나 오차는 피할 수 없게 되죠. 인터넷의 표준 프로토콜인 TCP/IP의 기본 특성상 라우팅을 많이 하고 여러 곳을 거치면 지연이 늘어나는 것은 당연합니다. 결국 이 경우에 가장 유효한 것은 다소의 오차는 무시하고 각 클라이언트가 항상 맞는 데이터를 가지고 있다고 생각하는 것 입니다.

앞에서도 언급했듯이 태양에 있는 사람이 현재 지구의 상태를 정확하게 알 수 있는 방법은 없습니다. 최소한 8분은 기다려야 그 사람의 얘기를 들을 수 있습니다. 하지만 그래도 상대방이 내 얘기를 듣고 있다고 생각하고 계속 말을 하는 것이 가장 경제적인 방법인 것처럼, 네트워크 플레이어에서도 자신이 가장 올바른 데이터를 가지고 있다고 판단하는 것이 가장 좋은 방법입니다.

7 조정 #

latency masking하는 다른 방법을 생각해 보죠. 스타크래프트에서는 자신의 유닛이 있는 곳만 fog가 걷히고 그 구역의 다른 유닛이나 지형을 볼 수 있도록 되어 있습니다. 만약 유닛들이 이렇게 각 클라이언트에서 서로 움직이고 있으나 거리가 멀어 상호작용의 가능성이 매우 낮다고 가정한다면, 이 유닛들의 위치를 정확하게 계산하기 위해서(사실은 이렇게 하는 것이 가장 좋지만) 동기화하는 부담을 질 필요는 없게 됩니다. 왜냐하면 현재 유닛들은 너무 멀리 떨어져 있고 서로 간섭하지 않을 것이라고 판단할 수 있기 때문입니다. 정확한 위치가 필요한 것은 서로간의 상호작용이 생겨서 판정이 요구될 때이지 이렇게 멀리 있을 때가 아닌거죠. 이 경우는 어느 정도 제법 큰 오차까지도 허용될 수 있습니다. 동기화는 아주 러프하게 해도 좋으며 다만 저크가 발생하지 않도록만 신경써줍니다. 이런 것을 Dead Reckoning과 같은 정확한 조정(hard reconciliation)에 대해 대략적인 조정(soft reconciliation)이라 말합니다.

스타크래프트에서는 fog로 가려진 적 유닛은 그리지 않음으로써 네트워크에 대한 부하를 많이 줄일 수 있었을 것입니다. 만약 서로의 유닛이 가까워져서 상호작용이 일어난다면 이때는 정확한 위치가 필요하게 됩니다. 이때의 상호작용은 반드시 직접적인 공격이나 원조 등이 아니더라도 가까운 적의 위치는 눈으로 보고 공격하게 되므로 거리가 어느 정도 수준이 되면 정확하게 동기화하기 시작해야 합니다. 객체를 부드럽게 움직이게 하기 위해서는 Dead Reckoning과 Rendevouz Positioning을 사용할 수 있지만 오차가 한계보다 커진 경우는 모든 클라이언트에게 메시지를 보내어 일시적으로 게임을 중단하고 정확한 위치로 이동시킨 후에 다시 모든 클라이언트에게 메시지를 보내 게임을 재 시작할 수 있습니다. 스타크래프트에서도 백병전시 유닛이 한꺼번에 많이 움직이면 게임이 멈칫멈칫하는 상황을 볼 수 있는데, 이 역시 동기화의 문제가 작용하고 있기 때문일 것입니다. 어쨌든 많은 객체들을 한꺼번에 동기화 하는것은 어렵기 때문에 대략적인 중재는 여러 게임에서 빠르게 latency masking하는 방법으로 사용될 수 있습니다.

8 지연 허용 범위와 Dead Reckoning #

8.1 지연 허용 범위는? #

유명한 스타크래프트를 보더라도 마린이나 시즈의 탄은 보이지 않으며(탄착점 만 알수 있다) 포토캐논의 폭발은 포탄보다 훨씬 큽니다. 커세어의 공격은 스플래쉬 데미지로 맞은 표적뿐만 아니라 주위 표적까지 피해를 줍니다. 이런 여러가지 기획설정들이 다소의 오차는 표시나지 않도록 커버해줄 수 있지 않을까요!

가장 중요한 것은 항상 지연에 의한 오차가 허용할 수 있는 범위 내에 있도록 하는 것입니다. 만약 직녀성과 지구가 교신하는 마지막 경우처럼 지연이 너무 길어지면 정상적인 플레이가 불가능해 게임이 성립되지 않게 됩니다. 배틀넷의 레드핑 한 개 짜리 아이디들은 ping값이 너무 높아 게임이 안 되는 경우를 많이 봤을 것입니다.

이렇게 어느 정도의 지연이 허용될 수 있는가? 하는 점은 게임에 따라 다르게 됩니다. 만약 네트워크 게임 상에서 상대방의 상태를 아주 정확하게 알려면 유일한 방법은 한 프레임마다 OK 사인을 교환해 돌아가며 패킷을 전달하는 방식뿐입니다(Hand Shaking). 그러나 이 방법으로는 스타크래프트의 slowest 게임보다 10배 느린 게임조차도 만드는것이 불가능합니다.

결론적으로 네트워크 게임 개발자는 상대방의 정확한 상태를 알겠다는 욕심을 버려야 합니다. 이는 빛의 속도를 초월하겠다는 말과도 같은 얘기이며, 현재 기술로는 불가능합니다. 마치 하이젠베르그의 불확정성 원리처럼 아무리 지연이 적더라도 현재의 상태를 전송하는 동안 시간은 흐르기 때문에 패킷이 도착했을 때 반대편의 상태가 현재 받은 패킷대로임을 확신할 수 없습니다. x0의 값은 절대로 t0시에 도착하지 않습니다. 우리가 할 수 있는 일은 언제나 오차가 허용할 수 있는 범위 안에 있도록 하는 것이 최선입니다.

8.2 Dead Reckoning #

Dead Reckoning의 원리는 간단합니다. 항해가 시작된 때부터 해도를 그리기 위해 사용되었던 기법입니다. 이름도 오해하기 쉽지만 원래의 의미는 Deduced-Reckoning으로 "추론하여 계산한다"라는 뜻입니다. 즉, 위치예측이라는 의미를 가지고 있습니다. 실제로 배를 다루는 여러 기관(미 해군) 에서는 항해의 필수과목으로 이 Dead Reckoning Navigation을 다루고 있으며, US NAVY 사이트에 가보면 Dead Reckoning의 6가지 원칙에 대해 설명하고 있습니다. 원래 의미의 Dead Reckoning은 현재 위치에서 방향과 속도를 아는 것으로 미래의 위치를 예측한다는 매우 단순한 사실을 응용하는 것이기 때문입니다. 거기에는 조류나 바람 등의 요소 외에 포함되어 있지 않지만 이들을 무시할 수 있다면 일정 시간마다 위치를 계산하고 위치가 목표한 코스에서 얼마나 벗어나는가를 아는 것이 가능하고 결과적으로 목적지까지 도달할 수 있기 때문입니다.

이런 점에서는 네트워크 게임의 객체가 움직이는 것과 비슷합니다. 대항해시대에 새로운 항로의 개척은 매우 중요한 일이었고, 항해 중에는 반드시 해도를 그려야 했습니다. 그러나 안개가 짙거나 폭풍우가 몰아쳐 주위의 지형지물을 사용할 수 없게 되면 현재의 위치와 방향, 그리고 속도를 명기함으로써 항로를 표시했던 것입니다. 네트워크 게임에서라면 현재 위치와 방향, 그리고 속도를 가지고 다음 동기화때까지 이동경로를 예측할 수 있습니다. 즉 현재의 정보를 가지고 다음 동기화 때까지 움직임을 Interpolation(보간)하는 기술이 바로 게임에서의 Dead Reckoning입니다. 이 이름에 상대의 움직임에 대한 정보를 매 프레임마다 전송받지 않고 "예측"한다는 데 의미가 있다는 것을 알아두시기 바랍니다. 그리고 네트워크 게임에서 사용하는 Dead Reckoning은 원래 의미의 Dead Reckoning이 아니라 DARPA에서 개발한 DIS에서의 Dead Reckoning이라는 점도 기억해두세요.

9 DIS에서의 확장 #

Dead Reckoning의 원래 의미는 이렇게 자신의 현재 위치를 알아내는데 사용되었지만, DIS에서는 한발 더 나아가 ghost의 위치를 같이 계산하여 필요한 경우가 아니라면 패킷을 전송하지 않아 네트워크상의 병목을 줄일 수 있었습니다. 이때는 한번씩 동기화를 한다는 것을 지정할 필요도 없이 Dead Reckon이 발생할 때마다 동기화를 하면 되었습니다.

그러나 여기에는 함정이 있습니다. 즉 개개의 클라이언트가 객체의 위치를 계속해 계산하고 이를 판정하지 않으면 안되는 비용이 있으며, 또한 가장 단순한 Dead Reckoning 모델에서는 latency에 대한 문제가 빠져있습니다. Dead Reckoning 자체가 latency를 masking하는 기술이지만 설명했듯이 동기화 패킷이 가는 동안 시간은 흐르기 때문에 전달된 패킷은 latency에 의해 오차를 항상 갖게 됩니다. 또한 역동적으로 모든 객체가 움직이는 상황이라면 Dead Reckoning은 별 효과를 보지 못하게 됩니다.

그러나 DIS 분야에서 수년에 걸쳐 시뮬레이션(Simulation) 한 결과에서 Dead Reckoning은 큰 효율을 보여주었고 긍정적인 가치가 있는 솔루션임을 증명했습니다. 물론 앞서 언급한 여러 가지 문제에 대한 해결방안도 그동안 꾸준하게 제기되었습니다.

10 Dead Reckoning의 적용 #

그러면 Dead Reckoning으로 실제 프로그래밍할 때는 어떻게 할 수 있을까요?

두 가지 방안이 있습니다. 첫 번째는 일정 기간 동안 정기적으로 동기화를 하고, 동기화 되는 동안의 위치를 각 클라이언트에서 예측하는 방식입니다. 이 방식에서는 일정 시간마다 현재 좌표와 속도를 전송해 동기화 합니다. 받은 쪽에서는 이 값을 가지고 Dead Reckoning을 적용해 다음 동기화 때까지의 ghost의 위치를 예측합니다. 이동궤적이 직선의 집합이 되기 때문에 동기화 시간이 길어지면 움직임이 부자연스러워지는 반면 동기화할 때마다 오차를 줄이려고 하기 때문에 오차가 누적되지 않는다는 장점이 있습니다.

이와 대조적으로 DIS에서 사용하는 Dead Reckoning은 자신이 소유한 객체의 위치를 매번 계산하고, 오차가 DR Threshold보다 커지면 그때 전송해 동기화하는 방식입니다. 네트워크의 객체들이 격렬하게 방향을 바꾸지 않으면 부하가 적어 좋으나, 반대인 경우는 효율이 크게 떨어지게 됩니다. 이때는 급격한 가/감속을 방지하도록 하는 기획적인 설정이 필요할 것 입니다.

Dead Reckoning은 많이 사용되는 기법이고 또한 효율적이지만 만능은 아닙니다. 더구나 어떤 방향으로 이동하는 것처럼 지속적이고 예측 가능한 이벤트에 대해서만 사용 가능할 뿐이고 폭발이나 아이템의 등장, 같은 단발적이고 예측 불가능한 이벤트를 다룰 수는 없습니다. 단지 이동시의 부자연스러움을 다소 감소시켜주는 것뿐입니다. 예측할 수 있는 것은 예측하고 그렇지 않은 것은 최대한 빨리 전송한다는 원칙에 입각한 기법인 것이죠.

11 객체의 위치 예측 #

Smoothing의 대상은 객체의 좌표가 되므로 대개 (x, y) 그리고 3차원 공간에서라면 z 좌표까지 모두 3개를 사용할 수 있습니다. 또한 각 좌표값에 대한 현재 속도와 가속도 역시 다음 위치를 결정하는 중요한 요인이 됩니다. 그러나 각 좌표를 다루는 방법은 모두 동일한만큼 여기서는 x 좌표 하나만 들어 설명하도록 하죠.

Dead Reckon이 발생해 전달된 객체의 좌표를 x0이라 하고 이 시점의 속도를 v0라 합시다. 가속도는 a0로 정의합니다. 저크를 무시할 수 있다면 전달된 시점에서 ghost의 위치는 x0에 동기화되며 다시 동기화되기 이전까지 객체는 등가속도 운동을 합니다. 이때 객체의 위치는 일반물리학의 공식을 사용해 다음과 같이 표현할 수 있습니다.
Xt=x0 + v0*t + (a0*t2) / 2

그러나 네트워크 게임에서는 대개 가속이 유지되는 경우는 드물어(a0=0), 위 공식은 다음과 같이 치환됩니다. 즉 일정 시간마다 일정하게 값이 증가하도록 합니다. 이것이 가장 단순하게 Dead Reckoning을 적용하는 방법입니다.
Xt=x0 + v0*t

12 Jerk가 발생했을 때 #

Dead Reckoning의 변형, 즉 일정 시간마다 동기화하는 방식에서는 Rendevouz Positioning을 사용해서 jerk를 처리하기 때문에 다소 오차가 커지더라도 부자연스럽게 움직이지는 않습니다. 대신 급가속이나 급감속을 표현하기 어렵기 때문에 관성과 같은 요소를 도입해 발생하지 않도록 하고 있습니다.

반면 Dead Reckoning을 원래 의미대로 적용하려면 항상 객체를 갖는 클라이언트는 이 객체에 대한 ghost를 같이 유지하며 오차가 정해진 크기를 넘는 경우에 한해 동기화 데이터를 전송합니다. 이때는 객체의 상태가 빠르게 변화하면 동기화 데이터가 자주 전달되므로 급가속이나 급감속도 무리없이 표현할 수 있습니다. 문제는 이 상태에서 jerk가 발생한 것을 어떻게 처리할 수 있느냐입니다.

먼저 이렇게 사용하는 경우는 초기 지연(Initial Latency)의 측정이 매우 중요합니다. 그리고 모든 클라이언트는 데이터를 전달받을 각 클라이언트에 대한 지연테이블을 유지할 필요가 있습니다. 5대의 클라이언트가 게임을 플레이한다면 각 클라이언트는 4개의 지연값을 측정해야 할 것 입니다. 그리고 호스트로부터 게임 시작 통보를 받게 됩니다.

호스트는 정해진 시간(예를 들면 5초) 후에 게임을 시작하라는 메시지를 모두에게 보냅니다. 그러면 각 클라이언트는 5초에서 호스트까지의 지연을 뺀 시간을 기다린 후 게임을 시작하게 됩니다. 물론 이 5초 동안은 각 클라이언트가 갖는 객체들에 대한 정보를 게임에 참여하는 모든 다른 클라이언트에게 전달해 이후 ghost를 유지할 수 있도록 해야 할 것 입니다.

시작한 시간은 모든 게임 클라이언트의 공통시간이 되며 게임이 시작되면 0으로 초기화할 수 있습니다. 이제 각 클라이언트들은 플레이어의 입력에 의해 자신의 객체를 움직이며, 데이터를 전달할 때는 공통시간(timestamp)을 같이 보내게 됩니다. 만약 전달받은 데이터의 공통시간이 자신이 생각하는 시간, 즉 현재시간에서 지연을 뺀 값이라면 초기지연은 정확합니다.

객체의 위치 기준으로 본다면 이때는 대략 (지연*속도)에서 어느 정도 가감한 만큼의 오차가 생기는데 이는 무시할 수 있는 값은 아닙니다. 급속한 변화를 그때그때 반영할 수 있는 반면, 동기화 때마다 어느 정도 jerk를 피할 수 없다는 단점이 있습니다.

13 Linear Smoothing #

이에 대한 해결 방안은 원래의 경로와 새로운 경로를 외삽(extrapolate)하는 방법으로 이동하는 것입니다. 다시 말하면 급격한 보정이 아닌 점진적인 보정(gradual correction)을 적용합니다. 한 지점에서 다른 지점까지의 경로는 간단한 공식에 의해 표현될 수 있으며 실제 DIS에서는 곡선이 많이 사용되지만 네트워크 게임은 대부분 직선경로를 채택합니다.

먼저 이동하던 경로, 즉 동기화 패킷이 전달되기 이전의 경로와 새로운 경로 모두를 그린 후에 현재 위치에서 가중치를 변경시켜 가면서 이동합니다. 처음에는 이전 경로에 많은 가중치를 두지만 점점 스텝이 증가하면서 새로운 경로로 무게를 둡니다. 이런 식으로 계속 이동하면 나중에 스텝이 여러 번 반복된 후에는 이전 경로의 가중치는 0이 되고 객체는 완전히 새로운 경로로 이동하게 됩니다. 여기서 스텝은 시간 의존적인 요소로 작용하며, 얼마나 빠르게 가중치를 변화시켜야 하는지는 게임 성격에 따라 다릅니다. 만약 지나치게 스텝을 빠르게 하면 역시 저크가 나타날 것이며 너무 느리게 움직이면 격렬한 객체의 움직임을 따라가기 쉽지 않을 것입니다. 이런 요소들은 어느 정도로 적용하는 것이 좋다라고 딱히 결론을 내릴 수는 없는 문제이며 수많은 테스트를 거쳐 각 시스템에 가장 잘 맞는 값들을 구해야 할 것입니다.

Dead Reckoning에서는 오차가 커질 경우 동기화하는 경우 외에도 일정 시간마다 "Keep-Alive"를 체크하는 동기화 패킷을 보내는 것이 일반적입니다. 대개 Rendevouz Positioning처럼 이 다음 동기화 시간에 같은 위치에 도달하도록 스텝을 분할하는 방식을 많이 사용합니다. 이때는 현재의 위치와 다음 동기화시간의 위치를 연결하는 직선을 구하고 이 직선의 방정식을 이용해 객체를 이동시킬 수 있습니다. 이런 방식을 사용하고 싶지 않다면, 실제 객체의 이동궤적 중 언제 어느 지점에서 만날 것인지를 결정하고 외삽 중의 이동공식을 새로 주면 이 지점에 도달할 때까지는 이 공식을 사용하고 그후는 실제 객체의 공식을 사용하면 됩니다.

이때 실체와 ghost의 방향을 연장해 만나는 점을 xf라 하고, 여기까지 몇 번의 스텝을 거쳐 도달해야 하는가를(이것은 실제로는 시간으로 대치된다) p라는 값으로 나타낸다고 가정하죠. 그리고 현재의 스텝이 i번째라고 한다면 현재의 Smoothing된 ghost의 위치는 다음 공식으로 표현할 수 있습니다.
Xi=x0+(xf-x0)/(i/p) 

14 향상된 Smoothing #

Smoothing 방법을 적용하면 훨씬 객체가 부드럽게 움직이게 됩니다. 이 방법을 사용한 객체의 궤적은 직선의 집합으로 나타나며 각 연결부위는 다소 날카롭게 돌출됩니다. 만약 사람의 걷는 모습을 시뮬레이션 하려면 이런 방식으로도 문제는 없습니다. 사람의 관절은 운동성이 좋아서 자동차나 비행기 등 다른 운동 객체가 구현하지 못하는 움직임도 거뜬하게 보여줄 수 있기 때문입니다.

하지만 상대적으로 순간적인 움직임의 범위가 한정되어 있는 탱크나 미사일 등을 시뮬레이션 한다면 이는 곤란해집니다. 똑바로 가다가 갑자기 오른쪽으로 90도 방향을 틀어서 날아가는 미사일을 상상할 수 있을까요? 물론 원본을 가지고 있는 쪽에서도 이렇게 과격한 움직임을 할 수 없는 것은 당연한 일입니다. 그러나 사본측에서는 원본과 일정오차가 생기지 않는 한 현 상태를 유지하려고 하기 때문에 상대적으로 움직임의 폭이 커지게 되는 거죠. 이를 개선하기 위해서는 다소 복잡한 공식을써서 객체 움직임의 변화가 적도록 배려해야 합니다. 결과적으로 Smoothing은 네 가지 요소에 대해 모두 적용되어야 합니다.
  • 위치(Position)
  • 속력(Velocity)
  • 가속력(Accelaration)
  • 방향(Orientation)

이 모든 요소에 대해서 Smoothing이 적용될 때 비로소 객체는 곡선을 그리면서 부드럽게 움직일 수 있습니다. 필요하다면 B-Spline의 공식을 적용하는 것도 고려해 볼만합니다. Bezier는 곡선의 궤적이 컨트롤 포인트를 통과하지 않기 때문에 문제가 있지만, B-Spline은 곡선이 양쪽 끝단 한쌍을 제외한 모든 컨트롤 포인트를 통과하기 때문에 이런 용도로 충분히 사용할 수 있습니다. 시작한 점을 하나의 컨트롤 포인트로 보고, DR이 발생한 지점을 역시 하나의 컨트롤 포인트로, 그리고 최종 목적위치를 마지막 컨트롤 포인트로 생각하면 세 점을 갖는 B-Spline을 그릴 수 있습니다. 실제 적용시는 끝점과 중간점의 다시 그 중간, 중간점과 마지막 점의 중간을 컨트롤 포인트로 잡아 그리면, 선은 우리가 선택한 세 점을 부드럽게 지나는 궤적을 만들 것 입니다. 이 궤적의 공식을 객체의 움직임에 주면 객체는 DR이 발생했더라도 전혀 부자연스럽지 않은 움직임을 보여줄 수 있게 됩니다.

다음 공식을 보죠. 도달해야 할 위치를 xf로 보고 동기화패킷이 전달된 시점의 ghost의 위치는 x0으로 정의합니다. 마찬가지로 간격을 p번으로 나누어 현재 어떤 위치에 있는가를 결정하는 값을 i로 합니다. 이때 위와 같은 식을 사용하면 xi는 i에 대한 3차 방정식 곡선을 그리게 됩니다. 따라서 원래의 1차식보다 좀더 부드러운 곡선을 보여줄 수 있습니다. 이 공식은 하나의 예일 뿐이며 절대적인 신뢰도를 갖는 것은 아닙니다. 필요에의해 2차식이나 더 높은 차수의 방정식을 얼마든지 사용할 수 있습니다. 그러나 필요한 정도로 차수를 조절하는 것이 전체 성능 향상에 기여할 것 입니다.

15 비표준적인 DIS 공식 #

표준 DIS에서는 동기화시 객체의 위치와 각 좌표계에 대한 속도와 가속도를 전달하도록 되어 있습니다. 보통 가속도까지 전달하지 않더라도 위치와 속도 정도는 전달합니다. 상황에 따라 이런 값들이 전달되지 않거나 네트워크의 부하를 극도로 줄이려는 목적에서 위치만을 전달하는 경우가 있게 됩니다. 이때는 가속도-속도에 의존한 DIS 표준공식을 사용할 수 없고 전달된 위치만으로 앞으로의 위치를 예측해야 합니다. 이 경우 사용할 수 있는 비표준 DIS 공식을 살펴보죠.

첫 번째 공식은 동기화 이전의 이동궤적중 한 구간만을 측정하여 그 기울기를 속도로 보고 이동시키는 t에 대한 1차식입니다. 객체의 궤적은 직선이 될 것입니다.

두 번째 공식은 이동구간 하나를 더 선택해 각 구간의 속도증감을 측정하여 속도와 가속도를 결정하고 이동궤적을 결정하는 방법으로, t에 대한 2차식이므로 곡선궤적을 보여주게 됩니다. 이런 방법은 항공기나 미사일처럼 빠르며 동시에 속도 변환이 급격하지 않고 일정한 방향성을 갖는 객체의 위치를 시뮬레이션하거나 추적하는데 알맞습니다.

Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2010-10-28 12:42:52
Processing time 0.7823 sec