void UCombatComponent::SetAiming(bool bIsAiming)
{
if (Character == nullptr || EquippedWeapon == nullptr) return;
bAiming = bIsAiming;
ServerSetAiming(bIsAiming);
if (Character)
{
Character->GetCharacterMovement()->MaxWalkSpeed = bIsAiming ? AimWalkSpeed : BaseWalkSpeed;
}
// Show Sniper Scope Widget
if (Character->IsLocallyControlled() && EquippedWeapon->GetWeaponType() == EWeaponType::EWT_SniperRifle)
{
Character->ShowSniperScopeWidget(bIsAiming);
}
if (Character->IsLocallyControlled())
{
bAimButtonPressed = bIsAiming;
}
}
void UCombatComponent::ServerSetAiming_Implementation(bool bIsAiming)
{
UE_LOG(LogTemp, Warning, TEXT("ServerSetAiming_Implementation!"));
bAiming = bIsAiming;
if (Character)
{
Character->GetCharacterMovement()->MaxWalkSpeed = bIsAiming ? AimWalkSpeed : BaseWalkSpeed;
}
void UCombatComponent::OnRep_Aiming()
{
if (Character && Character->IsLocallyControlled())
{
UE_LOG(LogTemp, Warning, TEXT("OnRep_Aiming!"));
bAiming = bAimButtonPressed;
}
}
ServerSetAiming은 클라이언트에서 호출하는 서버 RPC이고, Replicated된 변수 bAiming에게 Repnotify 함수인 OnRep_Aiming을 등록하였다.
빠르게 마우스 우클릭 후, 뗏을때 bAiming의 값이 false -> true -> false로 클라이언트에서 설정되고 서버또한 마찬가지이다. 이때 네트워크 지연이 된다면 false -> true -> false 이후에 다시 복제된 값이 클라이언트에게 전달되어 -> true -> false가 된다. (즉 false -> true -> false -> true -> false)
해결법 1
따라서 RepNotify 함수와 클라이언트에서만 사용하는 변수(bAimButtonPressed)를 통해 직접 클라이언트의 bAiming 변수를 제어를 하여 false->true->false(->false->false) 를 해주는 과정이다. 네트워크 지연으로 인해 예상치못하게 값이 제어되는 것을 Client에서 직접 컨트롤 하는 것이다. 이처럼 네트워크 지연 보상을 위해 Client Side Prediction을 구현할 수 있다.
물론 네트워크 지연 보상에 정답은 없는 것 같다. 게임의 목표나 보안목표 등에 따라 어떤 것을 보상해야할지 달라지기 때문이다. bAiming과 같은 단순히 마우스 우클릭을 했냐/안했냐 정도는 클라이언트에서 제어를 해도 상관 없으나
중요한 정보의 경우는 보상보다 '보안'이 더 중요하지 않을까 생각한다. 보상과 보안의 무게는 case by case...
아래부터는 검증되지 않은 내용입니다. (해결법 1도 정답은 아닙니다)
해결법 2
void UCombatComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
//DOREPLIFETIME(UCombatComponent, bAiming);
DOREPLIFETIME_CONDITION(UCombatComponent, bAiming, COND_SkipOwner);
}
해결법1에서는 조건없이 bAiming을 위와 같이 설정을 했다. 게시글작성중에 생각해보니깐 결국 해당 클라이언트에서만 bAiming값을 직접 제어하고, 서버부터는 서버 RPC로 전달한 값을 쓰고, 그 외 클라이언트에게 이 값을 전송하면 되는거 아닌가? 라고 생각이 들었다.
따라서 복제 조건중에 COND_SkipOwner를 사용하여 테스트 하였다.
( his property send to every connection EXCEPT the owner 라는 각주를 보고서 Owner(즉 여기서는 클라이언트)를 제외하고 bAiming값이 전송된다고 생각하였다.)
테스트 결과 변수(bAimButtonPressed)나 RepNotify 함수 없이 기존의 서버RPC만 이용하여도 네트워크 지연 상황에서 문제없이 작동한다. 네트워크 대역폭을 아낄 수 있는 방법이라 해결법1과 더불어 좋은 방법이라 생각한다.
하지만 의문점이 들어서 우선 Log를 통해 실행 순서를 확인할려고 했다.
도식화를 하면 아래와 같다. (못알아보겠으면 유감)
의문인점은 서버에서 복제된 값을 넘겨주어 bAiming을 true로 설정하고, OnRep함수에서 false가 되어 결국 bAiming은 false -> true -> false -> true -> false가 되는 것 아닌가 의문이 들었다.
(파란색이 복제되어 클라이언트에서의 bAiming이라고 예상 한 것)
하지만 직접 Log를 찍어보니 발견한 사실은
1. 예상한대로 ServerSetAiming은 2번 호출이 되나, RepNotify 함수는 한번만 실행되었다
(아마도 2번째 Repnotify함수(6번)는 실행되지 않음)
2. 클라이언트의 bAiming값은 클라이언트에서 직접 변경한 (false->true->false) 이후에 변경되지 않았다.
여기서 추론이 가능한 것은
1. RepNotify 함수 실행시점에서, 복제된 값이 클라이언트에서 이미 동일하다면 RepNotify 함수는 실행되지 않는다.
(6번에서 복제된 값 false, 클라이언트에서 false인 경우 6번 RepNotify 함수는 실행되지 않았다)
2. RepNotify 함수에서 복제 대상 변수 값을 직접 Set 한 경우에는 최종적으로 Set한 값이 변수에 할당된다.
3. RepNotify 함수에서 복제 대상 변수를 Set하지 않는다면, 서버에서 복제된 값이 할당된다.
(3번에서 true가 복제되어 클라이언트로 전송되었지만, 5번에서 false로 직접 할당하여 클라이언트의 bAiming값은 변경되지 않았다.)
물론 엔진코드를 직접 본 것은 아니고 단순히 PIE 환경에서 콘솔명령어로 지연을 시켜 확인한 결과임으로 완전히 신뢰가능한 추론은 아니다.
'Unreal Engine > Memo' 카테고리의 다른 글
단일 장치에서 멀티 플레이 테스팅을 위한 환경 구축 (0) | 2024.01.12 |
---|