본문 바로가기

공부/이득우의 언리얼 프로그래밍

[Study] Part 3 - 액터 리플리케이션 빈도와 연관성 (6/15)

728x90
반응형

 

 

 

  • 정리
    • 액터 리플리케이션 빈도와 연관성
      1. 언리얼 인사이트 도구의 사용 방법의 학습
      2. 액터 리플리케이션 빈도 속성 조절에 따른 변화 확인
      3. 액터 리플리케이션 연관성 설정에 따른 변화 확인

 

 

  • 언리얼 인사이트
    • 언리얼 프로그램의 다양한 퍼포먼스를 체크할 수 있는 강력한 프로파일링 도구
    • 언리얼 엔진에 포함되어 있음
    • 프로그램 프로파일링 뿐만 아니라 네트웍 상태도 확인할 수 있음 (Network Insights)
  • 액터 리플리케이션의 빈도(Frequency)
    • 클라이언트와 서버간에 진행되는 통신 빈도
    • NetUpdateFrequency : 리플리케이션 빈도의 최대치 설정
      • 1초당 몇 번 리플리케이션을 시도할지 지정한 값
      • 기본 값은 100. 즉 이론적으로는 서브는 1/100초 간격으로 리플리케이션을 시도
    • 네트웍 빈도는 최대치일뿐 이를 보장하진 않는다.
      • 서버의 Tick Rate에 따라 리플리케이션이 발생하지만, 서버의 성능에 따라 달라짐
      • 서버의 성능이 네트웍 빈도보다 낮은 경우, 서버의 성능으로 복제됨
      • 일반적으로 그래픽 기능이 없는 데디케이드 서버가 더 좋은 성능을 발휘함
  • 네트웍 데이터 줄이기
    • 규칙적으로 움직이는 액터의 네트웍 통신 데이터를 줄이는 예제
    • NetUpdateFrequency 속성 값을 1로 설정
    • 데이터 공백을 클라이언트에서 부드러운 움직임으로 보완하기
      • 이전 복제된 데이터에 기반해 현재 틱에서의 회전 값을 예측
      • 클라이언트에서 예측된 값을 보간해 회전

 

네트웍 데이터 줄이기

 

 

 

NetUpdateFrequency를 1.0f 값으로 변경 

 

  • NetUpdateFrequency를 1.0f 값으로 변경하니 클라이언트의 분수대가 뚝뚝 끊어지는 것을 확인할 수 있다.

 

 

NetUpdateFrequency = 1.0f

 

NetUpdateFrequency = 100.0f

 

  • 언리얼 인사이트 상으로도 1.0f으로 설정했을때 전송되는 빈도가 적어지는 것을 볼 수 있다. 반면, 100.0f의 경우 거의 매 프레임마다 BP_Fountain이 전송되는것을 볼 수 있다.

 

 

// Called every frame
void AABFountain::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	// 현재는 리슨 서버로 구성되어 있기 때문에 HasAuthority와 같은 체크 함수를 통해 클라이언트와 서버의 로직을 분리시켜줘야한다.
	// 데디케이트 서버로 구성되어 있다면 서버로직만 구성하면 된다.
	// Server
	if (HasAuthority())
	{
		AddActorLocalRotation(FRotator(0.0f, RotationRate * DeltaTime, 0.0f));
		ServerRotationYaw = RootComponent->GetComponentRotation().Yaw;
	}
	// Client
	else
	{
		// 현재 서버로부터 패킷을 받은 이후의 시간들이 계속 누적
		ClientTimeSinceUpdate += DeltaTime;

		// 값이 0에 가까우면 return
		if (ClientTimeBetweenLastUpdate < KINDA_SMALL_NUMBER)
			return;

		// 현재 서버로 받은 회전 값에 다음 패킷이 받아질 것으로 예상 되는 시간에다가 현재 회전, 회전율을 곱해준다.
		const float EstimateRotationYaw = ServerRotationYaw + RotationRate * ClientTimeBetweenLastUpdate;

		const float LerpRatio = ClientTimeSinceUpdate / ClientTimeBetweenLastUpdate;

		FRotator ClientRotator = RootComponent->GetComponentRotation();
		const float ClientNewYaw = FMath::Lerp(ServerRotationYaw, EstimateRotationYaw, LerpRatio);
		ClientRotator.Yaw = ClientNewYaw;
		RootComponent->SetWorldRotation(ClientRotator);
	}
}

// 콜백 함수
// 콜백 함수가 tick보다 더 적게 돌아가는 경우에는 보다 효율적으로 로직을 처리할 수 있다.
void AABFountain::OnRep_ServerRotationYaw()
{
	AB_LOG(LogABNetwork, Log, TEXT("Yaw : %f"), ServerRotationYaw);

	FRotator NewRotator = RootComponent->GetComponentRotation();
	NewRotator.Yaw = ServerRotationYaw;
	RootComponent->SetWorldRotation(NewRotator);

	// 서버로부터 데이터를 받았을 때 초기화시키도록
	ClientTimeBetweenLastUpdate = ClientTimeSinceUpdate;
	ClientTimeSinceUpdate = 0.0f;
}
  • ABFountain.cpp

 

  • 빈도는 NetUpdateFrequency = 1.0f으로 설정하지만, 클라이언트에서 보간을 통해 분수대가 자연스럽게 회전하도록 한다.
  • 로직은 다음과 같이 구현한다.
    • 현재 서버로 받은 회전 값에 다음 패킷이 받아질 것으로 예상 되는 시간에다가 현재 회전, 회전율을 곱해주고, Ratio를 통해 분수대에 Lerp를 시켜주면 된다.

 

 

NetUpdateFrequency = 1.0f이지만 클라이언트화면에서 분수대가 부드럽게 돌아가는 모습
  • 기본적인 예제이기 때문에 회전을 통한 Lerp를 구현했지만, 다양하게 클라이언트에서 보간하는 기법을 적절히 섞어 주면 부드럽게 게임을 구현할 수 있다.

 

  • 적응형 네트워크 업데이트(Adaptive Network Update)
    • 유의미한 업데이트가 없으면 빈도를 줄여서 부하를 줄이는 기법
    • MinNetUpdateFrequency : 리플리케이션 빈도의 최소치 설정을 사용함
      • 기본값은 2
    • 최소 값과 최대 값 사이에서 현재 액터에 맞는 최적의 전송 타이밍을 설정
    • 이를 사용하기 위해서는 설정에서 직접 활성화시켜줘야 함
      • DefaultEngine.ini
        • [SystemSettings] net.UseAdaptiveNetUpdateFrequency=1

 

 

  • 연관성(Relevancy)이란?
    • 서버의 관점에서 현재 액터가 클라이언트의 커넥션에 관련된 액터인지 확인하는 작업
    • 대형 레벨에 존재하는 모든 액터 정보를 클라이언트에게 보내는 것은 불필요
    • 클라이언트와 연관있는 액터만 체계적으로 모아 통신 데이터를 최소화하는 방법

 

연관성

 

 

  • 연관성에 관련된 다양한 속성
    • 연관성 판별을 위한 특별한 액터의 정의
      • 뷰어(Viewer) : 클라이언트의 커넥션을 담당하는 플레이어 컨트롤러를 가리킴
      • 뷰 타겟(View Target) : 플레이어 컨트롤러가 빙의한 폰
      • 가해자(Instigator) : 나에게 데미지를 가한 액터
    • 오너(Owner)의 정의
      • 액터를 소유하는 액터. 최상단의 소유 액터를 의미
  • 연관성의 점검
    • 서버에서는 틱마다 모든 커넥션과 액터에 대해 연관성을 점검
    • 클라이언트의 뷰어와 관련있고 뷰어와의 일정 거리 내에 있는 액터를 파악
    • 해당 액터 묶음의 정보를 클라이언트에게 전송

 

연관성의 점검

 

 

  • 액터 속성에 따른 연관성 판정을 위한 속성
    • AlwaysRelevant : 항상 커넥션에 대해 연관성을 가짐
      • 뷰어와 무관하게 항상 복제해야하는 액터들에게 설정. 네트워크 트래픽 up ex) 게임 스테이트, 플레이어 스테이트..
    • NetUseOwnerRelevancy : 자신의 연관성은 오너의 연관성으로 판정함
    • OnlyRelevantToOwner : 오너에 대해서만 연관성을 가짐
      • 뷰어나 뷰 타겟을 오너로 하지 않으면 아예 연관성에서 제외
    • NetCullDistance : 뷰어와의 거리에 따라 연관성 여부를 결정함
  • 액터, 폰, 플레이어 컨트롤러의 IsRelevantFor 코드 살펴보기

 

 

virtual bool IsNetRelevantFor(const AActor* RealViewer, const AActor* ViewTarget, const FVector& SrcLocation) const override;
  • ABFountain.h
 	// ...

	// 거리에 따른 연관성 파악 변수
	NetCullDistanceSquared = 4000000.0f;
  • ABFountain.cpp

 

  • Actor 클래스에 있는 IsNetRelevantFor함수를 오버라이드 하고, 생성자에서 NetCullDistanceSquared를 수정해서 확인해보자.

 

 

클라이언트가 멀어지니 분수대가 멈춘다.

 

  • NetCullDistanceSquared보다 더 멀리 떨어지게 되면 돌아가던 분수대가 멈추게 된다.

 

 

 

해당 포스트는 인프런의 <이득우의 언리얼 프로그래밍 Part3 - 네트웍 멀티플레이어 프레임웍의 이해>
강의를 수강하고 정리한 내용입니다.
 

이득우의 언리얼 프로그래밍 Part3 - 네트웍 멀티플레이 프레임웍의 이해 강의 | 이득우 - 인프런

이득우 | 또 하나의 언리얼 엔진이라고도 불리는 네트웍 멀티플레이어 프레임웍을 학습합니다. 네트웍 멀티플레이어 게임을 제작할 때 반드시 알아야 하는 주요 개념, 내부 동작 원리, 최적화

www.inflearn.com

 

 

 

728x90