[UE4/CameraManager] 서버의 CameraManager와 Client POV
- -
PlayerCameraManager의 멀티 환경 옵션에는 'bUseClientSideCameraUpdates' 라는 옵션이 있다.
해당 옵션은 PlayerController의 TickActor 를 Entry로 잡고 확인해볼 필요가 있다.
// Send a camera update if necessary.
// That position will be used as the base for replication
// (i.e., the origin that will be used when calculating NetCullDistance for other Actors / Objects).
// We only do this when the Pawn will move, to prevent spamming RPCs.
if (bIsClient && bIsLocallyControlled && GetPawn() && PlayerCameraManager->bUseClientSideCameraUpdates)
{
UPawnMovementComponent* PawnMovement = GetPawn()->GetMovementComponent();
if (PawnMovement != nullptr &&
!PawnMovement->IsMoveInputIgnored() &&
(PawnMovement->GetLastInputVector() != FVector::ZeroVector || PawnMovement->Velocity != FVector::ZeroVector))
{
PlayerCameraManager->bShouldSendClientSideCameraUpdate = true;
}
}
위 코드는 PlayerController의 TickActor의 일부이다. 이를 확인해보면 알 수 있듯, 클라이언트가 로컬 컨트롤되고 있으며,
'bUseClientSideCameraUpdates' 변수가 활성화 되어있으면, PlayerCameraManager의 'bShouldSendClientSideCameraUpdate' 가 활성화 됨을 알 수 있다.
그럼 이제 PlayerCameraManager에서 'bShouldSendClientSideCameraUpdate' 가 사용되는 로직을 살펴보자.
if ((PCOwner->Player && PCOwner->IsLocalPlayerController()) || !bUseClientSideCameraUpdates || bDebugClientSideCamera)
{
DoUpdateCamera(DeltaTime);
const float TimeDilation = FMath::Max(GetActorTimeDilation(), KINDA_SMALL_NUMBER);
TimeSinceLastServerUpdateCamera += (DeltaTime / TimeDilation);
if (bShouldSendClientSideCameraUpdate && IsNetMode(NM_Client))
{
SCOPE_CYCLE_COUNTER(STAT_ServerUpdateCamera);
const AGameNetworkManager* const GameNetworkManager = GetDefault<AGameNetworkManager>();
const float ClientNetCamUpdateDeltaTime = GameNetworkManager->ClientNetCamUpdateDeltaTime;
const float ClientNetCamUpdatePositionLimit = GameNetworkManager->ClientNetCamUpdatePositionLimit;
FMinimalViewInfo CurrentPOV = GetCameraCachePOV();
FMinimalViewInfo LastPOV = GetLastFrameCameraCachePOV();
FVector ClientCameraPosition = FRepMovement::RebaseOntoZeroOrigin(CurrentPOV.Location, this);
FVector PrevClientCameraPosition = FRepMovement::RebaseOntoZeroOrigin(LastPOV.Location, this);
const bool bPositionThreshold = (ClientCameraPosition - PrevClientCameraPosition).SizeSquared() > (ClientNetCamUpdatePositionLimit * ClientNetCamUpdatePositionLimit);
if (bPositionThreshold || (TimeSinceLastServerUpdateCamera > ClientNetCamUpdateDeltaTime))
{
// compress the rotation down to 4 bytes
int32 const ShortYaw = FRotator::CompressAxisToShort(CurrentPOV.Rotation.Yaw);
int32 const ShortPitch = FRotator::CompressAxisToShort(CurrentPOV.Rotation.Pitch);
int32 const CompressedRotation = (ShortYaw << 16) | ShortPitch;
int32 const PrevShortYaw = FRotator::CompressAxisToShort(LastPOV.Rotation.Yaw);
int32 const PrevShortPitch = FRotator::CompressAxisToShort(LastPOV.Rotation.Pitch);
int32 const PrevCompressedRotation = (PrevShortYaw << 16) | PrevShortPitch;
if ((CompressedRotation != PrevCompressedRotation) || !ClientCameraPosition.Equals(PrevClientCameraPosition) || (TimeSinceLastServerUpdateCamera > ServerUpdateCameraTimeout))
{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (ClientCameraPosition.X > 1048576.0f || ClientCameraPosition.X < -1048576.0f ||
ClientCameraPosition.Y > 1048576.0f || ClientCameraPosition.Y < -1048576.0f ||
ClientCameraPosition.Z > 1048576.0f || ClientCameraPosition.Z < -1048576.0f)
{
UE_LOG(LogPlayerCameraManager, Warning, TEXT("ClientCameraPosition %f %f %f doesn't fit in FVector_NetQuantize for ServerUpdateCamera, capping"), ClientCameraPosition.X, ClientCameraPosition.Y, ClientCameraPosition.Z);
}
#endif //!(UE_BUILD_SHIPPING || UE_BUILD_TEST)
const float MaxQuantize = 1048575.f;
const float MinQuantize = -1048575.f;
ClientCameraPosition.X = FMath::Clamp(ClientCameraPosition.X, MinQuantize, MaxQuantize);
ClientCameraPosition.Y = FMath::Clamp(ClientCameraPosition.Y, MinQuantize, MaxQuantize);
ClientCameraPosition.Z = FMath::Clamp(ClientCameraPosition.Z, MinQuantize, MaxQuantize);
PCOwner->ServerUpdateCamera(ClientCameraPosition, CompressedRotation);
TimeSinceLastServerUpdateCamera = 0.0f;
}
}
bShouldSendClientSideCameraUpdate = false;
}
}
위 코드를 살펴보면, 클라이언트 환경에서 로컬컨트롤 되고 있고 'bShouldSendClientSideCameraUpdate' 가 활성화 되어있는 경우, 카메라의 변위차가 임계보다 큰 경우 PlayerController의 ServerUpdateCamera를 통해 클라이언트 상 카메라의 Location과 Rotation 값을 업데이트 할 수 있도록 송신하는 것을 알 수 있다.
이렇게 데이터를 수신 받은 서버쪽 PlayerController의 ServerUpdateCamera에서는 PlayerCameraManager의 FillCameraCache를 수행하는데, 이를 통해 ServerSide에서 수정된 POV를 갖게 된다.
이 과정은 다음과 같은 상황에서 주의해야 한다.
- 클라이언트의 캐릭터가 일시적으로 MovementComponent가 비활성화된 상태로 다른 액터의 하위로 붙어있어야 하는 경우, 'bShouldSendClientSideCameraUpdate' 가 활성화될 수 없어 서버쪽에서 클라이언트의 PlayerCameraManager가 가진 POV가 갱신되지 않아 'GetPlayerViewPoint' 함수에서 미갱신된 값을 반환할 수 있다.
위 상황의 결과는 네트워크상에서 IsNetForRelevant나 WorldComposition, WorldPartition과 같이 클라이언트의 위치값을 'GetPlayerViewPoint'로 인지하는 모듈에서 치명적인 결과를 발생시킨다.
'개발 > UE4' 카테고리의 다른 글
[UE4/Tips] EffectQuality 0에서 Realtime Capture 결과가 타는 경우 (0) | 2023.05.28 |
---|---|
[UE/GAS]게임 플레이 이펙트의 Period 옵션 주의사항 (0) | 2023.02.27 |
[UE/GAS] GameplayCue 데디서버에서 동작해야 할 때 (0) | 2023.02.24 |
[UE4/Tips] CapsuleOverlapActors VS CapsuleTraceMulti (0) | 2023.01.25 |
[UE/Debug] 프로세스에 디버거가 붙지 않는 경우 (0) | 2023.01.19 |
소중한 공감 감사합니다