강의 영상을 보고 카트라이더:드리프트가 언리얼 데디케이티드 서버 기반이라는 걸 알았다.
영상의 대략 핵심 내용은 글로벌 서버에서 플레이하는 네트워크 게임 특성상
플레이어(나)는 항상 상대방의 과거의 위치를 볼 수 밖에 없는데,
레이싱 게임의 경우 항상 도착점을 향해 일정한 방향으로 달릴거라는 "기대"를 할 수 있으니
이를 기반으로 클라이언트내에서 서버로 부터 받은 상대방의 위치를 핑차이 만큼 보정하여 미래의 위치를 추측한다는 것이다.
이 강연에 해당하는 구현부는 프로토타입이고, 100ms 이상 High Ping 상황에서 카트의 드리프트 경로를 서버의 상태와 더 정확히 동기화하기 위해 시계열 예측을 사용하는 후속 강연이 NDC에 추가로 공개되어 있다.
https://youtu.be/r4ZaolMQOzE?si=whY5MRnPPzv1zNuV
---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
처음 생각 : movement는 input값을 기반으로 처리하는데, 이 input을 전부 서버에 넘겨줘서 처리하면 어떨까?
-> 한국서버 내에서만 핑이 20ms, 60fps 환경에서 3-4 프레임이 차이 나므로 레이싱 게임에서 부적합함
해법 : 클라이언트 측에서 시뮬레이션을하고 그 값을 서버에주고, 서버는 그 값이 유효한지 validation 검사만 하면됨
-> Client Simulated Model
처음 서버에서 전부 처리하는 방식에서 발생할 수 있는 버그는 다음과 같음
Client1의 카트에선 내가 더 빠르다 !
Client2의 카트에선 내가 더 빠르다 !
실제로는 둘다 같은 위치에 있음에도 서로 자기가 더 빠르다고 착각하게 됨
-> 0.001초 단위까지 나뉘어서 승부가 갈리므로 이렇게 하면 안됨
: Extrapolation이라는 기술을 사용해야함
Extrapolation을 하기 위해서는 4가지 정보가 필요함
클라이언트들은 각 서버에게 4가지 정보를 뿌려줌
Simulated Result Packet
1) Time Stamp : 이 클라이언트가 패킷을 보낸시간
2) Transform : 이 카트의 위치
3) Linear Velocity : 이 카트의 선속도
4) Angular Velocity : 이 카트의 각속도
기타 기능
Hard Snap
Validation
이건 나중에
Tick Sync
먼저 동기화를 하기 위해서 가장 중요한 것은 서로의 시간을 맞추는 것.
서로의 시계가 같다는 보장이 없으므로 서버의 시간을 받아서 서버의 시간으로 비교해주는 것이 중요함
UE에서는 GameStateBase 클래스를 통해 서버가 각 클라이언트들에게 ReplicatedWorldTimeSeconds를
초당 10frame (100ms당 한 번)으로 Replication해주고, 각자의 시계와 서버의 시계를 비교해서 Delta값을 구하고,
서버의 시간을 유추하고 있음
code)
void AGameStateBase::UpdateServerTimeSeconds()
{
ReplicatedWorldTimeSeconds = World->GetTimeSeconds();
}
void AGameStateBase::OnRep_ReplicatedWorldTimeSeconds()
{
ServerWorldTimeSecondsDelta = ReplicatedWorldTimeSeconds - GetTimeSeconds();
}
float AGameStateBase::GetServerWorldTimeSeconds() const
{
return GetTimeSeconds() + ServerWorldTimeSecondsDelta;
}
Time Stamp
Time Stamp 기록 방식
그 유추된 서버 시간을 Client1은 패킷에 찍어서 보내고, Client2는 그 패킷을 받아 Client1과 Client2가 얼마나 시간 차이 나는지
계산하는 공식임 (아래)
code)
CurrentTime = GetServerWorldTimeSeconds();
PingSeconds = CurrentTime - Replicated.TimeStamp;
ExtrapolationDeltaSeconds = PingSeconds * NetPingExtrapolation;
(*NetPingExtrapolation : 조절 파람)
UDP로 수신하기 때문에 순서가 뒤바뀔 수 있고, 손실 문제가 있으므로 TimeStamp로 재검사하여 패킷이 제대로 되었는지 검사가 필요함
Location Extrapolation
그렇다면 위치는 어떻게 Extrapolation할까?
Current(현재 위치)에서 패킷을 보내고, 패킷을 받은 Rep_Position(패킷의 위치)또한 이미 레이턴시 때문에 과거의 위치가 되어버림,
그래서 예측을 해야하는데 위치는 속도 * 시간이므로 이전에 구했었던 ExtrapolationDeltaSeconds에 패킷에 있던 선속도(Rep_LinearVelocity)를 곱하고, 패킷의 위치를 더해주면
예측된 위치, TargetPos가 나오게 됨 근데 이 값을 완전히 믿을 순 없으므로 Position Interpolation을 통해 NewLocation을 유추하고 있음
(------------------------- Position_Lerp -------------------------)
Current ------------- Rep_Location ------------ New Location ------------ TargetPos
code)
TargetPos = Rep_Position + (Rep_LinearVelocity * ExtrapolationDeltaSeconds);
NewLocation = Lerp(Current_Position, TargetPos, Position_Lerp);
(*Position_Lerp : 조절 파람)
조절 파람이라 함은 카트 뿐만 아니라 다른 프로젝트에서 적용할 때, 이 카트의 속도가 얼마나 빠른지, 정확도가 얼마나 되어야하는지에 따라 충분히 조절할 수 있음
UE에서는 기본값으로 0.1을 갖고 있는데, 카트는 속도가 워낙 빠르니 이 시간 사이에 굉장히 많은 거리를 이동할 수 있음, 그래서 0.2 값을 줘서 타 게임보다 굉장히 높은 값을 가짐.
Rotation Extrapolation
각속도 또한 위의 Location Extrapolation이랑 비슷하게 예측하고 있음
각도는 0~360도로 회전하고 있기 때문에 Linear Interpolation보단 Spherical interpolation을 사용하고 있음,
역시 ANGLE_LERP 값을 조절 파람으로 갖고있음
(*ANGLE_LERP : 조절 파람)
레이싱 게임은 보통 커브를 돌 때 굉장히 감속을 하게 되는데, 카트는 특이하게 커브길에서도 감속이 크게 일어나지 않기 때문 (부스터를 쓴채로 드리프트를 쓰면 감속이 적어서 그런듯?)
이 파라미터 값을 크게 가지고 있음
(------------------------- Angle_Lerp -------------------------)
Current ------------ Rep_Rotation ------------ New Angular ------------ TargetQuat
code)
Rep_AngularVelocity.ToDirectionAndLength(Rep_AngVelAxis, Rep_Angvel);
Rep_AngVel = DegreesToRedians(Rep_AngVel);
ExtrapolationDeltaQuaternion = FQuat(Rep_AngVelAxis, Rep_Angvel * ExtrapolationDeltaSeconds);
TargetQuat = ExtrapolationDeltaQuaternion * Rep_Rotation.Quaternion();
NewAngular = Slerp(CurrentState.Quaternion, TargetQuat, Angle_Lerp);
Velocity Extrapolation
이것 또한 마찬가지, Target과 Current의 차를 통해 LinearVelocityCoefficient와 DeltaTime을 통해 다음 Frame의 Extra_LinVel, Extra_AngVel를 유추할 수 있음
code)
Extra_LinVel = (Rep_LinearVelocity) + (LinDiff * LinearVelocityCoefficient * DeltaTime);
Extra_AngVel = (Rep_AngularVelocity) + (AngDiffAxis * AngDiff * AngularVelocityCoefficient * DeltaTime);
(*Coefficient : 조절 파람, *Diff = Target - Current)
Extrapolation Result
그럼 아까 구한 Transform 값, Angular 값, Position 값 그리고 새로운 LinearVelocity 값, 새로운 Angular Velocity 값을 적용하면 0.2초 전에 보낸 패킷도 현재 같이 달리는 것처럼
볼 수 있게 되는 것임
UE에서는 좋은 콘솔 명령어를 가지고 있는데요, 제가 테스트한 콘솔 명령어는
PktLag 패킷 전송을 지정된 밀리초만큼 지연시킵니다
PktLagVariance 패킷 전송 지연 시간에 약간의 가변성을 둡니다. 지정된 밀리초만큼 기간을 증감시키빈다
PktLoss 패킷 손실 시뮬레이션을 위해 나가는 패킷을 지정된 확률로 버립니다
PktDup 지정된 확률만큼 중복 패킷을 전송합니다 (패킷 덤프)
PktOrder 켜면 패킷을 순서없이 전송합니다 (1 = 켜짐, 0 = 꺼짐)
이렇게 스트레스 테스트를 인위적으로 진행가능
200ms 상황에서 충돌 등 거의 동시에 일어나게 보이나, 조금 뚝뚝 끊기는 현상이 있음 -> 200ms의 오차가 있으므로 예측을 하더라도 조금의 오차가 있어 끊긴 것
왜냐하면 플레이어가 0.2초 뒤에 왼쪽 키를 누를지, 오른쪽 키를 누를지, 드리프트를 할지 알 수 없기 때문, 또한 UDP를 이용하므로 패킷 손실이 발생하여
Extrapolation이 종종 실패할 수도 있음
실제 값과 예측 값의 오차가 크거나, 저런 오류가 누적됐을 때, HardSnap을 발생시킵니다
HardSnap을 아까 봤던 텔레포트 되는 현상을 말하는 것
이것도 Interpolation loss?? 보간되면서 움직일 수 있는데, HardSnap을 발생시키는 이유는 오히려 유저들이 생각하기에 Interpolation보다 HardSnap을
덜 버그라고 생각하게 인지한다는 것이 특징임
( : 예측값의 오차가 크거나 오류가 누적된다면 보간하지 않은 예측값을 그대로 때려박는게 실제 유저의 입장에서 더 자연스러워 보일 수 있어 이런 방법을 쓴다는 뜻인듯)
그래서 기존의 예측되어 있던 값들은 버리고 새로 연산된 Rep_LinearVelocity와 TargetPosition 등을 직접 바로 쓴다는 점이 다른 점임
파라미터 튜닝
아까 여러가지 파라미터들이 있었는데요, 이 파라미터들은 각 게임이나 모드랑, 상황마다 다르게 튜닝되어야 합니다
카트라이더안에서도 많은 파라미터가 다르게 작동하게 되는데요, 예를 들어 아까 같은 HardSnap이 굉장히 자주 발생한다면 버그라고 생각하기 쉬움,
그래서 나랑 멀리있는 유저는 나랑 충돌할 일도 없고, 나랑 1등 경합할 일도 없음, 그러므로 나랑 멀리있는 유저는 0.4초 (200ms <->) 이전 과거의 모습을 보여줘도
크게 문제가 되지 않기 때문에 예측을 좀 덜 해주고(파라미터 값을 높게 준다), 가까운 유저는 나랑 경합을 하는 유저고, 몸싸움이 일어날 수 있는 유저기 때문에
HardSnap을 좀 더 빈번하게 발생시켜서 정확한 위치를 표현해 줘야 함.
그리고 카트라이더같이 빠른 게임은 0.2초만에 굉장히 많은 거리를 이동하게 되는데요, HardSnap의 조건이 이런 캐릭터의 속도에 따라 다르게 주어져야 합니다
예를들어 걸어다니는 FPS 게임이다, 하는데 5미터 오차가 날 때까지 HardSnap을 발생시키지 않는다, 라고 조건을 건다면 아무리 총을 쏴도 총이 맞지 않게 되겠죠.
하지만 카트라이더는 굉장히 짧은 시간내에 5미터를 이동하기 때문에 HardSnap 조건을 좀 넉넉하게 줘야함, 에러 누적치도 마찬가지,
외삽비는 아까 여러가지 파라미터 였던 ExtrapolationCoefficient 이런 것들을 얘기하는 것입니다
각 파라미터를 게임/모두/상황에 맞게 튜닝
- 게임마다 특징이 달라, 해당 값들은 Heuristic하게 찾을 수 밖에 없음
- 외삽비
- HardSnap 조건
- 에러누적치
- Validation
'UE5 > Network' 카테고리의 다른 글
[UE5] Replication - Push Model (0) | 2024.07.31 |
---|---|
Listen Server에서 DestroySession()에 관한 궁금증 (0) | 2024.07.05 |
[UE5] Controller in BeginPlay on Server (0) | 2024.07.03 |
Multiplayer Best Practices in UE #NotGDC (1) | 2024.05.07 |
[UE5] 서버-클라이언트 왕복 시간 동기화 (0) | 2024.04.01 |