Deterministic에 대한 기본적인 컨셉에 대해 알아보았습니다.
두 Input이 똑같이 적용되면 양 컴퓨터의 상태(결과)가 같다.
다르게 얘기하면 싱크가 맞다고 말할 수 있습니다.
그래서 이 레이턴시를 잘 극복하느냐가 관건였는데요. 이 Sync가 같지 않은 상태를 Desync라고 합니다.
이 desync가 일어나지 않게 하는게 Deterministic의 가장 중요한 점입니다. 그래서 이걸 막는 몇가지 방법이 있는데요.
첫 번째 방법으로 Delay를 주는 것입니다.
예를 들어 Player1과 Player2가 있다고 가정할 때 이제 이것을 P1과 P2로 나타내겠다.
P1이 Time이 0ms일 때 버튼을 눌렀다고 가정하고, 이 버튼이 P2까지 적용되는데 시간이 걸립니다. P2도 시간이 50ms일 때 버튼을 눌렀을 때도 마찬가지로 시간이 걸립니다.
이 때 같은 두 포인트에 P1이 P2에게, P2가 P1에게 보냈을 때 서로 같은 네트워크 경로로 따라간다고 가정했을 때 걸리는 시간은 거의 같다고 볼 수 있어서 같은 시간이 걸린다고 가정해도 무방합니다.
이 때 서로에게 적용되는 시간이 100ms가 걸린다고 가정해보죠. 그랬을 때 P1의 버튼입력값이 P2에게는 100ms에 도착 합니다.
그리고 P2의 버튼 입력값은 50ms에 눌렀고, 100ms가 걸리기 때문에 150ms에 도착하게 됩니다.
그랬을 때 양 쪽 입력처리를 공평하게 처리하는 방법은 눌렀을 때 바로 적용되는게 아니라 양쪽 모두 입력에 지연시간 만큼의 Delay를 주는 것입니다.
그래서 P1의 입력값에 100의 Delay를 주어서 100ms에 실행하고, P2의 입력값이 50에서 딜레이를 주어서 150ms에 적용되면 아래의 그림처럼 상황이 똑같아 집니다.
P1에서 P1의 입력값이 100ms에 적용되고, P2에도 P1의 입력값이 100ms에 적용되고, P2의 입력값이 P2에는 150ms, P1에도 150ms에 적용이 되는 것을 알 수 있습니다.
두 컴퓨터가 양쪽이 똑같은 시간에 똑같은 입력을 처리하게 되고, 이 때는 Sync가 맞게 되죠.
그래서 레이턴시 시간만큼 Delay를 주어서 처리를 하게 되면 Sync가 똑같이 맞춰지게 됩니다.
이 방식의 문제는 말 그대로 Delay가 생긴다는 것에 있습니다. 버튼을 눌렀는데 입력값이 바로 안나가고 얼마동안 시간이 지난 다음에 입력이 되는 것이죠.
100ms -> 0.1초인데 0.1초 뒤에 입력값이 동작하는데 0.1초라는 것은 민감한 사람이 아니고서는 느끼기 힘든 Delay 입니다.
만약에 지연시간이 길어져서 200ms가 걸렸다고 쳐보죠. 0.2초인데 0.2초의 딜레이가 걸리면 먹먹한 느낌을 받습니다.
그래서 이 때는 일반 유저들도 충분히 느낄 수 있는 Delay가 생기기 때문에 보통 Delay는 100ms가 Maximum이라고 볼 수 있습니다.
만약에 레이턴시가 150ms가 걸렸다고 가정하면 이 상황에서 Delay를 150ms까지 줄 수 없으니까 어떻게 할 것인가의 문제가 생기는데 이 때 사용하는 방법이 Rollback이 있습니다.
Rollback
Rollback은 뒤로 돌린다는 이야기인데요.
Delay가 없는 상황이라고 가정하고, P1이 0에서 펀치버튼을 누르고, P2가 50에서 펀치버튼을 눌렀고, P1의 펀치버튼 값이 100ms가 걸려서 P2에 가고, P2의 펀치버튼 값도 100ms이 걸려서 P1에 150에 갈 것입니다.
그랬을 때 P1에서 펀치버튼 값이 바로 적용 될 것이고, P2도 50ms에서 펀치버튼 값이 적용될 텐데 이 때 양 컴퓨터의 이미지가 서로 다른 이미지를 그리고 있을 것입니다.
이 쪽 상황을 보죠.
P1의 컴퓨터에서는 P1의 주먹이 나갈 것이고, P2에서는 P2가 주먹을 뻗은 상황이 된다 왜냐하면 P1에서 보낸 펀치버튼 값이 100ms에 도착하기 때문입니다.
그래서 지금 상황은 서로 Sync가 맞지 않은 desync상태 입니다.
그럼 이제 이쪽 상황을 보죠.
이 쪽에서 P1의 펀치가 적중했다고 쳐보자 이 때 화면은 P1이 때린 펀치가 P2가 맞았고, P2는 여전히 주먹을 P1에게 치고 있는 상황이 될 것입니다.
한 쪽에서는 주먹이 적중한 화면인데 다른 한 쪽은 주먹을 뻗고 있는 상황이기 때문에 이 역시도 desync상태 입니다.
그런데 이 상황을 보면
P1의 입력값은 막 도착했고, P2의 주먹은 좀 만 있으면 맞는 상황인데, 그 때 P1의 입력이 왔다.
P1의 입력은 100ms에 왔지만 실제 온 값은 100이라는 레이턴시를 뺀 값인 0ms이다.
0ms에서 온 값이기 때문에 지금 현재 상태를 뒤로 돌려 버립니다.
동영상을 보는데 되감기를 했다고 생각하면 됩니다. 그러면 다시 0ms의 상황으로 다시 돌려서 0ms로 다시 옮깁니다.
이 쪽으로 옮겨놓고 다시 시작시킵니다. 그랬을 때 다시 앞으로 감아오면 이 상황이 어떻게 변하냐면
P1이 펀치가 P1의 컴퓨터와 P2의 컴퓨터가 0ms에 똑같이 들어가서 처리되기 때문에 P1과 P2의 컴퓨터에서는 P1 펀치가 적중하는 상황이 발생합니다.
이 때 Sync가 맞아지는 상황이 발생하는 것이죠.
이렇게 Desync되었다가 상대방의 입력값이 들어온 순간 뒤로 돌렸다가 앞으로 돌려 Sync가 맞추는 것을 Rollback인데
LOL게임의 Replay기능이라고 생각하면 됩니다.
하지만 Rollback 방법도 문제가 있는데
위의 그림을 토대로 보면 50ms일 때 P1가 P2에게 주먹을 내지르고 있고, P2화면에서는 주먹을 이제 내지르려 하고 있고,
100ms 일 때 P1은 P2에게 주먹을 적중시켰고, P2는 이제 P1에게 주먹을 적중시키기 직전입니다.
그 때 Rollback이 처리되고나서 양쪽 모두 Sync가 맞추어져서 P1이 P2에게 주먹을 때리는 시점이 발생되게 됩니다. 이 때 Sync가 맞아지게 되는데 P2입장에서 보면 내가 때리고 있는 상황에서 갑자기 화면이 바뀌면서 내가 맞는 상황으로 바뀐 것입니다.
이것을 프레임이 튀었다고 표현하는데 순간이동이 발생하거나, 내가 먼저 때렸는데 내가 맞은 상황이 발생합니다.
전체적으로 보면 P1이 먼저 때렸기 때문에 P2가 맞는게 공정하게 볼 수 있지만 P2입장에서는 내가 먼저 때렸는데 내가 먼저 맞은 것으로 느껴져서 P2 입장에서는 튀었거나 렉 때문에 맞았다고 생각해서 엄밀히 따지면 이게 공정한 것인데 입장에서는 공정한건지 알 수가 없습니다.
두 번째 문제는 만들기가 어렵습니다.
뒤감기 -> 앞감기 입력을 중간에 집어넣기를 구현해야하는데 뒤감기를 만들기 위해서는 지금까지 입력했던 모든 입력값들을 다 기록하고 있어야 하고, 이 입력값들을 뒤로 Rollback하는 기능들을 만들어야 합니다.
앞감기를 만들려면 이 입력들을 앞으로 이동시켜야 합니다.
그렇기 때문에 메모리도 사용하고 복잡성도 올라 갑니다.
그리고 새로운 입력을 중간에 포함해서 처리해주는 것도 다시 만들어 주어야 합니다.
사실 2인용 격투게임은 이것이 그렇게 복잡하지 않습니다. 두 캐릭터의 상태만 기록하면 되기 때문이죠.
그런데 그게 아니라 10 ~ 20명의 플레이어가 늘어났다고 가정하면 각 캐릭터의 모든 상태를 기억하고, Rollback하고, 앞감기를 해야합니다.
예를 들면 A가 B를 공격했고, B가 C를 공격했고, C가 A를 공격했다고 가정했을 때 C가 먼저 쏜 것이라 A를 먼저 맞추었고, A는 B에게 공격을 못했어야 했고, B가 공격을 당하지 말아야 하는 상황이 생깁니다. 그리고 B가 안맞았을 경우에는 B가 C에게 공격한 총알이 안나갔다고 가정했을 때 그걸 Rollback해서 다시 돌려버리면 공정하게 돌아간다 하더라도 유저 입장에서는 모든 상황이 전혀 딴판인 상황이 펼쳐지게 되는 것입니다.
그리고 이걸 모두 처리하는 것도 굉장히 힘든 일이기도 합니다.
그래서 보통은 Delay와 Rollback을 섞어 쓰고, Rollback을 제한적으로 사용하여 유저가 눈치채지 못할 정도로 자연스러운 형태로 사용합니다.
이 두가지를 제대로 사용하고 있는 게임이 '오버워치'인데 시간을 되돌리는 캐릭터인 '트레이서'라는 캐릭터가 Rollback기능을 만드는 부가기능으로 볼 수 있습니다.
Rollback을 만들려면 상태를 기록하고, 뒤로감기, 앞감기 기능을 만들어야 하는데 이것을 만들다 보니 '트레이서'라는 캐릭터가 사용하는 '시간역행'이라는 스킬을 만들기가 쉬워졌다고 볼 수 있습니다.
그리고 LOL게임에서 Replay기능도 상당히 어려운 기술인데, 앞감기와 뒷감기를 만듦으로써 Replay기능을 부가기능으로 쉽게 만드는 장점이 있지만 만들기 어렵다는 단점이 있습니다.
Rollback 또 다른 문제는 중간 접속처리가 어렵습니다.
이 문제는 사실 Rollback의 문제라기 보다 Deterministic방식 자체의 문제로 볼 수 있는데 Deterministic방식이 어떤것이 문제냐면
입력을 기반으로 하고 있습니다. 어떤 상태를 공유하는 게 아니라 입력을 주고 받습니다. 그렇기 때문에 캐릭터의 상태를 만들기 위해서는 그 간의 입력했던 모든 값들이 들어와야 합니다.
예를 들면 내가 게임을 하고 있었는데 한참 플레이를 하다 접속이 끊겨 Disconnected상태가 되어서 다시 재접속(Reconnect)했을 때 내가 지금 게임의 상황을 알기 위해서는 처음의 게임이 시작했던 순간부터 사람들의 입력했던 입력값을 모두 받아서 그것을 현재 시간까지 앞감기를 했을 때 현재 Sync가 맞춰지게 되는데 이게 Deterministic 방식에서 재접속 했을 시 Sync를 맞추는 방법입니다.
그래서 이러한 방법들이 워낙 복잡하기 때문에 어떤 게임에서는 Reconnect처리를 안하기도 합니다.
그리고 Deterministic에서는 Desync상황이 항상 문제가 되며 어디서 깨진건지 잡기가 정말 힘듭니다.
어느 한 순간에 상태가 서로 깨져버리면 그 상태를 공유하지 않는데 서로가 서로를 Sync가 맞고 있다고 믿고 있습니다.
그래서 둘 중 어느 곳 하나라도 살짝 차이가 나더라도 시간이 지나면 그 오차범위의 차이가 커지게 되는데 이 Desync문제는 언제 어디서 나타날지 모르기 때문에 굉장히 잡기가 어렵고, Rollback도 만들기가 어렵습니다. 그래서 Deterministic방식이 좋고, 많은 게임에서 사용 되지만 실제로 구현되기가 매우 어렵습니다.
'Networking' 카테고리의 다른 글
[바미] P2P, Relay 서버 방식에 대해 알아보자! (2) | 2020.12.18 |
---|---|
[바미] 중계서버와 그 외 유용한 방식들에 대해 알아봅시다. (0) | 2020.12.18 |
[바미] Deterministic방식에 대해 알아보자! (0) | 2020.12.18 |
[바미] Protocol TCP,UDP에 대해 알아봅시다. (0) | 2020.12.18 |
[바미] 게임 네트워킹에 대해 (0) | 2020.12.18 |