이번엔 클라이언트 캐릭터의 움직임이 버벅거리는 문제를 수정해보았다.
처음엔 서버에서 강제로 클라이언트의 움직임 오차를 잘못 체크하고 있어 발생한 문제인가 싶어 디테일 패널에서 클라이언트 오차 조정 옵션을 아예 꺼봤는데도 문제가 계속되었다.
지난 포스팅에서 캐릭터의 TickPose가 네트워크가 아닌 메쉬의 Tick에 의해서 작동되도록 했는데, 혹시 TickPose가 내 예상과는 다르게 한 프레임에 여러번 실행되어서 그랬나 싶어서 코드를 고쳐 네트워크에 의한 TickCharacterPose는 막아보았는데도 문제가 계속되었다.
이외에도 포럼을 뒤져가며 갖가지 방법을 시도해봤지만, 해결되지 않았다.
계속 고민하며 테스트를 반복하다보니, 유심히 보니까 캐릭터가 버벅거리는 것처럼 보이는 현상이 캐릭터의 rotation이 이상한 값으로 튀어서라는 것을 알 수 있었다. (상단 영상에서도 마찬가지)
그리고 시험삼아 캐릭터 rotation의 smooth time을 길게 늘려보았더니, 다음과 같이 캐릭터가 빙글빙글 도는 현상이 발생하였다.
또한, 해당 문제는 캐릭터가 앞으로만 갈 때는 괜찮은데, 왼쪽/오른쪽/뒤 방향으로 갈 때만 발생하는 것을 알 수 있었다.
이게 무슨 상황인가 곰곰이 생각해봤더니, 왼쪽/오른쪽/뒤 방향으로 이동시키는 경우는 '본래대로라면 캐릭터가 회전하는 상황'이라는 것을 알게되었다.
본 프로젝트는 게임 특성상 캐릭터의 회전까지 WASD로 가능하게 되면 매우 어려워질 수 있기에, 기본적으로 캐릭터의 회전은 시점과 항상 일치하도록 해두었었다. (UseControllerRotationYaw를 true로 해둠)
하지만 CMC의 bOrientRotationToMovement은 켜둔 채 그대로였고, 이에 따라 서버에서 클라이언트 입력을 시뮬레이션 할때, A/S/D키를 눌렀다면 캐릭터가 회전해야 하는 것으로 간주되어 빙글빙글 도는 것이었다.
또한, 해당 회전 값을 그대로 클라이언트에게 replicate해주기 때문에, 클라이언트 본인 화면에서도 자신의 캐릭터가 서버에 의해 강제로 회전되어 이상하게 보이는 것이었다.
이렇게 우여곡절 끝에 원인을 찾아냈고, 해결은 간단했다. 그냥 bOrientRotationToMovement를 꺼주면 되었다.
AISeeMeCharacter::AISeeMeCharacter()
{
//...
// Configure character movement
GetCharacterMovement()->bOrientRotationToMovement = false; // 캐릭터의 방향은 항상 시점 방향으로 고정이므로, false
//...
}
다음으로, 지금까지의 최적화가 '네트워크 상황이 안좋을 때만' 적용되도록 하고 싶다.
지금까지 한 작업을 정리해보면 다음과 같다.
- CameraBoom에 Lag 추가
- 캐릭터 위치 SmoothTime 증가
- 캐릭터 포즈 틱이 Mesh의 틱에 의해 호출되도록 수정
- bOrientRotationToMovement 비활성화
여기서 1번, 3번, 4번은 네트워크 상황이 좋을 때 그대로 적용되어도 딱히 문제가 없다. 오히려 잠재적인 문제를 막아줄 수 있다.
문제는 2번이다. 네트워크가 원활한데 굳이 보간을 질질 끌어서 소위 '빠릿빠릿한' 느낌을 없앨 필요는 없다.
따라서 핑이 높을 때에만 SmoothTime을 늘리도록 하였다.
void AISeeMeCharacter::BeginPlay()
{
// ...
// 서버에서 네트워크 품질 검사
if (HasAuthority() && !IsLocallyControlled())
GetWorldTimerManager().SetTimerForNextTick(this, &AISeeMeCharacter::CheckNetworkQuality);
}
void AISeeMeCharacter::CheckNetworkQuality()
{
if (!HasAuthority())
return;
if (APlayerState* PS = GetPlayerState())
{
float Ping = PS->GetPingInMilliseconds();
LOG_SCREEN("Ping : %f", Ping);
if (Ping >= 150.f) // 네트워크 상황이 안좋을 경우 보간 시간 늘림
{
LOG_SCREEN("Server: Bad network. Increasing smoothing time");
MulticastIncreaseSmoothingTime();
}
}
}
BeginPlay에서 if문 조건에 !IsLocallyControlled가 들어있는 이유는, '클라이언트가 접속할 때' 핑을 측정하기 위해서이다.
마지막으로 이전에 만들어두었던 CharacterMovementComponent를 상속받은 클래스는 그냥 삭제해버렸다.
원래 CMC 내부 함수를 오버라이딩해서 개조하는 것을 염두에 둔 것이었는데, 작업하다보니 대부분의 문제는 캐릭터 클래스에서 해결이 가능했고, CMC 내부에서 건드릴 것은 결국 SmoothTime 변수 2개밖에 없었는데, 또 이 2개는 public이어서 캐릭터 클래스에서 조작이 가능했기 때문이다.
이렇게 네트워크 렉 최적화 작업을 해보았다.
Before:
After:
이렇게 딱 비교해보니까 확연히 개선된게 느껴진다!
사실 처음에 자료를 찾기 위해 구글링을 했을 때 생각보다 정보가 많지 않아 막막하였다.
실제로 구글에 'unreal multiplayer smooth'라고 검색해도, 내 상황에 딱 맞는 정보는 없었으며,
Fab에 존재하는 SmoothSync이라는 유료 에셋을 설명하는 글이 대다수였다;;
그래도 오히려 그 덕에 정확한 문제 해결을 위해 언리얼 엔진 무브먼트 네트워크의 흐름을 다시 제대로 공부해보고 CMC 엔진 코드도 직접 까보면서 공부해보는 계기가 되었고,
작업하면서도 단순히 외부 자료를 따라하는 것이 아니라 내 사고력과 문제해결력을 발휘할 기회가 많았던 것 같다.
'언리얼 엔진 5 > 멀티플레이어 게임 폴리싱' 카테고리의 다른 글
| [UE5] 멀티플레이어 게임 폴리싱 (4) - 렉 최적화 마무리 (0) | 2025.11.17 |
|---|---|
| [UE5] 멀티플레이어 게임 폴리싱 (2) - 렉 최적화 (카메라, 애니메이션, 보간 시간) (0) | 2025.10.24 |
| [UE5] 멀티플레이어 게임 폴리싱 (1) - 렉 최적화 개요 (0) | 2025.10.16 |