소스엔진 멀티플레이어 네트워크 : 기본


https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking
https://pastebin.com/2bBFijFY

위키의 내용을 먼저 그대로 적어본다.
문장을 다듬거나 부연 설명이 필요한 것은 천천히 수정할 예정이다.


소스엔진의 멀티플레이어 게임은 클라이언트-서버 아키텍쳐를 사용한다. 보통 서버가 월드 시뮬레이션, 게임 룰, 플레이어 인풋 처리에 권위적인 별도의 게임을 구동한다. 클라이언트는 게임 서버와 연결된 플레이어의 컴퓨터이며 서버와 클라이언트는 서로 작은 패킷을 높은 빈도로 보내는 것으로 소통한다. (보통 초당 20~30개의 패킷을 보낸다.) 클라이언트는 현재 월드 상태를 서버로부터 전달받아 해당 업데이트를 바탕으로 비디오와 오디오 출력을 생성한다. 또한 입력 장치들 (키보드, 마우스, 마이크 등)의 데이터를 처리하고 처리된 데이터를 서버의 처리에 쓰기 위해 전달한다.
클라이언트들은 오직 게임 서버와 연결되며 서로 연결되진 않는다. (P2P 같은 연결이 아님.) 싱글 플레이어 게임와는 다르게 멀티플레이어 게임은 패킷 기반의 소통 방식에서 생기는 다양한 문제점들을 다루게 된다.

네트워크 대역폭은 한정적이므로 월드의 모든 변경 사항에 새로운 업데이트 패킷을 클라이언트들에게 보낼 순 없다. 대신에 서버에서 현재 월드 상태를 담은 스냅샷을 끊임없이 클라이언트에게 중계할 순 있다. 네트워크 패킷은 클라이언트와 서버 사이를 이동하는데 일정한 시간(핑 시간의 절반)이 걸리므로 클라이언트의 시간은 언제나 서버의 시간보다 살짝 못미친다. 클라이언트의 입력 데이터 패킷 또한 패킷 이동으로 인한 지연이 생기므로 서버에서는 유저의 명령어를 임시로 지연한다. 또한 각 클라이언트의 네트워크 지연 시간은 게임 외의 다른 프로그램에 의한 패킷과 프레임 레이트로 서로 다른 지연 시간을 가지게 된다. 이런 시간 차이는 서버와 클라이언트 사이의 논리적인 문제를 야기할 수 있고 지연 시간이 더 늘어난다면 문제가 더욱 커진다. 빠른 속도의 액션 게임에서는 아주 조금의 Milliseconds의 지연도 게임 플레이에 렉이 있다고 느낄 수 있으며 다른 플레이어를 적중하거나 움직이는 물체에 상호작용 하는 것이 어려워진다. 게다가 대역폭 제약과 네트워크 지연으로 패킷 손실이 일어나 정보를 잃을 수도 있다.

이런 네트워크 소통의 문제에 대처하기 위해 데이터 압축, 렉 보정과 같은 클라이언트에게 보이지 않는 기술을 사용했으며 클라이언트는 받은 데이터를 예측하고 보간하여 경험을 더욱 향상시켰다.

기본 네트워킹

서버는 ‘틱’이라는 분리된 시간 간격을 기준으로 게임을 시뮬레이션 하는데 기본적으로 이 간격은 15ms, 따라서 66.66.. 틱이 매 초마다 시뮬레이션된다. 모드는 이 간격을 마음대로 바꿀 수 있다. 매 틱마다 서버는 유저들의 명령어를 처리하고 물리적인 시뮬레이션을 수행, 게임 룰을 확인한 후 모든 오브젝트의 상태를 업데이트한다. 틱을 시뮬레이트 한 후에 서버가 클라이언트 중에 월드 업데이트가 필요하다고 판단되면 현재 월드 상태를 담은 스냅샷을 만든다. 높은 틱레이드일 수록 시뮬레이션의 정확도는 높아지지만 서버와 클라이언트에 더 많은 CPU 파워와 대역폭을 요구한다. 서버 관리자는 srcds의 명령어 매개변수 -tickrate <수치>를 설정해 틱레이트를 변경할 수 있지만 해당 모드에서 변경된 틱레이트에 정상적으로 작동하지 않을 수도 있으므로 추천하지 않는다.

+ -tickrate 명령어는 CSS, DoD S, TF2(66), 그리고 L4D, L4D2(30)에서는 사용할 수 없다. 위의 게임들은 틱레이트 변경이 서버 타이밍에 문제를 일으킬 수 있다.

클라이언트들은 보통 한정된 양의 대역폭을 가지고, 최악의 경우에는 모뎀으로 연결하여 초당 5~7KB 보다 많이 받을 수도 없을 것이다. 서버가 이런 클라이언트에게 더 높은 데이터 속도로 업데이트를 한다면 패킷 손실은 피할 수 없다. 그래서 클라이언트는 서버에게 자신의 대역폭 용량을 ConVar rate (byte/sec)로 설정하여 명시한다.
이건 클라이언트에게 가장 중요한 네트워크 변수이므로 최적의 게임 플레이 경험을 위해 적절히 설정되어야 한다. 또한 일정한 스냅샷 레이트 값을 변경하기 위해 cl_updaterate (기본: 20)에 설정한 수치만큼 서버에게 받는 것을 요청할 수 있지만 서버는 언제나 자기가 설정한 틱레이트를 넘거나 클라이언트의 rate를 넘지 않는 선에서 데이터를 전송한다. 서버 관리자는 sv_minrate, sv_maxrate (byte/sec)를 설정하여 클라이언트에 최소/최대 레이트를 지정해줄 수 있다. (스냅샷의 경우,  sv_minupdateratesv_maxupdaterate (snapshots/sec))

클라이언트는 서버와 같이 실행되는 틱에서 자신의 입력 장치를 처리하여 유저 명령어를 만들고 있으며 이는 기본적으로 플레이어의 현재 키보드와 마우스 상태의 스냅샷이다. 유저 명령어는 새로 생길 때마다 서버에게 보내진 않고 명령어 패킷을 초당 일정한 비율로 보내는데 (보통 30), 따라서 2개 이상의 유저 명령어가 같은 패킷 안에 담겨서 올 수 있다. 클라이언트는 이 명령어 전송 비중을 ConVar cl_cmdrate의 값을 높여 조작의 민감도를 높일 순 있지만 내보내는 트래픽(Outgoing)도 높이게 된다.

게임의 데이터는 네트워크 부하를 줄이기 위해 델타 압축으로 압축되므로 서버가 마지막으로 확인한 업데이트에서 바뀐 점만 담은 스냅샷을 만들게 하여 매번 모든 월드 스냅샷을 보내지 않게 하기 위해 클라이언트와 서버 사이에서 매 패킷을 보내는 데이터 전송 간에 인증용 번호가 붙는다. 풀-스냅샷(델타가 적용되지 않은 스냅샷)은 게임이 시작되거나 클라이언트가 심한 패킷 손실을 몇 초간 겪을 경우에 보낸다. 클라이언트는 명령어 cl_fullupdate로 수동으로 풀-스냅샷을 요청할 수 있다.

플레이어의 조작과 게임에서 보이는 피드백 사이의 시간을 의미하는 민감도는 여러가지 요인으로 결정되는데 서버와 클라이언트의 CPU 로드, 시뮬레이션 틱레이트나 데이터와 스냅샷 업데이트 설정, 그 중 가장 영향을 미치는 네트워크 패킷 전송 시간이 있다. 그 시간 사이에 클라이언트는 유저 커맨드를 보내고 서버는 거기에 응답하여 클라이언트가 지연시간(또는 핑)을 그 응답으로 받게된다. 낮은 지연시간은 멀티 플레이어 온라인 게임에서 특히 큰 장점이 되므로 예측이나 렉 보정과 같은 기술은 연결이 느린 유저도 공정한 게임에 참여할 수 있도록 한다. 네트워크 설정을 바꾸는 것은 필요한 대역폭이나 CPU 파워가 있다면 더 좋은 경험을 하도록 도움을 줄 순 있겠지만 부적절한 설정으로 인한 부작용이 있을 수 있어 기본 설정을 사용하는 것을 추천한다.

TickRate 명령어를 지원하는 게임들

아래 게임들은 -tickrate 매개변수를 수정하여 틱레이트를 변경할 수 있다.

  • Counter-Strike: Global Offensive
  • Half-Life 2: Deathmatch

아래 게임들은 서버 타이밍에 문제가 생겨 틱레이트를 바꿀 수 없다.

  • Tickrate 66
    • Counter-Strike: Source
    • Day of Defeat: Source
    • Team Fortress 2
  • Tickrate 30
    • Left 4 Dead
    • Left 4 Dead 2
엔티티 보간

기본적으로 클라이언트는 초당 20개의 스냅샷을 전달받는데 이 스냅샷들을 받는 때에만 내부에 적힌 위치 값을 이용해 렌더한다면 움직이는 사물과 애니메이션이 자주 끊겨 불안해보이게 된다. 또한 누락된 패킷도 눈에 띄는 문제가 될 수 있다.
이런 문제를 해결하기 위한 비결은 렌더를 위한 시간을 되돌아가서 최근에 받은 두 스냅샷 사이의 위치와 애니메이션의 사이를 지속적으로 보간할 수 있도록 하는 것이다. 매 초마다 20개의 스냅샷을 받는다면 50 milliseconds마다 새로운 업데이트를 받는 것이니 클라이언트의 렌더를 50 milliseconds 후에 하게 한다면 엔티티들은 항상 최근에 받은 스냅샷과 현재 받은 스냅샷 사이를 보간할 수 있게 된다.

소스엔진에서는 이 보간 지연시간의 기본 값을 100 milliseconds (cl_interp 0.1)로 설정해두어 만약에 스냅샷 하나가 누락되더라도 2개의 스냅샷으로 그 사이를 보간할 수 있도록 해둔다. 아래에서 월드 스냅샷이 도착하는 시점의 수치를 보여준다.

클라이언트가 마지막 스냅샷을 받은 시점은 틱 344, 10.30초가 지난 때다. 클라이언트의 시간은 이 스냅샷과 프레임 레이트를 기준으로 증가한다. 예제에서 새롭게 렌더될 비디오 프레임의 시간은 현재 클라이언트의 시간인 10.32와 보간 시간 0.1초를 뺀 10.22초에 스냅샷 340와 342 사이의 모든 엔티티와 애니메이션을 보간한다.

보간 지연 시간을 0.1초로 설정해두었으니 보간 자체는 스냅샷 342가 패킷 손실로 인해 없어져도 340~344 사이의 스냅샷을 사용할 수 있게 된다. 연속된 2개 이상 스냅샷을 잃었다면 보간은 히스토리 버퍼에 아무것도 남지 않아 정확히 이뤄질 수 없게 된다. 이 경우에는 렌더러 측에서 엔티티들의 히스토리를 기반으로 단순한 선형 보외를 수행한다. (cl_extrapolate 1) 보외는 0.25초 간의 패킷 손실을 위해 수행되며 (cl_extrapolate_amount) 더 큰 시간 설정은 예측의 에러가 너무 커질 수 있다.

이런 엔티티 보간으로 인해 100 milliseconds의 일정한 지연(렉)이 발생한다. 마찬가지로 서버의 호스트가 같은 기계의 클라이언트에서 직접 플레이하고 있어도 해당되지만 그렇다고 이게 누군가를 조준할 때 클라이언트의 엔티티 보간이 서버 사이드의 렉 보정에 영향을 미칠 것을 염두하여 조작해야 할 필요가 있다는 것을 의미하는 것은 아니다. (서버의 렉 보정은 클라이언트의 엔티티 보간을 알고 있어 이런 에러를 해소한다.)

Tip: 최근은 소스 엔진 게임들은 ConVar cl_interp_ratio를 가지는데 이것과 cl_interp을 0으로 설정하는 것을 통해 쉽고 안전하게 보간 지연시간을 줄일 수 있다. 그리고 cl_updaterate를 증가한 뒤, (서버 틱레이트에 제한되는 유용한 네트워크 대역폭 제한) net_graph 1을 통해 최종적인 보간 지연시간을 확인할 수 있다.

Note: 서버에서 sv_showhitboxes를 활성화한 경우,(소스 2009에선 되지 않음) 서버 타임에서의 플레이어 히트박스를 볼 수 있다. 보간 지연을 통해 연산된 후 렌더된 플레이어 모델 앞에 보이는 박스인데 완벽하게 평범하다!
입력 예측

150 milliseconds의 네트워크 지연시간을 가진 플레이어가 앞으로 이동했다고 가정해보자. 그 정보는 명령어가 할당된 키가 눌렸고 이 키에 저장된 명령어인 +FORWARD를 서버에 전송한다. 그럼 그 명령어를 플레이어 movement 코드에서 명령어를 읽고 처리하여 플레이어의 캐릭터가 게임 내에서 앞으로 움직이게 된다. 이런 월드 상태의 변경점은 모든 클라이언트가 받게될 다음 스냅샷 업데이트에 반영한다. 그래서 이렇게 움직이는 플레이어가 키를 누른 후 150 milliseconds가 지나야 다른 이들에게 그 움직임이 보이게 된다. 이런 지연은 모든 플레이어의 액션 (movement, 무기 발사 등등)에 해당되고 지연시간이 높아질수록 문제는 더욱 심해진다.

플레이어 입력과 보이는 피드백 사이의 지연은 부자연스럽고 이상한 느낌을 주어 움직이거나 조준을 하기 어렵게 만든다. 클라이언트 사이드의 입력 예측은 (cl_predict 1) 이런 지연을 없애고 플레이어의 액션을 즉각적이게 하는 방법인데, 서버가 플레이어의 위치를 업데이트 하는 것을 기다리는 대신 로컬 플레이어가 유저 명령어의 결과를 예측한다. 따라서 클라이언트는 서버에서 해당 유저 명령어를 처리하는 코드와 같은 정확히 같은 코드를 사용한다. 예측이 끝난 뒤에는 서버에서는 여전히 예전 위치로 플레이어를 바라보지만 로컬의 플레이어는 새로운 지점으로 즉시 움직이게 된다.

150 milliseconds가 지난 후에 클라이언트가 서버의 스냅샷을 받으면 그 안에 방금 예측했던 유저 명령어가 있게 된다. 받은 스냅샷에서 서버의 위치값과 예측했던 위치 값을 비교하고 만약 다르면 클라이언트가 다른 엔티티들과 환경에 대한 올바른 정보를 가지고 있지 않아 예측 에러가 발생한 것이고 서버는 클라이언트 사이드의 예측에 최종 권한을 가지고 있어 클라이언트 측에서 자신의 위치를 서버의 위치값으로 교정해야 한다. cl_showerror 1가 설정되었다면 클라이언트에 예측 에러가 발생할 때 직접 확인할 수 있다. 예측 에러의 교정 작업은 여러모로 눈에 띄고 시야를 엉뚱하게 만들 수 있어서 에러를 정해둔 짧은 시간 동안 (cl_smoothtime) 서서히 교정한다. 이 작업은 cl_smooth을 0으로 설정해 끌 수 있다.

렉 보정

렉 보정과 관련된 모든 소스코드는 소스 SDK에 있다. 구현 세부사항은  Lag compensation 문서 참고

플레이어가 클라이언트 시간 10.5초에 타켓을 향해 무기를 발사했다고 치자. 발사한 정보는 유저 명령어로 채우고 서버에 전송한다. 이 패킷이 네트워크에서 도는 동안 서버에서는 월드를 계속해서 시뮬레이트하고 타켓이 다른 지점으로 움직였을 수도 있다. 그 후에 유저 명령어가 서버 시간 10.6초에 도착했을 때, 타켓에 정확히 조준했어도 서버에서는 타켓을 맞추지 않았다고 파악할 수 있다. 이런 에러는 서버 사이드의 렉 보정으로 교정될 수 있다.

렉 보정은 최근 1초 간 플레이어들의 위치 히스토리를 저장하여 유저 명령어가 실행되었을 때에 서버가 그 명령어를 언제 실행할 지를 계산한다.

명령어 실행시간 = 현재 서버 시간 – 패킷 지연 시간 – 클라이언트 보간 시간

그러고 서버에서 모든 플레이어들을 그 명령어 실행시간에 있던 위치로 다시 이동시킨 뒤에 유저 명령어를 실행하면 조준이 올바르게 맞았다고 감지하게 되고 그 후에 플레이어들을 다시 원래 자리로 되돌린다.

Note: Since entity interpolation is included in the equation, failing to have it on can cause undesired results.

서버 호스트랑 같은 기계에서 플레이 중이면 sv_showimpacts 1을 통해 서버와 클라이언트 간의 히트박스 차이를 확인해 볼 수 있다.

이 스크린샷은 net_fakelag을 통해 200 milliseconds의 지연을 가진 서버에서 무기가 적중한 직후의 시점을 찍었다. 클라이언트 상의 타켓의 위치인 빨간 히트박스(100ms + 보간 지연 시간)가 유저 명령어가 도는 동안에도 계속해서 왼쪽으로 이동하는 모습을 볼 수 있다. 유저 명령어가 도착하고 나서는 유저 명령어가 실행된 시점의 위치로 되돌려지고 (파란 히트박스) 서버는 발사를 트레이스하여 적중한 것을 확인한다. (클라이언트는 혈흔 이펙트를 보게 된다.)

클라이언트와 서버의 히트박스는 시간 측정으로 인한 작은 정확도 에러가 있어 정확히 일치하지는 않는다. 고작 몇 milliseconds의 차이로도 빠르게 움직이는 오브젝트에서 서버와 클라이언트 간의 보이는 간격이 몇 인치나 차이날 수 있다. 멀티 플레이어의 적중 판정은 픽셀 단위로 완벽하지 않으며 해당 틱레이트와 움직이는 물체들의 속력에서 생기는 정확도 제약이 존재한다.

그렇다면 왜 서버에서 적중을 감지하는게 이렇게 복잡한 것인지 궁금해 할텐데 서버에서 플레이어들의 위치를 어렵게 추적하고 정확도 문제를 겪는 것보단 클라이언트 사이드에서 쉽게 픽셀 단위의 정확도로 적중을 감지해낼 수 있고 서버에 그냥 플레이어가 적중했고 어디서 맞았는지 ‘hit’ 메세지에 담아서 보내면 될텐데 말이다. 물론 이건 게임 서버가 이런 중요한 결정에서 클라이언트를 신뢰할 수 없기 때문이다. 클라이언트가 변조되지 않고 VAC (Valve Anti-Cheat) 보호를 받고 있다고 해도 패킷은 제 3의 장치로 인해 변경될 수 있다. 이런 ‘치트 프록시’는 VAC에 감지되지 않고도 ‘hit’ 메세지를 네트워크 패킷에 삽입할 수 있다. (중간자 공격이라고도 한다.)

네트워크 지연과 렉 보정은 현실과 비교했을 때 비논리적으로 보이는 모순들을 만들 수 있다. 예를 들어, 당신이 이미 엄폐를 해서 다른 사람이 분명 볼 수 없는 위치임에도 적중될 수 있다. 서버가 공격한 사람이 발사를 했던 시점으로 당신의 히트박스를 되돌렸을 때, 노출된 자리였었기 때문에 발생할 수 있었던 것이다. 이런 불일치 문제는 상대적으로 패킷의 속도가 느리므로 일반적으로 해결될 수 없다. 현실에서라면 (이런 패킷 역할인)빛이 엄청 빠르기 때문에 같은 세계에 있는 모든 사람들이 당장에 일어나는 일을 바로 볼 수 있어서 이런 문제를 눈치챌 순 없었겠지만 말이다.

네트워크 그래프

소스엔진에서는 클라이언트의 연결 속도와 품질을 확인할 수 있는 몇 가지 도구를 제공한다. 그 중 가장 유용한 네트워크 그래프(net_graph 2 또는 +graph)는 오는 패킷을 오른쪽에서 왼쪽으로 움직이는 조그만한 선으로 표시해준다. 만약 선 사이에 틈이 보인다면 패킷이 손실되었거나 순서를 잃은 채로 도착한거다. 선들은 각 데이터의 종류에 따라 색으로 분류된다.

네트워크 그래프의 아래에서 처음 보이는 줄은 현재 렌더된 프레임(fps)을 나타내고 평균적인 지연시간(ping), 현재 스냅샷 레이트 값 (cl_updaterate)이다. 두 번째 줄은 마지막으로 온 패킷(스냅샷)의 사이즈, 평균 대역폭 그리고 초당 받은 패킷의 수를 나타낸다. 세 번째 줄은 같은 데이터지만 빠져나가는 패킷을 보여준다. (유저 명령어들)

최적화

기본적인 네트워크의 설정 값은 게임 전용 서버의 인터넷 환경을 위해 설계되었지만 대부분의 클라이언트나 서버의 하드웨어와 네트워크 설정에 잘 작동되도록 균형이 이뤄져 있다. 인터넷 환경의 게임을 위해 클라이언트가 조정해야 하는 ConVar는 자신의 초당 bytes/sec 대역폭을 명시할 수 있는 rate이다. 여기에 설정할 적절한 값은 모뎀의 경우는 4500, ISDN은 6000, DSL은 10000 이상으로 설정하는 것이 좋다.

서버와 모든 클라이언트들이 가용할 하드웨어 자원이 충분하고 높은 성능의 네트워크 환경이라면 대역폭과 틱레이트 설정을 설정하여 게임 플레이에 더 정확한 정확도를 얻을 수 있다. 서버 틱레이트를 높이는 것은 일반적으로 움직임과 적중의 정확도를 개선할 수 있지만 CPU의 비용이 상승한다. 틱레이트를 100으로 설정하고 구동한다면 기본 틱레이트인 66보다 약 1.5배 높은 CPU 로드를 가져오므로 이게 심각한 연산 지연의 원인이 될 수 있다. 특히 많은 사람들이 한번에 발사를 하게 된다면 말이다. 이 경우에는 중요한 상황에 필요할 CPU 자원을 위해 기본 틱레이트인 66에서 더 높게 설정하지 않는 것을 추천한다.

Note: tickrate 명령어를 지원하는 게임들 항목에서 소스엔진 게임들 중에서 틱레이드 설정이 가능한 게임을 확인할 수 있다.

만약 게임 서버가 높은 틱레이트에서 가동 중이라면 클라이언트는 가용할 대역폭을 확보하는 선에서 스냅샷 레이트 값(cl_updaterate)과 유저 명령어 레이트 값(cl_cmdrate)을 높여볼 수 있다. 스냅샷 레이트 값은 서버 틱레이트에 의해 제한되며 서버는 틱당 하나보다 많은 스냅샷을 보낼 수 없다. 따라서 틱레이트가 66으로 설정된 서버에서는 클라이언트의 스냅샷 레이트(cl_updaterate)의 최댓값이 66이 된다. 혹여나 이 값을 높여봤는데 패킷 손실이나 포화가 된다면 값을 다시 낮춰야 한다. 스냅샷 레이트 값을 높여봤다면 보간 지연시간(cl_interp)을 낮춰볼 수도 있다. 보간 지연시간의 기본 값은 cl_updaterate의 기본 값인 20에서 파생된 0.1초이며 이로 인해 움직이는 플레이어가 정지된 플레이어를 1초 정도 더 일찍 볼 수 있게 된다. 이런 효과를 피할 순 없지만 보간 지연 시간을 낮춰서 현상을 줄여볼 순 있다. 만약 양쪽 플레이어 모두 움직이고 있다면 보간 지연 시간으로 인한 효과는 없어지게 된다.

스냅샷 레이트와 보간 지연 시간의 관계는 아래와 같다.

interpolation period = max( cl_interp, cl_interp_ratio / cl_updaterate )

“Max(x,y)”는 이들 중 가장 값이 높은 것을 의미한다. cl_interp를 0으로 설정하여도 여전히 안정된 수치의 보간 값을 가지고 있지만 여기서 cl_updaterate를 낮추면 보간 시간을 더욱 낮출 수 있게 된다. 스냅샷 레이트 값이 틱레이트보다 초과하게 된다면 제어할 수 있는 데이터보다 많이 들어와 넘쳐버릴 수도 있다.

  • 무슨 짓을 하는지 100% 확신할 수 없다면 콘솔 설정 값을 바꾸지 말라
    서버나 네트워크가 부하를 감당할 수 없다면 대부분의 높은 성능에서의 설정 값은 오히려 반대의 효과를 불러일으킨다.
  • 보간이나 렉 보정을 끄지 말라
    그게 움직임이나 발사 정확도를 개선하진 않는다.
  • 한 클라이언트를 위한 최적화된 설정은 다른 클라이언트에게는 먹히지 않을 수 있다
    그저 다른 클라이언트의 설정을 그대로 사용하지 말고 당신의 시스템을 확인해보라
  • 관전자나 SourceTV에서 플레이어를 1인칭으로 관전하고 있다면 그 플레이어가 보는 것을 정확히 따라서 보지 않는다
    관전자는 렉 보정이 없는 상태로 관전하기 때문이다.
,

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다