언리얼 엔진5 Advanced - 액터 리플리케이션 빈도와 연관성

타입·2025년 4월 3일
0

언리얼 강의

목록 보기
28/47

언리얼 인사이트 (Unreal Insights)

언리얼 프로그램의 다양한 퍼포먼스를 체크할 수 있는 강력한 프로파일링 도구
프로그램 프로파일링 뿐만 아니라 네트워크 상태도 확인할 수 있음 (Network Insights)

언리얼 인사이트 구동을 위한 환경설정

  1. 언리얼 엔진의 설치 폴더 확인
  2. 인사이트 프로그램의 숏컷 생성
  3. 언리얼 에디터 실행 파일의 PATH 설정
  4. 언리얼 에디터를 구동하기 위한 배치파일 제작
  • 언리얼 인사이트 실행 파일 위치
    C:\Program Files\Epic Games\UE_5.5\Engine\Binaries\Win64
    UnrealInsights.exe 파일 존재

환경 변수의 Path에 해당 경로를 추가합니다.

  • 배치파일 제작
    bat 확장자로 파일을 만든 후, 아래와 같이 입력하여 실행
    UnrealEditor.exe %cd%\ArenaBattle.uproject -NetTrace=1 -Trace=Net
    • NetTrace, Trace
      Networking Insights를 실행하기 위한 옵션

언리얼 에디터 우측 하단에서 사진과 같이 언리얼 인사이트를 실행하여 에디터와 연결합니다.

액터 리플리케이션 빈도 설정

액터 리플리케이션 빈도 (Frequency)

클라이언트와 서버간에 진행되는 통신 빈도

NetUpdateFrequency: 리플리케이션 빈도의 최대치 설정
1초당 몇 번 리플리케이션을 시도할지 지정한 값
기본 값은 100, 이론적으로 서버는 1/100초 간격으로 리플리케이션을 시도함

네트워크 빈도는 최대치일 뿐 이를 보장하진 않음
서버의 Tick Rate에 따라 리플리케이션이 발생하지만, 서버의 성능에 따라 달라짐
서버의 성능이 네트워크 빈도보다 낮은 경우, 서버의 성능으로 복제됨
일반적으로 그래픽 기능이 없는 데디케이티드 서버가 더 좋은 성능을 발휘함

주요 액터에 설정된 빈도 값

  • NetUpdateFrequency 속성
    • Actor: 100
    • Pawn: 100
    • PlayerController: 100
    • GameState: 10
    • PlayerState: 1

네트워크 데이터 줄이기

규칙적으로 움직이는 액터의 네트워크 통신 데이터를 줄이는 예제

NetUpdateFrequency 속성 값을 1로 설정

데이터 공백을 클라이언트에서 부드러운 움직임으로 보완하기
이전 복제된 데이터에 기반해 현재 틱에서의 회전 값을 예측
클라이언트에서 예측된 값을 보간해 회전

참고로 빌드 구성을 DebugGame Editor로 하고 빌드하면 uproject 파일로 프로젝트를 실행할 땐 반영되지 않습니다.
Development Editor로 설정되어 있는지 확인하고 빌드합니다!

적응형 네트워크 업데이트 (Adaptive Network Update)

유의미한 업데이트가 없으면 빈도를 줄여서 부하를 줄이는 기법

MinNetUpdateFrequency: 리플리케이션 빈도의 최소치 설정을 사용함
기본 값은 2

최소 값과 최대 값 사이에서 현재 액터에 맞는 최적의 전송 타이밍을 설정함

  • 이를 사용하기 위해선 설정에서 직접 활성화시켜줘야 함
    DefaultEngine.ini
[SystemSettings]
net.UseAdaptiveNetUpdateFrequency=1

액터 리플리케이션 연관성 설정

연관성(Relevancy)

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

연관성에 관련된 다양한 속성

  • 연관성 판별을 위한 특별한 액터의 정의

    • 뷰어(Viewer)
      클라이언트의 커넥션을 담당하는 플레이어 컨트롤러를 가리킴
    • 뷰 타겟(View Target)
      플레이어 컨트롤러가 빙의한 폰
    • 가해자(Instigator)
      나에게 대미지를 가한 액터
  • 오너(Owner)의 정의
    액터를 소유하는 액터, 최상단의 소유 액터를 의미

연관성의 점검

서버에서는 틱마다 모든 커넥션과 액터에 대해 연관성을 점검함
클라이언트의 뷰어와 관련있고, 뷰어와의 일정 거리 내에 있는 액터를 파악
해당 액터 묶음의 정보를 클라이언트에게 전송

액터 속성에 따른 연관성 판정을 위한 속성

  • AlwaysRelevant
    항상 커넥션에 대해 연관성을 가짐
  • NetUseOwnerRelevancy
    자신의 연관성은 오너의 연관성으로 판정함
  • OnlyRelevantToOwner
    오너에 대해서만 연관성을 가짐
  • Net Cull Distance
    뷰어와의 거리에 따라 연관성 여부를 결정함
    (실제 거리는 이 값에 루트를 씌운 값)

AActor::IsNetRelevantFor()

// ActorReplication.cpp
bool AActor::IsNetRelevantFor(const AActor* RealViewer, const AActor* ViewTarget, const FVector& SrcLocation) const
{
	if (bAlwaysRelevant || IsOwnedBy(ViewTarget) || IsOwnedBy(RealViewer) || this == ViewTarget || ViewTarget == GetInstigator())
	{
		return true;
	}
	else if (bNetUseOwnerRelevancy && Owner)
	{
		return Owner->IsNetRelevantFor(RealViewer, ViewTarget, SrcLocation);
	}
	else if (bOnlyRelevantToOwner)
	{
    	// 이미 뷰타겟이나 뷰어의 IsOwnedBy() 검사를 실패한 경우인데 옵션이 켜져있어 연관성 없음
		return false;
	}
	else if (RootComponent && RootComponent->GetAttachParent() && RootComponent->GetAttachParent()->GetOwner() && (Cast<USkeletalMeshComponent>(RootComponent->GetAttachParent()) || (RootComponent->GetAttachParent()->GetOwner() == Owner)))
	{
    	// 캐릭터에 부착된 무기나 악세사리 같은 경우 오너의 연관성에 따름
		return RootComponent->GetAttachParent()->GetOwner()->IsNetRelevantFor(RealViewer, ViewTarget, SrcLocation);
	}
	else if(IsHidden() && (!RootComponent || !RootComponent->IsCollisionEnabled()))
	{
    	// 보이지도 않고 콜리전 정보도 없음
		return false;
	}

	if (!RootComponent)
	{
		UE_LOG(LogNet, Warning, TEXT("Actor %s / %s has no root component in AActor::IsNetRelevantFor. (Make bAlwaysRelevant=true?)"), *GetClass()->GetName(), *GetName() );
		return false;
	}

	// 거리에 따른 연관성 옵션 확인(디폴트론 켜져있음)
    // SrcLocation은 플레이어 컨트롤러인 뷰어의 위치
	return !GetDefault<AGameNetworkManager>()->bUseDistanceBasedRelevancy ||
			IsWithinNetRelevancyDistance(SrcLocation);
}

bool AActor::IsWithinNetRelevancyDistance(const FVector& SrcLocation) const
{
	return FVector::DistSquared(SrcLocation, GetActorLocation()) < GetNetCullDistanceSquared();
}

APawn::IsNetRelevantFor()

bool APawn::IsNetRelevantFor(const AActor* RealViewer, const AActor* ViewTarget, const FVector& SrcLocation) const
{
	CA_SUPPRESS(6011);
	if (bAlwaysRelevant || RealViewer == Controller || IsOwnedBy(ViewTarget) || IsOwnedBy(RealViewer) || this == ViewTarget || ViewTarget == GetInstigator()
		|| IsBasedOnActor(ViewTarget) || (ViewTarget && ViewTarget->IsBasedOnActor(this)))
	{
		return true;
	}
	else if ((IsHidden() || bOnlyRelevantToOwner) && (!GetRootComponent() || !GetRootComponent()->IsCollisionEnabled())) 
	{
		return false;
	}
	else
	{
    	// 움직이는 플랫폼 위에 있는 경우 플랫폼의 연관성을 따름
		UPrimitiveComponent* MovementBase = GetMovementBase();
		AActor* BaseActor = MovementBase ? MovementBase->GetOwner() : nullptr;
		if ( MovementBase && BaseActor && GetMovementComponent() && ((Cast<const USkeletalMeshComponent>(MovementBase)) || (BaseActor == GetOwner())) )
		{
			return BaseActor->IsNetRelevantFor(RealViewer, ViewTarget, SrcLocation);
		}
	}

	return !GetDefault<AGameNetworkManager>()->bUseDistanceBasedRelevancy ||
			IsWithinNetRelevancyDistance(SrcLocation);
}

APlayerController::IsNetRelevantFor()

bool APlayerController::IsNetRelevantFor(const AActor* RealViewer, const AActor* ViewTarget, const FVector& SrcLocation) const
{
	// 클라이언트에 하나 밖에 없기 때문에 대부분 연관성을 통과함
	return ( this==RealViewer );
}

실습코드

https://github.com/dnjfs/ArenaBattle_Network/commit/6cb37c39d6cbcbb811674e8bcdf3187f1ecbf414

  • NetCullDistanceSquared
    거리에 따른 연관성 여부를 위한 거리 값을 제곱한 값

  • IsNetRelevantFor()
    해당 함수를 오버라이드하여 커스텀하게 연관성 검사 방식 변경 가능

profile
주니어 언리얼 프로그래머

0개의 댓글