언리얼 엔진 5/멀티플레이어 게임 폴리싱

[UE5] 멀티플레이어 게임 폴리싱 (4) - 렉 최적화 마무리

ciel45 2025. 11. 17. 03:05

프로젝트 팀장 분과 의논을 한 뒤, 마무리 수정을 몇가지 하게 되었다.

 

우선 NetworkSimulatedSmoothLocationTime, ListenServerNetworkSimulatedSmoothLocationTime 두 변수의 활용에 대해서이다.

 

두 변수의 역할은 다음과 같다.

 

NetworkSimulatedSmoothLocationTime: 클라이언트내 서버 pawn 위치를 보정하는 시간

ListenServerNetworkSimulatedSmoothLocationTime : 서버 내 클라이언트 pawn 위치를 보정하는 시간

 

이전 코드는 서버, 클라이언트 할 것 없이 각각의 머신에서 무조건 저 두 변수의 값을 변경하는 방식으로 했지만,

사실 클라이언트에서는 NetworkSimulatedSmoothLocationTime의 값만,

서버에서는 ListenServerNetworkSimulatedSmoothLocationTime의 값만 바꿔주면 된다.

 

이 점을 고려하여 함수를 새로 만들었다.

// must always be applied to the other person’s character.
void AISeeMeCharacter::SetSmoothCharacterMovement(bool bEnable)
{
	if (GetNetMode() == NM_ListenServer) // server 
	{
		if (bEnable)
		{
			GetCharacterMovement()->ListenServerNetworkSimulatedSmoothLocationTime = 0.3f;
		}
		else
		{
			GetCharacterMovement()->ListenServerNetworkSimulatedSmoothLocationTime = 0.04f;
		}
		GetCharacterMovement()->ResetPredictionData_Client(); // important
	}
	else // client
	{
		if (bEnable)
		{
			GetCharacterMovement()->NetworkSimulatedSmoothLocationTime = 0.4f;
		}
		else
		{
			GetCharacterMovement()->NetworkSimulatedSmoothLocationTime = 0.1f;
		}
		GetCharacterMovement()->ResetPredictionData_Client();
	}
}

 

처음에 인게임에서 SmoothTime을 바꿔도 적용이 되지 않고 한번 나갔다 들어와야 적용이 되는 문제가 발생하였다.

 

결론부터 말하자면, 이에 대한 해결을 위해 중간에 GetCharacterMovement()->ResetPredictionData_Client(); 이 문장이 반드시 필요했다.

 

처음에 문제 해결을 위해 CharacterMovementComponent(CMC) 코드를 분석하여 NetworkSimulatedSmoothLocationTime의 참조를 찾아봤더니, 다음과 같이 쓰이고 있는 것을 볼 수 있었다.

FNetworkPredictionData_Client_Character::FNetworkPredictionData_Client_Character(const UCharacterMovementComponent& ClientMovement)
	: ClientUpdateRealTime(0.f)
	, CurrentTimeStamp(0.f)
	//...
{
	MaxSmoothNetUpdateDist = ClientMovement.NetworkMaxSmoothUpdateDistance;
	NoSmoothNetUpdateDist = ClientMovement.NetworkNoSmoothUpdateDistance;

	const bool bIsListenServer = (ClientMovement.GetNetMode() == NM_ListenServer);
    
        /** 여기에서 사용됨 */
	SmoothNetUpdateTime = (bIsListenServer ? ClientMovement.ListenServerNetworkSimulatedSmoothLocationTime 
    		: ClientMovement.NetworkSimulatedSmoothLocationTime);
	SmoothNetUpdateRotationTime = (bIsListenServer ? ClientMovement.ListenServerNetworkSimulatedSmoothRotationTime 
    		: ClientMovement.NetworkSimulatedSmoothRotationTime);

	const AGameNetworkManager* GameNetworkManager = (const AGameNetworkManager*)(AGameNetworkManager::StaticClass()->GetDefaultObject());
	if (GameNetworkManager)
	{
		//...
}

 

이렇게 FNetworkPredictionData_Client_Character 클래스의 생성자 안에서 SmoothNetUpdateTime, SmoothNetUpdateRotationTime을 정하는데 단 한번 참조되고, 그 뒤에는 사용되지 않고 있었다.

 

게임이 실행되는 중에 NetworkSimulatedSmoothLocationTime, ListenServerNetworkSimulatedSmoothLocationTime을 아무리 바꿔봤자 이미 FNetworkPredictionData_Client_Character는 초기값을 토대로 만들어져서 사용되고 있었을 것이므로, 스무딩이 적용될 턱이 없었다.

 

해결을 위해서는 업데이트된 NetworkSimulatedSmoothLocationTime, ListenServerNetworkSimulatedSmoothLocationTime 변수를 FNetworkPredictionData_Client_Character 생성자의 인자로 하여 FNetworkPredictionData_Client_Character를 새로 다시 만들어줘야 했다.

 

이 방법을 찾기 위해 CMC 코드를 분석하여 찾은 함수가 ResetPredictionData_Client였다.

void UCharacterMovementComponent::ResetPredictionData_Client()
{
	ForceClientAdjustment();
	if (ClientPredictionData)
	{
		delete ClientPredictionData;
		ClientPredictionData = nullptr;
	}
}

ClientPredictionData를 싹 지워주며,

ClientPredictionData가 바로 FNetworkPredictionData_Client_Character 포인터 변수이다.

 

ClientPredictionData가 지워지면, CMC는 PredictionData를 다시 만들어내려 할 것이고, 이 때는 변경한 SmoothTime이 잘 반영될 터였다.

 

실제로 테스트 결과, 의도대로 잘 작동하는 것을 볼 수 있었다.


두번째 변경점으로,

네트워크 스무딩 적용 여부는 게임이 시작할 때의 핑 측정 결과에 따라서가 아닌, 옵션 설정에 따라서로 하기로 했다.

 

여러가지 이유로 게임이 시작될 때의 핑 환경이 평소보다 좋을 수도, 나쁠 수도 있기 때문.

 

따라서 옵션 UI를 다음과 같이 만들어주었다.

 

 

그리고 활성화/비활성화 콜백 함수는 다음과 같다.

void UISMOptionPanel::SetSmoothCharacterMovement(bool bEnable)
{
	if (UISMGameInstance* GI = GetGameInstance<UISMGameInstance>())
	{
		GI->bEnableSmoothCharacterMovement = bEnable;
		GI->SaveGame();

		SetNetworkSettingUI();

		if (APlayerController* PlayerController = GI->GetFirstLocalPlayerController())
		{
			if (AISMPlayerController* ISMPlayerController = Cast<AISMPlayerController>(PlayerController))
			{
				if (AISeeMeCharacter* OtherCharacter = ISMPlayerController->GetOtherCharacter())
				{
					OtherCharacter->SetSmoothCharacterMovement(bEnable); // 플레이어에게 적용
				}
			}
		}
	}
}

 

OtherCharacter->SetSmoothCharacterMovement(bEnable); 가 위에서 만든 함수를 사용하는 부분이다.

 

OtherCharacter에 대해 적용하는 것이 핵심이다. 움직임을 부드럽게 하고 싶은 캐릭터는 내 캐릭터가 아니고 상대방 캐릭터이기 때문.

 

 

 

변경사항은 여기까지가 되겠다.

수정사항은 재차 팀장 분과 테스트해서 문제 없는 것을 확인하고, 스팀에 업데이트로 올리게 되었다.