[UE Net] 액터 리플리케이션 빈도와 연관성 Actor Replication Frequency & Relevancy + 언리얼 인사이트 Unreal Insight
Network 통신 상태를 확인할 수 있는 언리얼 인사이트 도구의 활용 방법에 대해 알아보자. Actor Replication의 빈도 속성을 활용한 효율적인 Actor Replication의 구현해보자. Actor Replication에서 연관성의 개념과 용어 및 관련 설정의 학습하자. 거리 속성을 변경에 따른 연관성 동작의 구현해보자.
목차
Unreal Insight
액터 리플케이션의 연관성과 빈도에 대한 동작 방식의 확인
학습할 내용 - 연과성 & 빈도
언리얼 인사이트 Unreal Insight
Network 통신 상태를 확인할 수 있는 언리얼 인사이트 도구의 활용 방법
언리얼 인사이트 Unreal Insights
- 언리얼 프로그램의 다양한 퍼포먼스를 체크할 수 있는 강력한 프로파일링 도구
- 언리얼 엔진에 포함되어 있음.
- 프로그램 프로파일링 뿐만 아니라 네트웍 상태도 확인할수 있음. (Network Insight)
Unreal Insight 구동을 위한 환경 설정
- 언리얼 엔진의 설치 폴더 확인
- 인사이트 프로그램의 숏컷 새성
- 언리얼 에디터 실행 파일의 Path 설정
- 언리얼 에디터를 구동하기 위한 배치파일 제작
NetworkInsightsEditor.bat
UnrealEditor.exe%cd%\프로젝트명.uproject -NetTrace= -trace=net
※ 선행사항: 언리얼 에디터 내의 Plugin 설치
1. 언리얼 엔진의 설치 폴더 확인
- C:\Program Files\Epic Games\UE_5.3\Engine\Binaries\Win64
2. 인사이트 프로그램의 Shortcut 생성
- UnrealInsight.exe 우클릭 - 작업표시줄에 고정
3. 언리얼 에디터 실행 파일의 Path 설정
4. 언리얼 에디터를 구동하기 위한 배치파일 제작
- 프로젝트 폴더에 .bat 파일 생성
- NetworkInsightsEditor.bat
- UnrealEditor.exe %cd%\프로젝트명.uproject -NetTrace=1 -trace=net
일반적인 방법으로 언리얼 에디터+Visual Studio를 실행하지 않고 .bat로 에디터를 실행시킨다.
에디터가 열린다.
그 다음으로 언리얼 인사이트(Unreal Insight) 툴을 실행시킨다.
자신 컴퓨터의 IP를 로컬 호스트로 설정한다.
서버와 클라이언트 정보를 확인할 수 있다.
Actor Replication 빈도 설정
Actor Replication의 빈도 속성을 활용한 효율적인 Actor Replication의 구현
Actor Replication에서 연관성의 개념과 용어 및 관련 설정의 학습
거리 속성을 변경에 따른 연관성 동작의 실습
Actor Replication의 빈도(Frequency)란?
Actor Replication의 빈도란
" Client와 Server 간에 진행되는 통신 빈도 "
를 말한다.
NetUpdateFrequency: Replication 빈도의 최대치 설정
- 1초당 몇 번 Replication을 시도할지 지정한 값
- 기본값은 100. 즉, 이론적으로 Server는 1/100초 간격으로 Replication을 시도함
Network 빈도는 최대치일 뿐 이를 보장하진 않음
- Server의 Tick Rate에 따라 Replication이 발생하지만, Server의 성능에 따라 달라짐.
- ex. NetUpdateFrequency = 100으로 지정하더라도 서버의 틱이 1초에 50번 밖에 수행되지 않는다면 통신은 50번의 빈도로 진행된다.
- Server의 성능이 Network 빈도보다 낮은 경우, Server의 성능으로 복제됨.
- 일반적으로 그래픽 기능이 없는 Dedicated Server가 게임 기능이 탑재된 Listen Server 보다 더 좋은 성능을 발휘함.
왼쪽은 기본 빈도수인 100 설정. 오른쪽은 빈도수 1로 설정(1초에 한 번씩 전송됨)
주요 Actor에 설정된 빈도 기본값
NetUpdateFrequency 속성
Actor의 종류 | 업데이트 빈도 | 사용 |
Actor | 100.0 | |
Pawn | 100.0 | |
PlayerController | 100.0 | |
GameState | 10.0 | 게임상태 정보는 플레이에 영향을 주지 않기 때문에 낮게 설정되어 있다 |
PlayerState | 1.0 | 플레이어 점수 |
Network 데이터 줄이기
규칙적으로 움직이는 Actor의 Network 통신 데이터를 줄이는 예제
NetUpdateFrequency 속성 값을 1로 설정(= 1초에 한 번씩 전달).
데이터 공백을 Client에서 부드러운 움직임으로 보완하기
- 이전 복제된 데이터에 기반해 현재 틱에서의 회전 값을 예측.
- Client에서 예측된 값을 보간해 회전.
적응형 네트워크 업데이트 (Adaptive Network Update) - 고정형 빈도가 아닌 방법
적응형 네트워크 업데이트란?
" 유의미한 업데이트가 없으면 (언리얼 엔진이 스스로 파악해서) 빈도를 줄여서 부하를 줄이는 기법 "
MinNetUpdateFrequency
- Replication 빈도의 최소치 설정을 사용함
- 기본값은 2
최소값과 최대값 사이에서 현재 Actor에 맞는 최적의 전송 타이밍을 설정함.
이를 사용하기 위해서는 설정에서 직접 활성화 시켜줘야 함.
- Config - DefaultEngine.ini를 열어 아래의 문구를 기입해야 함.
- [SystemSettings]
net.UseAdaptiveNetUpdateFrequelncy=1
연관성(Relevancy)이란?
서버의 관점에서 현재 Actor가 Client의 Connection에 관련된 Actor인지 확인하는 작업
대형 레벨에 존재하는 모든 Actor 정보를 Client에게 보내는것은 불필요함.
- Server가 레벨에 있는 모든 Actor의 정보를 실시간으로 모든 Client에 보낸다면 네트워크로 전송하는 패킷 양이 감당할 수 없을 정도로 크다.
- ex. MMORPG 몹, NPC / Battleground 플레이어 정보
Client와 연관있는 Actor만 체계적으로 모아 통신 데이터를 최소화하는 방법.
참고) 언리얼 공식문서
" 거대한 레벨에서 일정 시점에, 플레이어는 그 레벨에 있는 액터의 작은 일부분만 볼 수 있습니다. 레벨에 있는 다른 액터는 대부분 보이지도, 들리지도 않으며, 플레이어에 중대한 영향을 끼치지도 않습니다. 서버가 보이는 것으로 또는 클라이언트에 영향을 끼칠 수 있는 것으로 여기는 액터 세트는, 그 클라이언트에 대해 연관성이 있는 액터 세트라 여깁니다. 언리얼 네트워크 코드의 상당한 대역폭 최적화는, 서버가 클라이언트에게 연관성이 있는 액터 세트에 대해서만 알려주는 것으로 이루어집니다. "
" 이러한 규칙은 플레이어에게 정말 영향을 끼칠 수 있는 액터 세트를 제대로 추정해 낼 수 있도록 디자인되어 있습니다. 물론, 완벽하지는 않습니다: 거리 검사는 가끔 커다란 액터에 거짓 음성 판단을 내릴 수 있으며 (물론 몇 가지 경험법칙을 사용하여 보완하고는 있습니다만), 앰비언트 사운드에 대한 사운드 오클루전같은 것은 지원하지 않습니다. 하지만 인터넷과 같은 지연시간 및 패킷 손실 특성이 있는 네트워크 환경에 내재된 오류의 범람으로 인하여 그러한 추정은 어쩔 수 없습니다. "
https://docs.unrealengine.com/5.1/ko/actor-relevancy-and-priority-in-unreal-engine/
연관성에 관련된 다양한 속성
연관성 판별을 위한 특별한 Actor의 정의
- 뷰어(Viewer): Client의 Connection을 담당하는 PlayerController를 가리킴.
- 뷰 타겟(View Target): PlayerController가 빙의한 폰(Possessed Pawn)
- 가해자(Instigatro): 나에게 대미지를 가한 Actor
Owner의 정의
- Actor를 소유하는 Actor. 최상단의 소유 Actor를 의미.
연관성의 점검
- 서버에서는 Tick 마다 모든 Connection과 Actor에 대해 연관성을 점검함
- Client의 뷰어와 관련있고 뷰어와의 일정 거리 내에 있는 Actor를 파악
- 해당 Actor 묶음의 정보를 Client에게 전송
- 연관성 검사는 Client Connection 당 진행된다.(= 각 클라이언트는 연관성 검사를 한다)
- 연관성 검사에 실패하는 순간에는 서버로부터 데이터를 받지 못하게 된다.
액터 속성에 따른 연관성 판정을 위한 속성
AlwaysRelevant | - 항상 Connection에 대해 연관성을 가짐 - 뷰어와 무관하게 항상 복제해야 되는 Actor들에게 설정 (= 네트워크 트래픽을 증가시킨다) - GameState와 PlayerState의 Actor에 이 옵션이 설정되어 있음. (항상 게임의 상태와 플레이어 상태를 전송해야 하기 때문이다.) |
NetUseOwnerRelevancy | - 자신의 연관성은 Owner의 연관성으로 판정됨 - ex. 시야에 안 들어오는 장치 + 그 장치에 장착된 무기 Actor가 있다고 가정. 무기에 대해서 따로 연관성을 파악하는것이 아니라 무기를 소유하고 있는 장치에 대해서 연관성을 판단 |
OnlyRelevantToOwner | - Owner에 대해서만 연관성을 가짐 - 이 옵션은 뷰어나 뷰타겟을 Owner로 하지 않으면 아예 연관성에서 제외시킴 - ex. 이 옵션을 체크하여 카메라 또는 내가 조종하는 캐릭터와 연관이 없으면 아예 Client 쪽에 보내지 않음. 연관성 검사에서 일찍 걸러지기 때문에 서버와 네트워크의 부하를 줄일 수 있다. |
NetCullDistance | - 뷰어(Viewer)와의 거리에 따라 연관성 여부를 결정함 - 기본값은 2억2500만. 제곱한 값이므로 루트값인 15000cm = 150m 다. |
이러한 연관성 판정에 대한 로직은 게임의 종류에 따라서 다양하게 직접 만들어서 적용할 수 있도록 가상함수(Virtual function)으로 구성되어 있다. 하지만 이 작업 자체가 로직이 많아질수록 서버에 부하가 많이 걸리기 때문에 최대한 가볍게 지정하는 것이 좋다.
Actor, Pawn, PlayerController의 virtual bool IsRelevantFor() 가상함수 코드 살펴보기
예제 코드
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ABFountain.generated.h"
UCLASS()
class ARENABATTLE_API AABFountain : public AActor
{
GENERATED_BODY()
public:
AABFountain();
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Mesh)
TObjectPtr<class UStaticMeshComponent> Body;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Mesh)
TObjectPtr<class UStaticMeshComponent> Water;
public:
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
virtual void OnActorChannelOpen(class FInBunch& InBunch, class UNetConnection* Connection) override;
virtual bool IsNetRelevantFor(const AActor* RealViewer, const AActor* ViewTarget, const FVector& SrcLocation) const override;
UPROPERTY(ReplicatedUsing = OnRep_ServerRotationYaw)
float ServerRotationYaw;
UFUNCTION()
void OnRep_ServerRotationYaw();
float RotationRate = 30.0f;
float ClientTimeSinceUpdate = 0.0f; // Server로부터 패킷을 받은 이후에 얼마만큼 시간이 경과됐는지를 기록하는 변수
float ClientTimeBetweenLastUpdate = 0.0f; // Server로부터 데이터를 받고 그 다음 데이터를 받았을때 걸린 시간을 기록하는 변수
};
#include "Prop/ABFountain.h"
#include "Components/StaticMeshComponent.h"
#include "Net/UnrealNetwork.h"
#include "ArenaBattle.h"
AABFountain::AABFountain()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
Body = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Body"));
Water = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Water"));
RootComponent = Body;
Water->SetupAttachment(Body);
Water->SetRelativeLocation(FVector(0.0f, 0.0f, 132.0f));
static ConstructorHelpers::FObjectFinder<UStaticMesh> BodyMeshRef(TEXT("/Script/Engine.StaticMesh'/Game/ArenaBattle/Environment/Props/SM_Plains_Castle_Fountain_01.SM_Plains_Castle_Fountain_01'"));
if (BodyMeshRef.Object)
{
Body->SetStaticMesh(BodyMeshRef.Object);
}
static ConstructorHelpers::FObjectFinder<UStaticMesh> WaterMeshRef(TEXT("/Script/Engine.StaticMesh'/Game/ArenaBattle/Environment/Props/SM_Plains_Fountain_02.SM_Plains_Fountain_02'"));
if (WaterMeshRef.Object)
{
Water->SetStaticMesh(WaterMeshRef.Object);
}
bReplicates = true;
NetUpdateFrequency = 1.0f; // 1초에 한 번씩 전송
NetCullDistanceSquared = 4000000.0f; // 20m 거리 범위
}
// Called when the game starts or when spawned
void AABFountain::BeginPlay()
{
Super::BeginPlay();
//if (HasAuthority())
//{
// FTimerHandle Handle;
// GetWorld()->GetTimerManager().SetTimer(Handle, FTimerDelegate::CreateLambda([&]
// {
// ServerRotationYaw += 1.0f;
// }
// ), 1.0f, true, 0.0f);
//}
}
// 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);
}
}
void AABFountain::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AABFountain, ServerRotationYaw);
}
void AABFountain::OnActorChannelOpen(FInBunch& InBunch, UNetConnection* Connection)
{
AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("Begin"));
Super::OnActorChannelOpen(InBunch, Connection);
AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("End"));
}
bool AABFountain::IsNetRelevantFor(const AActor* RealViewer, const AActor* ViewTarget, const FVector& SrcLocation) const
{
bool NetRelevantResult = Super::IsNetRelevantFor(RealViewer, ViewTarget, SrcLocation);
if (!NetRelevantResult)
{
AB_LOG(LogABNetwork, Log, TEXT("Not Relevant:[%s] %s"), *RealViewer->GetName(), *SrcLocation.ToCompactString());
}
return NetRelevantResult;
}
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;
}
AABFountain::AABFountain()
- bReplicates = true;
- NetUpdateFrequency = 1.0f;
- 1초에 한 번씩 전송
- NetCullDistanceSquared = 4000000.0f;
- 20m 거리 범위
bool AABFountain::IsNetRelevantFor(const AActor* RealViewer, const AActor* ViewTarget, const FVector& SrcLocation) const
- if (!NetRelevantResult) {
AB_LOG(LogABNetwork, Log, TEXT("Not Relevant:[%s] %s"), *RealViewer->GetName(), *SrcLocation.ToCompactString());
}
void AABFountain::OnRep_ServerRotationYaw()
- ClientTimeBetweenLastUpdate = ClientTimeSinceUpdate;
- ClientTimeSinceUpdate = 0.0f;
'⭐ Unreal Engine > UE 개념정리 - Network' 카테고리의 다른 글
[UE Net] RPC (Remote Procedure Call) (0) | 2024.03.02 |
---|---|
[UE Net] 액터 리플리케이션 로우레벨 플로우 Actor Replication - Low Level Flow (0) | 2024.02.12 |
[UE Net] 액터 리플리케이션 Actor Replication (1) | 2024.02.10 |
[UE Net] Connection Handshaking (0) | 2024.02.09 |
[UE Net] 액터의 역할과 커넥션 핸드셰이킹 Actor Role & Connection Handshaking (0) | 2024.02.09 |
댓글
이 글 공유하기
다른 글
-
[UE Net] RPC (Remote Procedure Call)
[UE Net] RPC (Remote Procedure Call)
2024.03.02 -
[UE Net] 액터 리플리케이션 로우레벨 플로우 Actor Replication - Low Level Flow
[UE Net] 액터 리플리케이션 로우레벨 플로우 Actor Replication - Low Level Flow
2024.02.12 -
[UE Net] 액터 리플리케이션 Actor Replication
[UE Net] 액터 리플리케이션 Actor Replication
2024.02.10 -
[UE Net] Connection Handshaking
[UE Net] Connection Handshaking
2024.02.09