네트워크 지연은 게임에 있어서 가장 중요한 요소 중 하나다.
소위 우리는 말하길 핑이라고 하는데
핑이 낮다는것이 지연율이 낮다는 것이다.
즉, 우리가 서버에 보낸 요청이 응답이 바로 되어 게임에 적용되는 그 사이 시간이 짧다는 것이다.
더 자세히 알아보자
네트워크 지연이란?
싱글 플레이 게임이나 Local Area Network를 사용하는 게임들은 지연에 대해 걱정할 필요가 없다.
1. 지터(파형에 변형이 일어남)
2. RTT(시작 지점에서 대상 지점으로 이동하고 시작 지점으로 다시 이동하는 데 걸리는 시간)
3. Packet Loss(패킷이 전달되지 못해서 유실됨)
어떠한 이유로든 서버와 클라이언트 사이에 정보를 보내거나 받는 작업이 생긴다면 위와 같은 문제에 대해 다시 생각해봐야한다.
그냥 정보를 보내고 받는 작업이라 단순하다고 생각하지만?
3가지 원칙이 있다.
1. 보안성
2. 반응성
3. 정확성(일관성)
하지만 3가지 모든 원칙을 충족시키기는 어렵다.
특히 반응성과 정확성을 같이 잡기는 어렵다.
그래서 우리는 어떤 것을 중요하게 여겨야 하는지 결정해야 한다.
멀티 플레이어 게임에서 연산을 하는 주체를 Authority라고 말하는데
이는 권한이라고 생각해도 된다.
Authority를 정의해야 현재 게임플레이에 어떻게 적용될 지 정할 수 있다.
일반적으로 2가지 경우가 있다
1. 서버 권한
2. 클라이언트 권한
여기서 생각해 볼 것은 서버는 수많은 클라이언트와 정보를 주고 받는다.
다시 말하면 클라이언트에서 연산을 하지 않고
서버에 정보를 보내 서버의 연산을 거쳐서 클라이언트가 받고 그 받은 것을 게임에 적용하는 것이다.
직접적으로 클라이언트가 계산한다면 서버의 연산을 기다리지 않아도 좋다.
이렇게 보면 딱히 서버로 할 이유를 모르겠다.
하지만 서버 권한은
보안성이 높고, 반응성이 낮다(지연시간) , 또한 동기화 문제가 생기지 않는다.
클라이언트 권한은
보안성이 현저히 낮고, 반응은 좋으며, 동기화 문제가 생긴다.
더군다나 클라이언트에서 변조한 데이터를 서버에 보내는 시도를 감지해야만 정상적으로 플레이가 가능하다.
**대부분의 핵이 클라이언트 데이터 변조다.
하지만 역시 여기도 정답은 없다.
게임의 플로우에 따라 정해질 것이다
예를 들면 몬스터가 있다고 해보자.
해당 몬스터는 5m 밖에서는 때릴 수 없다.
하지만 서버에 몬스터를 때렸다는 메세지를 보냈을 때, 서버는 클라이언트에서 정말 때려서 보낸 것인지 악의적으로 보냈는지 구분할 길이 없다.
즉, 서버에서 이러한 변조 행위를 막으려면 서버는 항상 Validation이 필요하다.
이러한 메세지가 정말 클라이언트에 의해 보내진 것인지 확인하는 작업이다.
아까 말했듯이 조금 더 자세히 알아보자
Server Authoritative를 먼저 알아보자
서버가 모든 걸 결정한다.
보안성✅
캐릭터의 체력이나 캐릭터의 위치는 서버 권한으로 하는 것이 좋다.
악의적으로 변경시키기 쉬운 것이며 변경되었을 때 그 영향이 크기 때문이다.
그래서 캐릭터의 체력이나 위치는 서버한테 받은 데이터로 결정한다.
일관성✅
서버에 일관성이 있는 이유는 무엇일까?
서버는 모든 게임 플레이에 관한 데이터를 관리해서 보내기 때문에 World's Consistency가 유지된다.
다시 말해서 누가 어디서 플레이를 하든 서버가 보내주는 데이터는 같다.
그래서 어디에서나 같은 플레이가 가능하다.
반응성🚫
하지만 반응성이 낮다.
역시나 클라이언트에서 연산할 데이터를 전달하거나 요청을 보내고
응답을 받을 때까지 기다려야하기 때문이다.
즉, 대기 시간이 길어질수록 게임 플레이가 어려워진다.
그 다음은 클라이언트 권한이다.
클라이언트라고 서버를 가지고 있지 않은 것은 아니고
그냥 World state를 갖고 있기 위한 수단일 뿐이고 진짜 게임 플레이는 클라이언트가 결정한다.
반응성✅
클라이언트는 반응성이 좋다.
당연히 서버의 응답을 기다리지 않아도 되기 때문이다.
바로바로 유저의 선택에 따른 결과를 게임에 보여줄 것이다.
일관성🚫
하지만 클라이언트는 동기화 문제가 생긴다.
실제로 클라이언트가 가지고 있는 정보와 일관성이 깨지는 데이터가 생기는 경우가 많아진다.
이는 클라이언트와 서버가 가진 정보가 다른 것을 의미다.
보안성 🚫
역시 보안성도 좋지 않다.
클라이언트가 데이터를 연산하고 다루는 것은 꽤나 위험하다.
아니 변조될 가능성이 농후한 일이다.
즉, 그 데이터는 클라이언트 단에서 변조가 가능하고 서버는 Validation을 하지 않으면 게임 플레이에 큰 영향을 줄 수 있다.
하지만 서버 권한 게임으로 만드는 것이 여러모로 편리한 경우가 많아서 많이 쓰이곤 한다.
다시 말해서 우리는 지연시간을 어떻게 처리해야할까?
라는 처음의 의문으로 다시 돌아왔다.
역시 답은 없다.
다만 몇가지 유용한 기법이 있다.
일반적으로 4가지가 있다.
1. 서버의 부담 경감
2. 클라이언트측 예상
3. 행동 예측
4. 서버 측 리와인드
차례대로 알아보자
1. Allow low impact client authority
설계할 때 서버 권한을 기반으로 한다면 언제 어디서 신속한 응답이 필요하고 보안성과 일관성이 필요한 지를 구분해야 한다.
예를 들어 유저 입력을 보자면
유저가 키를 눌렀을 때
키 입력 메세지를 서버에 보내고 서버에서 캐릭터가 할 일을 보내는 것이 과연 괜찮을까?
아니다.
우리는 바로바로 반응하기를 원한다.
적어도 유저 입력에 대해서는 반응성이 좋아야 한다는 말이다.
그래서 대부분 입력에 대해서는 클라이언트 단에서 작동한다.
특히 FPS 장르는 더욱 응답성이 필요하다.
클라이언트가 마우스의 입력을 받지 않고 바라보고 있는 방향을 결정하게 한다면
(마우스를 움직이면 방향이 바뀌는 형태)
그렇게 되면 조금 이상하게 바뀔 것이다.
보안을 위해서 이 방법을 선택했다면 유저에게 이상하게 느끼지 않게 하는 것은 우리가 해야할 과제다.
2. Client side prediction
클라이언트 단에서의 예측이다.
예측이란 틀릴 수 도 있지만 맞추었을 때 진가를 발휘한다.
즉, 우리가 제대로 예측하기만 한다면 서버 권한으로 게임이 플레이되더라도 반응성이 높다는 말이다.
이것이 어떻게 가능하냐??
예를 들면 우리가 플레이하는 클라이언트의 코드가 유저의 입력을 예측하는 것이다.
오른쪽 이동키의 req를 1초간 보낸다면.. 그 다음에도 오른쪽을 누르고 있을 확률이 높다고 판단할 수 있다.
(물론 아닐 수도 있다)
아무튼 그렇다면 우리는 오른쪽 이동키를 눌렀다고 서버에 보내서 응답을 받아서 움직이는 대신.. 클라이언트에서 바로 움직일 수 있다.
이게 바로 예측이다.
하지만 예측이 틀릴 경우 그 대가는 크지만 기다리는 것보다는 낫다.
영어로는 Reconcilation이라고 쓴다.
클라이언트는 입력을 바로 클라이언트에서 실행한다고 하더라도 해당 요청을 지워버리지는 않는다.
즉, 서버에 보내기는 한단 말이다.
그래서 클라이언트에서는 예측대로 움직이지 않았을 경우
서버의 응답을 받아서 다시 정상실행 할 수 있도록 한다.
즉, 클라이언트에서 예측한 것과 서버에서 받아온 것을 보간해서 게임을 플레이할 수 있게 하는 것이다.
클라이언트는 결국 서버의 응답을 받아와서 정확한 위치로 이동할 수 있게 된다.
많이들 쓰는 방법이다.
예측을 맞추었을 때 정확성과 응답성을 정말 높일 수 있어서 그렇다.
3. Action anticipation, action casting
다음은 행동 결정이다.
서버권한으로 게임플레이를 하지 않는 여러가지 이유가 있긴 하다.
적어도 유저 입력과 같은 영역에서는 서버 권한을 쓰면 응답성이 떨어져서 별로다.
하지만 유저 입력이 아니라 다른 것으로 반응성이 높아지게끔 보인다면 어떨까?
예를 들면 애니메이션, 시각효과, 소리 등과 같은 게임 플레이에 보다 적은 영향을 끼치는 요소라면 어떨까?
뭐 그래도 여전히 서버 권한 플레이라면 응답성은 떨어지는 것이 맞다.
예를 들면 내가 오른쪽으로 빨리 이동했는데..?
서버에서 애니메이션을 재생하라는 응답이 오기전에 멈춰버려서
사실 그 때는 애니메이션이 진행되면 안되는 상황이 있다.
?????
우리는 그 때.. 애니메이션과 같은 효과를 어떻게 적용해야하는지 결정해야하는데
그대로 실행할 건지.. 아니면 취소시킬 건지 결정해야 한다.
이런 상황도 서버 권한에서는 고려해야 한다.
과연 재생하는 것이 자연스러운지.. 취소하는 것이 자연스러운지 잘 생각해보자.
4. Server side rewind (AKA lag compensation)
서버 단 되돌이
서버에서 다시 되돌아간다는 것은.. 거의 일어나지 않는다.
시간은 거꾸로 흐르지 않기 때문이다.
다만 모종의 이유로 그럴 수 있는데
바로 보안상의 이유다.
클라이언트에서 벌어진 일과 서버와의 동기화를 위해서다.
예를 들면 클라이언트에서 "t" 시간에 목표를 맞췄다고 서버에 메세지를 보낸다.
서버는 t 시간에 보낸 요청을 t + RTT/2 시간에 받았다.
서버의 응답을 클라이언트가 받는 시간은 t + RTT 시간이다.
클라이언트는 RTT를 기다리는 동안도 진행되었기 때문에 RTT 시간동안 손해를 봤다.
서버는 타겟을 맞췄던 사격의 유효성을 검사하고
실제 게임 속 World를 클라이언트 측에서 눈치채지 못하게 되돌려서 맞춘다.
다시 말하면 플레이어는 서버 권한에서도 반응성이 좋다고 생각하게 되는 것이다.
다만 유저가 눈치채지 못하게하려면 같은 프레임에 일어나게 해야 한다.
예를 들면 프레임 시작에 요청을 보내고 프레임 끝쯤에 응답을 받아서 유저가 눈치 못채게 하는 것이다.
결론을 말하자면
멀티 플레이어게임은 정말 어렵다.
더군다나 실시간으로 지원해야하는 경우 더 그렇다.
하지만 사람들이랑 하니까 재밌는 게임이 아니겠는가..?
그래서 나도 이 글을 쓰고 있는 것이고.. 언젠가 만들고 싶으니까?
아무튼 우리는 뇌의 전기신호보다 빠른 인터넷 신호는 갖고 있지 않으므로..
지연시간은 발생한다.
하지만 이 지연시간을 어떻게 해결하느냐에 따라 우리의 뇌가 눈치를 챌 수도 있고 아닐 수도 있다.
그리고 이것들 보다 더 나은 방법도 존재할 수 있고.. 더 좋은 기술이 나올 수도 있다.
하지만 그 전까지는 우리는 선조의 지혜를 발판 삼아서 만들어보도록 하자.
이후 관련된 글
[Game Developer, 게임개발자/게임네트워크] - Client side Prection, 서버 단에서 예측
https://unity.com/how-to/manage-network-latency#server-authoritative-games
'Game Development, 게임개발 > 게임네트워크' 카테고리의 다른 글
멀티 플레이 환경에서의 클라이언트와 서버 - 1, 구조 (0) | 2022.05.10 |
---|---|
Client - side Prection, 클라이언트 단 예측 (0) | 2022.05.02 |
Socket으로 통신 라이브러리 만들기 (0) | 2021.10.02 |
TCP,UDP 소켓프로그래밍, C# - 소켓 생성부터 종료까지 (0) | 2021.09.28 |
온라인 게임에서 TCP 보다 UDP를 사용하는 이유 (0) | 2021.09.28 |