프로그래밍/General

멀티플레이 게임서버 구현 3편: 엔티티 인터폴레이션

Lou Park 2023. 9. 22. 01:57

들어가며

이 글은 https://www.gabrielgambetta.com/entity-interpolation.html의 글을 공부하면서 옮긴 것으로, 번역과 의역이 섞여있습니다.

이번에는 동일한 서버에서 다른 플레이어가 컨트롤하는 캐릭터에 대해서 탐구해보겠습니다.

 

 

서버 타임 스텝(Server time step)

이전 글에서 서버의 동작은 비교적 간단했습니다. 클라이언트가 주는 입력을 받아서 게임 상태를 업데이트하고 다시 돌려주면 되었죠. 하지만 여러 클라이언트가 연결된 경우, 메인 서버 루프는 다소 달라집니다.

 

이 시나리오에서는 여러 클라이언트가 동시에, 그리고 빠른 속도로 연이어 입력을 보낼 수 있습니다. 모든 클라이언트에서 입력이 수신될 때마다 게임 월드를 업데이트하고 게임 상태를 브로드캐스트 하는 것은 너무 많은 CPU 자원과 대역폭을 사용하게 됩니다.

 

더 나은 접근법은 클라이언트 입력이 수신될 때마다 입력을 처리하는 것이 아니라, 큐에 넣는 것입니다. 대신에, 게임 월드는 일정한 주기로 업데이트되죠. 예로 초당 10번, 100ms 마다 업데이트 하는것을 서버 타임 스텝이라고 합시다. 각 업데이트 루프에서 처리되지 않은 입력이 적용되며, 새로운 게임 상태가 클라이언트에 브로드 캐스트 됩니다.

요약하면 게임 월드는 클라이언트 입력의 존재나 그 양과는 관계없이 예측가능한 속도로 업데이트 된다는 것입니다.

 

 

업데이트 다루기

클라이언트의 관점에서 이 접근법은 이전과 별다를 것 없이 잘 작동할 것입니다. 클라이언트측 예측은 서버의 업데이트 딜레이와는 별개로 작동하므로 게임 상태가 100ms마다 처럼 드물게 업데이트 될때도 무리없이 동작하죠. 하지만 이 드물게 업데이트 되는 것 때문에 클라이언트는 게임 월드에 있는 다른 엔티티들에 대해서는 매우 제한된 정보만 가지고 있게 됩니다.

 

첫번째 구현에서는 상태 업데이트를 수신할때 다른 캐릭터의 위치를 업데이트 하게 됩니다. 그 결과 이동이 매우 끊어져 움직임이 부드럽게 나타나지 않고 100ms마다 뚝뚝 끊기며 점프가 발생하게 됩니다.

 

개발중인 게임의 유형에 따라 이 문제를 처리할 수 있는 방법은 다양하지만, 일반적으로 게임 내 엔티티가 예측하기 쉬울 수록 정확하게 처리하기도 쉽습니다.

 

 

데드 레커닝 (Dead reckoning)

레이싱 게임을 만든다고 해봅시다. 빠른 속도로 움직이는 자동차는 꽤 예측하기 쉽습니다. 예를 들어, 자동차가 100m/s의 속도로 움직인다면, 자동차는 1초 후 시작지점에서 대략 100m 앞에 위치할 것 입니다.

“대략”이라고 했던 이유는 그 1초동안 자동차가 더 가속 혹은 감속하거나 방향을 틀었을 수 있기 때문입니다. 빠르게 달리는 자동차의 위치는 갑자기 180도 회전한다거나는 하지 않죠.

 

그러면 100ms마다 게임 상태를 업데이트하는 서버에서는 어떻게 동작하게 될까요? 클라이언트는 모든 경주중인 자동차들에 대한 속도와 방향을 브로드캐스트 받습니다. 그 다음 100ms 동안 새로운 정보를 받지 않지만, 자동차는 계속 달리고 있어야합니다. 가장 간단한 방법은 100ms 동안 자동차의 방향과 가속도가 일정하게 유지될 것이라고 가정하고, 로컬에서 그렇게 실행하는 것입니다. 그런다음 100ms 이후 서버 업데이트가 일어나면 자동차 위치가 수정됩니다.

 

플레이어가 자동차의 속도와 방향을 변경하지 않았자면 예측된 위치는 수정된 위치와 정확히 일치하겠죠. 하지만 플레이어가 어딘가에 충돌했다면 예측된 위치는 크게 엇나갈 수 있습니다.

 

데드 레커닝은 전함 같은 저속 상황에서도 적용할 수 있습니다. 실제로 데드 레커닝(추측항법; Dead reckoning)이라는 용어는 해상 항법에서 비롯된 것이죠.

 

 

엔티티 인터폴레이션(Entity Interpolation)

데드 레커닝을 전혀 사용할 수 없는 상황들도 존재합니다. 특히, 플레이어의 속도와 방향이 즉각적으로 바뀌는 모든 시나리오에서 추측 항법은 쓸모가 없습니다. 예를 들어 콜 오브 듀티같은 슈팅 게임에서 플레이어는 보통 매우 빠른 속도로 달리고, 멈추며 코너를 돌기도 하죠. 이로인해 이전 데이터로부터 다음 위치를 전혀 예측할 수 없게 됩니다.

 

서버에서 보내주는 데이터로만 업데이트하게 되면 100ms 마다 짧은 거리를 텔레포트하는 플레이어들 때문에 제대로된 게임 플레이가 불가능하게 됩니다.

 

클라이언트 입장에서 가지고 있는 것은 100ms 마다 오는 게임 상태입니다. 해결책의 핵심은 플레이어를 기준으로, 다른 플레이어를 “과거”에 표시하는 것입니다.

 

예를 들어 t=900에서 초기 위치 데이터를 받았고, t=1000에서 새로운 위치 데이터를 수신했다고 가정해 봅시다. 클라이언트는 t=900~1000까지의 플레이어의 위치를 알 고 있습니다. 그래서 t=1000~1100에서 t=900~1000까지의 다른 플레이어가 어떤 행동을 했는지 표시합니다. 이렇게 하면 사용자에게 항상 실제 움직임 데이터를 표시하지만 100ms “늦게” 표시됩니다.

 

 

t=900에서 t=1000으로 보간(Interpolate)하는데 사용하는 위치 데이터는 게임에 따라 다릅니다. 일반적으로 잘 작동하긴하지만, 이것이 부자연스러워 보이는 경우 각 업데이트마다 더 자세한 위치 데이터를 보내는 것으로 보완할 수 있습니다.

 

이 테크닉을 사용하면 모든 플레이어가 현재의 자신, 과거의 다른 플레이어들을 보기때문에 서로 약간씩은 다른 렌더링을 보이게 되지만 빠른 페이스의 게임에서 이는 크게 눈에 띄는 일은 아닙니다.

 

하지만 예외 사항이 있죠. 다른 플레이어를 조준해서 총을 쏠 때와 같이 시/공간적 정확도가 필요한 경우입니다. 다른 플레이어의 과거만이 보이기 때문에 조준 했던 지점은 다른 플레이어가 100ms 전에 있던 곳이죠…

 

 

요약

게임 월드의 다른 엔티티의 상태에 대한 문제를 해결하는 방법을 알아보았습니다.

 

첫 번째 방법인 데드 레커닝은 엔티티 위치를 이전 엔티티 데이터를 사용하여 정확하게 추정할 수 있는 특정 유형의 시뮬레이션에 사용됩니다. 하지만 이러한 조건이 충족되지 않을때는 실패하죠.

 

두 번째 방법인 엔티티 인터폴레이션은 미래 위치를 예측하지 않으며, 서버에서 제공한 실제 엔티티 위치만 사용하므로 다른 엔티티가 약간의 시간 지연 후에 표시됩니다. 최종적으로는 컨트롤 중인 플레이어가 현재에 표시되고, 다른 플레이어의 움직임은 과거의 것으로 표시됩니다. 이로서 매우 원활한 경험을 제공하게 됩니다.

 

하지만 이대로만 개발한다면 “헤드샷” 판정이 불가능해집니다!

이를 어떻게 해결하면 좋을지 다음 글에서 보겠습니다.

 

 

시리즈 모두 보기

1편: 클라이언트 - 서버 아키텍쳐 
2편: 클라이언트측 예측과 서버측 재조정 
[*] 3편: 엔티티 인터폴레이션
4편: 지연 보상