< [이득우의 언리얼 프로그래밍 Part3 필기] 6. 액터 리플리케이션 빈도와 연관성

언리얼엔진5/[Part3] 이득우의 언리얼 프로그래밍

[이득우의 언리얼 프로그래밍 Part3 필기] 6. 액터 리플리케이션 빈도와 연관성

Rocketbabydolls 2024. 4. 30. 18:00

언리얼 인사이트

 

  • 언리얼 프로그램의 다양한 퍼포먼스를 체크할 수 있는 강력한 프로파일링 도구
  • 언리얼 엔진에 포함되어 있음
  • 프로그램 프로파일링 뿐만 아니라 네트웍 상태도 확인할 수 있다.

 

언리얼 인사이트

 

 

액터 리플리케이션의 빈도

 

 

 

주요 액터에 설정된 빈도 값

 

 

 

네트웍 데이터 줄이기

  • 규칙적으로 움직이는 액터의 네트웍 통신 데이터를 줄이는 예제
  • NetUpdateFrequency 속성 값을 1로 설정
  • 데이터 공백을 클라이언트에서 부드러운 움직임으로 보완하기
    이전 복제된 데이터에 기반해 현재 틱에서의 회전 값을 예측
    클라이언트에서 예측된 값을 보간해 회전

 

도식화

 

 

ABFountain 의 소스 코드

 

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

	if (HasAuthority())
	{
		AddActorLocalRotation(FRotator(0.0f, RotationRate * DeltaTime, 0.0f));
		ServerRotationYaw = RootComponent->GetComponentRotation().Yaw;
	}
	else
	{
		ClientTimeSinceUpdate += DeltaTime;
		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);
	}
}

 


float RotationRate = 30.0f;
float ClientTimeSinceUpdate = 0.0f;
float ClientTimeBetweenLastUpdate = 0.0f;

 

선언부에 위와 같이 변수를 선언해준 뒤


NetUpdateFrequency = 1.0f;

 

으로 생성자에서 설정한다.

 

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;
}

 

레플리케이션 함수에서 

ClientTimeBetweenLastUpdate 에 레플리케이션이 불리기 전까지의 시간을 저장해주고 다시 0으로 초기화 해준다.

 

 

 

간단하게 정리해본 보간법 예시

 

적응형 네트워크 업데이트

 

 

 

연관성(Relevancy) 이란?

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

++) 

인기 게임 배틀그라운드 를 생각하면 편하다. 반경 1km 이내의 적만 보이고 그 이상으로는 렌더링 되지 않는다.

 

도식화

 

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

 

 

연관성의 점검

 

 

 

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

  • AlwaysRelevant : 항상 커넥션에 대해 연관성을 가짐
  • NetUseOwnerRelevacy : 자신의 연광성은 오너의 연관성으로 판정함.
  • OnlyRelevantToOwner : 오너에 대해서만 연관성을 가짐
  • Net Cull Distance : 뷰어와의 거리에 따라 연관성 여부를 결정함 (계산을 적게 하기 위해 제곱 사용)

 

 

Relevant 코드 살펴보기


Actor

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)
	{
		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; // 보이지 않는데 루트컴포넌트가 없거나 충돌이 활성화 되어 있지 않다면 테스트 필요 X
	}

	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;
	}

	return !GetDefault<AGameNetworkManager>()->bUseDistanceBasedRelevancy ||
			IsWithinNetRelevancyDistance(SrcLocation);
            // 거리에 따른 연관성 검사 옵션을 활성화 할지 옵션을 통해 확인
            // 
}

 

 

Pawn

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);
}



PlayerController

bool APlayerController::IsNetRelevantFor(const AActor* RealViewer, const AActor* ViewTarget, const FVector& SrcLocation) const
{
	return ( this==RealViewer ); // 클라이언트에 하나만  존재하므로 연관성 통과
}